Python密码学:创建由现有CA签名的证书,并导出

13

我这样创建了一个类似CA的东西:

openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.cert

这给了我两个PEM文件。

然后调用此函数,其中cert_authorityprivate_key是上面生成的数据字符串。

def create_cert(cert_authority, private_key):
    one_day = datetime.timedelta(1, 0, 0)
    # Use our private key to generate a public key
    private_key = serialization.load_pem_private_key(
        private_key.encode("ascii"), password=None, backend=default_backend()
    )
    public_key = private_key.public_key()

    ca = x509.load_pem_x509_certificate(
        cert_authority.encode("ascii"), default_backend()
    )

    builder = x509.CertificateBuilder()
    builder = builder.subject_name(
        x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io")])
    )
    builder = builder.issuer_name(ca.issuer)
    builder = builder.not_valid_before(datetime.datetime.today() - one_day)
    builder = builder.not_valid_after(datetime.datetime.today() + (one_day * 30))
    builder = builder.serial_number(x509.random_serial_number())
    builder = builder.public_key(public_key)

    cert = builder.sign(
        private_key=private_key, algorithm=hashes.SHA256(), backend=default_backend()
    )

    print(cert.public_bytes(serialization.Encoding.PEM))

接着会生成一个看似证书的东西,不过当我将数据复制并粘贴到文件中(并且按照http://srdevspot.blogspot.com/2011/08/openssl-error0906d064pem.html的方法每64行进行一次换行符转换),在验证时出现了以下错误:

$ openssl verify -CAfile ca.crt -untrusted phone.crt
unable to load certificates

希望只是因为我还不熟悉这些内容而已,有简单的解决方法。

最后我想指出,如果cryptography不是最好的选择,我也愿意尝试使用另一个加密库。

编辑:

根据Paul非常有帮助的回复,我现在正在使用它。

def create_cert(cert_authority, private_key):
    one_day = datetime.timedelta(1, 0, 0)
    # Use our private key to generate a public key
    root_key = serialization.load_pem_private_key(
        private_key.encode("ascii"), password=None, backend=default_backend()
    )

    root_cert = x509.load_pem_x509_certificate(
        cert_authority.encode("ascii"), default_backend()
    )

    # Now we want to generate a cert from that root
    cert_key = rsa.generate_private_key(
        public_exponent=65537, key_size=2048, backend=default_backend()
    )
    new_subject = x509.Name(
        [
            x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"),
            x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"New Org Name!"),
        ]
    )
    cert = (
        x509.CertificateBuilder()
        .subject_name(new_subject)
        .issuer_name(root_cert.issuer)
        .public_key(cert_key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(datetime.datetime.utcnow())
        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=30))
        .add_extension(
            x509.SubjectAlternativeName([x509.DNSName(u"somedomain.com")]),
            critical=False,
        )
        .sign(root_key, hashes.SHA256(), default_backend())
    )

    # Dump to scratch
    with open("scratch/phone_cert.pem", "wb") as f:
        f.write(cert.public_bytes(encoding=serialization.Encoding.PEM))

    # Return PEM
    cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM)

    cert_key_pem = cert_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption(),
    )

    return cert_pem, cert_key_pem

这样保存文件并返回创建的证书和私钥的PEM字符串是否正确?

我还发现当我尝试使用openssl verify -verbose -CAfile ca.crt -untrusted phone_cert.pem命令验证已创建的证书时,该命令从未返回--可能是另一个问题,但我会感谢任何想法。

2个回答

23

我看到这里有两个问题。首先,你正在创建另一个自签名证书,因此生成的证书未由CA签名,它本身就是CA。要更正这一点,您需要使用您的CA的私钥(例如,在您的示例中为private_key)进行签名,但您需要创建与新证书关联的私钥,并将其公钥嵌入证书中。

certificate_private_key = <generate an ec or rsa key here>
certificate_public_key = certificate_private_key.public_key()

然后执行

builder = builder.public_key(certificate_public_key)

你的输出存在问题,因为你试图从打印语句中复制和粘贴内容。 cert.public_bytes(serialization.Encoding.PEM) 的输出将是一个带有分隔符和正确PEM行长度的有效X509证书,因此应直接将其写入文件中:

with open("cert.crt", "wb") as f:
    f.write(cert.public_bytes(serialization.Encoding.PEM))

该结果可以使用openssl x509 -noout -text -in cert.crt进行解析。

以下是一个完整的示例,利用cryptography创建自签名根CA并使用该CA签名证书。

import datetime

from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa


root_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
subject = issuer = x509.Name([
    x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
    x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"),
    x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"),
    x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"),
    x509.NameAttribute(NameOID.COMMON_NAME, u"My CA"),
])
root_cert = x509.CertificateBuilder().subject_name(
    subject
).issuer_name(
    issuer
).public_key(
    root_key.public_key()
).serial_number(
    x509.random_serial_number()
).not_valid_before(
    datetime.datetime.utcnow()
).not_valid_after(
    datetime.datetime.utcnow() + datetime.timedelta(days=3650)
).sign(root_key, hashes.SHA256(), default_backend())

# Now we want to generate a cert from that root
cert_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
new_subject = x509.Name([
    x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
    x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Texas"),
    x509.NameAttribute(NameOID.LOCALITY_NAME, u"Austin"),
    x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"New Org Name!"),
])
cert = x509.CertificateBuilder().subject_name(
    new_subject
).issuer_name(
    root_cert.issuer
).public_key(
    cert_key.public_key()
).serial_number(
    x509.random_serial_number()
).not_valid_before(
    datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=30)
).add_extension(
    x509.SubjectAlternativeName([x509.DNSName(u"somedomain.com")]),
    critical=False,
).sign(root_key, hashes.SHA256(), default_backend())

非常感谢 -- 超级有帮助。我在原始帖子中放了一些修改后的代码,看起来更正确吗? - Joenarr Bronarsson
是的,看起来好多了。SubjectKeyIdentifier/IssuerKeyIdentifiers也可以嵌入在这里,以使链路构建更容易,但这并不是必要的。 - Paul Kehrer

9

我必须发表一个答案,因为我是新手,还不能评论

我的实现非常依赖于Paul的答案,那非常有启发性和帮助性。但是我不得不在CA证书上添加一个扩展以使openssl verify -verbose -CAfile ca.crt client.crt正常工作。

.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)添加到根证书生成器中即可解决问题。

ca_crt = x509.CertificateBuilder() \
    .subject_name(subject) \
    .issuer_name(issuer) \
    .public_key(ca_key.public_key()) \
    .serial_number(x509.random_serial_number()) \
    .not_valid_before(datetime.datetime.today() - one_day) \
    .not_valid_after(datetime.datetime.today() + (one_day * 365)) \
    .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) \
    .sign(ca_key, hashes.SHA256(), default_backend())

我做的和Paul一样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接