四、整个证书校验的流程和浏览器对整个证书校验的过程

image

 

详细解释下面三个命令的详细步骤# 1. 生成私钥
openssl genrsa -out eureka.test.com.key 2048

# 2. 生成证书签名请求(CSR)
openssl req -new -key eureka.test.com.key -out eureka.test.com.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=eureka.test.com"

# 3. 自签名生成证书
openssl x509 -req -days 365 -in eureka.test.com.csr \
  -signkey eureka.test.com.key -out eureka.test.com.crt

步骤1:生成私钥

openssl genrsa -out eureka.test.com.key 2048

命令分解:

  • openssl:OpenSSL命令行工具。

  • genrsa:生成RSA私钥。

  • -out eureka.test.com.key:指定输出的私钥文件名。

  • 2048:指定私钥的位数,2048位是当前推荐的安全位数。

执行过程:

  1. OpenSSL会生成一个2048位的RSA私钥。

  2. 私钥以PEM格式保存在文件eureka.test.com.key中。

注意:​ 生成的私钥是加密的(如果使用-des3等参数会加密,这里没有加密),但为了安全,在生产环境中通常会对私钥进行加密保护。

步骤2:生成证书签名请求(CSR)

openssl req -new -key eureka.test.com.key -out eureka.test.com.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=eureka.test.com"

命令分解:

  • req:处理证书签名请求(CSR)。

  • -new:生成一个新的CSR。

  • -key eureka.test.com.key:指定用于签名的私钥文件。

  • -out eureka.test.com.csr:指定输出的CSR文件名。

  • -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=eureka.test.com":指定证书的主题信息,避免了交互式输入。

主题(Subject)字段解释:

  • C:国家代码,例如CN表示中国。

  • ST:州或省,例如Beijing。

  • L:城市,例如Beijing。

  • O:组织名称,例如Company。

  • CN:通用名称,通常为域名(如eureka.test.com),必须与你要保护的域名一致。

执行过程:

  1. 使用之前生成的私钥eureka.test.com.key来创建CSR。

  2. CSR中包含了主题信息以及公钥(从私钥中提取)。

  3. 生成的CSR文件eureka.test.com.csr可以发送给CA(证书颁发机构)进行签名,以获取正式证书。在这里,我们用于自签名。

步骤3:自签名生成证书

openssl x509 -req -days 365 -in eureka.test.com.csr \
  -signkey eureka.test.com.key -out eureka.test.com.crt

命令分解:

  • x509:X.509证书数据管理。

  • -req:指定输入文件是CSR。

  • -days 365:证书的有效期,单位为天(这里是一年)。

  • -in eureka.test.com.csr:指定输入的CSR文件。

  • -signkey eureka.test.com.key:指定用于签名的私钥,这里使用自己的私钥进行签名,即自签名。

  • -out eureka.test.com.crt:指定输出的证书文件。

执行过程:

  1. 使用CSR文件和私钥生成一个自签名的证书。

  2. 证书的有效期为365天。

  3. 证书文件eureka.test.com.crt包含了公钥、主题信息、签名以及有效期等。

注意:​ 自签名证书不会被浏览器或操作系统默认信任,通常用于测试或内部环境。在生产环境中,应该使用由受信任的CA签名的证书。

我来详细解释这三个 OpenSSL 命令的每一个步骤和内部工作原理:

命令一:生成私钥

openssl genrsa -out eureka.test.com.key 2048

详细执行过程

  1. openssl​ - 调用 OpenSSL 命令行工具

  2. genrsa​ - 指定生成 RSA 密钥对的操作

    • gen​ = generate(生成)

    • rsa​ = RSA 加密算法

  3. -out eureka.test.com.key​ - 指定输出文件路径和名称

  4. 2048​ - 指定密钥长度(位)

