使用Certbot的ACME模块申请证书
前言
我常用的网站要手机验证了。
代码
这里使用buypass
的证书,最多五个子域名,这个脚本使用DNS验证。
import json
import datetime
from acme import messages
from acme import challenges
from acme import client
from acme import messages
import josepy as jose
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def new_csr(domain):
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
with open(domain[0] + ".key", "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
))
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, domain[0]),
])).add_extension(
x509.SubjectAlternativeName([x509.DNSName(i) for i in domain]),
critical=False,
).sign(key, hashes.SHA256())
return csr.public_bytes(serialization.Encoding.PEM)
def main():
domain = ["your_domain_list"]
# 生成csr
csr = new_csr(domain)
# 加载用户秘钥
# RSA 2048bits-4096bits
with open("./private.key", "r") as f:
key = load_pem_private_key(f.read().encode("utf-8"), password=None)
# 获取jwk
acc_key = jose.JWKRSA(key=key)
net = client.ClientNetwork(acc_key, user_agent="acm/0.0.1")
# 本地测试用
# net.verify_ssl = False
# 取得acme目录
directory = messages.Directory.from_json(net.get("https://api.buypass.com/acme/directory").json())
# directory = messages.Directory.from_json(net.get("https://acme-v02.api.letsencrypt.org/directory").json())
client_acme = client.ClientV2(directory, net=net)
# 加载账户信息
# with open("./account.json", "r") as f:
# user = messages.RegistrationResource.json_loads(f.read())
# client_acme.query_registration(user)
# 首次注册
regr = client_acme.new_account(
messages.NewRegistration.from_data(
email=("[email protected]"),
terms_of_service_agreed=True,
)
)
# 保存账户信息
with open("./account.json", "w") as f:
f.write(json.dumps(regr.to_json()))
# 新建订单
order = client_acme.new_order(csr)
challenges_list = []
for authz in order.authorizations:
for i in authz.body.challenges:
# 选取DNS验证
if isinstance(i.chall, challenges.DNS01):
response, validation = i.response_and_validation(client_acme.net.key)
challenges_list.append([i, response])
print("[DNS]", "_acme-challenge." + authz.body.identifier.value)
print("[DNS]", validation)
while input("ok? (y) ") != "y":
pass
# 通知准备验证
for i in challenges_list:
client_acme.answer_challenge(i[0], i[1])
print("[INFO]", "wait...")
# 自动轮询2分钟并取回证书
finalized_orderr = client_acme.poll_and_finalize(order, deadline=datetime.datetime.now() + datetime.timedelta(minutes=2))
print("[OUT]", finalized_orderr.fullchain_pem)
with open(domain[0] + ".crt", "w") as f:
f.write(finalized_orderr.fullchain_pem)
pass
if __name__ == "__main__":
main()
JSON Web Key
使用JSON
的结构来承载公钥。(RFC7517)
import json
import base64
import binascii
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def b64(text):
return base64.urlsafe_b64encode(text).decode('utf8').replace("=", "")
def get_jwk(path):
with open(path, "r") as f:
pri = load_pem_private_key(f.read().encode("utf-8"), password=None)
pub = pri.public_key().public_numbers()
e = "{:x}".format(pub.e)
e = "0{0}".format(e) if int(e) % 2 else e
e = b64(binascii.unhexlify(e))
n = b64(binascii.unhexlify(hex(pub.n)[2:]))
return {"n": n, "e": e, "kty": "RSA"}
if __name__ == "__main__":
print(get_jwk("./private.key"))
使用了RSA
的模数与指数来代表一份公钥,如果是椭圆则是基点坐标。