【问题标题】:Python Explicit FTP doesn't negotiate TLSPython 显式 FTP 不协商 TLS
【发布时间】:2017-08-24 15:17:34
【问题描述】:

我正在尝试通过 FTP TLS 显式将文件传输到主机,但不知道如何处理证书。事务的 WinSCP 日志显示处理了 TLS 协商并验证了证书。但我的 Python 脚本无法做到这一点。我知道证书指纹和密码,但不知道如何实现。

我的脚本:

import ftplib
import ssl


def main():
    ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1_2)
    ftps = ftplib.FTP_TLS(context=ctx)

    try:
        ftps.set_debuglevel(2)
        print(ftps.connect(host))
        ftps.auth()
        print(ftps.login(username,password))
        print("1")
        ftps.prot_p()
        print("2")
        #ftps.cwd('/')
        print("3")
        print (ftps.retrlines('LIST'))
        print("4")
        ftps.storbinary("STOR test.csv", open('C:\\test.csv', 'rb'))
        print("5")
    except Exception as ex:
        print("error: ")
        print(ex)

    ftps.close()

    input('Hit <ENTER> to close...')


if __name__ == "__main__":
    main()

这是我的输出,最后一行是 Python 错误:

*get* '220 Microsoft FTP Service\n'
*resp* '220 Microsoft FTP Service'
220 Microsoft FTP Service
*cmd* 'AUTH TLS'
*put* 'AUTH TLS\r\n'
*get* '234 AUTH command ok. Expecting TLS Negotiation.\n'
*resp* '234 AUTH command ok. Expecting TLS Negotiation.'
*cmd* 'USER xxx'
*put* 'USER xxx\r\n'
*get* '331 Password required\n'
*resp* '331 Password required'
*cmd* 'PASS ********'
*put* 'PASS ********\r\n'
*get* '230 User logged in.\n'
*resp* '230 User logged in.'
230 User logged in.
1
*cmd* 'PBSZ 0'
*put* 'PBSZ 0\r\n'
*get* '200 PBSZ command successful.\n'
*resp* '200 PBSZ command successful.'
*cmd* 'PROT P'
*put* 'PROT P\r\n'
*get* '200 PROT command successful.\n'
*resp* '200 PROT command successful.'
2
3
*cmd* 'TYPE A'
*put* 'TYPE A\r\n'
*get* '200 Type set to A.\n'
*resp* '200 Type set to A.'
*cmd* 'PASV'
*put* 'PASV\r\n'
*get* '227 Entering Passive Mode (x,x,x,x,4,4).\n'
*resp* '227 Entering Passive Mode (x,x,x,x,4,4).'
*cmd* 'LIST'
*put* 'LIST\r\n'
*get* '125 Data connection already open; Transfer starting.\n'
*resp* '125 Data connection already open; Transfer starting.'
*get* '226 Transfer complete.\n'
*resp* '226 Transfer complete.'
226 Transfer complete.
4
*cmd* 'TYPE I'
*put* 'TYPE I\r\n'
*get* '200 Type set to I.\n'
*resp* '200 Type set to I.'
*cmd* 'PASV'
*put* 'PASV\r\n'
*get* '227 Entering Passive Mode (x,x,x,x,4,3).\n'
*resp* '227 Entering Passive Mode (x,x,x,x,4,3).'
*cmd* 'STOR test.csv'
*put* 'STOR test.csv\r\n'
*get* '125 Data connection already open; Transfer starting.\n'
*resp* '125 Data connection already open; Transfer starting.'
error: 
The read operation timed out

【问题讨论】:

  • 请显示您收到的任何错误消息。
  • 我编辑了我的帖子以包含错误日志。
  • 根据更新,您声称根本没有 TLS 协商问题。如果有的话,您甚至无法在 AUTH TLS 之后发出 USER 命令。问题是在第二次数据传输期间数据连接超时,这可能是由中间的防火墙或服务器上的某些防病毒软件或类似原因引起的。但这不是 TLS 协商问题。
  • 但是如果我记录一个 WinSCP 会话,我可以看到证书验证正在发生,而我的 Python 脚本似乎不会发生这种情况。不过,我会向我们的安全人员询问有关防火墙的问题。
  • 不可能是防火墙问题,因为传输是通过 WinSCP 进行的。

标签: python ssl ftp


【解决方案1】:

交易的 WinSCP 日志显示处理了 TLS 协商并验证了证书。但我的 Python 脚本无法做到这一点。我知道证书指纹和密码,但不知道如何实现。

您的 Python 脚本不会像您假设的那样在 TLS 协商中失败。

客户端使用AUTH TLS 命令请求从普通连接升级到 TLS,该命令被服务器接受,可以在日志中看到:

*put* 'AUTH TLS\r\n'
*get* '234 AUTH command ok. Expecting TLS Negotiation.\n'