内部工作原理

  1. 随机数生成:

    • OpenSSL 从系统熵池(/dev/random或 /dev/urandom)收集随机数

    • 使用这些随机数作为种子生成大素数

  2. 数学运算:

    # 简化的 RSA 密钥生成过程
    # 1. 随机选择两个大素数 p 和 q
    p = 生成大素数(1024位)
    q = 生成大素数(1024位)
    
    # 2. 计算 n = p * q
    n = p * q  # 2048位
    
    # 3. 计算欧拉函数 φ(n) = (p-1)*(q-1)
    phi_n = (p-1) * (q-1)
    
    # 4. 选择加密指数 e(通常为 65537)
    e = 65537
    
    # 5. 计算解密指数 d = e^(-1) mod φ(n)
    d = 模逆(e, phi_n)
    
    # 结果:公钥 = (n, e),私钥 = (n, d)
  3. 文件结构:

    -----BEGIN RSA PRIVATE KEY-----     # PEM 格式开始标记
    MIIEpAIBAAKCAQEAu1SVLQeF9q9zW7c6...  # Base64 编码的 DER 数据
    -----END RSA PRIVATE KEY-----       # PEM 格式结束标记
  4. 密钥内容(可用以下命令查看):

    # 查看私钥详细信息
    openssl rsa -in eureka.test.com.key -text -noout

    输出示例:

    Private-Key: (2048 bit)
    modulus: 00:ab:cd:ef:...  # 模数 n
    publicExponent: 65537 (0x10001)  # 公钥指数 e
    privateExponent: 12:34:56:...  # 私钥指数 d
    prime1: 00:aa:bb:cc:...  # 素数 p
    prime2: 00:dd:ee:ff:...  # 素数 q
    exponent1: ...  # d mod (p-1)
    exponent2: ...  # d mod (q-1)
    coefficient: ...  # q^(-1) mod p

安全注意事项

  • 默认情况下,私钥是未加密的

  • 如需加密,可添加 -aes256参数:

    openssl genrsa -aes256 -out eureka.test.com.key 2048

命令二:生成证书签名请求(CSR)

openssl req -new -key eureka.test.com.key -out eureka.test.com.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=eureka.test.com"

详细执行过程

  1. req​ - 处理证书签名请求

  2. -new​ - 创建新的 CSR

  3. -key eureka.test.com.key​ - 使用指定私钥生成 CSR

  4. -out eureka.test.com.csr​ - 输出 CSR 文件

  5. -subj​ - 指定主题信息,避免交互式输入

内部工作原理

  1. 提取公钥:

    • 从私钥文件中提取公钥部分 (n, e)

  2. 创建 CSR 数据结构:

    # CSR 数据结构
    CSR = {
        "version": "v1",
        "subject": {
            "C": "CN",        # 国家
            "ST": "Beijing",  # 省/州
            "L": "Beijing",   # 城市
            "O": "Company",   # 组织
            "CN": "eureka.test.com"  # 通用名称
        },
        "publicKey": 从私钥提取的公钥,
        "extensions": {},  # 扩展信息(可选)
        "signatureAlgorithm": "sha256WithRSAEncryption"
    }
  3. 生成数字签名:

    • 对 CSR 内容进行哈希计算

    • 使用私钥对哈希值进行签名

    • 签名附加到 CSR 末尾

  4. 编码为 PEM 格式:

    -----BEGIN CERTIFICATE REQUEST-----  # PEM 格式开始标记
    MIIC0DCCAbgCAQAwfjELMAkGA1UEBhM...  # Base64 编码的 DER 数据
    -----END CERTIFICATE REQUEST-----    # PEM 格式结束标记

查看 CSR 内容

# 查看 CSR 详细信息
openssl req -in eureka.test.com.csr -text -noout

输出示例:

Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C=CN, ST=Beijing, L=Beijing, O=Company, CN=eureka.test.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus: 00:ab:cd:ef:...
                Exponent: 65537 (0x10001)
        Attributes:  # 如果有扩展属性
            ...
    Signature Algorithm: sha256WithRSAEncryption
         12:34:56:78:...

主题字段详解

-subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=eureka.test.com"

字段

名称

说明

示例

C​

Country Name

国家代码(ISO 3166)

CN, US, JP

ST​

State or Province

省/州

Beijing, California

L​

Locality

城市

Beijing, San Francisco

O​

Organization

组织/公司

Company, Google Inc.

OU​

Organizational Unit

部门

IT, Engineering

CN​

Common Name

域名或主机名

eureka.test.com

emailAddress​

Email Address

邮箱

admin@test.com


命令三:自签名生成证书

