Python / sockets / ssl EOF违反协议

时间:2017-06-03 12:05:53

标签: python sockets ssl

我想在我的echo客户端/服务器程序中在客户端验证服务器。我在

上使用python 2.7.12ssl模块
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.5 LTS
Release:        14.04
Codename:       trusty

我使用openssl命令生成了客户端和服务器的证书和密钥:

openssl req -new -x509 -days 365 -nodes -out client.pem -keyout client.key
openssl req -new -x509 -days 365 -nodes -out server.pem -keyout server.key

openssl库本身的版本和python使用的openssl是相同的:

openssl version -a
OpenSSL 1.0.1f 6 Jan 2014
built on: Fri Sep 23 12:19:57 UTC 2016
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx) 
compiler: cc -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/usr/lib/ssl"

python -c "import ssl; print ssl.OPENSSL_VERSION"
OpenSSL 1.0.1f 6 Jan 2014

但是,下面的代码显示了服务器端的一些错误:EOF occurred in violation of protocol (_ssl.c:1645)(但服务器仍然有效),并且在客户端:

Traceback (most recent call last):
  File "/http_ssl_client.py", line 36, in <module>
    if not cert or ('commonName', 'test') not in cert['subject'][4]: raise Exception("Invalid SSL cert for host %s. Check if this is a man-in-themiddle attack!" )
Exception: Invalid SSL cert for host %s. Check if this is a man-in-themiddle attack!
{'notBefore': u'Jun  3 11:54:21 2017 GMT', 'serialNumber': u'BBDCBEED69655B6E', 'notAfter': 'Jun  3 11:54:21 2018 GMT', 'version': 3L, 'subject': ((('countryName', u'pl'),), (('stateOrProvinceName', u'test'),), (('localityName', u'test'),), (('organizationName', u'test'),), (('organizationalUnitName', u'test'),), (('commonName', u'test'),), (('emailAddress', u'test'),)), 'issuer': ((('countryName', u'pl'),), (('stateOrProvinceName', u'test'),), (('localityName', u'test'),), (('organizationName', u'test'),), (('organizationalUnitName', u'test'),), (('commonName', u'test'),), (('emailAddress', u'test'),))}

服务器代码:

#!/bin/usr/env python
import socket
import ssl

def main():
    HOST = '127.0.0.1'
    PORT = 1234

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((HOST, PORT))
    sock.listen(5)

    while True:
        conn = None
        client_sock, addr = sock.accept()
        try:
            ssl_client = ssl.wrap_socket(client_sock, server_side=True, certfile="server.pem", keyfile="server.key", ssl_version=ssl.PROTOCOL_TLSv1_2)
            data =  ssl_client.read(1024)
            print data
            ssl_client.write(data)
        except ssl.SSLError as e:
            print(e)
        finally:
            if conn:
                conn.close()
if __name__ == '__main__':
    main()

客户端:

#!/bin/usr/env python
import socket
import ssl

if __name__ == '__main__':

    HOST = '127.0.0.1'
    PORT = 1234

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.verify_mode = ssl.CERT_REQUIRED
    context.load_verify_locations('server.pem')

    if ssl.HAS_SNI:
        secure_sock = context.wrap_socket(sock, server_hostname=HOST)
    else:
        secure_sock = context.wrap_socket(sock)

    cert = secure_sock.getpeercert()
    print cert

    if not cert or ('commonName', 'test') not in cert['subject'][4]: raise Exception("Error" )

    secure_sock.write('hello')

    print secure_sock.read(1024)

    secure_sock.close()
    sock.close()

所有文件都在同一目录中。

1 个答案:

答案 0 :(得分:4)

