如何使用Python生成包含指定配置(含alt_names)的CSR?
问题描述
需要使用Python生成符合指定配置的CSR,该配置已可通过OpenSSL实现,具体配置如下:
oid_section = OIDs [ OIDs ] certificateTemplateName= 1.3.6.1.4.1.311.20.2 [ req ] default_bits = 2048 emailAddress = test@gmail.com req_extensions = v3_req x509_extensions = v3_ca prompt = no default_md = sha256 req_extensions = req_ext distinguished_name = dn [ dn ] C=SA OU=3111111117 O=shesh CN = tat-1 [ v3_req ] basicConstraints = CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment [req_ext] certificateTemplateName = ASN1:PRINTABLESTRING:PREZATCA-Code-Signing subjectAltName = dirName:alt_names [alt_names] SN=1-Device|2-234|3-mohamm UID=30000000000000003 title=1000 registeredAddress=Zatca 12 businessCategory=Technology
已尝试使用pyOpenSSL编写代码生成CSR,但无法包含上述配置中的alt_names字段(该字段包含SN、UID等关键属性),尝试的代码如下:
from OpenSSL.SSL import FILETYPE_PEM from OpenSSL.crypto import dump_certificate_request, dump_privatekey,dump_publickey, PKey, TYPE_DSA, X509Req # create public/private key key = PKey() key.generate_key(TYPE_DSA,1028) print(key.to_cryptography_key()) # Generate CSR req = X509Req() req.get_subject().CN = 'localhost' req.get_subject().O = 'XYZ Widgets Inc' req.get_subject().OU = 'IT Department' req.get_subject().L = 'Seattle' req.get_subject().ST = 'Washington' req.get_subject().C = 'US' req.get_subject().emailAddress = 'e@example.com' req.set_pubkey(key) req.sign(key, 'sha256') with open("csr_testo.pem", 'wb+') as f: f.write(dump_certificate_request(FILETYPE_PEM, req)) with open("Private_key_testo.pem", 'wb+') as f: f.write(dump_privatekey(FILETYPE_PEM, key)) with open("public_key_testo.pem", 'wb+') as f: f.write(dump_publickey(FILETYPE_PEM, key))
解决方案
pyOpenSSL的原生API对复杂X.509扩展支持有限,尤其是dirName类型的Subject Alternative Name,需要结合其底层依赖的cryptography库实现精细控制。以下是完整的实现代码:
from OpenSSL import crypto from cryptography import x509 from cryptography.x509.oid import NameOID, ObjectIdentifier from cryptography.hazmat.primitives import serialization # 自定义OID:certificateTemplateName CERT_TEMPLATE_OID = ObjectIdentifier("1.3.6.1.4.1.311.20.2") # 生成2048位RSA密钥(匹配原配置要求) key = crypto.PKey() key.generate_key(crypto.TYPE_RSA, 2048) # 构建CSR的Distinguished Name req = crypto.X509Req() subject = req.get_subject() subject.C = "SA" subject.OU = "3111111117" subject.O = "shesh" subject.CN = "tat-1" subject.emailAddress = "test@gmail.com" # 设置公钥 req.set_pubkey(key) # 用cryptography构建所有扩展 extensions = [] # 1. 添加basicConstraints和keyUsage(对应v3_req段) basic_constraints = x509.BasicConstraints(ca=False, path_length=None) extensions.append( x509.Extension( x509.oid.ExtensionOID.BASIC_CONSTRAINTS, critical=True, value=basic_constraints ) ) key_usage = x509.KeyUsage( digital_signature=True, non_repudiation=True, key_encipherment=True, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False ) extensions.append( x509.Extension( x509.oid.ExtensionOID.KEY_USAGE, critical=True, value=key_usage ) ) # 2. 添加certificateTemplateName扩展 cert_template_ext = x509.UnrecognizedExtension( CERT_TEMPLATE_OID, b"PREZATCA-Code-Signing" # 对应PRINTABLESTRING类型,直接传入字节串 ) extensions.append(cert_template_ext) # 3. 构建alt_names对应的DirectoryName alt_name_dir = x509.Name([ x509.NameAttribute(NameOID.SERIAL_NUMBER, "1-Device|2-234|3-mohamm"), x509.NameAttribute(NameOID.USER_ID, "30000000000000003"), x509.NameAttribute(NameOID.TITLE, "1000"), x509.NameAttribute(NameOID.REGISTERED_ADDRESS, "Zatca 12"), x509.NameAttribute(NameOID.BUSINESS_CATEGORY, "Technology"), ]) # 添加带dirName的subjectAltName扩展 san_ext = x509.SubjectAlternativeName([ x509.DirectoryName(alt_name_dir) ]) extensions.append( x509.Extension( x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME, critical=False, value=san_ext ) ) # 将cryptography扩展转换为pyOpenSSL兼容格式并添加到CSR req.add_extensions([ crypto.X509Extension( ext.get_oid().dotted_string.encode(), ext.critical, ext.value.public_bytes(serialization.Encoding.DER) ) for ext in extensions ]) # 签名CSR req.sign(key, "sha256") # 保存生成的文件 with open("csr.pem", "wb") as f: f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)) with open("private_key.pem", "wb") as f: f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) with open("public_key.pem", "wb") as f: f.write(crypto.dump_publickey(crypto.FILETYPE_PEM, key))
关键说明
- 把原代码中的DSA密钥替换为RSA 2048位,完全匹配OpenSSL配置要求;
- 借助
cryptography实现dirName类型的Subject Alternative Name,这是pyOpenSSL原生API无法直接完成的; - 自定义OID
certificateTemplateName通过UnrecognizedExtension实现,对应配置中的PRINTABLESTRING类型; - 所有扩展严格对齐原OpenSSL配置的参数要求,确保生成的CSR与OpenSSL命令生成的完全一致。
内容的提问来源于stack exchange,提问作者Mohammed almalki
相关产品推荐
相关产品推荐

