Linux上的ClientWebSocket抛出AuthenticationException(SSL)

时间:2017-08-15 11:04:33

标签: c# linux websocket .net-core

我在Windows上运行以下websocket客户端代码,一切正常 - 就像预期的那样。但是,如果代码是为linux-arm发布并复制到RaspberryPi3(在Raspian下运行),那么它将以AuthenticationException结尾。

csproj文件内容:

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
    <PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
  </ItemGroup>

连接尝试:(抛出异常的位置)

private readonly ClientWebSocket _socket;

public ApiConnection()
{
    _socket = new ClientWebSocket();
}

public async Task Connect()
{
    // the uri is like: wss://example.com/ws
    await _socket.ConnectAsync(new Uri(_settings.WebSocketUrl), CancellationToken.None);

    if (_socket.State == WebSocketState.Open)
        Console.WriteLine("connected.");
}

异常堆栈:

System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
         at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
         at System.Net.Security.SslState.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
         at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result)
         at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
         at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at System.Net.WebSockets.WebSocketHandle.<ConnectAsyncCore>d__24.MoveNext()
         at System.Net.WebSockets.WebSocketHandle.<ConnectAsyncCore>d__24.MoveNext()
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at System.Net.WebSockets.ClientWebSocket.<ConnectAsyncCore>d__16.MoveNext()
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

目标websocket服务器在Ubuntu上的nginx代理后面运行。我认为问题依赖于客户端,因为如果代码在Windows上执行,一切正常。

我还尝试将CA证书导入Raspians“证书存储区”。没有运气。

更新
http连接(ws://)也适用于linux。看来,WebSocketClient不信任我的LetsEncrypt证书?

3 个答案:

答案 0 :(得分:1)

在Windows上验证的证书不一定在Linux上验证。 每个操作系统都使用不同的证书和不同的方法来验证它们,此外还有Linux已知的证书,Windows不支持这些证书。

可能存在这样的情况,即您的LetsEncrypt证书被Windows识别,但Linux无法识别这一点,因此,抛出了AuthenticationException并且明确说明了

&#34;根据验证程序&#34;

,远程证书无效

意味着Linux试图验证证书,但失败了,因为它根本没有被Linux识别,但是你的Windows识别它并按预期行事。

我不太了解哪些证书适用于哪个Linux,但我建议研究这个问题,以便找到一种使用证书的方法,Windows和Linux都能识别,验证和合作。

答案 1 :(得分:1)

我最近尝试过类似的东西(尽管我使用的是Mono而不是.Net Core),而在我的情况下,仅仅是Raspberry上的系统时间已经过了几天(!),因此在证书之外运行#39; s&#34;有效来自&#34;到&#34;有效到&#34;时间戳。如果Raspi没有互联网连接通过NTP同步它的时间,就会发生这种情况。覆盆子不包含带缓冲电池的硬件时钟,因此在未开机时会丢失时间。

第一步是登录Pi并运行date,看系统时钟是否正确。

如果这是您的问题,您有几种可能的解决方法:

  • 启用对Raspberry Pi的Internet访问,以便它可以通过NTP同步其时钟
  • 自己设置正确的时间,然后始终打开Raspi电源,确保每次开机时都手动设置时间
  • 安装硬件时钟(3美元 - 5美元,如果按照说明操作,大约需要10分钟的工作时间),并完成问题(只要电池持续运行)

另一个想法可能是检查.Net Core期望安装CA证书的位置。至少使用Mono,这与Linux默认值不同。我使用X509Store C#API来安装证书而不是(Debian-)Linux系统工具。

答案 2 :(得分:1)

当浏览器/客户端不信任服务器向其投掷的SSL证书时,会发生这种情况。

要进行测试,请在浏览器中的同一网站上加载相同的网址/网址,您应该收到警告。

当证书问题得到解决后,警告就会消失。

解决SSL证书问题的确切过程取决于许多内容,例如......

操作系统,Web服务器,证书颁发机构,证书提供商门户网站,所以这里的任何人都几乎不可能为您提供有关修复证书问题的详细信息,但是说...

然而,在SE网络上有一些关于此的一般性建议......

https://unix.stackexchange.com/questions/90450/adding-a-self-signed-certificate-to-the-trusted-list

https://unix.stackexchange.com/questions/17748/trust-a-self-signed-pem-certificate

在你的情况下,由于rasbian基于debian,一些标准的debian建议可能会有所帮助......

在Debian中,证书存储位于/ etc / ssl / certs /中。该目录默认包含一系列符号链接,这些符号链接指向由ca-certificates包安装的证书(包括由c_rehash(1)生成的所需符号链接)和ca-certificates.crt,它是所有这些证书的串联。由update-ca-certificates(8)命令管理的所有内容,用于更新符号链接和ca-certificates.crt文件。

将新的(CA)证书添加到存储非常简单,因为update-ca-certificates(8)也在/ usr / local / share / ca-certificates /中查找文件,管理员只需要放置此目录中的PEM格式的新证书(扩展名为.crt)并以root身份运行update-ca-certificates(8)。系统上的所有应用程序(wget,...)现在应该信任它。

另一种可能的解决方案可能是&#34;我相信我的代码不会请求错误的网址,所以我会忽略SSL证书错误&#34;你可以做这样的事情......

C# Ignore certificate errors?

...但这并不理想,至少它会让你解决问题,直到你可以解决问题,最糟糕的情况是你仍然可以检查,但是通过编码你自己的支票而不仅仅是一揽子返回true。

最后一点:

我经常发现无论操作系统是什么,做一些像重启一样简单的事情,或者在测试/检查之间进行两次操作可以清除一些你通常认为不是问题的东西。