yuhan6665
2026-09-20 21:57:26 +00:00
committed by RPRX
parent 8cdf7bf9c7
commit 9124ee149d
25 changed files with 1491 additions and 1118 deletions
+276 -86
View File
@@ -12,6 +12,8 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/fips140"
"crypto/mldsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
@@ -20,6 +22,7 @@ import (
"fmt"
"io"
"net"
"runtime"
"slices"
"strings"
"sync"
@@ -67,7 +70,9 @@ const (
recordHeaderLen = 5 // record header length
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)
maxHandshakeCertificateMsg = 262144 // maximum certificate message size (256 KiB)
//////////////////////////////////// [REALITY] SECTION: change maxUselessRecords to match with OpenSSL
maxUselessRecords = 32 // maximum number of consecutive non-advancing records
//////////////////////////////////// [REALITY] SECTION END
)
// TLS record types.
@@ -145,19 +150,32 @@ const (
type CurveID uint16
const (
CurveP256 CurveID = 23
CurveP384 CurveID = 24
CurveP521 CurveID = 25
X25519 CurveID = 29
X25519MLKEM768 CurveID = 4588
CurveP256 CurveID = 23
CurveP384 CurveID = 24
CurveP521 CurveID = 25
X25519 CurveID = 29
X25519MLKEM768 CurveID = 4588
SecP256r1MLKEM768 CurveID = 4587
SecP384r1MLKEM1024 CurveID = 4589
MLKEM1024 CurveID = 514
)
func isTLS13OnlyKeyExchange(curve CurveID) bool {
return curve == X25519MLKEM768
switch curve {
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024:
return true
default:
return false
}
}
func isPQKeyExchange(curve CurveID) bool {
return curve == X25519MLKEM768
switch curve {
case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024:
return true
default:
return false
}
}
// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8.
@@ -203,11 +221,12 @@ const (
signatureRSAPSS
signatureECDSA
signatureEd25519
signatureMLDSA
)
// directSigning is a standard Hash value that signals that no pre-hashing
// should be performed, and that the input should be signed directly. It is the
// hash function associated with the Ed25519 signature scheme.
// hash function associated with the Ed25519 and ML-DSA signature schemes.
var directSigning crypto.Hash = 0
// helloRetryRequestRandom is set as the Random value of a ServerHello
@@ -304,11 +323,21 @@ type ConnectionState struct {
// client side.
ECHAccepted bool
// HelloRetryRequest indicates whether we sent a HelloRetryRequest if we
// are a server, or if we received a HelloRetryRequest if we are a client.
HelloRetryRequest bool
// LocalCertificate is the certificate chain presented to the peer, if any,
// during the handshake. This field is only populated for connections which
// are not resumed (DidResume is false).
LocalCertificate [][]byte
// ekm is a closure exposed via ExportKeyingMaterial.
ekm func(label string, context []byte, length int) ([]byte, error)
// testingOnlyDidHRR is true if a HelloRetryRequest was sent/received.
testingOnlyDidHRR bool
// testingOnlyPeerSignatureAlgorithm is the signature algorithm used by the
// peer to sign the handshake. It is not set for resumed connections.
testingOnlyPeerSignatureAlgorithm SignatureScheme
}
// ExportKeyingMaterial returns length bytes of exported key material in a new
@@ -316,11 +345,6 @@ type ConnectionState struct {
// the seed. If the connection was set to allow renegotiation via
// Config.Renegotiation, or if the connections supports neither TLS 1.3 nor
// Extended Master Secret, this function will return an error.
//
// Exporting key material without Extended Master Secret or TLS 1.3 was disabled
// in Go 1.22 due to security issues (see the Security Considerations sections
// of RFC 5705 and RFC 7627), but can be re-enabled with the GODEBUG setting
// tlsunsafeekm=1.
func (cs *ConnectionState) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) {
return cs.ekm(label, context, length)
}
@@ -407,6 +431,11 @@ const (
// EdDSA algorithms.
Ed25519 SignatureScheme = 0x0807
// ML-DSA algorithms.
MLDSA44 SignatureScheme = 0x0904
MLDSA65 SignatureScheme = 0x0905
MLDSA87 SignatureScheme = 0x0906
// Legacy signature and hash algorithms for TLS 1.2.
PKCS1WithSHA1 SignatureScheme = 0x0201
ECDSAWithSHA1 SignatureScheme = 0x0203
@@ -465,10 +494,17 @@ type ClientHelloInfo struct {
// connection to fail.
Conn net.Conn
// HelloRetryRequest indicates whether the ClientHello was sent in response
// to a HelloRetryRequest message.
HelloRetryRequest bool
// config is embedded by the GetCertificate or GetConfigForClient caller,
// for use with SupportsCertificate.
config *Config
// isQUIC indicates whether the connection is a QUIC connection.
isQUIC bool
// ctx is the context of the handshake that is in progress.
ctx context.Context
}
@@ -537,6 +573,7 @@ const (
RenegotiateFreelyAsClient
)
//////////////////////////////////// [REALITY] SECTION: define var
type LimitFallback struct {
AfterBytes uint64
BytesPerSec uint64
@@ -566,11 +603,15 @@ type Config struct {
LimitFallbackUpload LimitFallback
LimitFallbackDownload LimitFallback
//////////////////////////////////// [REALITY] SECTION END
// Rand provides the source of entropy for nonces and RSA blinding.
// Rand provides the source of entropy for the connection.
// If Rand is nil, TLS uses the cryptographic random reader in package
// crypto/rand.
// The Reader must be safe for use by multiple goroutines.
// crypto/rand. The Reader must be safe for use by multiple goroutines.
//
// Deprecated: this should be left nil in production. Not all TLS
// configurations are guaranteed to use Rand. Test code can use
// [testing/cryptotest.SetGlobalRandom] instead.
Rand io.Reader
// Time returns the current time as the number of seconds since the epoch.
@@ -636,10 +677,13 @@ type Config struct {
// If GetConfigForClient is nil, the Config passed to Server() will be
// used for all connections.
//
// If SessionTicketKey was explicitly set on the returned Config, or if
// SetSessionTicketKeys was called on the returned Config, those keys will
// If SessionTicketKey is explicitly set on the returned Config, or if
// SetSessionTicketKeys is called on the returned Config, those keys will
// be used. Otherwise, the original Config keys will be used (and possibly
// rotated if they are automatically managed).
// rotated if they are automatically managed). WARNING: this allows session
// resumption of connections originally established with the parent (or a
// sibling) Config, which may bypass the [Config.VerifyPeerCertificate]
// value of the returned Config.
GetConfigForClient func(*ClientHelloInfo) (*Config, error)
// VerifyPeerCertificate, if not nil, is called after normal
@@ -657,8 +701,10 @@ type Config struct {
// rawCerts may be empty on the server if ClientAuth is RequestClientCert or
// VerifyClientCertIfGiven.
//
// This callback is not invoked on resumed connections, as certificates are
// not re-verified on resumption.
// This callback is not invoked on resumed connections. WARNING: this
// includes connections resumed across Configs returned by [Config.Clone] or
// [Config.GetConfigForClient] and their parents. If that is not intended,
// use [Config.VerifyConnection] instead, or set [Config.SessionTicketsDisabled].
//
// verifiedChains and its contents should not be modified.
VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
@@ -714,11 +760,7 @@ type Config struct {
// the list is ignored. Note that TLS 1.3 ciphersuites are not configurable.
//
// If CipherSuites is nil, a safe default list is used. The default cipher
// suites might change over time. In Go 1.22 RSA key exchange based cipher
// suites were removed from the default list, but can be re-added with the
// GODEBUG setting tlsrsakex=1. In Go 1.23 3DES cipher suites were removed
// from the default list, but can be re-added with the GODEBUG setting
// tls3des=1.
// suites might change over time.
CipherSuites []uint16
// PreferServerCipherSuites is a legacy field and has no effect.
@@ -783,9 +825,7 @@ type Config struct {
//
// By default, TLS 1.2 is currently used as the minimum. TLS 1.0 is the
// minimum supported by this package.
//
// The server-side default can be reverted to TLS 1.0 by including the value
// "tls10server=1" in the GODEBUG environment variable.
MinVersion uint16
// MaxVersion contains the maximum TLS version that is acceptable.
@@ -803,6 +843,11 @@ type Config struct {
// From Go 1.24, the default includes the [X25519MLKEM768] hybrid
// post-quantum key exchange. To disable it, set CurvePreferences explicitly
// or use the GODEBUG=tlsmlkem=0 environment variable.
//
// From Go 1.26, the default includes the [SecP256r1MLKEM768] and
// [SecP384r1MLKEM1024] hybrid post-quantum key exchanges, too. To disable
// them, set CurvePreferences explicitly or use either the
// GODEBUG=tlsmlkem=0 or the GODEBUG=tlssecpmlkem=0 environment variable.
CurvePreferences []CurveID
// DynamicRecordSizingDisabled disables adaptive sizing of TLS records.
@@ -818,7 +863,7 @@ type Config struct {
// KeyLogWriter optionally specifies a destination for TLS master secrets
// in NSS key log format that can be used to allow external programs
// such as Wireshark to decrypt TLS connections.
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
// See https://datatracker.ietf.org/doc/draft-ietf-tls-keylogfile/.
// Use of KeyLogWriter compromises security and should only be
// used for debugging.
KeyLogWriter io.Writer
@@ -910,13 +955,34 @@ type Config struct {
// with a specific ECH config known to a client.
type EncryptedClientHelloKey struct {
// Config should be a marshalled ECHConfig associated with PrivateKey. This
// must match the config provided to clients byte-for-byte. The config
// should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
// HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
// AES-128-GCM (0x0001), AES-256-GCM (0x0002), ChaCha20Poly1305 (0x0003).
// must match the config provided to clients byte-for-byte. The config must
// use as KEM one of
//
// - DHKEM(P-256, HKDF-SHA256) (0x0010)
// - DHKEM(P-384, HKDF-SHA384) (0x0011)
// - DHKEM(P-521, HKDF-SHA512) (0x0012)
// - DHKEM(X25519, HKDF-SHA256) (0x0020)
// - ML-KEM-768 (0x0041)
// - ML-KEM-1024 (0x0042)
// - MLKEM768-P256 (0x0050)
// - MLKEM1024-P384 (0x0051)
// - MLKEM768-X25519 (0x647a)
//
// and as KDF one of
//
// - HKDF-SHA256 (0x0001)
// - HKDF-SHA384 (0x0002)
// - HKDF-SHA512 (0x0003)
//
// and as AEAD one of
//
// - AES-128-GCM (0x0001)
// - AES-256-GCM (0x0002)
// - ChaCha20Poly1305 (0x0003)
//
Config []byte
// PrivateKey should be a marshalled private key. Currently, we expect
// this to be the output of [ecdh.PrivateKey.Bytes].
// PrivateKey should be a marshalled private key, in the format expected by
// HPKE's DeserializePrivateKey (see RFC 9180), for the KEM used in Config.
PrivateKey []byte
// SendAsRetry indicates if Config should be sent as part of the list of
// retry configs when ECH is requested by the client but rejected by the
@@ -961,8 +1027,15 @@ func (c *Config) ticketKeyFromBytes(b [32]byte) (key ticketKey) {
// ticket, and the lifetime we set for all tickets we send.
const maxSessionTicketLifetime = 7 * 24 * time.Hour
// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a [Config] that is
// being used concurrently by a TLS client or server.
// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a
// [Config] that is being used concurrently by a TLS client or server.
//
// The returned Config can share session ticket keys with the original Config,
// which means connections could be resumed across the two Configs. WARNING:
// [Config.VerifyPeerCertificate] does not get called on resumed connections,
// including connections that were originally established on the parent Config.
// If that is not intended, use [Config.VerifyConnection] instead, or set
// [Config.SessionTicketsDisabled].
func (c *Config) Clone() *Config {
if c == nil {
return nil
@@ -970,6 +1043,7 @@ func (c *Config) Clone() *Config {
c.mutex.RLock()
defer c.mutex.RUnlock()
return &Config{
//////////////////////////////////// [REALITY] SECTION: define var
DialContext: c.DialContext,
Show: c.Show,
Type: c.Type,
@@ -983,6 +1057,7 @@ func (c *Config) Clone() *Config {
ShortIds: c.ShortIds,
LimitFallbackUpload: c.LimitFallbackUpload,
LimitFallbackDownload: c.LimitFallbackDownload,
//////////////////////////////////// [REALITY] SECTION END
Rand: c.Rand,
Time: c.Time,
Certificates: c.Certificates,
@@ -1201,7 +1276,7 @@ const roleServer = false
// supportedVersions returns the list of supported TLS versions, sorted from
// highest to lowest (and hence also in preference order).
func (c *Config) supportedVersions(isClient bool) []uint16 {
func (c *Config) supportedVersions(isClient, isQUIC bool) []uint16 {
versions := make([]uint16, 0, len(supportedVersions))
for _, v := range supportedVersions {
if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) {
@@ -1219,13 +1294,16 @@ func (c *Config) supportedVersions(isClient bool) []uint16 {
if c != nil && c.MaxVersion != 0 && v > c.MaxVersion {
continue
}
if isQUIC && v < VersionTLS13 {
continue
}
versions = append(versions, v)
}
return versions
}
func (c *Config) maxSupportedVersion(isClient bool) uint16 {
supportedVersions := c.supportedVersions(isClient)
func (c *Config) maxSupportedVersion(isClient, isQUIC bool) uint16 {
supportedVersions := c.supportedVersions(isClient, isQUIC)
if len(supportedVersions) == 0 {
return 0
}
@@ -1247,31 +1325,38 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 {
}
func (c *Config) curvePreferences(version uint16) []CurveID {
curvePreferences := defaultCurvePreferences()
if fips140tls.Required() {
curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool {
return !slices.Contains(allowedCurvePreferencesFIPS, x)
})
}
if c != nil && len(c.CurvePreferences) != 0 {
curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool {
return !slices.Contains(c.CurvePreferences, x)
})
}
if version < VersionTLS13 {
curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange)
}
return curvePreferences
return slices.DeleteFunc(curvePreferenceOrder(), func(x CurveID) bool {
return !c.supportsCurve(version, x)
})
}
func (c *Config) supportsCurve(version uint16, curve CurveID) bool {
return slices.Contains(c.curvePreferences(version), curve)
func (c *Config) supportsCurve(version uint16, x CurveID) bool {
if c != nil && len(c.CurvePreferences) != 0 {
if !slices.Contains(c.CurvePreferences, x) {
return false
}
// Ignore unimplemented entries in c.CurvePreferences.
if !slices.Contains(curvePreferenceOrder(), x) {
return false
}
} else {
if !defaultCurveEnabled(x) {
return false
}
}
if fips140tls.Required() && !slices.Contains(allowedCurvePreferencesFIPS, x) {
return false
}
if version < VersionTLS13 && isTLS13OnlyKeyExchange(x) {
return false
}
return true
}
// mutualVersion returns the protocol version to use given the advertised
// versions of the peer. The highest supported version is preferred.
func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) {
supportedVersions := c.supportedVersions(isClient)
func (c *Config) mutualVersion(isClient, isQUIC bool, peerVersions []uint16) (uint16, bool) {
supportedVersions := c.supportedVersions(isClient, isQUIC)
for _, v := range supportedVersions {
if slices.Contains(peerVersions, v) {
return v, true
@@ -1357,7 +1442,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
if config == nil {
config = &Config{}
}
vers, ok := config.mutualVersion(roleServer, chi.SupportedVersions)
vers, ok := config.mutualVersion(roleServer, chi.isQUIC, chi.SupportedVersions)
if !ok {
return errors.New("no mutually supported protocol versions")
}
@@ -1465,6 +1550,9 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
return errors.New("connection doesn't support Ed25519")
}
ecdsaCipherSuite = true
case *mldsa.PublicKey:
// ML-DSA requires TLS 1.3, which we already excluded above.
return errors.New("connection doesn't support ML-DSA")
case *rsa.PublicKey:
default:
return supportsRSAFallback(unsupportedCertificateError(c))
@@ -1578,7 +1666,10 @@ func (c *Config) writeKeyLog(label string, clientRandom, secret []byte) error {
_, err := c.KeyLogWriter.Write(logLine)
writerMutex.Unlock()
return err
if err != nil {
return fmt.Errorf("tls: KeyLogWriter: %w", err)
}
return nil
}
// writerMutex protects all KeyLogWriters globally. It is rarely enabled,
@@ -1589,9 +1680,14 @@ var writerMutex sync.Mutex
type Certificate struct {
Certificate [][]byte
// PrivateKey contains the private key corresponding to the public key in
// Leaf. This must implement crypto.Signer with an RSA, ECDSA or Ed25519 PublicKey.
// Leaf. This must implement [crypto.Signer] with an RSA, ECDSA, Ed25519
// (TLS 1.2+), or ML-DSA (TLS 1.3) PublicKey.
//
// For a server up to TLS 1.2, it can also implement crypto.Decrypter with
// an RSA PublicKey.
//
// If it implements [crypto.MessageSigner], SignMessage will be used instead
// of Sign for TLS 1.2 and later.
PrivateKey crypto.PrivateKey
// SupportedSignatureAlgorithms is an optional list restricting what
// signature algorithms the PrivateKey can be used for.
@@ -1680,6 +1776,10 @@ func (c *lruSessionCache) Put(sessionKey string, cs *ClientSessionState) {
return
}
if cs == nil {
return
}
if c.q.Len() < c.capacity {
entry := &lruSessionCacheEntry{sessionKey, cs}
c.m[sessionKey] = c.q.PushFront(entry)
@@ -1718,35 +1818,85 @@ func unexpectedMessageError(wanted, got any) error {
return fmt.Errorf("tls: received unexpected handshake message of type %T when waiting for %T", got, wanted)
}
var testingOnlySupportedSignatureAlgorithms []SignatureScheme
// supportedSignatureAlgorithms returns the supported signature algorithms for
// the given minimum TLS version, to advertise in ClientHello and
// CertificateRequest messages.
func supportedSignatureAlgorithms(minVers uint16) []SignatureScheme {
// the given range of TLS versions, to advertise in ClientHello and
// CertificateRequest messages. An algorithm is included if it is enabled at any
// version in the range.
func supportedSignatureAlgorithms(minVers, maxVers uint16) []SignatureScheme {
sigAlgs := defaultSupportedSignatureAlgorithms()
if fips140tls.Required() {
sigAlgs = slices.DeleteFunc(sigAlgs, func(s SignatureScheme) bool {
return !slices.Contains(allowedSignatureAlgorithmsFIPS, s)
})
if testingOnlySupportedSignatureAlgorithms != nil {
sigAlgs = slices.Clone(testingOnlySupportedSignatureAlgorithms)
}
if minVers > VersionTLS12 {
sigAlgs = slices.DeleteFunc(sigAlgs, func(s SignatureScheme) bool {
sigType, sigHash, _ := typeAndHashFromSignatureScheme(s)
return sigType == signaturePKCS1v15 || sigHash == crypto.SHA1
})
return slices.DeleteFunc(sigAlgs, func(s SignatureScheme) bool {
for v := minVers; v <= maxVers; v++ {
if !isDisabledSignatureAlgorithm(v, s, false) {
return false
}
}
return true
})
}
//var tlssha1 = godebug.New("tlssha1")
func isDisabledSignatureAlgorithm(version uint16, s SignatureScheme, isCert bool) bool {
if fips140tls.Required() && !slices.Contains(allowedSignatureAlgorithmsFIPS, s) {
return true
}
return sigAlgs
switch s {
case MLDSA44, MLDSA65, MLDSA87:
// ML-DSA is not available in FIPS 140-3 module v1.0.0.
if fips140.Version() == "v1.0.0" {
return true
}
// ML-DSA codepoints are only defined for TLS 1.3.
if version < VersionTLS13 {
return true
}
}
// For the _cert extension we include all algorithms, including SHA-1 and
// PKCS#1 v1.5, because it's more likely that something on our side will be
// willing to accept a *-with-SHA1 certificate (e.g. with a custom
// VerifyConnection or by a direct match with the CertPool), than that the
// peer would have a better certificate but is just choosing not to send it.
// crypto/x509 will refuse to verify important SHA-1 signatures anyway.
if isCert {
return false
}
// TLS 1.3 removed support for PKCS#1 v1.5 and SHA-1 signatures,
// and Go 1.25 removed support for SHA-1 signatures in TLS 1.2.
if version > VersionTLS12 {
sigType, sigHash, _ := typeAndHashFromSignatureScheme(s)
if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 {
return true
}
} else { //if tlssha1.Value() != "1" {
_, sigHash, _ := typeAndHashFromSignatureScheme(s)
if sigHash == crypto.SHA1 {
return true
}
}
return false
}
// supportedSignatureAlgorithmsCert returns the supported algorithms for
// signatures in certificates.
func supportedSignatureAlgorithmsCert() []SignatureScheme {
sigAlgs := defaultSupportedSignatureAlgorithmsCert()
if fips140tls.Required() {
sigAlgs = slices.DeleteFunc(sigAlgs, func(s SignatureScheme) bool {
return !slices.Contains(allowedSignatureAlgorithmsFIPS, s)
})
}
return sigAlgs
func supportedSignatureAlgorithmsCert(minVers, maxVers uint16) []SignatureScheme {
sigAlgs := defaultSupportedSignatureAlgorithms()
return slices.DeleteFunc(sigAlgs, func(s SignatureScheme) bool {
for v := minVers; v <= maxVers; v++ {
if !isDisabledSignatureAlgorithm(v, s, true) {
return false
}
}
return true
})
}
func isSupportedSignatureAlgorithm(sigAlg SignatureScheme, supportedSignatureAlgorithms []SignatureScheme) bool {
@@ -1806,3 +1956,43 @@ func fipsAllowChain(chain []*x509.Certificate) bool {
return true
}
// anyValidVerifiedChain reports if at least one of the chains in verifiedChains
// is valid, as indicated by none of the certificates being expired and the root
// being in opts.Roots (or in the system root pool if opts.Roots is nil). If
// verifiedChains is empty, it returns false.
func anyValidVerifiedChain(verifiedChains [][]*x509.Certificate, opts x509.VerifyOptions) bool {
for _, chain := range verifiedChains {
if len(chain) == 0 {
continue
}
if slices.ContainsFunc(chain, func(cert *x509.Certificate) bool {
return opts.CurrentTime.Before(cert.NotBefore) || opts.CurrentTime.After(cert.NotAfter)
}) {
continue
}
// Since we already validated the chain, we only care that it is rooted
// in a CA in opts.Roots. On platforms where we control chain validation
// (e.g. not Windows or macOS) this is a simple lookup in the CertPool
// internal hash map, which we can simulate by running Verify on the
// root. On other platforms, we have to do full verification again,
// because EKU handling might differ. We will want to replace this with
// CertPool.Contains if/once that is available. See go.dev/issue/77376.
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
opts.Intermediates = x509.NewCertPool()
for _, cert := range chain[1:max(1, len(chain)-1)] {
opts.Intermediates.AddCert(cert)
}
leaf := chain[0]
if _, err := leaf.Verify(opts); err == nil {
return true
}
} else {
root := chain[len(chain)-1]
if _, err := root.Verify(opts); err == nil {
return true
}
}
}
return false
}