之后 TLS 协商(即 TLS 握手)完成。如果握手失败,客户端将中止。但是握手成功了,所以客户端可以继续执行更多的命令,这些命令也被服务器接受:

*put* 'USER xxx\r\n'
*get* '331 Password required\n'

因此,TLS 协商没有问题。数据传输本身也不是问题,可以看出客户端成功从服务器传输数据,即目录列表:

*put* 'PASV\r\n'
*get* '227 Entering Passive Mode (x,x,x,x,4,4).\n'
*put* 'LIST\r\n'
*get* '125 Data connection already open; Transfer starting.\n'
*get* '226 Transfer complete.\n'

只有在将文件传输到服务器时才会失败,因为服务器没有在预期的时间内回复:

*put* 'STOR test.csv\r\n'
*get* '125 Data connection already open; Transfer starting.\n'
error: 
The read operation timed out

目前尚不清楚服务器为何无法响应。一个可能的原因可能是文件需要由某些应用程序(如防病毒软件)处理,然后服务器才能成功响应,并且此处理时间过长。

【讨论】:

  • 有解决办法吗?不过,这似乎很奇怪,因为 WinSCP 传输完成得非常快,而且 Python 脚本需要很长时间才会超时。
  • 我假设它与证书验证有关,因为 WinSCP 日志显示正在发生而 Python 调试输出没有。该证书验证就是我所包含的“TLS协商”。
  • 您可能会注意到 WinSCP 日志和 Python 日志之间的许多其他差异。它们记录不同的事情或以不同的方式记录相同的事情——Python 根本不会记录发生了 TLS 握手,尽管它发生了。尽管确实传输了数据,但它也不会记录传输了多少数据。
  • 有解决方法吗? - 由于我不知道原始问题的原因,因此很难说解决方法可能是什么。您是否将完全相同的文件传输到与 WinSCP 相同的文件夹中?
  • 是的。相同的文件,相同的位置,相同的系统。
【解决方案2】:

问题出在文件传输后打开套接字。更具体地说,shutdown() 方法似乎在等待服务器查看是否可以关闭套接字。在这种情况下,服务器不会应答。该文件确实被传输,但套接字问题在收到“文件传输完成”响应之前会引发错误。

看起来像这样(发送文件到服务器): 1.建立FTP TLS 2. 客户端发出 STOR 命令 3. 创建一个安全套接字 4.文件传输 5. 插座被拆开处理。 6. 从服务器收到“传输完成”响应。

但是第 5 步的问题阻止了第 6 步的发生。

我不确定这是否是错误。一点研究表明,可以关闭套接字而不是关闭套接字,并且服务器可能不会响应套接字关闭请求。

我的解决方案是修改我的本地 python 库来处理它。

【讨论】:

    【解决方案3】:

    评论
    ssl.create_default_context() 导致错误:“[SSL: CERTIFICATE_VERIFY_FAILED]”


    ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)
    为给定目的返回具有默认设置的新 SSLContext 对象。

    cafile、capath、cadata 代表可选的 CA 证书,用于证书验证,如 SSLContext.load_verify_locations()。如果这三个都是None,这个函数可以选择信任系统默认的CA证书。

    如下使用:

    ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
    

    问题:不知道如何处理证书

    使用ctx = ssl.load_cert_chain(certfile, keyfile=None, password=None),
    你已经有一个context=ctx

    class ftplib.FTP_TLS(host=”, user=”, passwd=”, acct=”, keyfile=None, certfile=None, context=None, timeout=None, source_address=None)

    按照 RFC 4217 中的描述为 FTP 添加 TLS 支持的 FTP 子类。
    context= 是一个 ssl.SSLContext 对象,它允许捆绑 SSL 配置选项、证书私有键到一个单一的(可能长期存在的)结构。

    keyfile=certfile= 是上下文的传统替代方案 - 它们可以分别指向用于 SSL 连接的 PEM 格式的私钥和证书链文件。


    自 3.6 版起已弃用
    keyfile 和 certfile 已弃用,支持 context。请改用ssl.SSLContext.load_cert_chain(),或让 ssl.create_default_context() 为您选择系统信任的 CA 证书。

    【讨论】:

    • 我使用的是 Python 3.5。不过需要明确的是,我没有证书文件……我正在验证主机的证书。
    • ssl.SSLContext.load_cert_chain() 需要一个证书文件路径,我没有...我没有使用私有证书。
    • 调用 ssl.create_default_context() 会导致错误:“[SSL: CERTIFICATE_VERIFY_FAILED]”
    • @Mike:我明白了你的观点,它必须是 PEM 格式。目前我不知道是否可以从您可用的 “证书指纹和密码” 构建 PEM 证书。
    • 如果 TLS 握手有问题,它在 AUTH TLS 中已经失败。日志显示在那之后它会愉快地继续,所以这不是 TLS 握手问题 - 无论 OP 声称什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-18
    相关资源
    最近更新 更多