风扇滑翔翼 4 luni în urmă
părinte
comite
ef1d468f73

+ 0 - 12
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 {

+ 0 - 40
main/commands/all/tls/certchainhash.go

@@ -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 <cert.pem>
-	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)
-}

+ 44 - 0
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 <cert.pem>
+	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)
+}

+ 9 - 3
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
 	}
 }

+ 1 - 1
main/commands/all/tls/tls.go

@@ -13,7 +13,7 @@ var CmdTLS = &base.Command{
 	Commands: []*base.Command{
 		cmdCert,
 		cmdPing,
-		cmdCertChainHash,
+		cmdLeafCertHash,
 		cmdECH,
 	},
 }

+ 24 - 24
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},
 							}),
 						},
 					},

+ 23 - 38
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
 				}
+
 			}
 		}
 	}

+ 19 - 22
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[:]
 }

+ 0 - 103
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