openssl x509 -req -days 365 -in eureka.test.com.csr \
  -signkey eureka.test.com.key -out eureka.test.com.crt

详细执行过程

  1. x509​ - 处理 X.509 证书

  2. -req​ - 输入是 CSR 文件

  3. -days 365​ - 证书有效期 365 天

  4. -in eureka.test.com.csr​ - 输入 CSR 文件

  5. -signkey eureka.test.com.key​ - 使用私钥进行签名

  6. -out eureka.test.com.crt​ - 输出证书文件

内部工作原理

  1. 验证 CSR:

    • 验证 CSR 的签名是否有效

    • 提取 CSR 中的公钥和主题信息

  2. 创建证书结构:

    # X.509 证书结构
    Certificate = {
        "tbsCertificate": {  # 待签名证书
            "version": "v3",
            "serialNumber": 自动生成的序列号,
            "signature": "sha256WithRSAEncryption",
            "issuer": 签发者(这里是自己),
            "validity": {
                "notBefore": 当前时间,
                "notAfter": 当前时间 + 365天
            },
            "subject": 从CSR中提取的主题,
            "subjectPublicKeyInfo": 从CSR中提取的公钥,
            "issuerUniqueID": None,  # 可选
            "subjectUniqueID": None,  # 可选
            "extensions": []  # 扩展字段
        },
        "signatureAlgorithm": "sha256WithRSAEncryption",
        "signatureValue": 数字签名
    }
  3. 生成数字签名:

    • 对 tbsCertificate部分进行 SHA256 哈希

    • 使用私钥对哈希值进行 RSA 加密

    • 结果作为签名值

  4. 编码为 PEM 格式:

    -----BEGIN CERTIFICATE-----     # PEM 格式开始标记
    MIIDXTCCAkWgAwIBAgIJAKL1WYpMp1eFMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
    -----END CERTIFICATE-----       # PEM 格式结束标记

查看证书内容

# 查看证书详细信息
openssl x509 -in eureka.test.com.crt -text -noout

输出示例:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1234567890 (0x499602d2)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=CN, ST=Beijing, L=Beijing, O=Company, CN=eureka.test.com
        Validity
            Not Before: Mar 16 00:00:00 2026 GMT
            Not After : Mar 16 00:00:00 2027 GMT
        Subject: C=CN, ST=Beijing, L=Beijing, O=Company, CN=eureka.test.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus: 00:ab:cd:ef:...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78
            X509v3 Authority Key Identifier: 
                keyid:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78
            X509v3 Basic Constraints: 
                CA:FALSE
    Signature Algorithm: sha256WithRSAEncryption
         2b:3c:4d:5e:...

扩展知识:完整的证书生成流程

流程图

graph TD
    A[生成私钥] --> B[创建CSR]
    B --> C{提交给CA?}
    C -->|是| D[CA签名]
    C -->|否| E[自签名]
    D --> F[获取证书]
    E --> F

CA 签名 vs 自签名

方面

自签名证书​

CA 签名证书​

签发者​

自己

受信任的 CA

信任链​

无信任链

完整的信任链

浏览器信任​

不被信任(显示警告)

自动信任

使用场景​

内部测试、开发

生产环境、公开网站

成本​

免费

需要购买

添加扩展信息(推荐)

自签名证书时可以添加扩展信息使其更完整:

# 创建扩展配置文件
cat > eureka.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = DNS:eureka.test.com, DNS:*.test.com
EOF

# 生成带有扩展的证书
openssl x509 -req -days 365 \
  -in eureka.test.com.csr \
  -signkey eureka.test.com.key \
  -out eureka.test.com.crt \
  -extfile eureka.ext

验证证书和私钥匹配

# 方法1:比较模数
openssl rsa -noout -modulus -in eureka.test.com.key | openssl md5
openssl x509 -noout -modulus -in eureka.test.com.crt | openssl md5
# 两个MD5值应该相同

# 方法2:验证证书签名
openssl verify -CAfile eureka.test.com.crt eureka.test.com.crt
# 自签名证书用自己验证自己

常见问题解答

Q1:为什么需要 CSR?

A:CSR 是向 CA 申请证书的中间文件。它包含公钥和申请者信息,但不包含私钥,可以安全地发送给 CA。

