【问题标题】:Why is my SSL handshake failing before sending any SSL messages?为什么在发送任何 SSL 消息之前我的 SSL 握手失败?
【发布时间】:2014-06-27 19:46:22
【问题描述】:

我正在尝试使用 TLS 1.2 和 Apple 的安全传输 API 将 iOS 客户端连接到 OS X 服务器。我让 BSD 套接字通信正常工作,但在用 TLS 包裹它时遇到了很多麻烦。据我从 Wireshark 的输出中可以看出,SSL 握手甚至还没有真正开始,所以我可能在一侧或另一侧错误地设置了 SSL,但我不确定我可能做错了什么.

服务器端:

void establish_connection(int sockfd) {
  SSLContexRef sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType);
  SSLSetIOFuncs(sslContext, readFromSocket, writeToSocket);
  SSLSetConnection(sslContext, (SSLConnectionRef)(long)sockfd);
  SSLSetProtocolVersionMin(sslContext, kTLSProtocol12);

  // Get self-signed certificate from p12 data
  CFDataRef cert_data = CFDataCreate(kCFAllocatorDefault, cert_p12, cert_p12_len);
  CFArrayRef items = NULL;
  const void *options_keys[] = { kSecImportExportPassphrase };
  const void *options_values[] = { CFSTR("password") };
  CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, options_keys, options_values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  SecPKCS12Import(cert_data, options, &items);
  CFRelease(options);
  CFDictionaryRef item = CFArrayGetValueAtIndex(items, 0);
  SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(item, kSecImportItemIdentity);
  CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **)&identity, 1, NULL);
  SSLSetCertificate(sslContext, certs);

  // Fails with errSSLProtocol
  SSLHandshake(sslContext);
  ...
}

客户端:

void establish_connection(int server_sockfd) {
  SSLContextRef sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
  SSLSetIOFuncs(sslContext, readFromSocket, writeToSocket);
  SSLSetConnection(sslContext, (SSLConnectionRef)server_sockfd);
  SSLSetProtocolVersionMin(sslContext, kTLSProtocol12);

  // Fails with errSSLProtocol
  SSLHandshake(sslContext);
  ...
}

Wireshark 尝试握手的转储:

Source Destination Protocol Length Info
client server      TCP      78     50743 > 49754 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=16 TSval=365690143 TSecr=0 SACK_PERM=1
server client      TCP      78     49754 > 50743 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=16 TSval=666304222 TSecr=365690143 SACK_PERM=1
client server      TCP      66     50743 > 49754 [ACK] Seq=1 Ack=1 Win=131760 Len=0 TSval=365690468 TSecr=666304222
server client      TCP      66     [TCP Window Update] 49754 > 50743 [ACK] Seq=1 Ack=1 Win=131760 Len=0 TSval=666304252 TSecr=365690468
server client      TCP      66     49754 > 50743 [FIN, ACK] Seq=1 Ack=1 Win=131760 Len=0 TSval=666304281 TSecr=365690468
client server      TCP      66     50743 > 49754 [ACK] Seq=1 Ack=2 Win=131760 Len=0 TSval=365690498 TSecr=666304281
server client      TCP      66     [TCP Dup ACK 9099#1] 49754 > 50743 [ACK] Seq=2 Ack=1 Win=131760 Len=0 TSval=666304283 TSecr=365690498

使用 Wireshark 尝试诊断问题,我什至没有看到任何 SSL 握手消息。我看到 TCP 连接已建立,然后立即关闭,没有长度大于 0 的数据包。我做错了什么?

【问题讨论】:

  • 你能显示完整的堆栈跟踪,和/或交换数据包的更多细节吗?
  • 我已经编辑了问题,为尝试的握手添加了 Wireshark 转储。
  • Wireshark 转储看起来像是显示了与服务器的一半关闭,而不是完全关闭。尽管如此,如果服务器希望协商 SSL 连接,它不应该关闭。我对 iOS 调用不熟悉,但我想知道您是否需要从两侧发起握手,或者是否应该只从一侧进行。

标签: ios macos ssl secure-transport


【解决方案1】:

问题似乎是我的 IO 函数实现不正确。我正在粘贴似乎可以工作的套接字 IO 函数,但对 IO 函数的要求并没有很好的记录,所以我可能会遗漏一些东西。

OSStatus readFromSocket(SSLConnectionRef connection, void *data, size_t *dataLength) {
  int sockfd = (int)connection;
  size_t bytesRequested = *dataLength;
  ssize_t status = read(sockfd, data, bytesRequested);
  if (status > 0) {
    *dataLength = status;
    if (bytesRequested > *dataLength)
      return errSSLWouldBlock;
    else
      return noErr;
  } else if (0 == status) {
    *dataLength = 0;
    return errSSLClosedGraceful;
  } else {
    *dataLength = 0;
    switch (errno) {
      case ENOENT:
        return errSSLClosedGraceful;
      case EAGAIN:
        return errSSLWouldBlock;
      case ECONNRESET:
        return errSSLClosedAbort;
      default:
        return errSecIO;
    }
    return noErr;
  }
}

OSStatus writeToSocket(SSLConnectionRef connection, const void *data, size_t *dataLength) {
  int sockfd = (int)connection;
  size_t bytesToWrite = *dataLength;
  ssize_t status = write(sockfd, data, bytesToWrite);
  if (status > 0) {
    *dataLength = status;
    if (bytesToWrite > *dataLength)
      return errSSLWouldBlock;
    else
      return noErr;
  } else if (0 == status) {
    *dataLength = 0;
    return errSSLClosedGraceful;
  } else {
    *dataLength = 0;
    if (EAGAIN == errno) {
      return errSSLWouldBlock;
    } else {
      return errSecIO;
    }
  }
}

【讨论】:

  • 嗨,格雷格,我也在做同样的事情。你能建议我握手后下一步做什么吗?我会非常感谢你..:)
  • 如果握手成功,您应该可以使用SSLReadSSLWrite 通过连接进行通信,就像使用readwrite 与标准套接字一样.
  • 是的,我已经在深夜实现了它。但无论如何感谢您的建议和帮助..thanx 很多..快乐编码..:)
  • 从我的实验来看,关键是当您没有读取到请求的字节数时,从读取回调中返回 errSSLWouldBlock。把它留在这里,以防其他人需要实现自定义回调并看到类似的问题。
  • 另一个警告:SSL 层可能会向回调请求数据,即使它的缓冲区中仍有数据。这在底层阻塞时特别烦人,因此应用程序可能仅在底层超时后才能获取缓冲数据。该问题的解决方案是使用 ioctl(或与您的较低层相关的任何内容)来检查较低层是否有可用数据。如果没有数据可用,则从回调中返回 errSSLWouldBlock,即使这意味着您没有向 SSL 层返回数据。
猜你喜欢
  • 2012-03-04
  • 2018-07-28
  • 1970-01-01
  • 2012-10-11
  • 2015-03-16
  • 2021-12-28
  • 2018-10-17
  • 2016-04-29
相关资源
最近更新 更多