参考 OpenSSL Cookbook - Creating a Private Certification Authority,使用 OpenSSL 命令行可以创建一个私有CA。
本文则基于python的cryptography库,编写一个简单的私有CA,并用其来签发一套测试使用的证书。
创建自签发的root CA
普通证书的签发需要使用 CA的私钥对其进行私钥加密,root CA由于位于该条信任链的起始,只能使用自身的私钥对其进行签发,即自签发证书。
自签发证书遵循以下步骤
- 生成私钥
- 使用私钥对自身进行签发
genRootCA.py:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
import datetime
# 生成 2048 位 rsa 私钥
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# 保存私钥到磁盘,并使用 key pass 进行加密
with open("rootca.key.secure", "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(b"12345678")
))
# Various details about who we are. For a self-signed certificate the
# subject and issuer are always the same.
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"CN"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Sichuan"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Chengdu"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"ctyun elb"),
x509.NameAttribute(NameOID.COMMON_NAME, u"test root CA"),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
# Our certificate will be valid for 1 years default
datetime.datetime.utcnow() + datetime.timedelta(365)
).add_extension(
x509.SubjectAlternativeName([x509.DNSName(u"localhost")]),
critical=False,
# 使用root CA的私钥签发root CA证书
).sign(key, hashes.SHA256())
# 保存证书到磁盘
with open("rootca.pem", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
执行程序
python3 genRootCA.py
得到受密码保护的RSA私钥(rootca.key.secure)和证书(rootca.pem)
使用root CA签发普通证书
root CA我们使用上一步得到的 rootca.key.secure 和 rootca.pem。
普通证书签发遵循以下步骤
- 加载CA私钥
- 生成私钥
- 使用CA私钥对证书进行签发
genCert.py:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
import datetime
# 加载 root CA 的证书与私钥
with open("rootca.pem", 'rb') as f:
datas = f.read()
cacert = x509.load_pem_x509_certificate(datas)
with open("rootca.key.secure", 'rb') as f:
datas = f.read()
cakey = serialization.load_pem_private_key(datas,b'12345678')
# 生成 2048 位 rsa 私钥
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# 保存私钥到磁盘,并使用 key pass 进行加密
with open("www.example.com.key.secure", "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(b"12345678")
))
# 保存私钥到磁盘,不加密
with open("www.example.com.key.unsecure", "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
))
# Various details about who we are. For a self-signed certificate the
# subject and issuer are always the same.
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"CN"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Sichuan"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Chengdu"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"ctyun elb"),
x509.NameAttribute(NameOID.COMMON_NAME, u"www.example.com"),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
cacert.subject
).public_key(
key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
# Our certificate will be valid for 1 years default
datetime.datetime.utcnow() + datetime.timedelta(365)
).add_extension(
x509.SubjectAlternativeName([x509.DNSName(u"example.com")]),
critical=False,
# 使用root CA的私钥签发root CA证书
).sign(cakey, hashes.SHA256())
# 保存证书到磁盘
with open("www.example.com.pem", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
执行程序
python3 genCert.py
得到受密码保护的RSA私钥(www.example.com.key.secure)、不受密码保护的RSA私钥(www.example.com.key.unsecure)和证书(www.example.com.pem)
这里提供一个更加完善的程序:orzPrivateCA