Q2:私钥泄露了怎么办?

A:立即吊销证书并重新生成新的密钥对。私钥一旦泄露,安全性完全丧失。

Q3:如何查看证书的指纹?

openssl x509 -in eureka.test.com.crt -fingerprint -noout
# SHA1 Fingerprint=12:34:56:78:...

Q4:证书过期了怎么办?

A:需要重新生成证书。可以使用相同的 CSR 重新签名,但建议生成新的密钥对。

Q5:如何转换证书格式?

# PEM 转 DER
openssl x509 -in eureka.test.com.crt -outform der -out eureka.test.com.der

# DER 转 PEM
openssl x509 -inform der -in eureka.test.com.der -out eureka.test.com.pem

这三个命令构成了 SSL/TLS 证书生成的基础流程,理解每个步骤对于安全部署 HTTPS 服务至关重要。

为啥自签名生成证书需要使用私钥进行签名,同时从浏览器访问到整个证书的校验流程是如何进行的

我们首先理解自签名证书的签名过程,然后解释浏览器验证证书的流程。

自签名证书使用私钥进行签名的原因:

在公钥密码学中,数字签名用于验证数据的完整性和来源。对于证书来说,签名是为了证明该证书的内容(包括公钥、持有者信息等)是由某个实体(签发者)确认的。

在自签名证书中,签发者就是证书的持有者自己。因此,使用自己的私钥对证书进行签名,可以证明该证书确实是由自己生成的。当其他人或软件(如浏览器)验证证书时,可以使用证书中包含的公钥(即持有者的公钥)来验证签名是否有效。如果验证通过,说明证书内容没有被篡改,并且确实是由该公钥对应的私钥持有者签发的。

但是,自签名证书的信任问题:由于没有上级CA的背书,浏览器或操作系统不信任自签名证书的签发者(因为签发者不在信任的CA列表中),所以会发出警告。而由受信任的CA签发的证书,浏览器已经内置了CA的公钥,可以验证CA的签名,从而信任该证书。

接下来,我们详细描述从浏览器访问到证书校验的整个流程:

  1. 建立TCP连接:浏览器与服务器通过三次握手建立TCP连接。

  2. SSL/TLS握手:

    a. 客户端发送ClientHello消息,包含支持的TLS版本、加密套件列表、随机数等。

    b. 服务器回应ServerHello消息,选择TLS版本、加密套件,并发送一个随机数。

    c. 服务器发送Certificate消息,将证书链(对于自签名证书,只有一个证书)发送给客户端。

    d. 服务器发送ServerHelloDone消息。

  3. 客户端验证证书:

    a. 客户端(浏览器)收到证书后,首先验证证书的格式和有效性(比如有效期、域名是否匹配等)。

    b. 然后,客户端需要验证证书的签名。对于自签名证书,客户端会使用证书中的公钥来验证证书的签名。因为自签名证书的签发者就是自己,所以验证签名就是使用证书中的公钥去验证证书的签名是否是由对应的私钥生成的。

    c. 但是,自签名证书的签发者不在客户端的信任CA列表中,所以客户端会提示用户该证书不受信任。

  4. 如果证书验证通过(或者用户选择继续),客户端生成一个随机数(预主密钥),用证书中的公钥加密,发送给服务器。

  5. 服务器使用自己的私钥解密得到预主密钥。

  6. 客户端和服务器使用之前交换的两个随机数和预主密钥,生成会话密钥。

  7. 握手完成,双方使用会话密钥进行加密通信。

注意:自签名证书在验证签名时,从技术上讲,签名是有效的,但因为签发者不被信任,所以浏览器会警告。而CA签发的证书,客户端会使用内置的CA公钥来验证证书的签名,并且会检查证书链,直到一个受信任的根证书。

下面我们详细说明浏览器验证证书的步骤(以CA签发的证书为例,自签名证书类似,但缺少对CA的信任):

