我正在尝试编写一个python服务器脚本,该脚本应根据其公钥对当前客户端进行身份验证。由于我正在使用扭曲,example in the twisted documenteation让我开始。
虽然我可以使用示例代码生成密钥,连接和通信,但我还没有找到以可用格式获取客户端公钥的方法。在this stackexchange question中,有人从OpenSSL.crypto.PKey
对象中提取公钥,但无法将其转换为可读格式。由于我可以使用PKey
方法访问x509证书的verifyCallback
对象,或者通过self.transport.getPeerCertificate()
从我的协议的任何方法访问,这将是一个很好的方法。 (未接受)答案建议尝试crypto.dump_privatekey(PKey)
。不幸的是,这并没有真正产生预期的结果:
虽然答案中的BEGIN PRIVATE KEY
和BEGIN PRIVATE KEY
可以通过简单的文本替换功能修复,但base64字符串似乎与公钥不匹配。我已按照here提取的openssl rsa -in client.key -pubout > client.pub
提取了公钥。它与dump_privatekey
函数的结果不匹配。
虽然仍有open bug towards OpenSSL on launchpad,但尚未修复。据报道,19个月前,有一些最近(2012年10月)的活动,我没有希望在回购中快速修复。
您是否有任何其他想法如何以与我上面提到的client.pub
文件相当的格式获取公钥?也许存在扭曲或OpenSSL连接特定对象来保存此信息。请注意,我必须将公钥存储在协议对象中,以便以后可以访问它。
抱歉,我没想到我无法将证书与连接关联起来。我已经添加了必须将公钥存储在协议实例中的要求。因此,如J.F.Sebastian建议的那样在peerX509.as_pem()
函数内使用postConnectionCheck
不起作用。此外,至少在python-m2crypto的0.21.1-2ubuntu3版本中,我必须调用peerX509.get_rsa().as_pem()
来获取正确的公钥。使用peerX509.as_pem(None)
(因为peerX509.as_pem()
仍需要密码短语)在PyOpenSSL中产生与crypto.dump_privatekey(PKey)
完全相同的输出。也许有一个错误。
除此之外,答案向我展示了使用以下Echo
协议类编写另一种解决方法的可能方法:
class Echo(Protocol):
def dataReceived(self, data):
"""As soon as any data is received, write it back."""
if self.transport.checked and not self.pubkeyStored:
self.pubkeyStored = True
x509 = m2.ssl_get_peer_cert(self.transport.ssl._ptr())
if x509 is not None:
x509 = X509.X509(x509, 1)
pk = x509.get_pubkey()
self.pubkey = pk.get_rsa().as_pem()
print pk.as_pem(None)
print self.pubkey
self.transport.write(data)
正如您所看到的,它使用了一些我想要阻止的内部类。我在犹豫是否提交一个小补丁,它会在M2Crypto.SSL.TwistedProtocolWrapper中为getCert
类添加TLSProtocolWrapper
方法。即使它被上游接受,它也会破坏我的脚本与m2crypto最简洁的版本之间的兼容性。你会做什么?
嗯,这是一个基于外部系统命令的难看的解决方法,在我看来比访问非公共属性更糟糕。
答案 0 :(得分:6)
以前的一些答案产生(显然是?)正在工作的PEM公钥文件,但据我所知,它们都没有产生与'openssl rsa -pubout -in priv.key'相同的输出。这对我的测试套件非常重要,并且在(0.15.1)PyOpenSSL代码中进行了探讨之后,这对于标准PKey对象和x509.get_pubkey()方法创建的仅公钥的PKey对象都很有效:
from OpenSSL import crypto
from OpenSSL._util import lib as cryptolib
def pem_publickey(pkey):
""" Format a public key as a PEM """
bio = crypto._new_mem_buf()
cryptolib.PEM_write_bio_PUBKEY(bio, pkey._pkey)
return crypto._bio_to_string(bio)
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
print pem_publickey(key)
答案 1 :(得分:2)
M2Crypto
中的openssl rsa -in client.key -pubout > client.pub
命令的模拟(比pyOpenSSL更完整的openssl包装)是:
def save_pub_key(cert, filename):
cert.get_pubkey().get_rsa().save_pub_key(filename)
您可以使用M2Crypto
代替pyOpenSSL
扭曲。要将ssl功能添加到echo服务器:
from twisted.internet import protocol, reactor
class Echo(protocol.Protocol):
def dataReceived(self, data):
self.transport.write(data)
class EchoFactory(protocol.Factory):
def buildProtocol(self, addr):
return Echo()
你可以:
import sys
from twisted.python import log
from M2Crypto import SSL, X509
from M2Crypto.SSL import Checker
from M2Crypto.SSL.TwistedProtocolWrapper import listenSSL
log.startLogging(sys.stderr)
cert = X509.load_cert('client.crt')
check = Checker.Checker(peerCertHash=cert.get_fingerprint('sha1'))
def postConnectionCheck(peerX509, expectedHost):
log.msg("client cert in pem format:\n", peerX509.as_pem())
save_pub_key(peerX509, 'client.pub')
return check(peerX509, host=None) # don't check client hostname
class SSLContextFactory:
def getContext(self):
ctx = SSL.Context()
ctx.load_verify_locations(cafile='ca.crt')
ctx.load_cert(certfile='server.crt', keyfile='server.key',
callback=lambda *a,**kw: 'keyfile passphrase')
ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
depth=9, callback=None)
return ctx
listenSSL(8000, EchoFactory(), SSLContextFactory(),
interface='localhost', reactor=reactor,
postConnectionCheck=postConnectionCheck)
reactor.run()
要尝试,请创建自签名证书:
$ openssl req -new -x509 -nodes -out server.crt -keyout server.key
# NOTE: server.key is unencrypted!
$ cp {server,client}.crt
$ cp {server,client}.key
$ cp {server,ca}.crt
并连接到服务器:
$ openssl s_client -cert client.crt -key client.key -CAfile ca.crt \
-verify 9 -connect localhost:8000 -no_ssl2
服务器将客户端的公钥保存到client.pub
文件。它与openssl
命令创建的命令相同:
$ openssl rsa -in client.key -pubout > openssl_client.pub
$ diff -s {openssl_,}client.pub
答案 2 :(得分:1)
获取公钥的一种可能性是通过外部调用的openssl实例来管理x509证书的pem版本,这是一种丑陋的解决方法:
def extractpublickey(x509):
x509pem = dump_certificate(FILETYPE_PEM,x509)
ossl = Popen(['openssl','x509','-pubkey','-noout'] , stdout=PIPE, stderr=PIPE, stdin=PIPE)
(stdout,_) = ossl.communicate(x509pem)
res = ""
if stdout[:26] != ("-----BEGIN PUBLIC KEY-----") or stdout[-24:] != ("-----END PUBLIC KEY-----"):
raise AttributeError("Could not extract key from x509 certificate in PEM mode: %s"%x509pem)
else:
res = stdout
return res
class Echo(Protocol):
def dataReceived(self, data):
"""As soon as any data is received, write it back."""
if self.transport.getPeerCertificate() == None:
print("unknown client")
else:
print extractpublickey(self.transport.getPeerCertificate())
self.transport.write(data)
答案 3 :(得分:0)
我最终使用来自Crypto.Util.asn1(pyasn1库)的pyOpenSSL和DerSequence类。
这是我的RSAKey类的一个方法(pkey是一个OpenSSL.crypto.PKey实例):
from OpenSSL.crypto import dump_privatekey, FILETYPE_ASN1
from Crypto.PublicKey import RSA
from Crypto.Util.asn1 import DerSequence
from base64 import b64decode, b64encode
...
def fromPKey_PublicKey(self, pkey):
src = dump_privatekey(FILETYPE_ASN1, pkey)
pub_der = DerSequence()
pub_der.decode(src)
self.key = RSA.construct((long(pub_der._seq[1]), long(pub_der._seq[2])))
这里的关键是pub_der._seq中的第一项是零,我们不需要它。 您可以将存储在self.key中的RSA密钥转换为您想要的任何格式:
def toPEM_PublicKey(self):
pemSeq = DerSequence()
pemSeq[:] = [ self.key.key.n, self.key.key.e ]
s = b64encode(pemSeq.encode())
src = '-----BEGIN RSA PUBLIC KEY-----\n'
while True:
src += s[:64] + '\n'
s = s[64:]
if s == '':
break
src += '-----END RSA PUBLIC KEY-----'
return src
我目前正在使用CSpace项目,它使用“ncrypt”库(这是另一个OpenSSL包装器),它不再受支持,它在Linux上提供了SegFault。所以我决定用pyOpenSSL替换ncrypt库,因为我在我的项目DataHaven.NET中使用它。从PEM格式的对等证书中获取公钥确实对我来说是一个问题。现在工作得很好。