【问题标题】:Negotiating TLS with ESMTP over sockets通过套接字与 ESMTP 协商 TLS
【发布时间】:2011-03-16 19:24:09
【问题描述】:

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

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

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

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

此外,一旦发出 STARTTLS 并完成后续协商,客户端是否需要任何其他编码/解码?

如果这些是简单的问题,我们深表歉意。我很难找到合适的例子。

干杯!

【问题讨论】:

    标签: objective-c cocoa sockets smtp ssl


    【解决方案1】:

    我刚刚发现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
    

    我希望这对某人有用... 干杯,

    【讨论】:

    • Alessandro Volz - 感谢您的详细回复。我前段时间解决了这个问题,但我相信你的回答对将来的人会有很大的帮助。
    【解决方案2】:

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

    一旦 SSL/TLS 握手成功完成,不需要额外的工作,只需正常发送剩余的 SMTP 命令,它们将被来回加密。一些客户端在建立 TLS 后会发出新的 HELO/EHLO 命令,以防服务器在加密模式下的能力不同。

    不幸的是,据我所知,NSStream 不支持在流打开后启动 SSL/TLS。这是 NSStream 的限制,是documented by Apple:

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

    [iStream setProperty:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];

    必须先设置属性 打开信息流。

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

    【讨论】:

    • 感谢您的回复。在发布最初的问题后,我进行了更多调查,最终得出了相同的结论。解决方案是为 OpenSSL 套接字编写一个简单的 Objective-C 包装器,并使用它来代替 NSStream。
    猜你喜欢
    • 2013-10-04
    • 2015-10-21
    • 1970-01-01
    • 2012-12-19
    • 1970-01-01
    • 2013-09-14
    • 1970-01-01
    • 2018-07-13
    • 1970-01-01
    相关资源
    最近更新 更多