From 6bc12448d81fe5f79ced3c49506e1dd21cef17ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Tue, 16 Sep 2025 08:20:43 +0000 Subject: [PATCH 1/3] Refactor certificate pinning --- transport/internet/tls/config.go | 102 +++++++++++------- transport/internet/tls/config.pb.go | 42 +++++--- transport/internet/tls/config.proto | 2 + transport/internet/tls/pin.go | 4 +- transport/internet/tls/pin_test.go | 157 ++++++++++++++-------------- 5 files changed, 176 insertions(+), 131 deletions(-) diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 90427b8d..ec3ccbc2 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -282,6 +282,36 @@ func (c *Config) parseServerName() string { } func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + // pinned chain has the highest priority + // pass if successfull and fail if not + if r.PinnedPeerCertificateChainSha256 != nil { + hashValue := GenerateCertChainHash(rawCerts) + for _, v := range r.PinnedPeerCertificateChainSha256 { + if hmac.Equal(hashValue, v) { + return nil + } + } + return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue)) + } + + // directly return success if pinned cert is leaf + // or add the CA to RootCAs if pinned cert is CA(and can be used in VerifyPeerCertInNames for Self signed CA) + RootCAs := r.RootCAs + if r.PinnedPeerCertificateSha256 != nil { + verifyResult, verifiedCert := verifyChain(verifiedChains, r.PinnedPeerCertificateSha256) + switch verifyResult { + case certNotFound: + return errors.New("peer cert is unrecognized") + case foundLeaf: + return nil + case foundCA: + RootCAs = RootCAs.Clone() + RootCAs.AddCert(verifiedCert) + default: + panic("impossible PinnedPeerCertificateSha256 verify result") + } + } + if r.VerifyPeerCertInNames != nil { if len(r.VerifyPeerCertInNames) > 0 { certs := make([]*x509.Certificate, len(rawCerts)) @@ -289,7 +319,7 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509 certs[i], _ = x509.ParseCertificate(asn1Data) } opts := x509.VerifyOptions{ - Roots: r.RootCAs, + Roots: RootCAs, CurrentTime: time.Now(), Intermediates: x509.NewCertPool(), } @@ -302,42 +332,15 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509 } } } - if r.PinnedPeerCertificateChainSha256 == nil { - return errors.New("peer cert is invalid.") - } - } - - if r.PinnedPeerCertificateChainSha256 != nil { - hashValue := GenerateCertChainHash(rawCerts) - for _, v := range r.PinnedPeerCertificateChainSha256 { - if hmac.Equal(hashValue, v) { - return nil - } - } - return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue)) - } - - if r.PinnedPeerCertificatePublicKeySha256 != nil { - for _, v := range verifiedChains { - for _, cert := range v { - publicHash := GenerateCertPublicKeyHash(cert) - for _, c := range r.PinnedPeerCertificatePublicKeySha256 { - if hmac.Equal(publicHash, c) { - return nil - } - } - } - } - return errors.New("peer public key is unrecognized.") } return nil } type RandCarrier struct { - RootCAs *x509.CertPool - VerifyPeerCertInNames []string - PinnedPeerCertificateChainSha256 [][]byte - PinnedPeerCertificatePublicKeySha256 [][]byte + RootCAs *x509.CertPool + VerifyPeerCertInNames []string + PinnedPeerCertificateChainSha256 [][]byte + PinnedPeerCertificateSha256 [][]byte } func (r *RandCarrier) Read(p []byte) (n int, err error) { @@ -362,10 +365,10 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { } randCarrier := &RandCarrier{ - RootCAs: root, - VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames), - PinnedPeerCertificateChainSha256: c.PinnedPeerCertificateChainSha256, - PinnedPeerCertificatePublicKeySha256: c.PinnedPeerCertificatePublicKeySha256, + RootCAs: root, + VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames), + PinnedPeerCertificateChainSha256: c.PinnedPeerCertificateChainSha256, + PinnedPeerCertificateSha256: c.PinnedPeerCertificateSha256, } config := &tls.Config{ Rand: randCarrier, @@ -526,3 +529,30 @@ func ParseCurveName(curveNames []string) []tls.CurveID { func IsFromMitm(str string) bool { return strings.ToLower(str) == "frommitm" } + +type verifyResult int + +const ( + certNotFound verifyResult = iota + foundLeaf + foundCA +) + +func verifyChain(verifiedChains [][]*x509.Certificate, PinnedPeerCertificateSha256 [][]byte) (verifyResult, *x509.Certificate) { + for _, v := range verifiedChains { + for _, cert := range v { + certHash := GenerateCertHash(cert) + for _, c := range PinnedPeerCertificateSha256 { + if hmac.Equal(certHash, c) { + if cert.IsCA { + return foundCA, cert + } else { + return foundLeaf, cert + } + + } + } + } + } + return certNotFound, nil +} diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index b93af678..97d84fe8 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -217,11 +217,12 @@ type Config struct { // @Document Replaces server_name to verify the peer cert. // @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried. // @Critical - VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` - EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` - EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` - EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` - EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` + VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` + EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` + EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` + EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` + EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` + PinnedPeerCertificateSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_certificate_sha256,json=pinnedPeerCertificateSha256,proto3" json:"pinned_peer_certificate_sha256,omitempty"` } func (x *Config) Reset() { @@ -394,6 +395,13 @@ func (x *Config) GetEchSocketSettings() *internet.SocketConfig { return nil } +func (x *Config) GetPinnedPeerCertificateSha256() [][]byte { + if x != nil { + return x.PinnedPeerCertificateSha256 + } + return nil +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -427,7 +435,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x45, 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, - 0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xe9, 0x07, 0x0a, 0x06, 0x43, 0x6f, + 0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xae, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, @@ -490,15 +498,19 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x65, 0x63, 0x68, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, - 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, - 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, + 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x1b, 0x70, + 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, + 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, + 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, + 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, + 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 6d39bc56..2bff498e 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -101,4 +101,6 @@ message Config { string ech_force_query = 20; SocketConfig ech_socket_settings = 21; + + repeated bytes pinned_peer_certificate_sha256 = 22; } diff --git a/transport/internet/tls/pin.go b/transport/internet/tls/pin.go index f561bfdf..2bbaf614 100644 --- a/transport/internet/tls/pin.go +++ b/transport/internet/tls/pin.go @@ -36,7 +36,7 @@ func GenerateCertChainHash(rawCerts [][]byte) []byte { return hashValue } -func GenerateCertPublicKeyHash(cert *x509.Certificate) []byte { - out := sha256.Sum256(cert.RawSubjectPublicKeyInfo) +func GenerateCertHash(cert *x509.Certificate) []byte { + out := sha256.Sum256(cert.Raw) return out[:] } diff --git a/transport/internet/tls/pin_test.go b/transport/internet/tls/pin_test.go index cfc60e17..3b2091bf 100644 --- a/transport/internet/tls/pin_test.go +++ b/transport/internet/tls/pin_test.go @@ -2,14 +2,14 @@ package tls import ( "crypto/x509" - "encoding/base64" + "encoding/hex" "encoding/pem" "testing" "github.com/stretchr/testify/assert" ) -func TestCalculateCertHash(t *testing.T) { +func TestCalculateCertChainHash(t *testing.T) { /* This is used to make sure that the hash signature generated is consistent Do NOT change this test to suit your modification. */ @@ -112,88 +112,89 @@ tzY45d4mjPs0fKCFKSsVM6YT0tX4NwIKsOaeQg30WLtRyDwYm6ma/a/UUUS0FloZ }) } -func TestCalculateCertPublicKeyHash(t *testing.T) { +func TestCalculateCertHash(t *testing.T) { const Single = `-----BEGIN CERTIFICATE----- -MIINWTCCC0GgAwIBAgITLQAxbA/A+lw/1sLDAAAAADFsDzANBgkqhkiG9w0BAQsF -ADBPMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u -MSAwHgYDVQQDExdNaWNyb3NvZnQgUlNBIFRMUyBDQSAwMjAeFw0yMjExMjUwMDU2 -NTZaFw0yMzA1MjUwMDU2NTZaMBcxFTATBgNVBAMTDHd3dy5iaW5nLmNvbTCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOH89lKmtkDnClFiQwfZofZO4h8C -Ye/+ChI67pEw5Q6/MxJzHiMKe8f1WaNuc+wkdHdct+BmQ+AftozIJt+eSN6IF7eY -dsutBvR87GNLFe40MBvfyvTQVM9Ulv04JxOpKTYnsf2wmktEI3y7FCgfm9RT71n+ -Zef8Z8fa4By7aGfbbCQ0DsHl5P9o3ug/eLQODzK9NuQlwcVBHD2Zvgo+K7WOsjgE -k8JnOr+2zc0WWT4OrWSDJE/3l+jvhxmZkrwgmks4m9zUZvAnYAz/xxVCJRqbI3Ou -S5fkJJ3f6IxPbS2i8OWz6tma1aIkgQaFNJQuYOJa1esfQcEzs6kb/Xx5DXUCAwEA -AaOCCWQwgglgMIIBfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgCt9776fP8QyIud -PZwePhhqtGcpXc+xDCTKhYY069yCigAAAYSsUtxtAAAEAwBHMEUCIQCP/Jpp337p -cKITqS/kNlA4bNY6TK1Ad0VlsdkzQU+oZgIgFZb2AcsyT1UKCmM3ziGsLdvS9MAT -D1g/kztyDXhkA70AdgBVgdTCFpA2AUrqC5tXPFPwwOQ4eHAlCBcvo6odBxPTDAAA -AYSsUtsZAAAEAwBHMEUCIQDvlqXrdA440PW6b+JLj4F0ZVQNKHcv1lub0FhQqHgR -wAIgAtC7eXvXXhVBuO+Bd3fkDI0aGQM+pcvIesBoygzStjQAdQB6MoxU2LcttiDq -OOBSHumEFnAyE4VNO9IrwTpXo1LrUgAAAYSsUtmfAAAEAwBGMEQCIDgjSYt6e/h8 -dv2KGEL3AJZUBH2gp1AA5saH8o3OyMJhAiBOCzo3oWlVFeF/8c0fxIIs9Fj4w8BY -INo0jNP/k7apgTAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMBMAoGCCsGAQUF -BwMCMD4GCSsGAQQBgjcVBwQxMC8GJysGAQQBgjcVCIfahnWD7tkBgsmFG4G1nmGF -9OtggV2Fho5Bh8KYUAIBZAIBJzCBhwYIKwYBBQUHAQEEezB5MFMGCCsGAQUFBzAC -hkdodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL21zY29ycC9NaWNyb3NvZnQl -MjBSU0ElMjBUTFMlMjBDQSUyMDAyLmNydDAiBggrBgEFBQcwAYYWaHR0cDovL29j -c3AubXNvY3NwLmNvbTAdBgNVHQ4EFgQUpuSPPchFlPGu8FTbzPhJTFxQ7RowDgYD -VR0PAQH/BAQDAgSwMIIFbQYDVR0RBIIFZDCCBWCCDHd3dy5iaW5nLmNvbYIQZGlj -dC5iaW5nLmNvbS5jboITKi5wbGF0Zm9ybS5iaW5nLmNvbYIKKi5iaW5nLmNvbYII -YmluZy5jb22CFmllb25saW5lLm1pY3Jvc29mdC5jb22CEyoud2luZG93c3NlYXJj -aC5jb22CGWNuLmllb25saW5lLm1pY3Jvc29mdC5jb22CESoub3JpZ2luLmJpbmcu -Y29tgg0qLm1tLmJpbmcubmV0gg4qLmFwaS5iaW5nLmNvbYIYZWNuLmRldi52aXJ0 -dWFsZWFydGgubmV0gg0qLmNuLmJpbmcubmV0gg0qLmNuLmJpbmcuY29tghBzc2wt -YXBpLmJpbmcuY29tghBzc2wtYXBpLmJpbmcubmV0gg4qLmFwaS5iaW5nLm5ldIIO -Ki5iaW5nYXBpcy5jb22CD2JpbmdzYW5kYm94LmNvbYIWZmVlZGJhY2subWljcm9z -b2Z0LmNvbYIbaW5zZXJ0bWVkaWEuYmluZy5vZmZpY2UubmV0gg5yLmJhdC5iaW5n -LmNvbYIQKi5yLmJhdC5iaW5nLmNvbYISKi5kaWN0LmJpbmcuY29tLmNugg8qLmRp -Y3QuYmluZy5jb22CDiouc3NsLmJpbmcuY29tghAqLmFwcGV4LmJpbmcuY29tghYq -LnBsYXRmb3JtLmNuLmJpbmcuY29tgg13cC5tLmJpbmcuY29tggwqLm0uYmluZy5j -b22CD2dsb2JhbC5iaW5nLmNvbYIRd2luZG93c3NlYXJjaC5jb22CDnNlYXJjaC5t -c24uY29tghEqLmJpbmdzYW5kYm94LmNvbYIZKi5hcGkudGlsZXMuZGl0dS5saXZl -LmNvbYIPKi5kaXR1LmxpdmUuY29tghgqLnQwLnRpbGVzLmRpdHUubGl2ZS5jb22C -GCoudDEudGlsZXMuZGl0dS5saXZlLmNvbYIYKi50Mi50aWxlcy5kaXR1LmxpdmUu -Y29tghgqLnQzLnRpbGVzLmRpdHUubGl2ZS5jb22CFSoudGlsZXMuZGl0dS5saXZl -LmNvbYILM2QubGl2ZS5jb22CE2FwaS5zZWFyY2gubGl2ZS5jb22CFGJldGEuc2Vh -cmNoLmxpdmUuY29tghVjbndlYi5zZWFyY2gubGl2ZS5jb22CDGRldi5saXZlLmNv -bYINZGl0dS5saXZlLmNvbYIRZmFyZWNhc3QubGl2ZS5jb22CDmltYWdlLmxpdmUu -Y29tgg9pbWFnZXMubGl2ZS5jb22CEWxvY2FsLmxpdmUuY29tLmF1ghRsb2NhbHNl -YXJjaC5saXZlLmNvbYIUbHM0ZC5zZWFyY2gubGl2ZS5jb22CDW1haWwubGl2ZS5j -b22CEW1hcGluZGlhLmxpdmUuY29tgg5sb2NhbC5saXZlLmNvbYINbWFwcy5saXZl -LmNvbYIQbWFwcy5saXZlLmNvbS5hdYIPbWluZGlhLmxpdmUuY29tgg1uZXdzLmxp -dmUuY29tghxvcmlnaW4uY253ZWIuc2VhcmNoLmxpdmUuY29tghZwcmV2aWV3Lmxv -Y2FsLmxpdmUuY29tgg9zZWFyY2gubGl2ZS5jb22CEnRlc3QubWFwcy5saXZlLmNv -bYIOdmlkZW8ubGl2ZS5jb22CD3ZpZGVvcy5saXZlLmNvbYIVdmlydHVhbGVhcnRo -LmxpdmUuY29tggx3YXAubGl2ZS5jb22CEndlYm1hc3Rlci5saXZlLmNvbYITd2Vi -bWFzdGVycy5saXZlLmNvbYIVd3d3LmxvY2FsLmxpdmUuY29tLmF1ghR3d3cubWFw -cy5saXZlLmNvbS5hdTCBsAYDVR0fBIGoMIGlMIGioIGfoIGchk1odHRwOi8vbXNj -cmwubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9NaWNyb3NvZnQlMjBSU0El -MjBUTFMlMjBDQSUyMDAyLmNybIZLaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br -aS9tc2NvcnAvY3JsL01pY3Jvc29mdCUyMFJTQSUyMFRMUyUyMENBJTIwMDIuY3Js -MFcGA1UdIARQME4wQgYJKwYBBAGCNyoBMDUwMwYIKwYBBQUHAgEWJ2h0dHA6Ly93 -d3cubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NwczAIBgZngQwBAgEwHwYDVR0j -BBgwFoAU/y9/4Qb0OPMt7SWNmML+DvZs/PowHQYDVR0lBBYwFAYIKwYBBQUHAwEG -CCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQB4OIB/EHxpF64iFZME7XkJjZYn -ZiYIfOfHs6EGDNn7fxvpZS9HVy1jOWv/RvzEbMuSV3b/fItaJN/zATBg5/6hb5Jq -HGIcnKmb+tYrKlYhSOngHSu/8/OYP1dFFIqcVe0769kwXaKUzLh6UVRaS+mB7GFc -sXmPMbv5NM7mCUEdMkOaoSmubfw/WzmmRGrcSmtCxtIwMcp8Jf13Esunq//4+9w3 -M/JXa8ubmXyrY63zt/Oz/NkVJvja89ueovscy6s5sw2r+Su4bRsJjmxwCbakp56K -rbh7z417LzW88MMuATvOyk/O8Rbw2KYVSEiQgO54kHI0YkHkJ/6IoeAT1pmCfHUE -Rd+Ec8T+/lE2BPLVqp8SjogDYiybb0IR5Gn2vYyUdzsS2h/C5qGNd2t5ehxfjQoL -G6Y3GJZQRxkSX6TLPYU0U63wWb4yeSxabpBlARaZMaAoqDa3cX53WCnrAXDz8vuH -yAtX2/Jq7IpybFK5kFzbxfI02Ik0aCWJUnXPL8L6esTskwvkzX8rSI/bjPrzcJL5 -B9pONLy6wc8/Arfu2eNlMbs8s/g8c5zkEc3fBZ9tJ1dqlnMAVgB2+fwI3aK4F34N -uyfZW7Xu65KkPhbMnO0GVGM7X4Lkyjm4ysQ9PIRV3MwMfXH+RBSXlIayLTcYG4gl -XF1a/qnao6nMjyTIyQ== +MIINWzCCC0OgAwIBAgITMwK6ajqdrV0tahuIrQAAArpqOjANBgkqhkiG9w0BAQwF +ADBdMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MS4wLAYDVQQDEyVNaWNyb3NvZnQgQXp1cmUgUlNBIFRMUyBJc3N1aW5nIENBIDA0 +MB4XDTI1MDkwOTEwMzE1NloXDTI2MDMwODEwMzE1NlowYzELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv +ZnQgQ29ycG9yYXRpb24xFTATBgNVBAMTDHd3dy5iaW5nLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMBflymLifrVkjp8K4/XrHSt+/xDrrZIJyTI +JOhIGZJZ88sNjo4OChQWV8O3CTQwrbKJDd6KjZFFc6BPKpEJZ891w2zkymMbE7wh +vQVviSCIVCO+49pLrEvfh5ZvdbXhtNzm/ZRvkoI8h4ZKPBRNmX5sGpSQ9p0loJBj +Jk1HbzLv0vRk5bLb/J6x7YexaAu86C9TjqnC4irO+AZZNI/0S70ZHxX+ETZVV0EX +QU8UmqV68e4YhAQwiLYdAQw125n2hGWoLokQSZTyEiIIoubB00pE5zf0Qaq6Q4s8 +Go5Ukw1A4HjWMisHVKq369pgI8VDZtMzOhS+O0DEQZLwOFETZxECAwEAAaOCCQww +ggkIMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdgCWl2S/VViXrfdDh2g3CEJ3 +6fA61fak8zZuRqQ/D8qpxgAAAZkuEXLdAAAEAwBHMEUCIBLzX4AJgVJdQshSMBLS +hBMQX8zgRm2U3IXjLk37JM3QAiEAkVrmCFx0+BM3NOoCAXBU1WzVuniPxJP3Ysbd +OO3dkEAAdwBkEcRspBLsp4kcogIuALyrTygH1B41J6vq/tUDyX3N8AAAAZkuEXKd +AAAEAwBIMEYCIQCCO1ys+tlI8Fhp4J/Dqk3VVtSi408Nuw8T6YciDL6LPgIhAPjp +fm/gMkASgNimNuMFH8oiJbqeQ/yo2zQfub894iMuAHcAVmzVo3a+g9/jQrZ1xJwj +JJinabrDgsurSaOHfZqzLQEAAAGZLhFy2QAABAMASDBGAiEA/93O6XiiYhfeANHh +0n2nJyVvFAc6sBNT2S7WOR28vR0CIQC7i+leDRRIeY2BYJwaRlAqHlSyU4DZu5IG +caxiWFeavzAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMB +MDwGCSsGAQQBgjcVBwQvMC0GJSsGAQQBgjcVCIe91xuB5+tGgoGdLo7QDIfw2h1d +gqvnMIft8R8CAWQCAS0wgbQGCCsGAQUFBwEBBIGnMIGkMHMGCCsGAQUFBzAChmdo +dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUy +MEF6dXJlJTIwUlNBJTIwVExTJTIwSXNzdWluZyUyMENBJTIwMDQlMjAtJTIweHNp +Z24uY3J0MC0GCCsGAQUFBzABhiFodHRwOi8vb25lb2NzcC5taWNyb3NvZnQuY29t +L29jc3AwHQYDVR0OBBYEFAsWImxddBew8yEv3yGDsmy90FzPMA4GA1UdDwEB/wQE +AwIFoDCCBREGA1UdEQSCBQgwggUEghMqLnBsYXRmb3JtLmJpbmcuY29tggoqLmJp +bmcuY29tgghiaW5nLmNvbYIWaWVvbmxpbmUubWljcm9zb2Z0LmNvbYITKi53aW5k +b3dzc2VhcmNoLmNvbYIZY24uaWVvbmxpbmUubWljcm9zb2Z0LmNvbYIRKi5vcmln +aW4uYmluZy5jb22CDSoubW0uYmluZy5uZXSCDiouYXBpLmJpbmcuY29tgg0qLmNu +LmJpbmcubmV0gg0qLmNuLmJpbmcuY29tghBzc2wtYXBpLmJpbmcuY29tghBzc2wt +YXBpLmJpbmcubmV0gg4qLmFwaS5iaW5nLm5ldIIOKi5iaW5nYXBpcy5jb22CD2Jp +bmdzYW5kYm94LmNvbYIWZmVlZGJhY2subWljcm9zb2Z0LmNvbYIbaW5zZXJ0bWVk +aWEuYmluZy5vZmZpY2UubmV0gg5yLmJhdC5iaW5nLmNvbYIQKi5yLmJhdC5iaW5n +LmNvbYIPKi5kaWN0LmJpbmcuY29tgg4qLnNzbC5iaW5nLmNvbYIQKi5hcHBleC5i +aW5nLmNvbYIWKi5wbGF0Zm9ybS5jbi5iaW5nLmNvbYINd3AubS5iaW5nLmNvbYIM +Ki5tLmJpbmcuY29tgg9nbG9iYWwuYmluZy5jb22CEXdpbmRvd3NzZWFyY2guY29t +gg5zZWFyY2gubXNuLmNvbYIRKi5iaW5nc2FuZGJveC5jb22CGSouYXBpLnRpbGVz +LmRpdHUubGl2ZS5jb22CGCoudDAudGlsZXMuZGl0dS5saXZlLmNvbYIYKi50MS50 +aWxlcy5kaXR1LmxpdmUuY29tghgqLnQyLnRpbGVzLmRpdHUubGl2ZS5jb22CGCou +dDMudGlsZXMuZGl0dS5saXZlLmNvbYILM2QubGl2ZS5jb22CE2FwaS5zZWFyY2gu +bGl2ZS5jb22CFGJldGEuc2VhcmNoLmxpdmUuY29tghVjbndlYi5zZWFyY2gubGl2 +ZS5jb22CDWRpdHUubGl2ZS5jb22CEWZhcmVjYXN0LmxpdmUuY29tgg5pbWFnZS5s +aXZlLmNvbYIPaW1hZ2VzLmxpdmUuY29tghFsb2NhbC5saXZlLmNvbS5hdYIUbG9j +YWxzZWFyY2gubGl2ZS5jb22CFGxzNGQuc2VhcmNoLmxpdmUuY29tgg1tYWlsLmxp +dmUuY29tghFtYXBpbmRpYS5saXZlLmNvbYIObG9jYWwubGl2ZS5jb22CDW1hcHMu +bGl2ZS5jb22CEG1hcHMubGl2ZS5jb20uYXWCD21pbmRpYS5saXZlLmNvbYINbmV3 +cy5saXZlLmNvbYIcb3JpZ2luLmNud2ViLnNlYXJjaC5saXZlLmNvbYIWcHJldmll +dy5sb2NhbC5saXZlLmNvbYIPc2VhcmNoLmxpdmUuY29tghJ0ZXN0Lm1hcHMubGl2 +ZS5jb22CDnZpZGVvLmxpdmUuY29tgg92aWRlb3MubGl2ZS5jb22CFXZpcnR1YWxl +YXJ0aC5saXZlLmNvbYIMd2FwLmxpdmUuY29tghJ3ZWJtYXN0ZXIubGl2ZS5jb22C +FXd3dy5sb2NhbC5saXZlLmNvbS5hdYIUd3d3Lm1hcHMubGl2ZS5jb20uYXWCE3dl +Ym1hc3RlcnMubGl2ZS5jb22CGGVjbi5kZXYudmlydHVhbGVhcnRoLm5ldIIMd3d3 +LmJpbmcuY29tMAwGA1UdEwEB/wQCMAAwagYDVR0fBGMwYTBfoF2gW4ZZaHR0cDov +L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwQXp1cmUl +MjBSU0ElMjBUTFMlMjBJc3N1aW5nJTIwQ0ElMjAwNC5jcmwwZgYDVR0gBF8wXTBR +BgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3Nv +ZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMAgGBmeBDAECAjAfBgNV +HSMEGDAWgBQ7cNFT6XYlnWCoymYPxpuub1QWajAdBgNVHSUEFjAUBggrBgEFBQcD +AgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEMBQADggIBAEQCoppNllgoHtfLJt2m7cVL +AILYFxJdi9qc4LUBfaQEdUwAfsC1pSk5YFB0aGcmVFKMvMMOeENOrWgNJVTLYI05 +8mu6XmbiqUeIu1Rlye/yNirYm33Js2f3VXYp6HSzisF5cWq4QwYqA6XIMfDl61/y +IXVb5l5eTfproM2grn3RcVVbk5DuEUfyDPzYYNm8elxzac4RrbkDif/b+tVFxmrJ +CUx1o3VLiVVzbIFCDc5r6pPArm1EdgseJ7pRdXzg6flwA0INRpeLCpjtvkHeZCh7 +GS2JUBhFv7M+lneJljNU/trTkYiho+ZRW9AgLcN73c4+1wHttPHk+w19m5Ge182V +HzCQdO27IGovKN8jkprGafGxYhyCn4KdSYbRrG7fjkckzpJrjCpF2/bJJ+o4Zi9P +rJIKHzY5lIMXcD7wwwT2WwlKXoTDrgm4QKN18V+kZaoOILdKyMlEww4jPFUqk6j1 +0Qeod55F5h4tCq2lmwDIa/jyWTGgqTr4UESqj46NB5+JkGYl0O1PPbS1nUm9sN1l +hkY45iskXVXqLl6AVVcXyxMTefD43M81tFVuJJgpdD/BaMaXAuBdNDfTQcJwhP99 +uI6HqHFD3iEct8fBkYfQiwH2e1eu9OwgujiWHsutyK8VvzVB3/YnhQ/TzciRjPqz +7ykUutQNUALq8dQwoTnK -----END CERTIFICATE----- + ` t.Run("singlepublickey", func(t *testing.T) { block, _ := pem.Decode([]byte(Single)) cert, err := x509.ParseCertificate(block.Bytes) assert.Equal(t, err, nil) - hash := GenerateCertPublicKeyHash(cert) - hashstr := base64.StdEncoding.EncodeToString(hash) - assert.Equal(t, "xI/4mNm8xF9uDT4vA9G1+aKAaybwNlkRECnN8vGAHTM=", hashstr) + hash := GenerateCertHash(cert) + fingerprint, _ := hex.DecodeString("ae243d668ec9c7f74a0dcd1ad21c6676b4efe30c39728934b362093af886bf77") + assert.Equal(t, fingerprint, hash) }) } From 10a185bb65391363e11c48ac8619248a70c045f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Tue, 16 Sep 2025 08:32:27 +0000 Subject: [PATCH 2/3] conf --- infra/conf/transport_internet.go | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 4a12761d..7ac3eb51 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -395,27 +395,27 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) { } type TLSConfig struct { - Insecure bool `json:"allowInsecure"` - Certs []*TLSCertConfig `json:"certificates"` - ServerName string `json:"serverName"` - ALPN *StringList `json:"alpn"` - EnableSessionResumption bool `json:"enableSessionResumption"` - DisableSystemRoot bool `json:"disableSystemRoot"` - MinVersion string `json:"minVersion"` - MaxVersion string `json:"maxVersion"` - CipherSuites string `json:"cipherSuites"` - Fingerprint string `json:"fingerprint"` - RejectUnknownSNI bool `json:"rejectUnknownSni"` - PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"` - PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"` - CurvePreferences *StringList `json:"curvePreferences"` - MasterKeyLog string `json:"masterKeyLog"` - ServerNameToVerify string `json:"serverNameToVerify"` - VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` - ECHServerKeys string `json:"echServerKeys"` - ECHConfigList string `json:"echConfigList"` - ECHForceQuery string `json:"echForceQuery"` - ECHSocketSettings *SocketConfig `json:"echSockopt"` + Insecure bool `json:"allowInsecure"` + Certs []*TLSCertConfig `json:"certificates"` + ServerName string `json:"serverName"` + ALPN *StringList `json:"alpn"` + EnableSessionResumption bool `json:"enableSessionResumption"` + DisableSystemRoot bool `json:"disableSystemRoot"` + MinVersion string `json:"minVersion"` + MaxVersion string `json:"maxVersion"` + CipherSuites string `json:"cipherSuites"` + Fingerprint string `json:"fingerprint"` + RejectUnknownSNI bool `json:"rejectUnknownSni"` + PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"` + PinnedPeerCertificateSha256 *[]string `json:"pinnedPeerCertificateSha256"` + CurvePreferences *StringList `json:"curvePreferences"` + MasterKeyLog string `json:"masterKeyLog"` + ServerNameToVerify string `json:"serverNameToVerify"` + VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` + ECHServerKeys string `json:"echServerKeys"` + ECHConfigList string `json:"echConfigList"` + ECHForceQuery string `json:"echForceQuery"` + ECHSocketSettings *SocketConfig `json:"echSockopt"` } // Build implements Buildable. @@ -469,14 +469,14 @@ func (c *TLSConfig) Build() (proto.Message, error) { } } - if c.PinnedPeerCertificatePublicKeySha256 != nil { - config.PinnedPeerCertificatePublicKeySha256 = [][]byte{} - for _, v := range *c.PinnedPeerCertificatePublicKeySha256 { - hashValue, err := base64.StdEncoding.DecodeString(v) + if c.PinnedPeerCertificateSha256 != nil { + config.PinnedPeerCertificateSha256 = [][]byte{} + for _, v := range *c.PinnedPeerCertificateSha256 { + hashValue, err := hex.DecodeString(v) if err != nil { return nil, err } - config.PinnedPeerCertificatePublicKeySha256 = append(config.PinnedPeerCertificatePublicKeySha256, hashValue) + config.PinnedPeerCertificateSha256 = append(config.PinnedPeerCertificateSha256, hashValue) } } From ef1d468f730660eb3073283aea82bca037cdc6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Tue, 16 Sep 2025 16:40:05 +0000 Subject: [PATCH 3/3] Refactor --- infra/conf/transport_internet.go | 12 --- main/commands/all/tls/certchainhash.go | 40 ---------- main/commands/all/tls/leafcerthash.go | 44 +++++++++++ main/commands/all/tls/ping.go | 12 ++- main/commands/all/tls/tls.go | 2 +- testing/scenarios/tls_test.go | 48 ++++++------ transport/internet/tls/config.go | 61 ++++++--------- transport/internet/tls/pin.go | 41 +++++----- transport/internet/tls/pin_test.go | 103 ------------------------- 9 files changed, 120 insertions(+), 243 deletions(-) delete mode 100644 main/commands/all/tls/certchainhash.go create mode 100644 main/commands/all/tls/leafcerthash.go diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 7ac3eb51..ea4ed6ff 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -406,7 +406,6 @@ type TLSConfig struct { CipherSuites string `json:"cipherSuites"` Fingerprint string `json:"fingerprint"` RejectUnknownSNI bool `json:"rejectUnknownSni"` - PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"` PinnedPeerCertificateSha256 *[]string `json:"pinnedPeerCertificateSha256"` CurvePreferences *StringList `json:"curvePreferences"` MasterKeyLog string `json:"masterKeyLog"` @@ -458,17 +457,6 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.RejectUnknownSni = c.RejectUnknownSNI - if c.PinnedPeerCertificateChainSha256 != nil { - config.PinnedPeerCertificateChainSha256 = [][]byte{} - for _, v := range *c.PinnedPeerCertificateChainSha256 { - hashValue, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, err - } - config.PinnedPeerCertificateChainSha256 = append(config.PinnedPeerCertificateChainSha256, hashValue) - } - } - if c.PinnedPeerCertificateSha256 != nil { config.PinnedPeerCertificateSha256 = [][]byte{} for _, v := range *c.PinnedPeerCertificateSha256 { diff --git a/main/commands/all/tls/certchainhash.go b/main/commands/all/tls/certchainhash.go deleted file mode 100644 index 304f668d..00000000 --- a/main/commands/all/tls/certchainhash.go +++ /dev/null @@ -1,40 +0,0 @@ -package tls - -import ( - "flag" - "fmt" - "os" - - "github.com/xtls/xray-core/main/commands/base" - "github.com/xtls/xray-core/transport/internet/tls" -) - -var cmdCertChainHash = &base.Command{ - UsageLine: "{{.Exec}} certChainHash", - Short: "Calculate TLS certificates hash.", - Long: ` - xray tls certChainHash --cert - Calculate TLS certificate chain hash. - `, -} - -func init() { - cmdCertChainHash.Run = executeCertChainHash // break init loop -} - -var input = cmdCertChainHash.Flag.String("cert", "fullchain.pem", "The file path of the certificates chain") - -func executeCertChainHash(cmd *base.Command, args []string) { - fs := flag.NewFlagSet("certChainHash", flag.ContinueOnError) - if err := fs.Parse(args); err != nil { - fmt.Println(err) - return - } - certContent, err := os.ReadFile(*input) - if err != nil { - fmt.Println(err) - return - } - certChainHashB64 := tls.CalculatePEMCertChainSHA256Hash(certContent) - fmt.Println(certChainHashB64) -} diff --git a/main/commands/all/tls/leafcerthash.go b/main/commands/all/tls/leafcerthash.go new file mode 100644 index 00000000..06bbf5cd --- /dev/null +++ b/main/commands/all/tls/leafcerthash.go @@ -0,0 +1,44 @@ +package tls + +import ( + "flag" + "fmt" + "os" + + "github.com/xtls/xray-core/main/commands/base" + "github.com/xtls/xray-core/transport/internet/tls" +) + +var cmdLeafCertHash = &base.Command{ + UsageLine: "{{.Exec}} tls leafCertHash", + Short: "Calculate TLS leaf certificate hash.", + Long: ` + xray tls leafCertHash --cert + Calculate TLS leaf certificate hash. + `, +} + +func init() { + cmdLeafCertHash.Run = executeLeafCertHash // break init loop +} + +var input = cmdLeafCertHash.Flag.String("cert", "fullchain.pem", "The file path of the leaf certificate") + +func executeLeafCertHash(cmd *base.Command, args []string) { + fs := flag.NewFlagSet("leafCertHash", flag.ContinueOnError) + if err := fs.Parse(args); err != nil { + fmt.Println(err) + return + } + certContent, err := os.ReadFile(*input) + if err != nil { + fmt.Println(err) + return + } + certChainHashB64, err := tls.CalculatePEMLeafCertSHA256Hash(certContent) + if err != nil { + fmt.Println("failed to decode cert", err) + return + } + fmt.Println(certChainHashB64) +} diff --git a/main/commands/all/tls/ping.go b/main/commands/all/tls/ping.go index ccc5fe6b..6417b74c 100644 --- a/main/commands/all/tls/ping.go +++ b/main/commands/all/tls/ping.go @@ -3,7 +3,7 @@ package tls import ( gotls "crypto/tls" "crypto/x509" - "encoding/base64" + "encoding/hex" "fmt" "net" "strconv" @@ -156,8 +156,14 @@ func printTLSConnDetail(tlsConn *gotls.Conn) { func showCert() func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - hash := GenerateCertChainHash(rawCerts) - fmt.Println("Certificate Chain Hash: ", base64.StdEncoding.EncodeToString(hash)) + var hash []byte + for _, asn1Data := range rawCerts { + cert, _ := x509.ParseCertificate(asn1Data) + if cert.IsCA { + hash = GenerateCertHash(cert) + } + } + fmt.Println("Certificate Leaf Hash: ", hex.EncodeToString(hash)) return nil } } diff --git a/main/commands/all/tls/tls.go b/main/commands/all/tls/tls.go index a93da1c3..159bf769 100644 --- a/main/commands/all/tls/tls.go +++ b/main/commands/all/tls/tls.go @@ -13,7 +13,7 @@ var CmdTLS = &base.Command{ Commands: []*base.Command{ cmdCert, cmdPing, - cmdCertChainHash, + cmdLeafCertHash, cmdECH, }, } diff --git a/testing/scenarios/tls_test.go b/testing/scenarios/tls_test.go index 250e5d47..fe58b33b 100644 --- a/testing/scenarios/tls_test.go +++ b/testing/scenarios/tls_test.go @@ -92,7 +92,7 @@ func TestSimpleTLSConnection(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -203,7 +203,7 @@ func TestAutoIssuingCertificate(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -304,7 +304,7 @@ func TestTLSOverKCP(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -400,7 +400,7 @@ func TestTLSOverWebSocket(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -512,7 +512,7 @@ func TestGRPC(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -624,7 +624,7 @@ func TestGRPCMultiMode(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -674,7 +674,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) { defer tcpServer.Close() certificateDer := cert.MustGenerate(nil) certificate := tls.ParseCertificate(certificateDer) - certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate}) + certHash := tls.GenerateCertHash(certificateDer.Certificate) userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ @@ -731,7 +731,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -743,8 +743,8 @@ func TestSimpleTLSConnectionPinned(t *testing.T) { SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ - AllowInsecure: true, - PinnedPeerCertificateChainSha256: [][]byte{certHash}, + AllowInsecure: true, + PinnedPeerCertificateSha256: [][]byte{certHash}, }), }, }, @@ -771,7 +771,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) { defer tcpServer.Close() certificateDer := cert.MustGenerate(nil) certificate := tls.ParseCertificate(certificateDer) - certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate}) + certHash := tls.GenerateCertHash(certificateDer.Certificate) certHash[1] += 1 userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() @@ -829,7 +829,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -841,8 +841,8 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) { SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ - AllowInsecure: true, - PinnedPeerCertificateChainSha256: [][]byte{certHash}, + AllowInsecure: true, + PinnedPeerCertificateSha256: [][]byte{certHash}, }), }, }, @@ -869,7 +869,7 @@ func TestUTLSConnectionPinned(t *testing.T) { defer tcpServer.Close() certificateDer := cert.MustGenerate(nil) certificate := tls.ParseCertificate(certificateDer) - certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate}) + certHash := tls.GenerateCertHash(certificateDer.Certificate) userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ @@ -926,7 +926,7 @@ func TestUTLSConnectionPinned(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -938,9 +938,9 @@ func TestUTLSConnectionPinned(t *testing.T) { SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ - Fingerprint: "random", - AllowInsecure: true, - PinnedPeerCertificateChainSha256: [][]byte{certHash}, + Fingerprint: "random", + AllowInsecure: true, + PinnedPeerCertificateSha256: [][]byte{certHash}, }), }, }, @@ -967,7 +967,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) { defer tcpServer.Close() certificateDer := cert.MustGenerate(nil) certificate := tls.ParseCertificate(certificateDer) - certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate}) + certHash := tls.GenerateCertHash(certificateDer.Certificate) certHash[1] += 1 userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() @@ -1025,7 +1025,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) { Receiver: &protocol.ServerEndpoint{ Address: net.NewIPOrDomain(net.LocalHostIP), Port: uint32(serverPort), - User: &protocol.User{ + User: &protocol.User{ Account: serial.ToTypedMessage(&vmess.Account{ Id: userID.String(), }), @@ -1037,9 +1037,9 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) { SecurityType: serial.GetMessageType(&tls.Config{}), SecuritySettings: []*serial.TypedMessage{ serial.ToTypedMessage(&tls.Config{ - Fingerprint: "random", - AllowInsecure: true, - PinnedPeerCertificateChainSha256: [][]byte{certHash}, + Fingerprint: "random", + AllowInsecure: true, + PinnedPeerCertificateSha256: [][]byte{certHash}, }), }, }, diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index ec3ccbc2..b9ba0cae 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -7,7 +7,6 @@ import ( "crypto/rand" "crypto/tls" "crypto/x509" - "encoding/base64" "os" "slices" "strings" @@ -281,31 +280,25 @@ func (c *Config) parseServerName() string { return c.ServerName } -func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - // pinned chain has the highest priority - // pass if successfull and fail if not - if r.PinnedPeerCertificateChainSha256 != nil { - hashValue := GenerateCertChainHash(rawCerts) - for _, v := range r.PinnedPeerCertificateChainSha256 { - if hmac.Equal(hashValue, v) { - return nil - } - } - return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue)) +func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) (err error) { + // extract x509 certificates from rawCerts(verifiedChains will be nil if InsecureSkipVerify is true) + certs := make([]*x509.Certificate, len(rawCerts)) + for i, asn1Data := range rawCerts { + certs[i], _ = x509.ParseCertificate(asn1Data) } // directly return success if pinned cert is leaf // or add the CA to RootCAs if pinned cert is CA(and can be used in VerifyPeerCertInNames for Self signed CA) RootCAs := r.RootCAs if r.PinnedPeerCertificateSha256 != nil { - verifyResult, verifiedCert := verifyChain(verifiedChains, r.PinnedPeerCertificateSha256) + verifyResult, verifiedCert := verifyChain(certs, r.PinnedPeerCertificateSha256) switch verifyResult { case certNotFound: return errors.New("peer cert is unrecognized") case foundLeaf: return nil case foundCA: - RootCAs = RootCAs.Clone() + RootCAs = x509.NewCertPool() RootCAs.AddCert(verifiedCert) default: panic("impossible PinnedPeerCertificateSha256 verify result") @@ -314,10 +307,6 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509 if r.VerifyPeerCertInNames != nil { if len(r.VerifyPeerCertInNames) > 0 { - certs := make([]*x509.Certificate, len(rawCerts)) - for i, asn1Data := range rawCerts { - certs[i], _ = x509.ParseCertificate(asn1Data) - } opts := x509.VerifyOptions{ Roots: RootCAs, CurrentTime: time.Now(), @@ -337,10 +326,9 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509 } type RandCarrier struct { - RootCAs *x509.CertPool - VerifyPeerCertInNames []string - PinnedPeerCertificateChainSha256 [][]byte - PinnedPeerCertificateSha256 [][]byte + RootCAs *x509.CertPool + VerifyPeerCertInNames []string + PinnedPeerCertificateSha256 [][]byte } func (r *RandCarrier) Read(p []byte) (n int, err error) { @@ -365,10 +353,9 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { } randCarrier := &RandCarrier{ - RootCAs: root, - VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames), - PinnedPeerCertificateChainSha256: c.PinnedPeerCertificateChainSha256, - PinnedPeerCertificateSha256: c.PinnedPeerCertificateSha256, + RootCAs: root, + VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames), + PinnedPeerCertificateSha256: c.PinnedPeerCertificateSha256, } config := &tls.Config{ Rand: randCarrier, @@ -538,19 +525,17 @@ const ( foundCA ) -func verifyChain(verifiedChains [][]*x509.Certificate, PinnedPeerCertificateSha256 [][]byte) (verifyResult, *x509.Certificate) { - for _, v := range verifiedChains { - for _, cert := range v { - certHash := GenerateCertHash(cert) - for _, c := range PinnedPeerCertificateSha256 { - if hmac.Equal(certHash, c) { - if cert.IsCA { - return foundCA, cert - } else { - return foundLeaf, cert - } - +func verifyChain(certs []*x509.Certificate, PinnedPeerCertificateSha256 [][]byte) (verifyResult, *x509.Certificate) { + for _, cert := range certs { + certHash := GenerateCertHash(cert) + for _, c := range PinnedPeerCertificateSha256 { + if hmac.Equal(certHash, c) { + if cert.IsCA { + return foundCA, cert + } else { + return foundLeaf, cert } + } } } diff --git a/transport/internet/tls/pin.go b/transport/internet/tls/pin.go index 2bbaf614..060f5d73 100644 --- a/transport/internet/tls/pin.go +++ b/transport/internet/tls/pin.go @@ -3,40 +3,37 @@ package tls import ( "crypto/sha256" "crypto/x509" - "encoding/base64" + "encoding/hex" "encoding/pem" ) -func CalculatePEMCertChainSHA256Hash(certContent []byte) string { - var certChain [][]byte +func CalculatePEMLeafCertSHA256Hash(certContent []byte) (string, error) { + var leafCert *x509.Certificate for { + var err error block, remain := pem.Decode(certContent) if block == nil { break } - certChain = append(certChain, block.Bytes) + leafCert, err = x509.ParseCertificate(block.Bytes) + if err != nil { + return "", err + } certContent = remain } - certChainHash := GenerateCertChainHash(certChain) - certChainHashB64 := base64.StdEncoding.EncodeToString(certChainHash) - return certChainHashB64 + certHash := GenerateCertHash(leafCert) + certHashHex := hex.EncodeToString(certHash) + return certHashHex, nil } -func GenerateCertChainHash(rawCerts [][]byte) []byte { - var hashValue []byte - for _, certValue := range rawCerts { - out := sha256.Sum256(certValue) - if hashValue == nil { - hashValue = out[:] - } else { - newHashValue := sha256.Sum256(append(hashValue, out[:]...)) - hashValue = newHashValue[:] - } +// []byte must be ASN.1 DER content +func GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte { + var out [32]byte + switch v := any(cert).(type) { + case *x509.Certificate: + out = sha256.Sum256(v.Raw) + case []byte: + out = sha256.Sum256(v) } - return hashValue -} - -func GenerateCertHash(cert *x509.Certificate) []byte { - out := sha256.Sum256(cert.Raw) return out[:] } diff --git a/transport/internet/tls/pin_test.go b/transport/internet/tls/pin_test.go index 3b2091bf..e0558c45 100644 --- a/transport/internet/tls/pin_test.go +++ b/transport/internet/tls/pin_test.go @@ -9,109 +9,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCalculateCertChainHash(t *testing.T) { - /* This is used to make sure that the hash signature generated is consistent - Do NOT change this test to suit your modification. - */ - const CertBundle = ` ------BEGIN CERTIFICATE----- -MIIFJjCCBA6gAwIBAgISBL8FgUdEcVYEjdMkTZPgC3ocMA0GCSqGSIb3DQEBCwUA -MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD -EwJSMzAeFw0yMTAzMjkwMTM2MzlaFw0yMTA2MjcwMTM2MzlaMBsxGTAXBgNVBAMT -EHNlY3VyZS5ra2Rldi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDOF/j+s7rHaDMXdhYjffoOFjNZb7n3sCuvubI3qOcgJmr1WPlCEry50KoY8FaB -IF2HstMIZceN4NoUK7mr3WAvsQTA47uBfuhp+XQmAQW0T/fYD+XbvxtCENEin+xm -JsvTKZLTKbE08E964J4H+1sBmueP6rvy2Wt95z0XkqoQiikpmLE87WdltQcATvVX -qqrL64hV0nN4Hdi2Bv1cQ92aR7lZGj8jiQRtTj8y5Ah3Gk3fPoao+yI7gnzembqo -fddePzz/u8iEuvYAsIYZKn9bbS7rkYoJazL2/xiDZR7usn0SomzmM6lGXDD3FF4b -lyTkLYwgFVgbGWoz1+eOHD5BAgMBAAGjggJLMIICRzAOBgNVHQ8BAf8EBAMCBaAw -HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD -VR0OBBYEFOPRdApL8XENLXDuU3oPisykGyp+MB8GA1UdIwQYMBaAFBQusxe3WFbL -rlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDov -L3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5v -cmcvMBsGA1UdEQQUMBKCEHNlY3VyZS5ra2Rldi5vcmcwTAYDVR0gBEUwQzAIBgZn -gQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5s -ZXRzZW5jcnlwdC5vcmcwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQD2XJQv0Xcw -IhRUGAgwlFaO400TGTO/3wwvIAvMTvFk4wAAAXh71yBGAAAEAwBGMEQCIDmziDOn -ehPY2KoAFX8fPWiCm4EBTbGJXBWF1LCotPJBAiBLSCg+krXvbyoABnTm8knv0hbG -/ZOk8LV6qpw9VoQwGwB3AG9Tdqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kT -AAABeHvXIIAAAAQDAEgwRgIhAOkeKc52wR3n5QWZfa3zbbicMMSQrTGbQ+1fHNs7 -SsRvAiEAqbflDx1nZRsTA22FfNYfgF6v5Z3/svjiTleWSQad4WswDQYJKoZIhvcN -AQELBQADggEBAEj8tg+Agf5sNBM9CvjeXbA0fkpGDaQzXEkwefAea5vPgKoGiWSN -pHDkyr0i7+mqa7cMXhmmo20V0/+QDv0nrsCw8pgJuviC3GvK6agT6WfkXM2djJuo -osPeXOL9KEF/ATv0EyM5tr9TIoRSSYQoRhuqEdg3Dz9Ii8GXR5HhbYr6Cd7gwNHS -kYeivKDmgv31GHb4twPSE9LZ/U+56lNVvSbJ4csupIF3GnxnxrFSmijYNOPoM6mj -tzY45d4mjPs0fKCFKSsVM6YT0tX4NwIKsOaeQg30WLtRyDwYm6ma/a/UUUS0FloZ -2/p85glOgzownfoRjzTbqHu8ewtMd6Apc0E= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEZTCCA02gAwIBAgIQQAF1BIMUpMghjISpDBbN3zANBgkqhkiG9w0BAQsFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTIwMTAwNzE5MjE0MFoXDTIxMDkyOTE5MjE0MFow -MjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxCzAJBgNVBAMT -AlIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwIVKMz2oJTTDxLs -jVWSw/iC8ZmmekKIp10mqrUrucVMsa+Oa/l1yKPXD0eUFFU1V4yeqKI5GfWCPEKp -Tm71O8Mu243AsFzzWTjn7c9p8FoLG77AlCQlh/o3cbMT5xys4Zvv2+Q7RVJFlqnB -U840yFLuta7tj95gcOKlVKu2bQ6XpUA0ayvTvGbrZjR8+muLj1cpmfgwF126cm/7 -gcWt0oZYPRfH5wm78Sv3htzB2nFd1EbjzK0lwYi8YGd1ZrPxGPeiXOZT/zqItkel -/xMY6pgJdz+dU/nPAeX1pnAXFK9jpP+Zs5Od3FOnBv5IhR2haa4ldbsTzFID9e1R -oYvbFQIDAQABo4IBaDCCAWQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E -BAMCAYYwSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5p -ZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTE -p7Gkeyxx+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEE -AYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2Vu -Y3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0 -LmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYf -r52LFMLGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0B -AQsFAAOCAQEA2UzgyfWEiDcx27sT4rP8i2tiEmxYt0l+PAK3qB8oYevO4C5z70kH -ejWEHx2taPDY/laBL21/WKZuNTYQHHPD5b1tXgHXbnL7KqC401dk5VvCadTQsvd8 -S8MXjohyc9z9/G2948kLjmE6Flh9dDYrVYA9x2O+hEPGOaEOa1eePynBgPayvUfL -qjBstzLhWVQLGAkXXmNs+5ZnPBxzDJOLxhF2JIbeQAcH5H0tZrUlo5ZYyOqA7s9p -O5b85o3AM/OJ+CktFBQtfvBhcJVd9wvlwPsk+uyOy2HI7mNxKKgsBTt375teA2Tw -UdHkhVNcsAKX1H7GNNLOEADksd86wuoXvg== ------END CERTIFICATE----- -` - t.Run("bundle", func(t *testing.T) { - hash := CalculatePEMCertChainSHA256Hash([]byte(CertBundle)) - assert.Equal(t, "WF65fBkgltadMnXryOMZ6TEYeV4d5Q0uu4SGXGZ0RjI=", hash) - }) - const Single = `-----BEGIN CERTIFICATE----- -MIIFJjCCBA6gAwIBAgISBL8FgUdEcVYEjdMkTZPgC3ocMA0GCSqGSIb3DQEBCwUA -MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD -EwJSMzAeFw0yMTAzMjkwMTM2MzlaFw0yMTA2MjcwMTM2MzlaMBsxGTAXBgNVBAMT -EHNlY3VyZS5ra2Rldi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDOF/j+s7rHaDMXdhYjffoOFjNZb7n3sCuvubI3qOcgJmr1WPlCEry50KoY8FaB -IF2HstMIZceN4NoUK7mr3WAvsQTA47uBfuhp+XQmAQW0T/fYD+XbvxtCENEin+xm -JsvTKZLTKbE08E964J4H+1sBmueP6rvy2Wt95z0XkqoQiikpmLE87WdltQcATvVX -qqrL64hV0nN4Hdi2Bv1cQ92aR7lZGj8jiQRtTj8y5Ah3Gk3fPoao+yI7gnzembqo -fddePzz/u8iEuvYAsIYZKn9bbS7rkYoJazL2/xiDZR7usn0SomzmM6lGXDD3FF4b -lyTkLYwgFVgbGWoz1+eOHD5BAgMBAAGjggJLMIICRzAOBgNVHQ8BAf8EBAMCBaAw -HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD -VR0OBBYEFOPRdApL8XENLXDuU3oPisykGyp+MB8GA1UdIwQYMBaAFBQusxe3WFbL -rlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDov -L3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5v -cmcvMBsGA1UdEQQUMBKCEHNlY3VyZS5ra2Rldi5vcmcwTAYDVR0gBEUwQzAIBgZn -gQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5s -ZXRzZW5jcnlwdC5vcmcwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQD2XJQv0Xcw -IhRUGAgwlFaO400TGTO/3wwvIAvMTvFk4wAAAXh71yBGAAAEAwBGMEQCIDmziDOn -ehPY2KoAFX8fPWiCm4EBTbGJXBWF1LCotPJBAiBLSCg+krXvbyoABnTm8knv0hbG -/ZOk8LV6qpw9VoQwGwB3AG9Tdqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kT -AAABeHvXIIAAAAQDAEgwRgIhAOkeKc52wR3n5QWZfa3zbbicMMSQrTGbQ+1fHNs7 -SsRvAiEAqbflDx1nZRsTA22FfNYfgF6v5Z3/svjiTleWSQad4WswDQYJKoZIhvcN -AQELBQADggEBAEj8tg+Agf5sNBM9CvjeXbA0fkpGDaQzXEkwefAea5vPgKoGiWSN -pHDkyr0i7+mqa7cMXhmmo20V0/+QDv0nrsCw8pgJuviC3GvK6agT6WfkXM2djJuo -osPeXOL9KEF/ATv0EyM5tr9TIoRSSYQoRhuqEdg3Dz9Ii8GXR5HhbYr6Cd7gwNHS -kYeivKDmgv31GHb4twPSE9LZ/U+56lNVvSbJ4csupIF3GnxnxrFSmijYNOPoM6mj -tzY45d4mjPs0fKCFKSsVM6YT0tX4NwIKsOaeQg30WLtRyDwYm6ma/a/UUUS0FloZ -2/p85glOgzownfoRjzTbqHu8ewtMd6Apc0E= ------END CERTIFICATE----- -` - t.Run("single", func(t *testing.T) { - hash := CalculatePEMCertChainSHA256Hash([]byte(Single)) - assert.Equal(t, "FW3SVMCL6um2wVltOdgJ3DpI82aredw83YoCblkMkVM=", hash) - }) -} - func TestCalculateCertHash(t *testing.T) { const Single = `-----BEGIN CERTIFICATE----- MIINWzCCC0OgAwIBAgITMwK6ajqdrV0tahuIrQAAArpqOjANBgkqhkiG9w0BAQwF