2022年1月

前言

我常用的网站要手机验证了。

代码

这里使用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的模数与指数来代表一份公钥,如果是椭圆则是基点坐标。

前言

接上一篇文章,本文章将讲述一个二/多级CA与OCSP服务器的搭建。

通常的CA架构

通常CA为多级架构,由根CA签署若干个不同用途的子CA,再由子CA签发给客户,以本站的证书为例,它是一个两级CA架构:

105252.png

二级从名字可以看出是专门于签署DV SSL证书的。一般CA机构会颁发三种用途的CA,分别是用于SSL,邮件/文件签名和代码签名。
要实现上面三种CA,首先了解一下扩展密钥用法,扩展密钥用法指定了CA及其所颁发证书的用途,在OpenSSL中有如下用法:

serverAuth             SSL服务器验证
clientAuth             SSL客户端验证
codeSigning            代码签名
emailProtection        邮件加密
timeStamping           可信时间戳
OCSPSigning            OCSP签名
ipsecIKE               密钥交换协议
msCodeInd              Microsoft 个人代码签名
msCodeCom              Microsoft 商业代码签名
msCTLSign              Microsoft 信任列表签名
msEFS                  Microsoft Windows文件系统加密
(RFC 5280)(https://www.openssl.org/docs/manmaster/man5/x509v3_config.html)
题外话:在Windows中,内核驱动必须被微软交叉签名的CA所签署的证书所签署,在早期有被泄露出来的代码签名证书,可以通过个人搭建时间戳服务器来绕过签名的时间戳认证,即让系统认为签署的时间在证书的有效期中,从而实现加载未正儿八经签名的内核驱动。(HT Srl)

在OpenSSL配置文件中指定extendedKeyUsage即可为证书赋予不同的用途。其实这些属性代表的是oid,即对象标识符,除了通用的oid外,还用于证书私有策略。
比方说本站证书中的1.3.6.1.4.1.6449.1.2.2.64,前缀1.3.6.1.4.1.为命名空间,6449代表私营企业编号(PEN,由IANA分配,http://www.iana.org/assignments/enterprise-numbers),这里是查询到的是Sectigo Limited,.1.2.2.64代表私有的数字签名证书颁发策略。

CRLOCSP

为保证证书的有效性,引入了CRLOCSP
CRL证书吊销列表是一个由CA签名的结构化列表文件,包含CA信息,吊销的证书序列号和日期,和下一次更新时间,一般情况下这个文件通过http提供,由操作系统下载导入,并在到达更新时间时更新。
OCSP在线证书状态协议是继CRL后出现的证书状态检查协议,解决了要下载CRL和实时性问题,一般使用http协议承载,返回三个状态:GOOD,REVOKED,UNKNOWN
OCSP虽然解决了大部分CRL所带来的问题,但是OCSP会像DNS一样存在隐私问题,所以一般会让服务器缓存一个最近的OCSP响应来解决,此外,OCSP还容易遭到复读机攻击(重放),解决方法类似于解决CSRF问题一样,由客户端请求时附带一个nonce,让服务器原样返回。

根CA与子CA

构建CA的配置文件大部分均相同,只需要修改一些主题名称,扩展密钥用法和crlocsp地址即可:

[ ca ]
default_ca    = CA_default
[ CA_default ]
dir        = .
certs        = $dir/certs
crl_dir        = $dir/crl
database    = $dir/index.txt
new_certs_dir    = $dir/newcerts

certificate    = $dir/cacert.crt
serial        = $dir/serial
crlnumber    = $dir/crlnumber

crl        = $dir/crl.crl 
private_key    = $dir/cakey.key

x509_extensions    = usr_cert

name_opt     = ca_default
cert_opt     = ca_default

default_days    = 730
default_crl_days= 30
default_md    = default
preserve    = no
policy        = policy_match
copy_extensions = none

#[ crl_info ]
#URI.0 = http://127.0.0.1/root/ca.crl

[ policy_match ]
countryName        = optional
stateOrProvinceName    = optional
organizationName    = optional
organizationalUnitName    = optional
commonName        = optional
emailAddress        = optional

[ req ]
utf8 = yes
default_bits        = 2048
default_keyfile     = privkey.pem
distinguished_name    = req_distinguished_name
x509_extensions    = v3_ca
string_mask = utf8only

[ req_distinguished_name ]
# 默认主题信息
countryName            = Country Name (2 letter code)
countryName_default        = CN
countryName_min            = 2
countryName_max            = 2

stateOrProvinceName        = State or Province Name (full name)
stateOrProvinceName_default    = Beijing

localityName            = Locality Name (eg, city)

0.organizationName        = Organization Name (eg, company)
0.organizationName_default    = Strategic Fooyou Agency

#1.organizationName        = Second Organization Name (eg, company)
#1.organizationName_default    = World Wide Web Pty Ltd

organizationalUnitName        = Organizational Unit Name (eg, section)

commonName            = Common Name (e.g. server FQDN or YOUR name)
commonName_default = Strategic Fooyou Agency Root CA
commonName_max            = 64

emailAddress            = Email Address
emailAddress_max        = 64


[ usr_cert ]
basicConstraints=CA:FALSE
extendedKeyUsage = emailProtection,clientAuth
crlDistributionPoints = URI:http://127.0.0.1/root/ca.crl
authorityInfoAccess = OCSP;URI:http://127.0.0.1/root/ocsp
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

# Email CA
[ email_ca ]
basicConstraints= critical,CA:true
# 扩展密钥用法
extendedKeyUsage = emailProtection,clientAuth
# CRL
crlDistributionPoints = URI:http://127.0.0.1/root/ca.crl
# OCSP
authorityInfoAccess = OCSP;URI:http://127.0.0.1/root/ocsp
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
keyUsage = cRLSign, keyCertSign

# OCSP证书
[ v3_ocsp ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = OCSPSigning
crlDistributionPoints   = URI:http://127.0.0.1/root/ca.crl
authorityInfoAccess = OCSP;URI:http://127.0.0.1/root/ocsp
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

# Root CA
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = cRLSign, keyCertSign
crlDistributionPoints = URI:http://127.0.0.1/root/ca.crl #@crl_info
authorityInfoAccess = OCSP;URI:http://127.0.0.1/root/ocsp

CA目录配置:

mkdir rootCA && cd rootCA
mkdir certs
mkdir crl
mkdir newcerts
echo "01" > serial
echo "01" > crlnumber
touch index.txt

自签根CA:

openssl req -new -config rootca.cnf -out ca.csr -keyout cakey.key
openssl ca -selfsign -config rootca.cnf -in ca.csr -out cacert.crt -extensions v3_ca

生成子CA请求:

# 建议新建目录复制和修改配置文件再生成
openssl req -new -config rootca.cnf -out emailca.csr -keyout emailcakey.key -extensions email_ca

签署子CA:

openssl ca -config rootca.cnf -in emailca.csr -out emailca.crt -extensions email_ca

134000.png

用户

同上节所述,将上一节的emailca.crtemailca.key移动到新的子CA目录,按上述步骤配置,以及修改配置文件。

请求文件配置:

[ req ]
default_bits = 2048
encrypt_key = no
utf8 = yes
string_mask = utf8only
req_extensions = v3_req
prompt = yes
distinguished_name = distinguished_name
# SSL SAN
#req_extensions = server_reqext

#[ server_reqext ]
#subjectAltName = @domain_san
#
#[ domain_san ]
#DNS.1 = sfa.org.cn

[distinguished_name]
# 准确来说这里CN应该等于Email,作为友好名称,但我好像没办法让它自动复制
#commonName = Main Domain
#commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64

[ v3_req ]
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

生成签发请求:

openssl req -new -config email.cnf -out certs/email.csr -keyout certs/email.key

由子CA签发证书:

openssl ca -config emailca.cnf -in certs/email.csr -out certs/email.crt -extensions usr_cert -days 365

135208.png

安全性说明:

在签发证书前,要仔细检查(扩展)主题信息,因为OpenSSL没有提供(没查到)编辑主题信息的有效方法。

管理CA

以下提供了简单管理CA的方法。

吊销

openssl ca -config emailca.cnf -revoke certs/email.crt

生成CRL与发布

openssl ca -config emailca.cnf -gencrl -out email.crl [-crldays 1]

之后将这个crl文件放入证书中指定的路径即可,更新时间在配置文件的default_crl_days中已经指定。

响应OCSP

OpenSSL可以作为OCSP服务器使用,在此之前,我们需要签署一个扩展密钥用法为OCSPSigning的证书:

openssl req -new -config rootca.cnf -out ocsp.csr -keyout ocsp.key -extensions v3_ocsp
openssl ca -config rootca.cnf -in ocsp.csr -out ocsp.crt -extensions v3_ocsp

140846.png

开启服务器:

# 复制到新目录
mkdir ocsp && cd ocsp
cp ../emailCA/emailca.crt .
cp ../emailCA/index.txt .
cp ../rootCA/ocsp.crt .
cp ../rootCA/ocsp.key .

# 启动
openssl ocsp -index index.txt -port 80 -rsigner ocsp.crt -rkey ocsp.key -CA emailca.crt -text

验证:

openssl ocsp -issuer chain.crt -cert email.crt -url http://127.0.0.1/

152122.png

(原本在Linux下测试的,后面为了补图又在Windows下测试)

结尾

使用OpenSSL命令行工具告一段落,上述可以用于学习,但不适合与应用集成,现在大多数语言的加密库附带x509功能,可以依托x509来实现颁发管理自动化。

前言

之前写了一篇关于Nginx的客户端验证的文章,让大家使用XCA这个东西,没有讲用OpenSSL来建一个CA,故本文章来讲一讲。
本文章主要介绍搭建一个简单的一级CA,由根证书直接签发最终用户证书。

配置与签发

本次使用的配置文件如下:

[ ca ]
default_ca    = CA_default
[ CA_default ]
# 一些目录配置
dir        = .
certs        = $dir/certs
crl_dir        = $dir/crl
database    = $dir/index.txt
new_certs_dir    = $dir/newcerts

certificate    = $dir/cacert.crt
serial        = $dir/serial
crlnumber    = $dir/crlnumber

crl        = $dir/crl.crl 
private_key    = $dir/cakey.key

# x509证书扩展配置
x509_extensions    = usr_cert

# 证书主题选项
name_opt     = ca_default
cert_opt     = ca_default

# 有效期
default_days    = 730
# 默认crl列表更新时间
default_crl_days= 30
# 默认签名哈希算法
default_md    = default
# 保持DN顺序
preserve    = no
# 证书主题策略
policy        = policy_match
# 不复制来自证书请求文件的扩展
copy_extensions = none

[ crl_info ]
# crl分发点,这个链接必须为URI且指向crl列表
URI.0 = http://ca.coat.jp/root/ca.crl

[ policy_match ]
# 主题均为可选
countryName        = optional
stateOrProvinceName    = optional
organizationName    = optional
organizationalUnitName    = optional
commonName        = optional
emailAddress        = optional

[ req ]
default_bits        = 2048
default_keyfile     = privkey.pem
# 要求填写的字段名DN
distinguished_name    = req_distinguished_name
x509_extensions    = v3_ca
string_mask = utf8only

[ req_distinguished_name ]
countryName            = Country Name (2 letter code)
countryName_default        = JP
countryName_min            = 2
countryName_max            = 2

stateOrProvinceName        = State or Province Name (full name)
stateOrProvinceName_default    = Simokitazawa

localityName            = Locality Name (eg, city)

0.organizationName        = Organization Name (eg, company)
0.organizationName_default    = COAT Inc.

#1.organizationName        = Second Organization Name (eg, company)
#1.organizationName_default    = World Wide Web Pty Ltd

organizationalUnitName        = Organizational Unit Name (eg, section)

commonName            = Common Name (e.g. server FQDN or YOUR name)
commonName_default = COAT Email CA
commonName_max            = 64

emailAddress            = Email Address
emailAddress_max        = 64


[ usr_cert ]
# 基本约束
basicConstraints=CA:FALSE
# 扩展用户用法,这里指定了证书的用途
# 如果你看了之前那片文章并使用了XCA,在它的选项卡中有这些选项,不填写为所有应用程序策略
# https://www.openssl.org/docs/manmaster/man5/x509v3_config.html
extendedKeyUsage = emailProtection,clientAuth
# 配置crl列表
crlDistributionPoints   = @crl_info
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer


[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
# 秘钥用法
keyUsage = cRLSign, keyCertSign
extendedKeyUsage = emailProtection,clientAuth
crlDistributionPoints   = @crl_info

配置CA目录:

mkdir emailCA && cd emailCA
mkdir certs
mkdir crl
mkdir newcerts
echo "01" > serial
echo "01" > crlnumber
touch index.txt


首先生成根证书证书请求:

openssl req -new -config openssl.cnf -out ca.csr -keyout cakey.key

自签名根证书:

openssl ca -selfsign -config openssl.cnf -in ca.csr -out cacert.crt -extensions v3_ca

签发后长这样:

0118112027.png

用户证书配置文件:

[ req ]
default_bits = 2048
encrypt_key = no
utf8 = yes
string_mask = utf8only
req_extensions = v3_req
prompt = yes
distinguished_name = distinguished_name

[distinguished_name]
emailAddress = Email Address
emailAddress_max = 64

[ v3_req ]
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

生成用户证书请求:

openssl req -new -config email.cnf -out certs/email.csr -keyout certs/email.key

签发用户证书请求:

openssl ca -config openssl.cnf -in certs/email.csr -out certs/email.crt -extensions usr_cert -days 365

签发后长这样:

1321278785.png

吊销证书:

openssl ca -config openssl.cnf -revoke certs/email.crt

生成crl列表:

openssl ca -config openssl.cnf -gencrl -out ca.crl

结尾

一级CA搭建较为简单,但并不常用,一般都是由根签发二级CA,再由二级CA签发给用户,这种方式方便不同用途的证书管理,同时防止CA被爆破导致签发的证书作废带来的影响。

在下一篇文章将讲述一个二级CAOCSP服务器的搭建。

解决起来很简单,只需要加入一行参数,但要注意这个加入的时机。

ignoreHeadless=TRUE

安装

78439423742.png

(借用网上的图)

legacy模式启动,即不选择UEFI开头的那个设备,这时进入的是Boot Menu
在倒计时结束前,按下Tab键,键入空格和上面的参数,回车正常的安装流程。

安装后的重启

这时ESXI是以UEFI启动的,所以会直接进入内核引导的倒计时,在倒计时前按下ShiftO,同样键入空格和上面的参数,回车正常启动。

固定参数

我们要固定这个参数,登录Web控制台,开启如下:

201120.png

之后使用SSH连接,用户名为root,密码为控制台密码,键入如下:

esxcfg-advcfg --set-kernel "TRUE" ignoreHeadless

再输入下面的命令检查,看看是否生效:

esxcfg-advcfg --get-kernel ignoreHeadless
output: ignoreHeadless = TRUE

此参数的说明

ACPI没有声明存在显卡,故不会加载本地控制台,所以要禁用这个无头模式。

https://kb.vmware.com/s/article/2054090