參考 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