浏览器验证证书的步骤:

  1. 验证证书的有效期:检查当前时间是否在证书的有效期内。

  2. 验证证书的域名:检查证书中的域名是否与访问的域名匹配。

  3. 验证证书的完整性:使用证书中指定的签名算法和证书中的公钥(对于自签名证书,就是证书本身的公钥;对于CA签发的证书,需要使用上级CA的公钥)对证书的签名进行验证,确保证书内容没有被篡改。

  4. 验证证书链:对于CA签发的证书,浏览器会尝试构建一条从服务器证书到根证书的信任链。浏览器会检查每个证书的签名,直到找到一个受信任的根证书。如果找不到,则证书不被信任。

  5. 检查证书是否被吊销:浏览器可能会通过OCSP或CRL检查证书是否被吊销。

对于自签名证书,第4步会失败,因为自签名证书的签发者(自己)不在浏览器的信任列表中。因此,浏览器会提示用户证书不受信任。

总结:自签名证书使用私钥进行签名是为了证明证书的完整性,但因为它没有受信任的第三方CA的背书,所以浏览器不信任。而浏览器验证证书的过程包括检查有效期、域名、完整性和信任链。自签名证书在完整性验证上可以通过,但信任链验证会失败。

这是一个非常好的问题!让我详细解释为什么自签名证书需要用私钥签名,以及浏览器访问时的完整证书校验流程。

一、为什么自签名证书需要用私钥签名?

1. 数字签名的基本原理

数字签名就像现实世界中的手写签名,但更安全、不可伪造。它的核心原理是:

数据 + 私钥加密 = 数字签名

2. 私钥签名的必要性

步骤

作用

类比

1. 私钥签名​

证明"我是我"

用你的私章盖章

2. 公钥验证​

验证"确实是你"

用你的公章验证章的真伪

关键点:

  • 私钥是保密的,只有持有者知道

  • 公钥是公开的,任何人都可以获得

  • 用私钥加密的数据,只能用对应的公钥解密

3. 自签名证书的签名过程

# 简化的签名过程
certificate_data = {
    "subject": "eureka.test.com",
    "public_key": "公钥内容",
    "validity": "有效期",
    "issuer": "签发者信息"
}

# 1. 计算证书数据的哈希值
hash_value = sha256(certificate_data)

# 2. 用私钥对哈希值进行加密(这就是签名)
signature = rsa_encrypt(hash_value, private_key)

# 3. 将签名附加到证书
certificate = certificate_data + signature

4. 为什么不能只用公钥?

因为公钥是公开的,任何人都可以获得。如果只用公钥:

  • 攻击者可以伪造证书

  • 无法证明证书的真实性

  • 无法防止中间人攻击

私钥签名的唯一性确保了:

  1. 身份认证:只有持有私钥的人才能生成有效签名

  2. 数据完整性:签名验证确保证书内容未被篡改

  3. 不可否认性:签名者不能否认自己签发的证书


二、浏览器访问时的完整证书校验流程

流程图:HTTPS 握手与证书验证

sequenceDiagram
    participant Client as 浏览器
    participant Server as 服务器
    participant CA as 证书颁发机构
    
    Client->>Server: ClientHello (TLS版本、加密套件)
    Server->>Client: ServerHello (选择加密套件)
    Server->>Client: 发送证书链
    Note over Client: 开始证书验证
    Client->>Client: 1. 检查证书格式
    Client->>Client: 2. 检查有效期
    Client->>Client: 3. 检查域名匹配
    Client->>Client: 4. 验证签名链
    Client->>CA: 5. 检查吊销状态(可选)
    Client->>Server: 验证通过,发送预主密钥
    Server->>Client: 握手完成,开始加密通信

详细验证步骤(共 7 步)

第 1 步:建立 TCP 连接

浏览器: SYN
服务器: SYN-ACK  
浏览器: ACK
TCP 连接建立完成

第 2 步:TLS 握手开始

浏览器 → 服务器: ClientHello
    - TLS 版本 (TLS 1.2/1.3)
    - 支持的加密套件
    - 客户端随机数
    - Session ID (如果有)

第 3 步:服务器响应

服务器 → 浏览器: ServerHello
    - 选择的 TLS 版本
    - 选择的加密套件
    - 服务器随机数
    
服务器 → 浏览器: Certificate
    - 发送完整的证书链
    - 包含服务器证书和中间 CA 证书

第 4 步:浏览器证书验证(核心步骤)

