github.com/letsencrypt/boulder@v0.20251208.0/precert/corr.go (about) 1 package precert 2 3 import ( 4 "bytes" 5 encoding_asn1 "encoding/asn1" 6 "errors" 7 "fmt" 8 9 "golang.org/x/crypto/cryptobyte" 10 "golang.org/x/crypto/cryptobyte/asn1" 11 ) 12 13 // Correspond returns nil if the two certificates are a valid precertificate/final certificate pair. 14 // Order of the arguments matters: the precertificate is first and the final certificate is second. 15 // Note that RFC 6962 allows the precertificate and final certificate to have different Issuers, but 16 // this function rejects such pairs. 17 func Correspond(precertDER, finalDER []byte) error { 18 preTBS, err := tbsDERFromCertDER(precertDER) 19 if err != nil { 20 return fmt.Errorf("parsing precert: %w", err) 21 } 22 23 finalTBS, err := tbsDERFromCertDER(finalDER) 24 if err != nil { 25 return fmt.Errorf("parsing final cert: %w", err) 26 } 27 28 // The first 7 fields of TBSCertificate must be byte-for-byte identical. 29 // The next 2 fields (issuerUniqueID and subjectUniqueID) are forbidden 30 // by the Baseline Requirements so we assume they are not present (if they 31 // are, they will fail the next check, for extensions). 32 // https://datatracker.ietf.org/doc/html/rfc5280#page-117 33 // TBSCertificate ::= SEQUENCE { 34 // version [0] Version DEFAULT v1, 35 // serialNumber CertificateSerialNumber, 36 // signature AlgorithmIdentifier, 37 // issuer Name, 38 // validity Validity, 39 // subject Name, 40 // subjectPublicKeyInfo SubjectPublicKeyInfo, 41 // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, 42 // -- If present, version MUST be v2 or v3 43 // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, 44 // -- If present, version MUST be v2 or v3 45 // extensions [3] Extensions OPTIONAL 46 // -- If present, version MUST be v3 -- } 47 for i := range 7 { 48 if err := readIdenticalElement(&preTBS, &finalTBS); err != nil { 49 return fmt.Errorf("checking for identical field %d: %w", i, err) 50 } 51 } 52 53 // The extensions should be mostly the same, with these exceptions: 54 // - The precertificate should have exactly one precertificate poison extension 55 // not present in the final certificate. 56 // - The final certificate should have exactly one SCTList extension not present 57 // in the precertificate. 58 // - As a consequence, the byte lengths of the extensions fields will not be the 59 // same, so we ignore the lengths (so long as they parse) 60 precertExtensionBytes, err := unwrapExtensions(preTBS) 61 if err != nil { 62 return fmt.Errorf("parsing precert extensions: %w", err) 63 } 64 65 finalCertExtensionBytes, err := unwrapExtensions(finalTBS) 66 if err != nil { 67 return fmt.Errorf("parsing final cert extensions: %w", err) 68 } 69 70 precertParser := extensionParser{bytes: precertExtensionBytes, skippableOID: poisonOID} 71 finalCertParser := extensionParser{bytes: finalCertExtensionBytes, skippableOID: sctListOID} 72 73 for i := 0; ; i++ { 74 precertExtn, err := precertParser.Next() 75 if err != nil { 76 return err 77 } 78 79 finalCertExtn, err := finalCertParser.Next() 80 if err != nil { 81 return err 82 } 83 84 if !bytes.Equal(precertExtn, finalCertExtn) { 85 return fmt.Errorf("precert extension %d (%x) not equal to final cert extension %d (%x)", 86 i+precertParser.skipped, precertExtn, i+finalCertParser.skipped, finalCertExtn) 87 } 88 89 if precertExtn == nil && finalCertExtn == nil { 90 break 91 } 92 } 93 94 if precertParser.skipped == 0 { 95 return fmt.Errorf("no poison extension found in precert") 96 } 97 if precertParser.skipped > 1 { 98 return fmt.Errorf("multiple poison extensions found in precert") 99 } 100 if finalCertParser.skipped == 0 { 101 return fmt.Errorf("no SCTList extension found in final cert") 102 } 103 if finalCertParser.skipped > 1 { 104 return fmt.Errorf("multiple SCTList extensions found in final cert") 105 } 106 return nil 107 } 108 109 var poisonOID = []int{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3} 110 var sctListOID = []int{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2} 111 112 // extensionParser takes a sequence of bytes representing the inner bytes of the 113 // `extensions` field. Repeated calls to Next() will return all the extensions 114 // except those that match the skippableOID. The skipped extensions will be 115 // counted in `skipped`. 116 type extensionParser struct { 117 skippableOID encoding_asn1.ObjectIdentifier 118 bytes cryptobyte.String 119 skipped int 120 } 121 122 // Next returns the next extension in the sequence, skipping (and counting) 123 // any extension that matches the skippableOID. 124 // Returns nil, nil when there are no more extensions. 125 func (e *extensionParser) Next() (cryptobyte.String, error) { 126 if e.bytes.Empty() { 127 return nil, nil 128 } 129 130 var next cryptobyte.String 131 if !e.bytes.ReadASN1(&next, asn1.SEQUENCE) { 132 return nil, fmt.Errorf("failed to parse extension") 133 } 134 135 var oid encoding_asn1.ObjectIdentifier 136 nextCopy := next 137 if !nextCopy.ReadASN1ObjectIdentifier(&oid) { 138 return nil, fmt.Errorf("failed to parse extension OID") 139 } 140 141 if oid.Equal(e.skippableOID) { 142 e.skipped++ 143 return e.Next() 144 } 145 146 return next, nil 147 } 148 149 // unwrapExtensions takes a given a sequence of bytes representing the `extensions` field 150 // of a TBSCertificate and parses away the outermost two layers, returning the inner bytes 151 // of the Extensions sequence. 152 // 153 // https://datatracker.ietf.org/doc/html/rfc5280#page-117 154 // 155 // TBSCertificate ::= SEQUENCE { 156 // ... 157 // extensions [3] Extensions OPTIONAL 158 // } 159 // 160 // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension 161 func unwrapExtensions(field cryptobyte.String) (cryptobyte.String, error) { 162 var extensions cryptobyte.String 163 if !field.ReadASN1(&extensions, asn1.Tag(3).Constructed().ContextSpecific()) { 164 return nil, errors.New("error reading extensions") 165 } 166 167 var extensionsInner cryptobyte.String 168 if !extensions.ReadASN1(&extensionsInner, asn1.SEQUENCE) { 169 return nil, errors.New("error reading extensions inner") 170 } 171 172 return extensionsInner, nil 173 } 174 175 // readIdenticalElement parses a single ASN1 element and returns an error if 176 // their tags are different or their contents are different. 177 func readIdenticalElement(a, b *cryptobyte.String) error { 178 var aInner, bInner cryptobyte.String 179 var aTag, bTag asn1.Tag 180 if !a.ReadAnyASN1Element(&aInner, &aTag) { 181 return fmt.Errorf("failed to read element from first input") 182 } 183 if !b.ReadAnyASN1Element(&bInner, &bTag) { 184 return fmt.Errorf("failed to read element from first input") 185 } 186 if aTag != bTag { 187 return fmt.Errorf("tags differ: %d != %d", aTag, bTag) 188 } 189 if !bytes.Equal([]byte(aInner), []byte(bInner)) { 190 return fmt.Errorf("elements differ: %x != %x", aInner, bInner) 191 } 192 return nil 193 } 194 195 // tbsDERFromCertDER takes a Certificate object encoded as DER, and parses 196 // away the outermost two sequences to get the inner bytes of the TBSCertificate. 197 // 198 // https://datatracker.ietf.org/doc/html/rfc5280#page-116 199 // 200 // Certificate ::= SEQUENCE { 201 // tbsCertificate TBSCertificate, 202 // ... 203 // 204 // TBSCertificate ::= SEQUENCE { 205 // version [0] Version DEFAULT v1, 206 // serialNumber CertificateSerialNumber, 207 // ... 208 func tbsDERFromCertDER(certDER []byte) (cryptobyte.String, error) { 209 var inner cryptobyte.String 210 input := cryptobyte.String(certDER) 211 212 if !input.ReadASN1(&inner, asn1.SEQUENCE) { 213 return nil, fmt.Errorf("failed to read outer sequence") 214 } 215 216 var tbsCertificate cryptobyte.String 217 if !inner.ReadASN1(&tbsCertificate, asn1.SEQUENCE) { 218 return nil, fmt.Errorf("failed to read tbsCertificate") 219 } 220 221 return tbsCertificate, nil 222 }