2013-10-25 72 views
2

我有一個簡單的TCP服務器/客戶端設置。這個連接實際上效果很好。支持SSL/TLS的Objective-C TCP服務器

現在我想爲套接字連接實施SSL/TLS加密。我使用鑰匙串訪問創建了PKCS12證書。在我的服務器我有接受回調中下面的代碼:

NSString *certificatePath = [[NSBundle mainBundle] pathForResource:@"TCPServerCertificate" ofType:@"p12"]; 
NSData *certificateData = [NSData dataWithContentsOfFile:certificatePath]; 

CFArrayRef keyRef; 
OSStatus status = SecPKCS12Import((__bridge CFDataRef)certificateData, (__bridge CFDictionaryRef)@{(__bridge NSString *)kSecImportExportPassphrase: @"1234"}, &keyRef); 

if (status != noErr) { 
    NSLog(@"PKCS12 import error %i", status); 
    return; 
} 

CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyRef, 0); 
SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity); 

SecCertificateRef certificate; 
status = SecIdentityCopyCertificate(identityRef, &certificate); 

if (status != noErr) { 
    NSLog(@"sec identity copy failed: %i", status); 
    return; 
} 

NSArray *certificates = [NSArray arrayWithObjects:(__bridge id)identityRef, (__bridge id)certificate, nil]; 

NSDictionary *settings = @{(NSString *)kCFStreamPropertyShouldCloseNativeSocket: [NSNumber numberWithBool:YES], 
          (NSString *)kCFStreamSSLValidatesCertificateChain:  [NSNumber numberWithBool:YES], 
          (NSString *)kCFStreamSSLAllowsExpiredCertificates:  [NSNumber numberWithBool:NO], 
          (NSString *)kCFStreamSSLAllowsExpiredRoots:    [NSNumber numberWithBool:NO], 
          (NSString *)kCFStreamSSLAllowsAnyRoot:     [NSNumber numberWithBool:YES], 
          (NSString *)kCFStreamSSLCertificates:     certificates, 
          (NSString *)kCFStreamSSLIsServer:      [NSNumber numberWithBool:YES], 
          (NSString *)kCFStreamSSLLevel:       (NSString *)kCFStreamSocketSecurityLevelTLSv1}; 

CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings); 
CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings); 

然後,我創建了流NSStream實例和另一個類處理它們。

當我運行服務器並連接客戶端時,我得到了我的委託中的常規NSStreamEventOpenCompleted。當我再嘗試寫入流或事件,如果我只是關閉連接,我收到以下錯誤:

2013-10-25 13:27:08.584 TCPServer[6435:303] CFNetwork SSLHandshake failed (-9800) 
2013-10-25 13:27:08.584 TCPServer[6435:303] NSStreamEventOpenCompleted 
2013-10-25 13:27:08.585 TCPServer[6435:303] NSStreamEventErrorOccurred 

我不知道我在客戶端實現。另外我想知道爲什麼在發送數據或從客戶端斷開連接時遇到握手故障。無論何時發生此錯誤,客戶端都認爲它仍處於連接狀態。

是否有任何良好的TCP SSL/TLS教程或涵蓋客戶端和服務器端的其他材料?

+0

是否使用相同的代碼在連接的兩端?如果是這樣,那麼它看起來像在連接的兩端將'kCFStreamSSLIsServer'屬性設置爲'YES'。 –

+0

我現在不在辦公室。但它可能是正確的。我應該刪除'kCFStreamSSLIsServer'嗎? –

+0

我想你會想保留這行代碼 - 在服務器的連接端,保持原樣。但在客戶端,您需要在字典中將此值設置爲「NO」。 –

回答

1

此代碼存在一些問題: 您無法使用kCFStreamPropertySSLSettings設置kCFStreamPropertyShouldCloseNativeSocket,並且不應混用服務器和客戶端代碼。

對於服務器,如果你想覆蓋驗證鏈(See Apple doc on overriding chain validation),你應該做到以下幾點,你應該只設置一個證書

CFReadStreamSetProperty(read, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 
CFWriteStreamSetProperty(write, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 
//kCFStreamPropertySocketSecurityLevel 
//Note: If you set this key, you must do so before setting any other SSL options, such as kCFStreamPropertySSLSettings. 
CFReadStreamSetProperty(read, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL); 
CFWriteStreamSetProperty(write, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL); 
//Creating server dictionnary 

//kCFStreamSSLIsServer 
//If the value of this key is kCFBooleanTrue, the kCFStreamSSLCertificates key must contain a valid value 

//kCFStreamSSLCertificates 
//Security property key whose value is a CFArray of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef. 
//For more information, see SSLSetCertificate() in Security/SecureTransport.h. 

NSDictionary *settings = @{(id)kCFStreamSSLCertificates:     certificates, 
          (id)kCFStreamSSLIsServer:      @YES}; 


//Apply settings 
CFReadStreamSetProperty(read, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings)); 
CFWriteStreamSetProperty(write, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings)); 

對於客戶端:

CFReadStreamSetProperty(read, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 
CFWriteStreamSetProperty(write, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 
//kCFStreamPropertySocketSecurityLevel 
//Note: If you set this key, you must do so before setting any other SSL options, such as kCFStreamPropertySSLSettings. 

CFReadStreamSetProperty(read, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL); 
CFWriteStreamSetProperty(write, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL); 

//DO NOT USE kCFStreamPropertySSLContext as it overrides the following configuration 
//create dictionnary for kCFStreamPropertySSLSettings 

//keys for dictionnary we want to change:kCFStreamSSLAllowsExpiredCertificates;kCFStreamSSLAllowsExpiredRoots;kCFStreamSSLAllowsAnyRoot; 
//kCFStreamSSLValidatesCertificateChain => no need to worry about the root 
//kCFStreamSSLPeerName kCFNull prevents name verification 


settings = @{(id)kCFStreamSSLValidatesCertificateChain: @NO,//The delegate will verify this 
      (id)kCFStreamSSLPeerName: (id)kCFNull};//prevents name verification if server is not fixed (eg. IP) 

//Apply settings 
CFReadStreamSetProperty(read, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings)); 
CFWriteStreamSetProperty(write, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings)); 
+0

你能解釋爲什麼我必須設置'kCFStreamPropertyShouldCloseNativeSocket'嗎? –

+0

kCFStreamPropertyShouldCloseNativeSocket將在對象生命週期結束時關閉連接。否則,你必須手動關閉本地套接字。 – impact27

+0

太好了,謝謝澄清! –