4.1 检查证书基本格式
// 伪代码:证书格式验证
function validateCertificateFormat(cert) {
    // 检查是否为有效的 X.509 证书
    if (!isValidX509(cert)) return false;
    
    // 检查版本号
    if (cert.version !== 'v3') return false;
    
    // 检查序列号
    if (!cert.serialNumber) return false;
    
    return true;
}
4.2 检查有效期
function validateValidity(cert) {
    const now = new Date();
    const notBefore = parseDate(cert.validity.notBefore);
    const notAfter = parseDate(cert.validity.notAfter);
    
    if (now < notBefore) {
        throw new Error("证书尚未生效");
    }
    if (now > notAfter) {
        throw new Error("证书已过期");
    }
    return true;
}
4.3 检查域名匹配
function validateDomain(cert, requestedHost) {
    const commonName = cert.subject.CN;
    const altNames = cert.extensions.subjectAltName;
    
    // 检查 Common Name
    if (commonName === requestedHost) return true;
    
    // 检查 Subject Alternative Names
    if (altNames && altNames.includes(requestedHost)) return true;
    
    // 检查通配符证书
    if (commonName.startsWith('*.')) {
        const domain = commonName.substring(2);
        if (requestedHost.endsWith(domain)) return true;
    }
    
    throw new Error("证书域名不匹配");
}
4.4 验证签名链(最重要的一步)
function validateSignatureChain(certChain) {
    // certChain = [服务器证书, 中间CA证书, 根CA证书]
    
    for (let i = 0; i < certChain.length - 1; i++) {
        const childCert = certChain[i];
        const parentCert = certChain[i + 1];
        
        // 1. 提取子证书的签名
        const signature = childCert.signature;
        
        // 2. 计算子证书待签名数据的哈希
        const tbsData = childCert.tbsCertificate;
        const hash = sha256(tbsData);
        
        // 3. 用父证书的公钥解密签名
        const decryptedHash = rsaDecrypt(signature, parentCert.publicKey);
        
        // 4. 比较哈希值
        if (hash !== decryptedHash) {
            throw new Error("签名验证失败");
        }
        
        // 5. 检查父证书是否被信任
        if (!isTrustedCA(parentCert)) {
            throw new Error("证书链包含不受信任的CA");
        }
    }
    
    return true;
}

签名验证的数学原理:

子证书签名 = RSA_加密(子证书哈希值, 父证书私钥)
验证时:RSA_解密(子证书签名, 父证书公钥) == 子证书哈希值
4.5 检查吊销状态(可选但重要)
async function checkRevocation(cert) {
    // 方法1:CRL(证书吊销列表)
    const crlUrl = cert.extensions.crlDistributionPoints;
    if (crlUrl) {
        const crl = await fetchCRL(crlUrl);
        if (crl.isRevoked(cert.serialNumber)) {
            throw new Error("证书已被吊销");
        }
    }
    
    // 方法2:OCSP(在线证书状态协议)
    const ocspUrl = cert.extensions.authorityInfoAccess.ocsp;
    if (ocspUrl) {
        const ocspResponse = await queryOCSP(ocspUrl, cert);
        if (ocspResponse.status !== 'good') {
            throw new Error("证书状态异常");
        }
    }
    
    return true;
}
4.6 检查密钥用途
function validateKeyUsage(cert) {
    const keyUsage = cert.extensions.keyUsage;
    
    // 服务器证书必须包含 digitalSignature 和 keyEncipherment
    if (!keyUsage.includes('digitalSignature')) {
        throw new Error("证书不支持数字签名");
    }
    if (!keyUsage.includes('keyEncipherment')) {
        throw new Error("证书不支持密钥加密");
    }
    
    // 检查扩展密钥用途
    const extKeyUsage = cert.extensions.extendedKeyUsage;
    if (!extKeyUsage.includes('serverAuth')) {
        throw new Error("证书不适用于服务器认证");
    }
    
    return true;
}
4.7 检查证书策略
function validateCertificatePolicies(cert) {
    const policies = cert.extensions.certificatePolicies;
    
    // 检查是否符合预期策略
    // 例如:EV证书需要特定策略标识符
    if (isEVCertificate && !policies.includes('2.23.140.1.1')) {
        throw new Error("不符合扩展验证证书策略");
    }
    
    return true;
}

第 5 步:验证结果处理

