3

私は現在、公開鍵に基づいて現在のクライアントを認証するPythonサーバースクリプトを作成しようとしています。ツイストを使用しているので、ツイストドキュメンテーションの例から始めました。

サンプルコードを使用してキーを生成し、接続して通信することはできますが、クライアントの公開キーを使用可能な形式で取得する方法はまだ見つかりません。このスタック交換の質問では、誰かがオブジェクトから公開鍵を抽出しOpenSSL.crypto.PKeyますが、それを読み取り可能な形式に変換することはできません。メソッド内またはプロトコルの任意のメソッドを介しPKeyてx509証明書のオブジェクトにアクセスできるので、これは良い方法です。(受け入れられない)答えは試してみることを示唆しています。残念ながら、これは実際には期待される結果をもたらしません。答えのとは簡単なテキスト置換関数で修正できますが、base64文字列は公開鍵と一致しないようです。公開鍵を抽出しましたverifyCallbackself.transport.getPeerCertificate()crypto.dump_privatekey(PKey)BEGIN PRIVATE KEYBEGIN PRIVATE KEYopenssl rsa -in client.key -pubout > client.pubここで述べたように。関数の結果と一致しませんdump_privatekey

ランチパッドのOpenSSLに対する未解決のバグはまだありますが、まだ修正されていません。これは19か月前に報告されたもので、最近(2012年10月)のアクティビティがいくつかあります。リポジトリが迅速に修正される見込みはありません。

client.pub上記のファイルに匹敵する形式で公開鍵を取得する方法について、他に何かアイデアはありますか?おそらく、この情報を保持するツイストまたはOpenSSL接続固有のオブジェクトがあります。後でアクセスできるように、公開鍵をプロトコルオブジェクトに保存する必要があることに注意してください。

なぜ回答が受け入れられないのですか?

JFセバスティアンによるM2Crypto

申し訳ありませんが、証明書を接続に関連付けることができない可能性については考えていませんでした。プロトコルインスタンス内に公開鍵を保存する必要があるという要件を追加しました。したがって、 JFセバスティアンによって提案され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

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)

ツイストM2Crypto代わりに使えます。pyOpenSSLSSL 機能をエコー サーバーに追加するには、次の手順を実行します。

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を提供します. そこで、DataHaven.NET というプロジェクトで使用しているため、ncrypt ライブラリを pyOpenSSL に置き換えることにしました。そして、PEM 形式のピア証明書から公開鍵を取得することは、私にとって本当に問題でした。今では問題なく動作しています。

于 2013-06-24T07:04:42.120 に答える
1

公開鍵を取得する 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 に答える