如何验证证书链的签出关系
在证书链中,通常由根CA证书,签出中间CA证书,再签出服务证书。这是一个签出链关系:
Root CA -> Intermediate CA1 -> Intermediate CA2 -> ... Intermediate CAn -> Server Certificate
那么如何验证谁是谁的签出关系呢,意思就是验证一个证书是由谁签发的。
通常我们拿到一个完整服务端证书的时候会包含:
- certificate.pem // 证书PEM
- key.pem // 私钥PEM
- ca-chain.pem // CA PEM
ca-chain.pem里面可能包含多个CA证书,如何找到直接签出那个CA证书呢。
我们查看证书的一般内容:
$ $ openssl x509 -text -noout -in cert.pem
Signature Algorithm: ecdsa-with-SHA256
Issuer: CN=<cn>, OUT=<ou>, O=<o>, L=<l>, ST=<st>, C=<c>
Validity
Not Before: Jun 23 00:13:00 2020 GMT
Not After : Jun 23 00:18:00 2023 GMT
Subject: CN=<cn1>, OUT=<ou1>, O=<o>, L=<l>, ST=<st>, C=<c>
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:f5:XXXX:aa:a9:
b3:6d:XXXX:95:1f:
30:70:XXXX:44:19:
70:e5:XXXX:fb:54:
b1:8a:e8:be:bf
ASN1 OID: prime256v1
NIST CURVE: P-256
...
Signature Algorithm: ecdsa-with-SHA256
30:45:XXXX:c3:e8:
1a:54:XXXX:01:e1:
55:02:XXXX:bb:b2:
45:af:XXXX:d1:d3
有几个信息:
- Issuer:就是谁签发的,签发者的Subject。
- Subject:当前证书的Subject。
- Public-Key:证书包含的public key。
这里Issuer就明确表明了当前证书是由谁签发的,那我们去找ca-chain里面的所有证书,挨个遍历找出其Subject和这个一样,那么就说明是签发证书。
理论上是这样的,但是Subject只是包含文本信息,用户可以篡改伪造,只要是同名Subject就可以啦。所以这个字段只是便于肉眼辨识使用,真正使用验证的是signature信息。
在前面证书内容中,最后我们看到有Signature Algorithm域,它的值就是签出证书的signature,这个签名是根据签出证书的内容生成的,只有签出证书本身才能得出这个signature。所以我们需要对照CA证书,生成他的signature值,然后和服务端证书里的这个域值进行比较,如果相当就说明是签出证书。
这个算法比较复杂,不细究了,golang里面提供了函数可以直接调用:
func (c *Certificate) CheckSignatureFrom(parent *Certificate) error
举例:
package main
import (
"fmt"
"os"
"io/ioutil"
"crypto/x509"
"encoding/pem"
)
func loadCertificate(pemFile string) (*x509.Certificate, error) {
certBuff, err := ioutil.ReadFile(pemFile)
if err != nil {
fmt.Printf("ERROR: failed to read keystore file: %s, error: %s\n", pemFile, err)
return nil, err
}
block, _ := pem.Decode(certBuff)
if block == nil {
fmt.Printf("ERROR: block of decoded private key is nil\n")
return nil, err
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
fmt.Printf("ERROR: failed get ECDSA private key, error: %v\n", err)
return nil, err
}
return cert, nil
}
func main() {
cert, err := loadCertificate(os.Args[1])
if err != nil {
fmt.Printf("ERROR: failed to load cert, error: %v\n", err)
return
}
issuer, err := loadCertificate(os.Args[2])
if err != nil {
fmt.Printf("ERROR: failed to load issuer, error: %v\n", err)
return
}
err = cert.CheckSignatureFrom(issuer)
if err != nil {
fmt.Printf("ERROR: verify failed, error: %v\n", err)
return
}
fmt.Printf("cert subject: %s\n", cert.Subject)
fmt.Printf("cert issuer : %s\n", cert.Issuer)
fmt.Printf("issuer Subject: %s\n", issuer.Subject)
}
输入:go build test.go && ./test cert.pem ca.pem
验证ca.pem是不是cert.pem的直接签出证书。
还有一个办法是使用: func (Certificate) Verify() 函数。
如果verify成功,在返回的chains [][]Certificate中每一列的:
- 第一个certificate是自己
- 第二个certificate就是直接签出证书。
- 最后一个certificate是Roots里面的一个证书。
网友评论