// 浏览器验证逻辑
async function validateCertificate(serverCert, certChain, requestedHost) {
    try {
        // 1-3: 基本验证
        validateCertificateFormat(serverCert);
        validateValidity(serverCert);
        validateDomain(serverCert, requestedHost);
        
        // 4: 签名链验证
        validateSignatureChain(certChain);
        
        // 5: 吊销检查
        await checkRevocation(serverCert);
        
        // 6-7: 扩展验证
        validateKeyUsage(serverCert);
        validateCertificatePolicies(serverCert);
        
        // 所有验证通过
        return { valid: true, trustLevel: determineTrustLevel(certChain) };
        
    } catch (error) {
        // 验证失败
        return { 
            valid: false, 
            error: error.message,
            showWarning: shouldShowWarning(error)
        };
    }
}

第 6 步:密钥交换

验证通过后:
1. 浏览器生成预主密钥
2. 用服务器证书的公钥加密预主密钥
3. 发送给服务器:ClientKeyExchange

服务器:
1. 用自己的私钥解密得到预主密钥
2. 双方用预主密钥+随机数生成会话密钥

第 7 步:完成握手

浏览器 → 服务器: ChangeCipherSpec, Finished
服务器 → 浏览器: ChangeCipherSpec, Finished

开始加密通信...

三、自签名证书 vs CA 签名证书的验证差异

验证流程对比

验证步骤

自签名证书​

CA 签名证书​

结果差异​

格式检查​

✓ 通过

✓ 通过

相同

有效期​

✓ 通过

✓ 通过

相同

域名匹配​

✓ 通过

✓ 通过

相同

签名验证​

✓ 通过(用自己公钥验证)

✓ 通过(用CA公钥验证)

技术上都通过

信任链验证​

✗ 失败(不在信任库)

✓ 通过(链到根证书)

关键差异​

吊销检查​

通常无

✓ 可选检查

自签名无吊销机制

浏览器提示​

红色警告​

绿色锁标志​

用户体验不同

自签名证书的验证问题

// 浏览器验证自签名证书时的逻辑
function validateSelfSignedCert(cert) {
    // 1. 签名验证(技术通过)
    const hash = sha256(cert.tbsCertificate);
    const decryptedHash = rsaDecrypt(cert.signature, cert.publicKey);
    
    if (hash === decryptedHash) {
        console.log("签名验证通过:证书确实由该私钥签名");
    } else {
        throw new Error("签名无效");
    }
    
    // 2. 信任链验证(失败)
    const rootCAs = loadTrustedRootCAs(); // 加载系统信任的根证书
    
    // 检查是否在信任库中
    if (!rootCAs.includes(cert.issuer)) {
        // 自签名证书的签发者是自己,不在信任库中
        showSecurityWarning("此证书不是由受信任的机构颁发");
        
        // 给用户选择
        const userChoice = promptUser("是否继续?");
        if (userChoice === 'continue') {
            return { valid: true, trusted: false };
        } else {
            return { valid: false, trusted: false };
        }
    }
}

四、实际网络抓包分析

Wireshark 抓包示例

# TLS 握手过程
Frame 1: Client Hello
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 512
    Handshake Type: Client Hello (1)
    Random: 5a5b5c5d...
    Cipher Suites: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Frame 2: Server Hello
    Content Type: Handshake (22)
    Version: TLS 1.2 (0x0303)
    Length: 89
    Handshake Type: Server Hello (2)
    Random: 6a6b6c6d...
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)

Frame 3: Certificate
    Content Type: Handshake (22)
    Length: 2356
    Handshake Type: Certificate (11)
    Certificates Length: 2353
    Certificates (3 certificates):
        Certificate 1 Length: 1200  # 服务器证书
        Certificate 2 Length: 953   # 中间CA证书
        Certificate 3 Length: 200   # 根CA证书

Frame 4: Server Key Exchange
Frame 5: Server Hello Done

浏览器开发者工具查看证书

  1. 点击锁图标​ → 连接是安全的​ → 证书有效

  2. 查看证书路径:

    根证书颁发机构
    ├── 中间证书颁发机构
    │   └── eureka.test.com (您的服务器)
  3. 证书详情:

    • 颁发给:eureka.test.com

    • 颁发者:Let's Encrypt Authority X3

    • 有效期:2026-03-16 至 2027-03-16

    • 公钥:RSA 2048 位

    • 签名算法:SHA256 with RSA


