为什么我的SSL握手在发送任何SSL消息之前失败?

4
我正在尝试使用苹果的Secure Transport API,将iOS客户端与OS X服务器连接到TLS 1.2。我已经成功地实现了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的数据包。我可能做错了什么?


你能展示完整的堆栈跟踪和/或更多关于交换的数据包的具体信息吗? - Warren Dew
我已经编辑了问题,添加了Wireshark转储文件以尝试握手。 - Greg
Wireshark的转储文件显示服务器似乎只进行了半关闭而非完全关闭。然而,如果服务器希望协商SSL连接,则不应该关闭。我不熟悉iOS调用,但我想知道是否需要从双方都发起握手,或者是否只需要从一方发起。 - Warren Dew
1个回答

4

看起来问题出在我的IO函数实现上。我粘贴了似乎可用的socket 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;
    }
  }
}

嗨,Greg,我也在做同样的事情。握手后,请您建议我下一步该做什么。非常感谢您.. :) - Sahil Mahajan
如果握手成功,您应该能够使用SSLReadSSLWrite来与连接通信,就像使用标准套接字的readwrite一样。 - Greg
是的,我在深夜实现了它。但无论如何,感谢您的建议和帮助。非常感谢。祝编码愉快。 :) - Sahil Mahajan
1
从我的实验来看,关键是在读取回调中返回errSSLWouldBlock,每当您没有读取到请求的确切字节数。我把这个留在这里,以防其他人需要实现自定义回调并遇到类似问题。 - FD_
1
另一个注意事项:即使SSL层仍然在其缓冲区中具有数据,它也可能会向回调请求数据。当底层层是阻塞的时候,这特别令人恼火,因此应用程序可能只会在较低层超时后获得缓冲数据。解决该问题的方法是使用ioctl(或适用于您的较低层的任何内容)来检查较低层是否有可用数据。如果没有可用数据,请从回调返回errSSLWouldBlock,即使这意味着您不向SSL层返回任何数据。 - FD_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接