我将要解决我发现的问题(尽管有些人已经被其他人发现,并且已经修复)。请注意,我已尝试使用 Python 2.7.10 2.7.13 Win 10 em> 3.4.4 , 3.5.3 OpenSSL 1.0.2d 1.0.2j (w / wo fips ):

  • server.py

    • conn 变量初始化为 None 并且就是这样。 finally 下的 if 子句是无用的。 client_sock 应该进行测试
    • 提示:服务器套接字( sock )可能被包装(而不是 client_sock );这样 client_sock (由sock.accept()返回)已被包装
  • client.py

    • 1 st 一个是次要的:代码段中的代码(raise Exception("Error"))与追溯中的代码(raise Exception("Invalid SSL cert for host %s. Check if this is a man-in-themiddle attack!")
    • 不同
    • 搜索证书属性的方式。正如@TomaszPlaskota注意到的, commonName 元组索引是错误的。这是我机器上的漂亮打印的证书(字段值可能会/会有所不同):

      {
          'issuer': ((('countryName', 'AU'),),
                     (('stateOrProvinceName', 'Some-State'),),
                     (('localityName', 'CJ'),),
                     (('organizationName', 'Internet Widgits Pty Ltd'),),
                     (('organizationalUnitName', 'OU'),),
                     (('commonName', 'cfati-e5550-0'),),
                     (('emailAddress', 'a@a.com'),)),
          'notAfter': 'Jun  5 17:21:03 2018 GMT',
          'notBefore': 'Jun  5 17:21:03 2017 GMT',
          'serialNumber': 'C4A03B2BE4F959A9',
          'subject': ((('countryName', 'AU'),),
                      (('stateOrProvinceName', 'Some-State'),),
                      (('localityName', 'CJ'),),
                      (('organizationName', 'Internet Widgits Pty Ltd'),),
                      (('organizationalUnitName', 'OU'),),
                      (('commonName', 'cfati-e5550-0'),),
                      (('emailAddress', 'a@a.com'),)),
          'version': 3
      }
      

      当证书不完整时,可能存在(至少在理论上)。更有力的检查形式是:

      def check_certificate(cert, field=("commonName", "test")):
          if not cert:
              return False
          for pairs in cert.get("subject", ()):
              if field in pairs:
                  return True
          return False
      
      # ....
      
      if not check_certificate(cert):
          raise Exception("Error")
      
    • 处理错误的方法。如果服务器证书不是 OK ,则只会引发错误,该错误会中断通信并异常退出客户端程序。当服务器尝试写回客户端(ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:590))时,服务器端会触发ssl_client.write(data)(来自您的其他问题:[SO]: Mutual ssl authentication in simple ECHO client/server [Python / sockets / ssl modules], ssl.SSLEOFError: EOF occurred in violation of protocol)。
      虽然在程序终止的某些情况下, OS 会清理资源,但建议始终从代码中清除。所以,不要只是提出异常,而是做一些事情(如@JamesKPolk建议的那样):

      plain_sock = secure_sock.unwrap()
      plain_sock.shutdown(socket.SHUT_RDWR)
      plain_sock.close()
      # Any other cleanup action here
      
  • 最大的问题是您使用的是2个自签名证书(彼此之间没有任何关系)。自签名证书意味着:

    • 它是自己的签名者(或父母)。这就是为什么 Issuer Subject 字段相同(或更严格:颁发者密钥标识符主题标识符扩展名相同)
    • 它是 CA (证书颁发机构)证书(如果查看基本约束,您会注意到主题类型 CA )。这就像前子弹的结果

关于证书的几句话:它们在中组织:意味着将有一个根节点,也称为根CA 证书。该节点可能有多个子节点(子节点表示它的父节点已经签名)。这些节点也可以是 CA 最终用户 EU )证书。每个 CA 证书也可能有子项,并且有我们的树。它的叶子是 EU 证书。 根CA 与叶证书(由根CA 中间CA EU 组成的路径之间的路径)被称为证书链
自签名证书可以表示为由单个节点组成的树。请注意,作为 CA 证书(如我们的情况),它也可用于签署其他证书(它可以有孩子)。

证书验证 - 验证证书时确保证书是&#34;谁&#34;它声称是。这是通过检查它的父 CA 来完成的(并且递归地检查链中的所有 CA 直到达到根CA ) 。如果一切都确定,则证书有效。这当然是一个非常简单的版本,主题相当复杂,但有很多信息可以在互联网上找到。毋庸置疑,执行验证的实体需要能够访问链中的 CA 。您可以将Web浏览器作为示例,其证书&#34; vault&#34;包含:

  • 可选:提供给您的证书( EU ),以便能够连接到某些网站(例如个人证书
  • 验证Web服务器(受信任的根/中间证书颁发机构)提供的其他证书所需的证书( CA

[IBM]: An overview of the SSL or TLS handshake(尽管还有很多其他地方)简要介绍了 SSL 连接的工作原理。通常,安全系统将为其每个客户端发出(唯一的)证书( EU );该证书将绑定到客户端计算机(* IP8地址或 FQDN );这里 CRL OCSP 值得一提。

回到问题:因为我们有一个特定的情况(2个证书,其中每个 CA ,但它们也被用作 EU ),它可能不是如此明显,但我会尽力解释。考虑到证书验证将在两个通信端(双向)发生,两个证书都必须在两个应用程序中加载。例如,在 server app:

显然,对于客户端,它将是另一种方式。