// TODO(@gregorycooke) - Remove when only golang 1.19+ is supported
//go:build go1.19

/*
 *
 * Copyright 2021 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package advancedtls

import (
	"bytes"
	"crypto/sha1"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/binary"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/crypto/cryptobyte"
	cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
	"google.golang.org/grpc/grpclog"
)

var grpclogLogger = grpclog.Component("advancedtls")

// Cache is an interface to cache CRL files.
// The cache implementation must be concurrency safe.
// A fixed size lru cache from golang-lru is recommended.
type Cache interface {
	// Add adds a value to the cache.
	Add(key, value any) bool
	// Get looks up a key's value from the cache.
	Get(key any) (value any, ok bool)
}

// RevocationOptions allows a user to configure certificate revocation behavior.
type RevocationOptions struct {
	// RootDir is the directory to search for CRL files.
	// Directory format must match OpenSSL X509_LOOKUP_hash_dir(3).
	// Deprecated: use CRLProvider instead.
	RootDir string
	// DenyUndetermined controls if certificate chains with RevocationUndetermined
	// revocation status are allowed to complete.
	DenyUndetermined bool
	// AllowUndetermined controls if certificate chains with RevocationUndetermined
	// revocation status are allowed to complete.
	//
	// Deprecated: use DenyUndetermined instead
	AllowUndetermined bool
	// Cache will store CRL files if not nil, otherwise files are reloaded for every lookup.
	// Only used for caching CRLs when using the RootDir setting.
	// Deprecated: use CRLProvider instead.
	Cache Cache
	// CRLProvider is an alternative to using RootDir directly for the
	// X509_LOOKUP_hash_dir approach to CRL files. If set, the CRLProvider's CRL
	// function will be called when looking up and fetching CRLs during the
	// handshake.
	CRLProvider CRLProvider
}

// RevocationConfig contains options for CRL lookup.
//
// Deprecated: use RevocationOptions instead.
type RevocationConfig = RevocationOptions

// revocationStatus is the revocation status for a certificate or chain.
type revocationStatus int

const (
	// RevocationUndetermined means we couldn't find or verify a CRL for the cert.
	RevocationUndetermined revocationStatus = iota
	// RevocationUnrevoked means we found the CRL for the cert and the cert is not revoked.
	RevocationUnrevoked
	// RevocationRevoked means we found the CRL and the cert is revoked.
	RevocationRevoked
)

// CRL contains a pkix.CertificateList and parsed extensions that aren't
// provided by the golang CRL parser.
// All CRLs should be loaded using NewCRL() for bytes directly or ReadCRLFile()
// to read directly from a filepath
type CRL struct {
	certList *x509.RevocationList
	// RFC5280, 5.2.1, all conforming CRLs must have a AKID with the ID method.
	authorityKeyID []byte
	rawIssuer      []byte
}

// NewCRL constructs new CRL from the provided byte array.
func NewCRL(b []byte) (*CRL, error) {
	crl, err := parseRevocationList(b)
	if err != nil {
		return nil, fmt.Errorf("fail to parse CRL: %v", err)
	}
	crlExt, err := parseCRLExtensions(crl)
	if err != nil {
		return nil, fmt.Errorf("fail to parse CRL extensions: %v", err)
	}
	crlExt.rawIssuer, err = extractCRLIssuer(b)
	if err != nil {
		return nil, fmt.Errorf("fail to extract CRL issuer failed err= %v", err)
	}
	return crlExt, nil
}

// ReadCRLFile reads a file from the provided path, and returns constructed CRL
// struct from it.
func ReadCRLFile(path string) (*CRL, error) {
	b, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("cannot read file from provided path %q: %v", path, err)
	}
	crl, err := NewCRL(b)
	if err != nil {
		return nil, fmt.Errorf("cannot construct CRL from file %q: %v", path, err)
	}
	return crl, nil
}

const tagDirectoryName = 4

var (
	// RFC5280, 5.2.4 id-ce-deltaCRLIndicator OBJECT IDENTIFIER ::= { id-ce 27 }
	oidDeltaCRLIndicator = asn1.ObjectIdentifier{2, 5, 29, 27}
	// RFC5280, 5.2.5 id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }
	oidIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}
	// RFC5280, 5.3.3 id-ce-certificateIssuer   OBJECT IDENTIFIER ::= { id-ce 29 }
	oidCertificateIssuer = asn1.ObjectIdentifier{2, 5, 29, 29}
	// RFC5290, 4.2.1.1 id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::=  { id-ce 35 }
	oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
)

// x509NameHash implements the OpenSSL X509_NAME_hash function for hashed directory lookups.
//
// NOTE: due to the behavior of asn1.Marshal, if the original encoding of the RDN sequence
// contains strings which do not use the ASN.1 PrintableString type, the name will not be
// re-encoded using those types, resulting in a hash which does not match that produced
// by OpenSSL.
func x509NameHash(r pkix.RDNSequence) string {
	var canonBytes []byte
	// First, canonicalize all the strings.
	for _, rdnSet := range r {
		for i, rdn := range rdnSet {
			value, ok := rdn.Value.(string)
			if !ok {
				continue
			}
			// OpenSSL trims all whitespace, does a tolower, and removes extra spaces between words.
			// Implemented in x509_name_canon in OpenSSL
			canonStr := strings.Join(strings.Fields(
				strings.TrimSpace(strings.ToLower(value))), " ")
			// Then it changes everything to UTF8 strings
			rdnSet[i].Value = asn1.RawValue{Tag: asn1.TagUTF8String, Bytes: []byte(canonStr)}

		}
	}

	// Finally, OpenSSL drops the initial sequence tag
	// so we marshal all the RDNs separately instead of as a group.
	for _, canonRdn := range r {
		b, err := asn1.Marshal(canonRdn)
		if err != nil {
			continue
		}
		canonBytes = append(canonBytes, b...)
	}

	issuerHash := sha1.Sum(canonBytes)
	// Openssl takes the first 4 bytes and encodes them as a little endian
	// uint32 and then uses the hex to make the file name.
	// In C++, this would be:
	// (((unsigned long)md[0]) | ((unsigned long)md[1] << 8L) |
	// ((unsigned long)md[2] << 16L) | ((unsigned long)md[3] << 24L)
	// ) & 0xffffffffL;
	fileHash := binary.LittleEndian.Uint32(issuerHash[0:4])
	return fmt.Sprintf("%08x", fileHash)
}

// checkRevocation checks the connection for revoked certificates based on RFC5280.
// This implementation has the following major limitations:
//   - Indirect CRL files are not supported.
//   - CRL loading is only supported from directories in the X509_LOOKUP_hash_dir format.
//   - OnlySomeReasons is not supported.
//   - Delta CRL files are not supported.
//   - Certificate CRLDistributionPoint must be URLs, but are then ignored and converted into a file path.
//   - CRL checks are done after path building, which goes against RFC4158.
func checkRevocation(conn tls.ConnectionState, cfg RevocationOptions) error {
	return checkChainRevocation(conn.VerifiedChains, cfg)
}

// checkChainRevocation checks the verified certificate chain
// for revoked certificates based on RFC5280.
func checkChainRevocation(verifiedChains [][]*x509.Certificate, cfg RevocationOptions) error {
	// Iterate the verified chains looking for one that is RevocationUnrevoked.
	// A single RevocationUnrevoked chain is enough to allow the connection, and a single RevocationRevoked
	// chain does not mean the connection should fail.
	count := make(map[revocationStatus]int)
	for _, chain := range verifiedChains {
		switch checkChain(chain, cfg) {
		case RevocationUnrevoked:
			// If any chain is RevocationUnrevoked then return no error.
			return nil
		case RevocationRevoked:
			// If this chain is revoked, keep looking for another chain.
			count[RevocationRevoked]++
			continue
		case RevocationUndetermined:
			count[RevocationUndetermined]++
			// TODO(gtcooke94) Remove when deprecated AllowUndetermined is removed
			// For now, if the deprecated value is explicitly set, use it
			if cfg.AllowUndetermined {
				cfg.DenyUndetermined = !cfg.AllowUndetermined
			}
			if cfg.DenyUndetermined {
				continue
			}
			return nil
		}
	}
	return fmt.Errorf("no unrevoked chains found: %v", count)
}

// checkChain will determine and check all certificates in chain against the CRL
// defined in the certificate with the following rules:
// 1. If any certificate is RevocationRevoked, return RevocationRevoked.
// 2. If any certificate is RevocationUndetermined, return RevocationUndetermined.
// 3. If all certificates are RevocationUnrevoked, return RevocationUnrevoked.
func checkChain(chain []*x509.Certificate, cfg RevocationOptions) revocationStatus {
	chainStatus := RevocationUnrevoked
	for _, c := range chain {
		switch checkCert(c, chain, cfg) {
		case RevocationRevoked:
			// Easy case, if a cert in the chain is revoked, the chain is revoked.
			return RevocationRevoked
		case RevocationUndetermined:
			// If we couldn't find the revocation status for a cert, the chain is at best RevocationUndetermined
			// keep looking to see if we find a cert in the chain that's RevocationRevoked,
			// but return RevocationUndetermined at a minimum.
			chainStatus = RevocationUndetermined
		case RevocationUnrevoked:
			// Continue iterating up the cert chain.
			continue
		}
	}
	return chainStatus
}

func cachedCrl(rawIssuer []byte, cache Cache) (*CRL, bool) {
	val, ok := cache.Get(hex.EncodeToString(rawIssuer))
	if !ok {
		return nil, false
	}
	crl, ok := val.(*CRL)
	if !ok {
		return nil, false
	}
	// If the CRL is expired, force a reload.
	if hasExpired(crl.certList, time.Now()) {
		return nil, false
	}
	return crl, true
}

// fetchIssuerCRL fetches and verifies the CRL for rawIssuer from disk or cache if configured in cfg.
func fetchIssuerCRL(rawIssuer []byte, crlVerifyCrt []*x509.Certificate, cfg RevocationOptions) (*CRL, error) {
	if cfg.Cache != nil {
		if crl, ok := cachedCrl(rawIssuer, cfg.Cache); ok {
			return crl, nil
		}
	}

	crl, err := fetchCRLOpenSSLHashDir(rawIssuer, cfg)
	if err != nil {
		return nil, fmt.Errorf("fetchCRL() failed: %v", err)
	}

	if err := verifyCRL(crl, crlVerifyCrt); err != nil {
		return nil, fmt.Errorf("verifyCRL() failed: %v", err)
	}
	if cfg.Cache != nil {
		cfg.Cache.Add(hex.EncodeToString(rawIssuer), crl)
	}
	return crl, nil
}

func fetchCRL(c *x509.Certificate, crlVerifyCrt []*x509.Certificate, cfg RevocationOptions) (*CRL, error) {
	if cfg.CRLProvider != nil {
		crl, err := cfg.CRLProvider.CRL(c)
		if err != nil {
			return nil, fmt.Errorf("CrlProvider failed err = %v", err)
		}
		if crl == nil {
			return nil, fmt.Errorf("no CRL found for certificate's issuer")
		}
		if err := verifyCRL(crl, crlVerifyCrt); err != nil {
			return nil, fmt.Errorf("verifyCRL() failed: %v", err)
		}
		return crl, nil
	}
	return fetchIssuerCRL(c.RawIssuer, crlVerifyCrt, cfg)
}

// checkCert checks a single certificate against the CRL defined in the
// certificate. It will fetch and verify the CRL(s) defined in the root
// directory (or a CRLProvider) specified by cfg. If we can't load (and verify -
// see verifyCRL) any valid authoritative CRL files, the status is
// RevocationUndetermined.
// c is the certificate to check.
// crlVerifyCrt is the group of possible certificates to verify the crl.
func checkCert(c *x509.Certificate, crlVerifyCrt []*x509.Certificate, cfg RevocationOptions) revocationStatus {
	crl, err := fetchCRL(c, crlVerifyCrt, cfg)
	if err != nil {
		// We couldn't load any valid CRL files for the certificate, so we don't
		// know if it's RevocationUnrevoked or not. This is not necessarily a
		// problem - it's not invalid to have no CRLs if you don't have any
		// revocations for an issuer. It also might be an indication that the CRL
		// file is invalid.
		// We just return RevocationUndetermined and there is a setting for the user
		// to control the handling of that.
		grpclogLogger.Warningf("fetchCRL() err = %v", err)
		return RevocationUndetermined
	}
	revocation, err := checkCertRevocation(c, crl)
	if err != nil {
		grpclogLogger.Warningf("checkCertRevocation(CRL %v) failed: %v", crl.certList.Issuer, err)
		// We couldn't check the CRL file for some reason, so we don't know if it's RevocationUnrevoked or not.
		return RevocationUndetermined
	}
	// Here we've gotten a CRL that loads and verifies.
	// We only handle all-reasons CRL files, so this file
	// is authoritative for the certificate.
	return revocation
}

func checkCertRevocation(c *x509.Certificate, crl *CRL) (revocationStatus, error) {
	// Per section 5.3.3 we prime the certificate issuer with the CRL issuer.
	// Subsequent entries use the previous entry's issuer.
	rawEntryIssuer := crl.rawIssuer

	// Loop through all the revoked certificates.
	for _, revCert := range crl.certList.RevokedCertificates {
		// 5.3 Loop through CRL entry extensions for needed information.
		for _, ext := range revCert.Extensions {
			if oidCertificateIssuer.Equal(ext.Id) {
				extIssuer, err := parseCertIssuerExt(ext)
				if err != nil {
					grpclogLogger.Info(err)
					if ext.Critical {
						return RevocationUndetermined, err
					}
					// Since this is a non-critical extension, we can skip it even though
					// there was a parsing failure.
					continue
				}
				rawEntryIssuer = extIssuer
			} else if ext.Critical {
				return RevocationUndetermined, fmt.Errorf("checkCertRevocation: Unhandled critical extension: %v", ext.Id)
			}
		}

		// If the issuer and serial number appear in the CRL, the certificate is revoked.
		if bytes.Equal(c.RawIssuer, rawEntryIssuer) && c.SerialNumber.Cmp(revCert.SerialNumber) == 0 {
			// CRL contains the serial, so return revoked.
			return RevocationRevoked, nil
		}
	}
	// We did not find the serial in the CRL file that was valid for the cert
	// so the certificate is not revoked.
	return RevocationUnrevoked, nil
}

func parseCertIssuerExt(ext pkix.Extension) ([]byte, error) {
	// 5.3.3 Certificate Issuer
	// CertificateIssuer ::=     GeneralNames
	// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
	var generalNames []asn1.RawValue
	if rest, err := asn1.Unmarshal(ext.Value, &generalNames); err != nil || len(rest) != 0 {
		return nil, fmt.Errorf("asn1.Unmarshal failed: %v", err)
	}

	for _, generalName := range generalNames {
		// GeneralName ::= CHOICE {
		// otherName                       [0]     OtherName,
		// rfc822Name                      [1]     IA5String,
		// dNSName                         [2]     IA5String,
		// x400Address                     [3]     ORAddress,
		// directoryName                   [4]     Name,
		// ediPartyName                    [5]     EDIPartyName,
		// uniformResourceIdentifier       [6]     IA5String,
		// iPAddress                       [7]     OCTET STRING,
		// registeredID                    [8]     OBJECT IDENTIFIER }
		if generalName.Tag == tagDirectoryName {
			return generalName.Bytes, nil
		}
	}
	// Conforming CRL issuers MUST include in this extension the
	// distinguished name (DN) from the issuer field of the certificate that
	// corresponds to this CRL entry.
	// If we couldn't get a directoryName, we can't reason about this file so cert status is
	// RevocationUndetermined.
	return nil, errors.New("no DN found in certificate issuer")
}

// RFC 5280,  4.2.1.1
type authKeyID struct {
	ID []byte `asn1:"optional,tag:0"`
}

// RFC5280, 5.2.5
// id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }

// IssuingDistributionPoint ::= SEQUENCE {
// 		distributionPoint          [0] DistributionPointName OPTIONAL,
// 		onlyContainsUserCerts      [1] BOOLEAN DEFAULT FALSE,
// 		onlyContainsCACerts        [2] BOOLEAN DEFAULT FALSE,
// 		onlySomeReasons            [3] ReasonFlags OPTIONAL,
// 		indirectCRL                [4] BOOLEAN DEFAULT FALSE,
// 		onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }

// -- at most one of onlyContainsUserCerts, onlyContainsCACerts,
// -- and onlyContainsAttributeCerts may be set to TRUE.
type issuingDistributionPoint struct {
	DistributionPoint          asn1.RawValue  `asn1:"optional,tag:0"`
	OnlyContainsUserCerts      bool           `asn1:"optional,tag:1"`
	OnlyContainsCACerts        bool           `asn1:"optional,tag:2"`
	OnlySomeReasons            asn1.BitString `asn1:"optional,tag:3"`
	IndirectCRL                bool           `asn1:"optional,tag:4"`
	OnlyContainsAttributeCerts bool           `asn1:"optional,tag:5"`
}

// parseCRLExtensions parses the extensions for a CRL
// and checks that they're supported by the parser.
func parseCRLExtensions(c *x509.RevocationList) (*CRL, error) {
	if c == nil {
		return nil, errors.New("c is nil, expected any value")
	}
	certList := &CRL{certList: c}

	for _, ext := range c.Extensions {
		switch {
		case oidDeltaCRLIndicator.Equal(ext.Id):
			return nil, fmt.Errorf("delta CRLs unsupported")

		case oidAuthorityKeyIdentifier.Equal(ext.Id):
			var a authKeyID
			if rest, err := asn1.Unmarshal(ext.Value, &a); err != nil {
				return nil, fmt.Errorf("asn1.Unmarshal failed: %v", err)
			} else if len(rest) != 0 {
				return nil, errors.New("trailing data after AKID extension")
			}
			certList.authorityKeyID = a.ID

		case oidIssuingDistributionPoint.Equal(ext.Id):
			var dp issuingDistributionPoint
			if rest, err := asn1.Unmarshal(ext.Value, &dp); err != nil {
				return nil, fmt.Errorf("asn1.Unmarshal failed: %v", err)
			} else if len(rest) != 0 {
				return nil, errors.New("trailing data after IssuingDistributionPoint extension")
			}

			if dp.OnlyContainsUserCerts || dp.OnlyContainsCACerts || dp.OnlyContainsAttributeCerts {
				return nil, errors.New("CRL only contains some certificate types")
			}
			if dp.IndirectCRL {
				return nil, errors.New("indirect CRLs unsupported")
			}
			if dp.OnlySomeReasons.BitLength != 0 {
				return nil, errors.New("onlySomeReasons unsupported")
			}

		case ext.Critical:
			return nil, fmt.Errorf("unsupported critical extension: %v", ext.Id)
		}
	}

	if len(certList.authorityKeyID) == 0 {
		return nil, errors.New("authority key identifier extension missing")
	}
	return certList, nil
}

func fetchCRLOpenSSLHashDir(rawIssuer []byte, cfg RevocationOptions) (*CRL, error) {
	var parsedCRL *CRL
	// 6.3.3 (a) (1) (ii)
	// According to X509_LOOKUP_hash_dir the format is issuer_hash.rN where N is an increasing number.
	// There are no gaps, so we break when we can't find a file.
	for i := 0; ; i++ {
		// Unmarshal to RDNSeqence according to http://go/godoc/crypto/x509/pkix/#Name.
		var r pkix.RDNSequence
		rest, err := asn1.Unmarshal(rawIssuer, &r)
		if len(rest) != 0 || err != nil {
			return nil, fmt.Errorf("asn1.Unmarshal(Issuer) len(rest) = %d failed: %v", len(rest), err)
		}
		crlPath := fmt.Sprintf("%s.r%d", filepath.Join(cfg.RootDir, x509NameHash(r)), i)
		crlBytes, err := os.ReadFile(crlPath)
		if err != nil {
			// Break when we can't read a CRL file.
			grpclogLogger.Infof("readFile: %v", err)
			break
		}

		crl, err := parseRevocationList(crlBytes)
		if err != nil {
			// Parsing errors for a CRL shouldn't happen so fail.
			return nil, fmt.Errorf("parseRevocationList(%v) failed: %v", crlPath, err)
		}
		var certList *CRL
		if certList, err = parseCRLExtensions(crl); err != nil {
			grpclogLogger.Infof("fetchCRL: unsupported crl %v: %v", crlPath, err)
			// Continue to find a supported CRL
			continue
		}

		rawCRLIssuer, err := extractCRLIssuer(crlBytes)
		if err != nil {
			return nil, err
		}
		certList.rawIssuer = rawCRLIssuer
		// RFC5280, 6.3.3 (b) Verify the issuer and scope of the complete CRL.
		if bytes.Equal(rawIssuer, rawCRLIssuer) {
			parsedCRL = certList
			// Continue to find the highest number in the .rN suffix.
			continue
		}
	}

	if parsedCRL == nil {
		return nil, fmt.Errorf("fetchCrls no CRLs found for issuer")
	}
	return parsedCRL, nil
}

func verifyCRL(crl *CRL, chain []*x509.Certificate) error {
	// RFC5280, 6.3.3 (f) Obtain and validate the certification path for the issuer of the complete CRL
	// We intentionally limit our CRLs to be signed with the same certificate path as the certificate
	// so we can use the chain from the connection.

	for _, c := range chain {
		// Use the key where the subject and KIDs match.
		// This departs from RFC4158, 3.5.12 which states that KIDs
		// cannot eliminate certificates, but RFC5280, 5.2.1 states that
		// "Conforming CRL issuers MUST use the key identifier method, and MUST
		// include this extension in all CRLs issued."
		// So, this is much simpler than RFC4158 and should be compatible.
		if bytes.Equal(c.SubjectKeyId, crl.authorityKeyID) && bytes.Equal(c.RawSubject, crl.rawIssuer) {
			// RFC5280, 6.3.3 (f) Key usage and cRLSign bit.
			if c.KeyUsage != 0 && c.KeyUsage&x509.KeyUsageCRLSign == 0 {
				return fmt.Errorf("verifyCRL: The certificate can't be used for issuing CRLs")
			}
			// RFC5280, 6.3.3 (g) Validate signature.
			return crl.certList.CheckSignatureFrom(c)
		}
	}
	return fmt.Errorf("verifyCRL: No certificates matched CRL issuer (%v)", crl.certList.Issuer)
}

// pemType is the type of a PEM encoded CRL.
const pemType string = "X509 CRL"

var crlPemPrefix = []byte("-----BEGIN X509 CRL")

func crlPemToDer(crlBytes []byte) []byte {
	block, _ := pem.Decode(crlBytes)
	if block != nil && block.Type == pemType {
		crlBytes = block.Bytes
	}
	return crlBytes
}

// extractCRLIssuer extracts the raw ASN.1 encoding of the CRL issuer. Due to the design of
// pkix.CertificateList and pkix.RDNSequence, it is not possible to reliably marshal the
// parsed Issuer to it's original raw encoding.
func extractCRLIssuer(crlBytes []byte) ([]byte, error) {
	if bytes.HasPrefix(crlBytes, crlPemPrefix) {
		crlBytes = crlPemToDer(crlBytes)
	}
	der := cryptobyte.String(crlBytes)
	var issuer cryptobyte.String
	if !der.ReadASN1(&der, cbasn1.SEQUENCE) ||
		!der.ReadASN1(&der, cbasn1.SEQUENCE) ||
		!der.SkipOptionalASN1(cbasn1.INTEGER) ||
		!der.SkipASN1(cbasn1.SEQUENCE) ||
		!der.ReadASN1Element(&issuer, cbasn1.SEQUENCE) {
		return nil, errors.New("extractCRLIssuer: invalid ASN.1 encoding")
	}
	return issuer, nil
}

func hasExpired(crl *x509.RevocationList, now time.Time) bool {
	return !now.Before(crl.NextUpdate)
}

// parseRevocationList comes largely from here
// x509.go:
// https://github.com/golang/go/blob/e2f413402527505144beea443078649380e0c545/src/crypto/x509/x509.go#L1669-L1690
// We must first convert PEM to DER to be able to use the new
// x509.ParseRevocationList instead of the deprecated x509.ParseCRL
func parseRevocationList(crlBytes []byte) (*x509.RevocationList, error) {
	if bytes.HasPrefix(crlBytes, crlPemPrefix) {
		crlBytes = crlPemToDer(crlBytes)
	}
	crl, err := x509.ParseRevocationList(crlBytes)
	if err != nil {
		return nil, err
	}
	return crl, nil
}
