我想在我的echo客户端/服务器程序中在客户端验证服务器。我在
上使用python 2.7.12
和ssl
模块
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()
所有文件都在同一目录中。
答案 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 :
sock.accept()
返回)已被包装client.py :
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个自签名证书(彼此之间没有任何关系)。自签名证书意味着:
关于证书的几句话:它们在树中组织:意味着将有一个根节点,也称为根CA 证书。该节点可能有多个子节点(子节点表示它的父节点已经签名)。这些节点也可以是 CA 或最终用户( EU )证书。每个 CA 证书也可能有子项,并且有我们的树。它的叶子是 EU 证书。 根CA 与叶证书(由根CA ,中间CA 和 EU 组成的路径之间的路径)被称为证书链。
自签名证书可以表示为由单个节点组成的树。请注意,作为 CA 证书(如我们的情况),它也可用于签署其他证书(它可以有孩子)。
证书验证 - 验证证书时确保证书是&#34;谁&#34;它声称是。这是通过检查它的父 CA 来完成的(并且递归地检查链中的所有 CA 直到达到根CA ) 。如果一切都确定,则证书有效。这当然是一个非常简单的版本,主题相当复杂,但有很多信息可以在互联网上找到。毋庸置疑,执行验证的实体需要能够访问链中的 CA 。您可以将Web浏览器作为示例,其证书&#34; vault&#34;包含:
[IBM]: An overview of the SSL or TLS handshake(尽管还有很多其他地方)简要介绍了 SSL 连接的工作原理。通常,安全系统将为其每个客户端发出(唯一的)证书( EU );该证书将绑定到客户端计算机(* IP8地址或 FQDN );这里 CRL 和 OCSP 值得一提。
回到问题:因为我们有一个特定的情况(2个证书,其中每个 CA ,但它们也被用作 EU ),它可能不是如此明显,但我会尽力解释。考虑到证书验证将在两个通信端(双向)发生,两个证书都必须在两个应用程序中加载。例如,在 server app:
中显然,对于客户端,它将是另一种方式。