223 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| package precert
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	encoding_asn1 "encoding/asn1"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 
 | |
| 	"golang.org/x/crypto/cryptobyte"
 | |
| 	"golang.org/x/crypto/cryptobyte/asn1"
 | |
| )
 | |
| 
 | |
| // Correspond returns nil if the two certificates are a valid precertificate/final certificate pair.
 | |
| // Order of the arguments matters: the precertificate is first and the final certificate is second.
 | |
| // Note that RFC 6962 allows the precertificate and final certificate to have different Issuers, but
 | |
| // this function rejects such pairs.
 | |
| func Correspond(precertDER, finalDER []byte) error {
 | |
| 	preTBS, err := tbsDERFromCertDER(precertDER)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("parsing precert: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	finalTBS, err := tbsDERFromCertDER(finalDER)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("parsing final cert: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// The first 7 fields of TBSCertificate must be byte-for-byte identical.
 | |
| 	// The next 2 fields (issuerUniqueID and subjectUniqueID) are forbidden
 | |
| 	// by the Baseline Requirements so we assume they are not present (if they
 | |
| 	// are, they will fail the next check, for extensions).
 | |
| 	// https://datatracker.ietf.org/doc/html/rfc5280#page-117
 | |
| 	// TBSCertificate  ::=  SEQUENCE  {
 | |
| 	//      version         [0]  Version DEFAULT v1,
 | |
| 	//      serialNumber         CertificateSerialNumber,
 | |
| 	//      signature            AlgorithmIdentifier,
 | |
| 	//      issuer               Name,
 | |
| 	//      validity             Validity,
 | |
| 	//      subject              Name,
 | |
| 	//      subjectPublicKeyInfo SubjectPublicKeyInfo,
 | |
| 	//      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
 | |
| 	//      					 -- If present, version MUST be v2 or v3
 | |
| 	//      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
 | |
| 	//      					 -- If present, version MUST be v2 or v3
 | |
| 	//      extensions      [3]  Extensions OPTIONAL
 | |
| 	//      					 -- If present, version MUST be v3 --  }
 | |
| 	for i := range 7 {
 | |
| 		if err := readIdenticalElement(&preTBS, &finalTBS); err != nil {
 | |
| 			return fmt.Errorf("checking for identical field %d: %w", i, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// The extensions should be mostly the same, with these exceptions:
 | |
| 	//  - The precertificate should have exactly one precertificate poison extension
 | |
| 	//    not present in the final certificate.
 | |
| 	//  - The final certificate should have exactly one SCTList extension not present
 | |
| 	//    in the precertificate.
 | |
| 	//  - As a consequence, the byte lengths of the extensions fields will not be the
 | |
| 	//    same, so we ignore the lengths (so long as they parse)
 | |
| 	precertExtensionBytes, err := unwrapExtensions(preTBS)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("parsing precert extensions: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	finalCertExtensionBytes, err := unwrapExtensions(finalTBS)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("parsing final cert extensions: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	precertParser := extensionParser{bytes: precertExtensionBytes, skippableOID: poisonOID}
 | |
| 	finalCertParser := extensionParser{bytes: finalCertExtensionBytes, skippableOID: sctListOID}
 | |
| 
 | |
| 	for i := 0; ; i++ {
 | |
| 		precertExtn, err := precertParser.Next()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		finalCertExtn, err := finalCertParser.Next()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		if !bytes.Equal(precertExtn, finalCertExtn) {
 | |
| 			return fmt.Errorf("precert extension %d (%x) not equal to final cert extension %d (%x)",
 | |
| 				i+precertParser.skipped, precertExtn, i+finalCertParser.skipped, finalCertExtn)
 | |
| 		}
 | |
| 
 | |
| 		if precertExtn == nil && finalCertExtn == nil {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if precertParser.skipped == 0 {
 | |
| 		return fmt.Errorf("no poison extension found in precert")
 | |
| 	}
 | |
| 	if precertParser.skipped > 1 {
 | |
| 		return fmt.Errorf("multiple poison extensions found in precert")
 | |
| 	}
 | |
| 	if finalCertParser.skipped == 0 {
 | |
| 		return fmt.Errorf("no SCTList extension found in final cert")
 | |
| 	}
 | |
| 	if finalCertParser.skipped > 1 {
 | |
| 		return fmt.Errorf("multiple SCTList extensions found in final cert")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var poisonOID = []int{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
 | |
| var sctListOID = []int{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
 | |
| 
 | |
| // extensionParser takes a sequence of bytes representing the inner bytes of the
 | |
| // `extensions` field. Repeated calls to Next() will return all the extensions
 | |
| // except those that match the skippableOID. The skipped extensions will be
 | |
| // counted in `skipped`.
 | |
| type extensionParser struct {
 | |
| 	skippableOID encoding_asn1.ObjectIdentifier
 | |
| 	bytes        cryptobyte.String
 | |
| 	skipped      int
 | |
| }
 | |
| 
 | |
| // Next returns the next extension in the sequence, skipping (and counting)
 | |
| // any extension that matches the skippableOID.
 | |
| // Returns nil, nil when there are no more extensions.
 | |
| func (e *extensionParser) Next() (cryptobyte.String, error) {
 | |
| 	if e.bytes.Empty() {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	var next cryptobyte.String
 | |
| 	if !e.bytes.ReadASN1(&next, asn1.SEQUENCE) {
 | |
| 		return nil, fmt.Errorf("failed to parse extension")
 | |
| 	}
 | |
| 
 | |
| 	var oid encoding_asn1.ObjectIdentifier
 | |
| 	nextCopy := next
 | |
| 	if !nextCopy.ReadASN1ObjectIdentifier(&oid) {
 | |
| 		return nil, fmt.Errorf("failed to parse extension OID")
 | |
| 	}
 | |
| 
 | |
| 	if oid.Equal(e.skippableOID) {
 | |
| 		e.skipped++
 | |
| 		return e.Next()
 | |
| 	}
 | |
| 
 | |
| 	return next, nil
 | |
| }
 | |
| 
 | |
| // unwrapExtensions takes a given a sequence of bytes representing the `extensions` field
 | |
| // of a TBSCertificate and parses away the outermost two layers, returning the inner bytes
 | |
| // of the Extensions SEQUENCE.
 | |
| //
 | |
| // https://datatracker.ietf.org/doc/html/rfc5280#page-117
 | |
| //
 | |
| //	TBSCertificate  ::=  SEQUENCE  {
 | |
| //	   ...
 | |
| //	   extensions      [3]  Extensions OPTIONAL
 | |
| //	}
 | |
| //
 | |
| // Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
 | |
| func unwrapExtensions(field cryptobyte.String) (cryptobyte.String, error) {
 | |
| 	var extensions cryptobyte.String
 | |
| 	if !field.ReadASN1(&extensions, asn1.Tag(3).Constructed().ContextSpecific()) {
 | |
| 		return nil, errors.New("error reading extensions")
 | |
| 	}
 | |
| 
 | |
| 	var extensionsInner cryptobyte.String
 | |
| 	if !extensions.ReadASN1(&extensionsInner, asn1.SEQUENCE) {
 | |
| 		return nil, errors.New("error reading extensions inner")
 | |
| 	}
 | |
| 
 | |
| 	return extensionsInner, nil
 | |
| }
 | |
| 
 | |
| // readIdenticalElement parses a single ASN1 element and returns an error if
 | |
| // their tags are different or their contents are different.
 | |
| func readIdenticalElement(a, b *cryptobyte.String) error {
 | |
| 	var aInner, bInner cryptobyte.String
 | |
| 	var aTag, bTag asn1.Tag
 | |
| 	if !a.ReadAnyASN1Element(&aInner, &aTag) {
 | |
| 		return fmt.Errorf("failed to read element from first input")
 | |
| 	}
 | |
| 	if !b.ReadAnyASN1Element(&bInner, &bTag) {
 | |
| 		return fmt.Errorf("failed to read element from first input")
 | |
| 	}
 | |
| 	if aTag != bTag {
 | |
| 		return fmt.Errorf("tags differ: %d != %d", aTag, bTag)
 | |
| 	}
 | |
| 	if !bytes.Equal([]byte(aInner), []byte(bInner)) {
 | |
| 		return fmt.Errorf("elements differ: %x != %x", aInner, bInner)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // tbsDERFromCertDER takes a Certificate object encoded as DER, and parses
 | |
| // away the outermost two SEQUENCEs to get the inner bytes of the TBSCertificate.
 | |
| //
 | |
| // https://datatracker.ietf.org/doc/html/rfc5280#page-116
 | |
| //
 | |
| //		Certificate  ::=  SEQUENCE  {
 | |
| //		    tbsCertificate       TBSCertificate,
 | |
| //		    ...
 | |
| //
 | |
| //		TBSCertificate  ::=  SEQUENCE  {
 | |
| //		    version         [0]  Version DEFAULT v1,
 | |
| //		    serialNumber         CertificateSerialNumber,
 | |
| //	     ...
 | |
| func tbsDERFromCertDER(certDER []byte) (cryptobyte.String, error) {
 | |
| 	var inner cryptobyte.String
 | |
| 	input := cryptobyte.String(certDER)
 | |
| 
 | |
| 	if !input.ReadASN1(&inner, asn1.SEQUENCE) {
 | |
| 		return nil, fmt.Errorf("failed to read outer sequence")
 | |
| 	}
 | |
| 
 | |
| 	var tbsCertificate cryptobyte.String
 | |
| 	if !inner.ReadASN1(&tbsCertificate, asn1.SEQUENCE) {
 | |
| 		return nil, fmt.Errorf("failed to read tbsCertificate")
 | |
| 	}
 | |
| 
 | |
| 	return tbsCertificate, nil
 | |
| }
 |