通过套接字与ESMTP协商TLS

时间:2010-07-16 06:53:28

标签: objective-c cocoa sockets smtp ssl

我有一个简单的SMTP客户端,我试图添加TLS支持。我不确定客户端发出'STARTTLS'命令后会发生什么。大多数来源(包括RFC本身)将其描述为TLS会话的协商,但这并不是特别清楚。

如何做到这一点?我的客户端是用Objective C编写的,并使用Cocoa的流对象(套接字的包装器)。 Cocoa流能够将TLS指定为具有NSStream的setProperty函数的套接字安全级别系统。

然而,似乎必须在打开连接之前完成此操作。如果是这种情况,那么客户端是否应该在从服务器接收代码220(响应STARTTLS)后断开连接,然后在指定TLS时重新连接?

或者更确切地说,这只是NSStream的限制吗?普通套接字是否在不关闭的情况下重新协商TLS或SSL?

此外,一旦STARTTLS发布并且后续协商完成,客户端是否还有其他编码/解码?

如果这些是简单的问题,请道歉。我很难找到合适的例子。

干杯!

2 个答案:

答案 0 :(得分:7)

我刚刚发现NSStream允许您通过设置kCFStreamSSLLevel属性并重新打开流来将已经活动的非TLS连接升级到TLS。我刚刚在smtp.gmail.com:25的SMTP连接上测试了这个,令人惊讶的是它有效!我正在描述NSStream / SMTP序列以防任何人感兴趣:

NSHost* host = [NSHost hostWithName:address];
[NSStream getStreamsToHost:host port:port inputStream:&is outputStream:&os];
[is setDelegate:self];
[os setDelegate:self];
[is scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[os scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[is open];
[os open];

SMTP对话,直到我们发送STARTTLS并且服务器说OK:

-> 220 mx.google.com ESMTP gb6sm4472052wbb.0
<- EHLO example.address.com
-> 250-mx.google.com at your service, [84.227.165.204]
-> 250-SIZE 35882577
-> 250-8BITMIME
-> 250-STARTTLS
-> 250 ENHANCEDSTATUSCODES
<- STARTTLS
-> 220 2.0.0 Ready to start TLS

此时我们执行以下操作将套接字升级到SSL:

NSMutableDictionary* settings = [NSMutableDictionary dictionary];
[settings setObject:NSStreamSocketSecurityLevelNegotiatedSSL forKey:(NSString*)kCFStreamSSLLevel];
[settings setObject:address forKey:(NSString*)kCFStreamSSLPeerName];
[is setProperty:settings forKey:(NSString*)kCFStreamPropertySSLSettings];
[os setProperty:settings forKey:(NSString*)kCFStreamPropertySSLSettings];
[is open];
[os open];

我们现在可以像以前一样使用is和os。现在可用的AUTH选项证明服务器认为连接是安全的。

<- EHLO example.address.com
-> 250-mx.google.com at your service, [84.227.165.204]
-> 250-SIZE 35882577
-> 250-8BITMIME
-> 250-AUTH LOGIN PLAIN XOAUTH
-> 250 ENHANCEDSTATUSCODES
<- AUTH PLAIN hIdDeNbAsE64dAtA
-> 235 2.7.0 Accepted
<- MAIL FROM: <example.address@gmail.com>
-> 250 2.1.0 OK gb6sm4472052wbb.0
<- RCPT TO: <another.address@gmail.com>
-> 250 2.1.5 OK gb6sm4472052wbb.0
<- DATA
<- From: =?UTF-8?B?QWxlc3NhbmRybyBWb2x6?= <example.address@gmail.com>
<- To: =?UTF-8?B?QWxl?= <another.address@gmail.com>
<- Subject: =?UTF-8?B?VGVzdA==?=
<- Mime-Version: 1.0;
<- Content-Type: text/html; charset="UTF-8";
<- Content-Transfer-Encoding: 7bit;
<- 
<- Ciao!
<- .
-> 354  Go ahead gb6sm4472052wbb.0
-> 250 2.0.0 OK 1307994916 gb6sm4472052wbb.0
<- QUIT
-> 221 2.0.0 closing connection gb6sm4472052wbb.0

我希望这对某人有用...... 欢呼声,

答案 1 :(得分:1)

在客户端发送STARTTLS命令并且服务器回复成功代码后,客户端必须在同一个套接字上启动其SSL / TLS握手。在启动SSL / TLS握手之前,请勿断开套接字。这将启动一个新的SMTP会话,您将不得不重新发出STARTTLS命令。

成功完成SSL / TLS握手后,无需其他工作,只需正常发送剩余的SMTP命令,即可来回加密。一些客户端在建立TLS后发出新的HELO / EHLO命令,以防服务器的功能在加密模式下不同。

不幸的是,从我所看到的情况来看,NSStream不支持在流打开后启动SSL / TLS。这是NSStream的限制,是documented by Apple

  

对于SSL安全性,NSStream定义   各种安全级别的属性(for   例,   NSStreamSocketSecurityLevelSSLv2)。您   通过发送设置这些属性   setProperty:forKey:到流   使用密钥的对象   NSStreamSocketSecurityLevelKey,如   此示例消息:

     

[IStream的   的setProperty:NSStreamSocketSecurityLevelTLSv1   forKey:NSStreamSocketSecurityLevelKey];

     

您必须先设置该属性   打开小溪。

我不知道Objective C / Cocoa中是否可以这样做,但您可能必须编写自己的流类附加到主SMTP流。然后,您可以在准备好时启动SSL / TLS,并将其委派给主流以进行输入/输出。或者找到一个第三方SMTP类来为您处理这些细节。