Web Security – Ceritificate
Table of Contents
1. Concepts
1) 密钥对(私钥 & 公钥)
What
- 私钥:只属于持有者的秘密数据;用于签名或解密。
- 公钥:可公开分发;用于验签或加密。
- 常见算法:RSA(2048/3072/4096)、ECDSA(P-256/384/521)、Ed25519(签名友好、体积小)。
Why
- 将“机密性”和“身份/完整性”分离:公开分发公钥不会泄密;只有私钥才能产生可信签名。
- 支持大规模、跨组织的互联网信任模型(PKI)。
When
- TLS/HTTPS、mTLS、JWT 签名、软件发布签名(如容器镜像/包)、SSH 登录等。
Who
- 服务器/服务实例、反向代理、客户端(mTLS)、CI/CD 签名者、KMS/HSM 托管方、开发者/运维。
How(要点 & 实践)
- 生成与存放:优先 KMS/HSM 或文件系统 + 严格权限;避免将私钥写入镜像与代码库。
-
算法选择:
- 兼容性优先:RSA-2048;
- 性能与体积:ECDSA P-256;
- 现代签名:Ed25519(但看 TLS/库兼容性)。
- 备份与轮换:定期轮换(如 6–12 个月),并保留旧私钥用于历史数据验签。
2) 数字签名(Digital Signature)
What
- 用私钥对“数据的哈希”做签名(即用私钥对这个摘要进行加密),任何人用公钥可验证“是谁签的、有没有被改”。
Why
- 证明 身份(只有私钥持有者能签)、保证 完整性(数据改了就验不过)。
When
- CA 签发证书;TLS 握手;JWT/消息/工件(artifact)签名;代码签名。
Who
- 签名者:持私钥方(服务器、CA、CI/CD)。
- 验签者:浏览器、服务端、消费者。
How(流程)
- 对数据做哈希(SHA-256 等)。
- 私钥对哈希签名(加密) → 产生签名值。
- 验签时:公钥还原签名(解密)并比对哈希是否一致。
- TLS/证书里,验签逻辑由
x509.Verify
、浏览器/系统完成。
3) CSR(证书签名请求)
What
- 包含 申请者公钥 + 主题信息(CN/组织) + 扩展(SAN) 的请求文件,本身由申请者私钥签名。
Why
-
CA 通过 CSR 确认:
- 你拥有这把私钥;
- 你声明的身份/域名是什么。
When
- 向公有 CA(Let’s Encrypt/DigiCert)或内部 CA 申请证书。
Who
- 申请证书的主体(网站/服务),或自动化工具(cert-manager、lego、acme.sh)。
How(关键点)
- 在生成 CSR 时就把 SAN 写全(DNS/IP/URI)。
- 保护私钥;CSR 可公开。
- 通过 ACME/HTTP-01/DNS-01 完成域名所有权验证(公有 CA)。
4) 证书(X.509)与 SAN
What
- 证书 = 公钥 + 身份信息 + 有效期 + 扩展(KeyUsage/EKU/SAN/AIA/AKI/SKI),由 CA 私钥签名。
- SAN(Subject Alternative Name):列出证书覆盖的所有主机名/IP/URI。现代客户端仅看 SAN,不再依赖 CN。
Why
- 将“是谁(身份)”与“这把公钥”绑定;SAN 保障一个证书可安全覆盖多个域名。
When
- Web 服务器证书、mTLS 客户端证书、内网服务网格证书。
- 通配符:
*.example.com
覆盖单层子域,场景合适时减少证书数量。
Who
- 颁发者:CA(中级 CA 为主)。
- 使用者:服务器/客户端;验证者:浏览器、反向代理、API 客户端。
How(实务)
-
KeyUsage/EKU:
- 服务器证书:
DigitalSignature|KeyEncipherment
+ExtKeyUsageServerAuth
; - 客户端证书:
ExtKeyUsageClientAuth
; - CA:
CertSign|CRLSign
、IsCA=true
。
- 服务器证书:
- 有效期:公有 TLS 证书常见 ≤ 398 天;内部可更短以便自动轮换。
- 一定部署完整链(leaf + intermediates),否则部分客户端“链不完整”。
5) CA / 根 CA / 中级 CA
What
- CA:受信的签发机构。
- 根 CA(Root):自签,公钥预置在系统/浏览器;几乎不直接签发终端证书。
- 中级 CA(Intermediate):由 Root 或上级中级签发,真正负责面向终端的证书签发。
Why
- 分层降低风险:一旦中级泄露可吊销该中级,不伤根。
- 运营与审计职责分离,满足合规。
When
- 互联网 PKI 必备;企业内部 PKI 也建议采用“根离线 + 多中级”架构。
Who
- 根 CA 运维团队(通常离线 HSM、实体安保);
- 中级 CA 运营(在线签发、审计、速率限制、策略)。
How(落地)
- 根 CA 置于离线/受控环境,使用 HSM;
- 中级 CA 在线服务,配合审批/ACME/审计与告警;
- 定期轮换中级;根 CA 更长周期轮换;使用 CRL/OCSP 发布状态。
6) 证书链与验证
What
- 链 = 服务器证书 ← 中级 CA 证书 ← 根 CA 证书(信任锚)。
- 验证 = 逐级验签 + 策略检查(有效期、KeyUsage/EKU、SAN/主机名、吊销)。
Why
- 让任意两方在不预先交换私钥的情况下,基于公共“信任锚”(Root)建立信任。
When
- 浏览器访问 HTTPS、反向代理到上游、服务间 mTLS、API 客户端校验。
Who
- 验证者:客户端/浏览器/网关/SDK;
- 提供链:服务器必须连同中级证书一起发送。
How(步骤)
- 使用上一级证书的公钥验下一级证书签名;
- 检查证书有效期、KeyUsage/EKU 是否允许当前用途;
- 校验 SAN 是否覆盖访问的主机名;
- 检查 吊销(CRL/OCSP/OCSP-Stapling);
- 根 CA 作为信任锚存在于本机信任库中(系统/浏览器/容器镜像)。
7) 吊销(CRL & OCSP)
What
- CRL:由 CA 发布的“被吊销证书列表”;
- OCSP:在线查询某一证书是否有效;
- OCSP Stapling:服务器把新鲜的 OCSP 响应随握手一并提供,减少客户端外呼。
Why
- 私钥泄露、误签、主体不再合法等场景必须“立即失效”证书。
When
- 任何你怀疑密钥泄露、错误签发、主体终止时;
- 公网站点建议开启 Stapling 以降低时延与隐私泄露。
Who
- 吊销方:签发该证书的 CA;
- 查询方:客户端/浏览器/网关。
How(实践)
- 部署 OCSP Stapling(Nginx/Envoy/Apache 支持);
- 监控 CRL/OCSP 可用性;
- 对关键内网系统引入 短周期证书 + 自动轮换,降低对吊销实时性的依赖。
好的,我们继续把证书体系中的 EKU、CRL、OCSP 拆开讲清楚,从 What / Why / When / Who / How 五个角度来理解。
8) EKU (Extended Key Usage,扩展密钥用法)
What
- EKU 是证书中的一个扩展字段(X.509 v3 扩展),用来指定该证书的具体用途。
-
例如:
serverAuth
→ 用于服务器身份认证(HTTPS 网站证书)clientAuth
→ 用于客户端身份认证(双向 TLS)codeSigning
→ 用于代码签名(软件发布)emailProtection
→ 用于加密/签名电子邮件timeStamping
→ 用于时间戳服务
Why
- 防止证书被误用。比如一个服务器证书不能用来签发代码,否则可能被滥用。
- 明确证书的边界,提高安全性和合规性。
When
- 当颁发证书时,CA 会根据用途设置 EKU。
- 浏览器或系统在使用证书时会验证 EKU 字段,确保用途匹配。
Who
- 由 CA 签发时设定。
- 客户端(浏览器、操作系统、应用程序)负责验证。
How
- 证书中
Extended Key Usage
OID 表示用途。 -
例如:
1.3.6.1.5.5.7.3.1
→ TLS Web Server Authentication1.3.6.1.5.5.7.3.2
→ TLS Web Client Authentication
9) CRL (Certificate Revocation List,证书吊销列表)
What
- CRL 是由 CA 定期发布的“被吊销的证书列表”。
- 包含证书序列号、吊销时间、吊销原因。
Why
-
有些证书在有效期内可能需要失效:
- 私钥泄露
- 持有者身份不再可信
- 证书错误签发
- CRL 能让客户端避免信任已不再安全的证书。
When
- CA 会定期(如每天或每周)生成并发布 CRL 文件。
- 客户端在验证证书时,可以下载并检查证书是否在 CRL 中。
Who
- 由 CA 维护和发布。
- 客户端(浏览器、系统)使用。
How
- CA 在证书中写入
CRL Distribution Point (CDP)
扩展,告诉客户端从哪里获取 CRL。 - 客户端下载 CRL 文件(一般是
.crl
格式),检查证书序列号是否在其中。
⚠️ 缺点:CRL 文件可能很大,更新不够实时。
10) OCSP (Online Certificate Status Protocol,在线证书状态协议)
What
- OCSP 是一种比 CRL 更高效的证书状态检查协议。
- 通过网络实时向 CA 或 OCSP 服务器查询某个证书是否有效。
Why
- CRL 太笨重(下载整个列表),OCSP 可以直接查询单个证书的状态。
- 更实时,通常延迟只有几秒到几分钟。
When
- 浏览器访问 HTTPS 网站时,会发送 OCSP 请求,CA 响应“Good / Revoked / Unknown”。
- 在安全性要求高的场景(金融、支付、政务系统)尤其常用。
Who
- OCSP Responder:通常由颁发证书的 CA 运行。
- 客户端:浏览器、应用、操作系统负责发起查询。
How
- 证书里会有
Authority Information Access (AIA)
扩展,指明 OCSP 服务器的 URL。 - 客户端构造一个 OCSP 请求,发送到服务器。
-
服务器返回一个带签名的响应:
- good → 证书有效
- revoked → 证书已吊销
- unknown → 不认识这个证书
⚠️ 缺点:
- 如果 OCSP 服务器宕机,可能导致访问延迟或失败。
- 有些浏览器会用 OCSP Stapling(服务器提前缓存并返回 OCSP 响应,避免客户端单独查询)。
2. 典型使用场景与决策
Web 服务器(单域/多域)
- Why:HTTPS 与 SEO/合规;
- When:所有公网服务默认启用;
- How:ACME 自动签发与续期(Let’s Encrypt + certbot/cert-manager),SAN 列全域名;启用 HTTP/2/3,OCSP Stapling。
API 网关 ↔ 上游(内网)
- Why:上游鉴别与加密;
- How:网关验证上游服务证书链与 SAN;若需要双向认证,启用 mTLS 并为客户端颁发 ClientAuth 证书。
服务网格 / mTLS(微服务)
- Why:零信任/细粒度身份;
- How:Istio/Linkerd 会托管 CA(或对接外部 CA),为 Workload 自动签发短期证书(如 24h),Sidecar 负责轮换与分发;策略由 SPIFFE ID/SAN 绑定。
移动/IoT 设备
- Why:抗中间人、设备身份;
- How:出厂烧录设备私钥与证书,服务端校验;可结合证书**钉(pinning)**或透明日志(CT)。
3. 操作与安全最佳实践(给 Web/后端/DevOps)
- 私钥绝不进代码库;尽量使用 KMS/HSM 或独立密钥卷。
- 最小权限:密钥文件仅服务进程可读;容器用
readOnlyRootFilesystem
、fsGroup
控制访问。 - 自动化:证书短期(30–90 天)+ 自动续期(ACME/cert-manager);失败告警。
- 完整链部署:确保中级证书随同发送;用
openssl s_client -showcerts
或在线工具验证。 - 严格 SAN:访问名必须出现在 SAN;别再依赖 CN。
- 用途正确:KeyUsage/EKU 与角色匹配(ServerAuth/ClientAuth/CodeSigning)。
- 日志与可观测:OCSP/CRL 查询、握手失败原因、证书过期预警。
- 应急:密钥疑似泄露 → 立刻吊销 + 重新签发 + 强制轮换。
4. 快速命令备忘(OpenSSL 示例)
# 生成私钥(RSA 或 EC)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key
# 或:EC P-256
openssl ecparam -genkey -name prime256v1 -noout -out server.key
# 生成含 SAN 的 CSR(用 -addext)
openssl req -new -key server.key -out server.csr \
-subj "/CN=example.com/O=Example Ltd" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com,DNS:api.example.com"
# 由中级 CA 签发(举例)
openssl x509 -req -in server.csr -CA intermediate.pem -CAkey intermediate.key \
-CAcreateserial -out server.pem -days 398 -sha256 \
-extfile <(printf "keyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:example.com,DNS:www.example.com,DNS:api.example.com")
# 验证证书链与主机名(OpenSSL 3)
cat root.pem intermediate.pem > chain.pem
openssl verify -CAfile chain.pem -verify_hostname www.example.com server.pem
建议:把签发与存储迁到 ACME / cert-manager / step-ca / Vault PKI 等自动化方案;根 CA 离线保存,中级 CA 在线托管并审计。
5. Example
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
)
// ---- Helpers ----
func mustRSA(bits int) *rsa.PrivateKey {
key, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
panic(err)
}
return key
}
func mustCreateCert(template, parent *x509.Certificate, pub *rsa.PublicKey, parentKey *rsa.PrivateKey) []byte {
der, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentKey)
if err != nil {
panic(err)
}
return der
}
func pemEncodeCert(der []byte) []byte {
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
}
func pemEncodeKey(key *rsa.PrivateKey) []byte {
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
}
func mustSerial() *big.Int {
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
panic(err)
}
return serial
}
func subjectKeyID(pub *rsa.PublicKey) []byte {
spki, _ := x509.MarshalPKIXPublicKey(pub)
sum := sha1.Sum(spki)
return sum[:]
}
func printPEM(title string, b []byte) {
fmt.Printf("\n===== %s =====\n%s\n", title, string(b))
}
func days(n int) time.Time { return time.Now().Add(time.Duration(n) * 24 * time.Hour) }
// ---- Main flow ----
func main() {
// 1) 生成 Root CA(自签)
rootKey := mustRSA(2048)
rootTmpl := &x509.Certificate{
SerialNumber: mustSerial(),
Subject: pkix.Name{CommonName: "Demo Root CA", Organization: []string{"Acme Corp"}},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: days(3650), // 10 年
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{},
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 2,
SubjectKeyId: subjectKeyID(&rootKey.PublicKey),
}
rootDER := mustCreateCert(rootTmpl, rootTmpl, &rootKey.PublicKey, rootKey)
rootCert, _ := x509.ParseCertificate(rootDER)
printPEM("ROOT CA CERT", pemEncodeCert(rootDER))
printPEM("ROOT CA KEY", pemEncodeKey(rootKey))
// 2) 生成 Intermediate CA,由 Root 签发
intKey := mustRSA(2048)
intTmpl := &x509.Certificate{
SerialNumber: mustSerial(),
Subject: pkix.Name{CommonName: "Demo Intermediate CA", Organization: []string{"Acme Corp"}},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: days(3650),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 1,
SubjectKeyId: subjectKeyID(&intKey.PublicKey),
AuthorityKeyId: rootCert.SubjectKeyId,
}
intDER := mustCreateCert(intTmpl, rootCert, &intKey.PublicKey, rootKey)
intCert, _ := x509.ParseCertificate(intDER)
printPEM("INTERMEDIATE CA CERT", pemEncodeCert(intDER))
printPEM("INTERMEDIATE CA KEY", pemEncodeKey(intKey))
// 3) 生成服务器私钥与 CSR(包含 SAN)
leafKey := mustRSA(2048)
csrTmpl := &x509.CertificateRequest{
Subject: pkix.Name{CommonName: "fanyamin.com", Organization: []string{"Fanyamin Ltd"}},
DNSNames: []string{"fanyamin.com", "www.fanyamin.com", "api.fanyamin.com"}, // SAN
}
csrDER, err := x509.CreateCertificateRequest(rand.Reader, csrTmpl, leafKey)
if err != nil {
panic(err)
}
csr, err := x509.ParseCertificateRequest(csrDER)
if err != nil {
panic(err)
}
if err := csr.CheckSignature(); err != nil {
panic(fmt.Errorf("CSR signature invalid: %w", err))
}
printPEM("SERVER CSR", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}))
printPEM("SERVER KEY", pemEncodeKey(leafKey))
// 4) 中级 CA 根据 CSR 签发服务器证书(服务器证书不是 CA)
leafTmpl := &x509.Certificate{
SerialNumber: mustSerial(),
Subject: csr.Subject, // 沿用 CSR 主题
NotBefore: time.Now().Add(-time.Hour),
NotAfter: days(825), // 约 27 个月,符合常见限制
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: false,
DNSNames: csr.DNSNames, // 从 CSR 注入 SAN
SubjectKeyId: subjectKeyID(&leafKey.PublicKey),
AuthorityKeyId: intCert.SubjectKeyId,
}
leafDER := mustCreateCert(leafTmpl, intCert, &leafKey.PublicKey, intKey)
leafCert, _ := x509.ParseCertificate(leafDER)
printPEM("SERVER CERT", pemEncodeCert(leafDER))
// 5) 构建验证所需的根与中级池,并验证证书链 + 主机名
roots := x509.NewCertPool()
roots.AddCert(rootCert)
inters := x509.NewCertPool()
inters.AddCert(intCert)
// 验证:主机名 www.fanyamin.com 是否被证书覆盖(SAN 中包含)
if _, err := leafCert.Verify(x509.VerifyOptions{
DNSName: "www.fanyamin.com",
Roots: roots,
Intermediates: inters,
}); err != nil {
panic(fmt.Errorf("verify failed (www.fanyamin.com): %w", err))
}
fmt.Println("✓ 证书链与主机名验证通过:www.fanyamin.com")
// 故意用一个未在 SAN 中的主机名,看看失败情况
if _, err := leafCert.Verify(x509.VerifyOptions{
DNSName: "not-in-san.fanyamin.com",
Roots: roots,
Intermediates: inters,
}); err != nil {
fmt.Println("✗ 预期的验证失败(主机名不在 SAN):", err)
} else {
panic("unexpected: verification should have failed for not-in-san.fanyamin.com")
}
// 6) 补充:说明数字签名在链路验证中的体现
fmt.Println("\n说明:以上 x509.Verify 实际做了“用上级证书公钥验证下级证书签名”的工作,")
fmt.Println("即:Root 公钥 验证 Intermediate 的签名;Intermediate 公钥 验证 Leaf 的签名;")
fmt.Println("再结合 DNSName 检查 SAN 与证书有效期、用途等扩展,最终确定是否可信。")
}
Comments |0|
Category: 似水流年