3

我目前正在尝试编写一个 python 服务器脚本,该脚本应该根据其公钥对当前客户端进行身份验证。由于我使用的是 twisted,因此 twisted 文档中的示例让我开始了。

虽然我可以使用示例代码生成密钥、连接和通信,但我还没有找到以可用格式获取客户端公钥的方法。在这个 stackexchange 问题中,有人从对象中提取公钥,OpenSSL.crypto.PKey但无法将其转换为可读格式。由于我可以在方法中或通过我的协议的任何方法访问PKeyx509 证书的对象,这将是一个不错的方法。(不接受)答案建议尝试. 不幸的是,这并没有真正产生预期的结果:虽然答案中的and可以通过简单的文本替换函数来修复,但 base64 字符串似乎与公钥不匹配。我已经提取了公钥verifyCallbackself.transport.getPeerCertificate()crypto.dump_privatekey(PKey)BEGIN PRIVATE KEYBEGIN PRIVATE KEYopenssl rsa -in client.key -pubout > client.pub正如这里提到的。dump_privatekey它与函数的结果不匹配。

虽然在 launchpad 上仍然存在针对 OpenSSL 的开放错误,但尚未修复。这是 19 个月前报道的,最近(2012 年 10 月)有一些活动,我不希望在回购中快速修复。

client.pub您是否还有其他想法如何以与我上面提到的文件相当的格式获取公钥?也许有一个扭曲的或 OpenSSL 连接特定的对象来保存此信息。请注意,我必须将公钥存储在协议对象中,以便以后可以访问它。

为什么没有答案被接受?

JF Sebastian 的 M2Crypto

抱歉,我没有想到我无法将证书与连接相关联的可能性。我添加了必须将公钥存储在协议实例中的要求。因此,按照 JF 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.TwistedProtocolWrappergetCert中的类添加一个方法。TLSProtocolWrapper即使它被上游接受,它也会破坏我的脚本与除了最先进的 m2crypto 版本之外的任何版本的兼容性。你会怎么做?

我的外部 OpenSSL 调用

好吧,这是一个基于外部系统命令的丑陋解决方法,在我看来,这比访问非公共属性还要糟糕。

4

4 回答 4

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)
于 2015-06-19T03:27:23.353 回答
2

openssl rsa -in client.key -pubout > client.pub中的命令模拟M2Crypto(比 pyOpenSSL 更完整的 openssl 包装器)是:

def save_pub_key(cert, filename):
    cert.get_pubkey().get_rsa().save_pub_key(filename)

你可以用twistedM2Crypto代替。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
于 2012-12-14T14:51:53.957 回答
1

我最终使用 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 格式的对等证书中获取公钥对我来说确实是个问题。现在它工作得很好。

于 2013-06-24T07:04:42.120 回答
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)
于 2012-12-17T05:04:15.710 回答