文章目录
- 证书的SCT列表
- 验证SCT
-
- 依赖包
- 加载证书
- 初始化log机构信息
- 离线验证+在线验证
证书的SCT列表
浏览器对证书链的合法性检查通过后,会再检查服务端证书附件里的SCT列表(Signed Certificate Timestamp);
浏览器内置了一批certificate transparency log机构的公钥和访问地址,如果SCT申明证书在某个log机构注册了,但是SCT里的签名通过不了log机构的公钥验证,则抛出错误NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED
验证SCT
依赖包
使用github.com/google/certificate-transparency-go工具
import ( "context" "encoding/base64" "encoding/pem" "errors" "io" "log" "net/http" "os" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/ctutil" "github.com/google/certificate-transparency-go/loglist3" ctX509 "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" )
加载证书
假设服务端证书以及签发该证书的上级CA证书,已保存为PEM格式的文件
func VerifySCT(certLocation string, issuerLocation string) error { // 服务端证书 certByte, err := os.ReadFile(certLocation) if err != nil { return err } block, _ := pem.Decode(certByte) if block == nil || len(block.Bytes) == 0 { return errors.New("error decoding certificate") } cert, err := ctX509.ParseCertificate(block.Bytes) if err != nil { return err } // 上级CA certByte, _ = os.ReadFile(issuerLocation) block, _ = pem.Decode(certByte) if block == nil || len(block.Bytes) == 0 { return errors.New("error decoding issuer CA") } issuer, _ := ctX509.ParseCertificate(block.Bytes) // 生成merkle tree leaf,用于验证sct(Signed Certificate Timestamp) merkleLeaf, err := ct.MerkleTreeLeafForEmbeddedSCT([]*ctX509.Certificate{cert, issuer}, 0) if err != nil { return err } // 获取证书里附带的sct列表 sctList, err := x509util.ParseSCTsFromSCTList(&cert.SCTList) if err != nil { log.Printf("ParseCertificate failed %v", err) return err } log.Printf("验证证书%s的SCT列表", cert.Subject)
初始化log机构信息
使用和chrome一致的机构列表:https://www.gstatic.com/ct/log_list/v3/log_list.json
// 获取chrome使用的certificate transparency log机构列表,包含机构使用的公钥和查询api地址 resp, err := http.DefaultClient.Get(loglist3.LogListURL) if err != nil { return errors.New("下载certificate transparency log地址列表失败") } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return errors.New("下载certificate transparency log地址列表失败") } loglistEntry, _ := loglist3.NewFromJSON(body) logsByHash, _ := ctutil.LogInfoByKeyHash(loglistEntry, http.DefaultClient)
离线验证+在线验证
logInfo.VerifySCTSignature方法不需要和log机构在线交互,是使用已知的log机构公钥对SCT进行离线验证
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() for _, sct := range sctList { // 验证sct,参考https://github.com/google/certificate-transparency-go/blob/master/ctutil/sctscan/sctscan.go log.Printf("sct signature: %s, %s", base64.StdEncoding.EncodeToString(sct.Signature.Signature), time.Unix(0, int64(sct.Timestamp)*int64(time.Millisecond)).Format(time.RFC3339Nano)) logInfo, ok := logsByHash[sct.LogID.KeyID] if !ok { log.Printf("sct key_hash: %s,不存在对应certificate transparency log机构", base64.StdEncoding.EncodeToString(sct.LogID.KeyID[:])) continue } log.Printf("颁发sct的certificate transparency log机构是: %s,地址:%s, 公钥哈希:%s", logInfo.Description, logInfo.Client.BaseURI(), base64.StdEncoding.EncodeToString(sct.LogID.KeyID[:])) err = logInfo.VerifySCTSignature(*sct, *merkleLeaf) if err != nil { log.Printf("Verify SCT failed %v", err) continue } log.Println("Verify SCT offline OK") // 线上验证,非必须 if _, err := logInfo.VerifyInclusionLatest(ctx, *merkleLeaf, sct.Timestamp); err != nil { sth := logInfo.LastSTH() if sth != nil { delta := time.Duration(sth.Timestamp-sct.Timestamp) * time.Millisecond if delta < logInfo.MMD { // 如果生效时间(logInfo.MMD)还未到,那么机构查询不到该sct的merkle tree leaf信息是正常的 log.Printf("SCT's MMD has not passed %d -> %d < %v", sct.Timestamp, sth.Timestamp, logInfo.MMD) continue } } log.Printf("Failed to verify SCT online: %v", err) } else { log.Println("Verify SCT online OK") } }
每个SCT分别是不同log机构签发的,如果一个证书附带的两个SCT是由同一个log机构签发,浏览器似乎也会报错