AcmeN

RFC8555 自动证书管理环境Python客户端。

特性

AcmeN实现了RFC8555中描述的以下特性:

  • 注册新账户
  • 更新联系方式
  • 轮换账户密钥
  • 注销账户
  • 签发证书
  • 吊销证书

注意: 我们尚未对AcmeN进行完整的测试,如需在生产环境中使用,你需要自行测试其可靠性。另外,AcmeN仍在开发阶段,v0.*.*版本无法保证API向后兼容性。

安装

自0.5.0起,AcmeN已发布到PyPI,你可以使用pip安装AcmeN:

pip install acmen

创建ACME账户

from acmen import AcmeN
acme = AcmeN('account.key', key_passphrase='<your_password>')
acme.register_account(contact=['mailto:xxx@yyy.zzz'])

这将在Let's Encrypt的ACME服务器上创建一个账户,并使用邮箱xxx@yyy.zzz作为联系方式。在进行接下来的大部分操作前,都需要一个ACME账户。当此密钥已经注册过账户时,此方法会更新contact信息,不会因账户已存在而发生异常。

签发证书

AcmeN使用RFC8555中的dns-01http-01tls-alpn-01验证方式验证域名控制权。可以通过调用DNS服务商的API更新或删除DNS记录、自动或手动创建http-01认证文件、创建tls-alpn-01所需的tls服务器。
目前,AcmeN集成了CloudflareGodaddyAliyunDnsPod的API客户端。
以Cloudflare为例,main.py 中给出了签发证书的过程:

import logging
from acmen import AcmeN
from acmen.handlers import CloudflareDnsHandler

logging.basicConfig(level=logging.INFO)

handler = CloudflareDnsHandler(api_token='*****')
acme = AcmeN('account.key')
acme.register_account(contact=['mailto:xxx@yyy.zzz'])
acme.get_cert_by_domain('example.com', ['alt1.example.com', 'alt2.example.com'], handler)