五、证书验证的数学原理

RSA 签名验证公式

签名生成:
签名 = (消息哈希值)^d mod n
其中:d 是私钥指数,n 是模数

签名验证:
计算:签名^e mod n
其中:e 是公钥指数(通常为 65537)

如果:(签名^e mod n) == 消息哈希值
则签名验证通过

实际计算示例

# 简化的 RSA 签名验证
import hashlib

# 假设的 RSA 参数
n = 3233  # 模数
e = 17    # 公钥指数
d = 2753  # 私钥指数

# 1. 原始消息
message = "eureka.test.com certificate data"

# 2. 计算哈希
hash_value = int(hashlib.sha256(message.encode()).hexdigest(), 16) % n

# 3. 用私钥签名(服务器端)
signature = pow(hash_value, d, n)  # hash_value^d mod n

# 4. 用公钥验证(浏览器端)
decrypted_hash = pow(signature, e, n)  # signature^e mod n

# 5. 比较
if decrypted_hash == hash_value:
    print("✓ 签名验证通过")
else:
    print("✗ 签名验证失败")

六、常见错误及原因

浏览器证书错误类型

错误代码

原因

解决方案

NET::ERR_CERT_AUTHORITY_INVALID​

自签名或不受信任的CA

导入证书到信任库

NET::ERR_CERT_DATE_INVALID​

证书过期或未生效

更新证书

NET::ERR_CERT_COMMON_NAME_INVALID​

域名不匹配

使用正确的域名

NET::ERR_CERT_REVOKED​

证书被吊销

申请新证书

NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM​

弱签名算法

使用 SHA256 或更高

自签名证书的浏览器警告

您的连接不是私密连接
攻击者可能会试图从 eureka.test.com 窃取您的信息
(例如:密码、通讯内容或信用卡信息)。了解详情

NET::ERR_CERT_AUTHORITY_INVALID

隐藏详情
此服务器无法证明它是 eureka.test.com;
您计算机的操作系统不信任其安全证书。这可能是由配置错误或攻击者拦截您的连接而导致的。

七、生产环境最佳实践

1. 使用 Let's Encrypt(免费)

# 自动获取和续期证书
certbot --nginx -d eureka.test.com

# 生成的证书位置
/etc/letsencrypt/live/eureka.test.com/
├── cert.pem      # 证书
├── chain.pem     # 中间证书
├── fullchain.pem # 完整证书链
└── privkey.pem   # 私钥

2. 配置完整的证书链

# Nginx 配置
ssl_certificate /etc/ssl/eureka.test.com/fullchain.pem;
ssl_certificate_key /etc/ssl/eureka.test.com/privkey.pem;

3. 启用 OCSP Stapling

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/ca-certs.pem;

4. 监控证书过期

# 检查证书过期时间
openssl x509 -in eureka.test.com.crt -noout -dates

# 自动续期脚本
0 0 1 * * certbot renew --quiet --post-hook "systemctl reload nginx"

总结

核心要点

  1. 私钥签名的作用:

    • 证明证书的真实性和完整性

    • 确保只有私钥持有者能生成有效证书

    • 防止证书被篡改或伪造

  2. 浏览器验证流程:

    • 7 个关键步骤:格式→有效期→域名→签名→信任链→吊销→扩展

    • 信任链是核心:自签名证书失败在信任链验证

    • 数学基础:RSA 非对称加密确保安全性

  3. 自签名 vs CA 签名:

    • 技术层面:签名验证都通过

    • 信任层面:自签名缺少第三方背书

    • 用户体验:自签名显示警告,CA 签名显示绿锁

  4. 生产建议:

    • 开发测试用自签名

    • 生产环境用 Let's Encrypt 或商业 CA

    • 定期监控和续期证书

理解这个流程对于部署安全的 HTTPS 服务至关重要,特别是在 Kubernetes 和微服务架构中,证书管理是安全的基础设施之一。

posted on 2026-03-16 15:16  luzhouxiaoshuai  阅读(80)  评论(0)    收藏  举报

导航