这将使用account.key在默认CA(Let's Encrypt)注册一个ACME账户,使用xxx@yyy.zzz作为联系方式,并为example.com获取证书,且包含alt1.example.com和alt2.example.com两个可选名称(subjectAlternativeName),并通过调用Cloudflare的DNS API来完成认证。
默认情况下,这将使用Cryptography库生成3072位RSA私钥,如果希望使用OpenSSL命令行生成私钥或者希望生成其他类型和长度的私钥,请参阅文档


指定私钥位置

上述实例中,account.key代表账户私钥位置。

如果账户私钥使用密码保护,可以在创建AcmeN实例时传入密码。

from acmen import AcmeN
acme = AcmeN('account.key', key_passphrase='<your_password>')

如果你没有账户私钥,可以使用OpenSSL生成:

openssl genrsa -out account.key 4096

注册ACME账户及更改账户联系方式

from acmen import AcmeN
acme = AcmeN('account.key')
acme.register_account(contact=['mailto:xxx@yyy.zzz'])

此方法将注册新账户并将联系方式设置为xxx@yyy.zzz。若账户已存在,将更新联系方式为xxx@yyy.zzz。
此方法在账户已存在时不会发生异常。


指定证书签发机构(CA)

AcmeN目前内置了来自3个CA机构的5个服务器。三个CA机构是:Let's EncryptBuyPassZeroSSL

from acmen import AcmeN, SupportedCA
acme = AcmeN('account.key', ca=SupportedCA.LETSENCRYPT)

ca的默认值是SupportedCA.LETSENCRYPT,其他有效值是:SupportedCA.LETSENCRYPTSupportedCA.BUYPASSSupportedCA.ZEROSSLSupportedCA.LETSENCRYPT_STAGINGSupportedCA.BUYPASS_STAGING


创建ChallengeHandler

目前AcmeN内置了Cloudflare、Godaddy、Aliyun、Dnspod的DNS API,更多的ChallengeHandler将在未来添加。

from acmen.handlers import CloudflareDnsHandler
# Cloudflare (APIToken):
dns = CloudflareDnsHandler(api_token='<your_token>')

另外,你也可以实现自己的ChallengeHandler,详见ChallengeHandlers


获取证书

有以下两种方式可以获取证书

  • 通过CSR文件获取证书:
from acmen import AcmeN
from acmen.handlers import CloudflareDnsHandler
acme = AcmeN(...)
dns = CloudflareDnsHandler(...)
acme.get_cert_by_csr('<path/to/csr_file>', challenge_handler=dns)
  • 通过域名获取证书:
from acmen import AcmeN, KeyType
from acmen.handlers import CloudflareDnsHandler
acme = AcmeN(...)
dns = CloudflareDnsHandler(...)
acme.get_cert_by_domain('foo.example.com', subject_alternative_name=['foo1.example.com', 'foo2.example.com'], key_type=KeyType.RSA3072, challenge_handler=dns)

其中foo.example.com是证书的CommonName。
subject_alternative_name:DNS名称,指定DNS名称可以创建多域名证书,当不需要SAN时,可传入空列表[]
key_type:证书类型,默认值是3072位RSA证书,你也可以选择使用KeyType.ECC384、KeyType.ECC256、KeyType.RSA4096、KeyType.RSA2048。其中ECC384和ECC256分别使用secp384r1和secp256r1(prime256v1)曲线。
challenge_handler:在上面步骤中创建的challenge_handler。


正常情况下,AcmeN会生成:
密钥文件:{域名}.{时间戳}.{密钥类型}.key
证书文件:{域名}.{时间戳}.{密钥类型}.crt

吊销证书

使用证书私钥吊销证书

使用证书的私钥实例化AcmeN,并调用revoke_cert方法,传入证书文件的路径。

from acmen import AcmeN
acme = AcmeN('cert_private_key.key', ca=...)
acme.revoke_cert('/path/to/cert_file.crt')

cert_file:需要吊销的证书文件的路径。


使用账户密钥吊销证书

from acmen import AcmeN
from acmen.handlers import CloudflareDnsHandler
handler = CloudflareDnsHandler(...)
acme = AcmeN('account.key', ca=...)
acme.revoke_cert('/path/to/cert_file.crt', challenge_handler=handler)

cert_file:需要吊销的证书文件路径。

在使用账户密钥吊销证书时,若Acme服务器缓存了对域名所有权的验证,或签发此证书的账户与当前账户是同一账户,则可以直接吊销。否则AcmeN会使用challenge_handler完成域名所有权验证,然后吊销证书。详情请见文档

账户密钥轮换

以旧密钥创建AcmeN实例后,调用key_change方法:

from acmen import AcmeN
acme = AcmeN('old_key.key')
acme.key_change('/path/to/new_key_file.key', password='')

new_key_file:新密钥文件
password:保护新密钥的密码

注销账户

from acmen import AcmeN
acme = AcmeN(...)
acme.deactivate_account()

注意: 根据RFC8555 Account Deactivation的规定,ACME 不提供 重新启用账户的方法。

使用其他ACME服务商

默认情况下,AcmeN使用Let's Encrypt 提供的证书签发服务。但同时,AcmeN也内置了buypasszerossl 的服务器地址。如需使用其他服务商,可在实例化AcmeN时传入ACME服务器的Directory目录:

from acmen import AcmeN
acme = AcmeN('account.key', ca='https://acme.zerossl.com/v2/DV90')

查找旧版本的文档

目前我们暂未单独提供旧版本的文档,但文档与代码一同进行版本控制,你可以使用git检出之前的版本,在/docs目录下可找到对应的文档。目前文档使用mkdocs渲染,你可以使用mkdocs在/docs文件夹下构建html版本。

国际化域名(IDN)

*目前AcmeN提供实验性国际化域名支持

非英语域名会使用punycode转义后传递给ACME服务器,默认输出文件名仍然是原文。当使用CSR申请证书时,若CSR中包含经punycode编码的CommonName,确定默认输出文件名时会将CN中punycode编码的域名转义回非英语域名。
我并未测试IDN与DNS服务商API的兼容性,而且并不是所有ACME服务商都同意签发IDN证书。

为不同的域名设置不同的Handler

example.com通过Cloudflare进行DNS解析、example.org通过Dnspod进行DNS解析,但需要一个同时包含二者的证书,可使用HandlerSet。

from acmen import AcmeN
from acmen.handlers import *
s = HandlerSet()
s['example.com'] = CloudflareDnsHandler(...)
s['example.org'] = DnspodDnsHandler(...)

a = AcmeN(...)
a.get_cert_by_domain('example.com', subject_alternative_name=['example.org', 'www.example.com'],challenge_handler=s)

通过代理服务器访问

为AcmeN提供网络操作的requests库会遵循http_proxyhttps_proxy环境变量的设置。也可以手动设置代理服务器,如:

from acmen import AcmeN
acme = AcmeN('account.key', proxy='http://localhost:8080')