1// Copyright 2017 Google Inc. All Rights Reserved. 2// 3// Use of this source code is governed by a BSD-style 4// license that can be found in the LICENSE file. 5 6package x509 7 8import ( 9 "bytes" 10 "encoding/pem" 11 "time" 12 13 "github.com/google/certificate-transparency-go/asn1" 14 "github.com/google/certificate-transparency-go/x509/pkix" 15) 16 17var ( 18 // OID values for CRL extensions (TBSCertList.Extensions), RFC 5280 s5.2. 19 OIDExtensionCRLNumber = asn1.ObjectIdentifier{2, 5, 29, 20} 20 OIDExtensionDeltaCRLIndicator = asn1.ObjectIdentifier{2, 5, 29, 27} 21 OIDExtensionIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28} 22 // OID values for CRL entry extensions (RevokedCertificate.Extensions), RFC 5280 s5.3 23 OIDExtensionCRLReasons = asn1.ObjectIdentifier{2, 5, 29, 21} 24 OIDExtensionInvalidityDate = asn1.ObjectIdentifier{2, 5, 29, 24} 25 OIDExtensionCertificateIssuer = asn1.ObjectIdentifier{2, 5, 29, 29} 26) 27 28// RevocationReasonCode represents the reason for a certificate revocation; see RFC 5280 s5.3.1. 29type RevocationReasonCode asn1.Enumerated 30 31// RevocationReasonCode values. 32var ( 33 Unspecified = RevocationReasonCode(0) 34 KeyCompromise = RevocationReasonCode(1) 35 CACompromise = RevocationReasonCode(2) 36 AffiliationChanged = RevocationReasonCode(3) 37 Superseded = RevocationReasonCode(4) 38 CessationOfOperation = RevocationReasonCode(5) 39 CertificateHold = RevocationReasonCode(6) 40 RemoveFromCRL = RevocationReasonCode(8) 41 PrivilegeWithdrawn = RevocationReasonCode(9) 42 AACompromise = RevocationReasonCode(10) 43) 44 45// ReasonFlag holds a bitmask of applicable revocation reasons, from RFC 5280 s4.2.1.13 46type ReasonFlag int 47 48// ReasonFlag values. 49const ( 50 UnusedFlag ReasonFlag = 1 << iota 51 KeyCompromiseFlag 52 CACompromiseFlag 53 AffiliationChangedFlag 54 SupersededFlag 55 CessationOfOperationFlag 56 CertificateHoldFlag 57 PrivilegeWithdrawnFlag 58 AACompromiseFlag 59) 60 61// CertificateList represents the ASN.1 structure of the same name from RFC 5280, s5.1. 62// It has the same content as pkix.CertificateList, but the contents include parsed versions 63// of any extensions. 64type CertificateList struct { 65 Raw asn1.RawContent 66 TBSCertList TBSCertList 67 SignatureAlgorithm pkix.AlgorithmIdentifier 68 SignatureValue asn1.BitString 69} 70 71// ExpiredAt reports whether now is past the expiry time of certList. 72func (certList *CertificateList) ExpiredAt(now time.Time) bool { 73 return now.After(certList.TBSCertList.NextUpdate) 74} 75 76// Indication of whether extensions need to be critical or non-critical. Extensions that 77// can be either are omitted from the map. 78var listExtCritical = map[string]bool{ 79 // From RFC 5280... 80 OIDExtensionAuthorityKeyId.String(): false, // s5.2.1 81 OIDExtensionIssuerAltName.String(): false, // s5.2.2 82 OIDExtensionCRLNumber.String(): false, // s5.2.3 83 OIDExtensionDeltaCRLIndicator.String(): true, // s5.2.4 84 OIDExtensionIssuingDistributionPoint.String(): true, // s5.2.5 85 OIDExtensionFreshestCRL.String(): false, // s5.2.6 86 OIDExtensionAuthorityInfoAccess.String(): false, // s5.2.7 87} 88 89var certExtCritical = map[string]bool{ 90 // From RFC 5280... 91 OIDExtensionCRLReasons.String(): false, // s5.3.1 92 OIDExtensionInvalidityDate.String(): false, // s5.3.2 93 OIDExtensionCertificateIssuer.String(): true, // s5.3.3 94} 95 96// IssuingDistributionPoint represents the ASN.1 structure of the same 97// name 98type IssuingDistributionPoint struct { 99 DistributionPoint distributionPointName `asn1:"optional,tag:0"` 100 OnlyContainsUserCerts bool `asn1:"optional,tag:1"` 101 OnlyContainsCACerts bool `asn1:"optional,tag:2"` 102 OnlySomeReasons asn1.BitString `asn1:"optional,tag:3"` 103 IndirectCRL bool `asn1:"optional,tag:4"` 104 OnlyContainsAttributeCerts bool `asn1:"optional,tag:5"` 105} 106 107// TBSCertList represents the ASN.1 structure of the same name from RFC 108// 5280, section 5.1. It has the same content as pkix.TBSCertificateList 109// but the extensions are included in a parsed format. 110type TBSCertList struct { 111 Raw asn1.RawContent 112 Version int 113 Signature pkix.AlgorithmIdentifier 114 Issuer pkix.RDNSequence 115 ThisUpdate time.Time 116 NextUpdate time.Time 117 RevokedCertificates []*RevokedCertificate 118 Extensions []pkix.Extension 119 // Cracked out extensions: 120 AuthorityKeyID []byte 121 IssuerAltNames GeneralNames 122 CRLNumber int 123 BaseCRLNumber int // -1 if no delta CRL present 124 IssuingDistributionPoint IssuingDistributionPoint 125 IssuingDPFullNames GeneralNames 126 FreshestCRLDistributionPoint []string 127 OCSPServer []string 128 IssuingCertificateURL []string 129} 130 131// ParseCertificateList parses a CertificateList (e.g. a CRL) from the given 132// bytes. It's often the case that PEM encoded CRLs will appear where they 133// should be DER encoded, so this function will transparently handle PEM 134// encoding as long as there isn't any leading garbage. 135func ParseCertificateList(clBytes []byte) (*CertificateList, error) { 136 if bytes.HasPrefix(clBytes, pemCRLPrefix) { 137 block, _ := pem.Decode(clBytes) 138 if block != nil && block.Type == pemType { 139 clBytes = block.Bytes 140 } 141 } 142 return ParseCertificateListDER(clBytes) 143} 144 145// ParseCertificateListDER parses a DER encoded CertificateList from the given bytes. 146// For non-fatal errors, this function returns both an error and a CertificateList 147// object. 148func ParseCertificateListDER(derBytes []byte) (*CertificateList, error) { 149 var errs Errors 150 // First parse the DER into the pkix structures. 151 pkixList := new(pkix.CertificateList) 152 if rest, err := asn1.Unmarshal(derBytes, pkixList); err != nil { 153 errs.AddID(ErrInvalidCertList, err) 154 return nil, &errs 155 } else if len(rest) != 0 { 156 errs.AddID(ErrTrailingCertList) 157 return nil, &errs 158 } 159 160 // Transcribe the revoked certs but crack out extensions. 161 revokedCerts := make([]*RevokedCertificate, len(pkixList.TBSCertList.RevokedCertificates)) 162 for i, pkixRevoked := range pkixList.TBSCertList.RevokedCertificates { 163 revokedCerts[i] = parseRevokedCertificate(pkixRevoked, &errs) 164 if revokedCerts[i] == nil { 165 return nil, &errs 166 } 167 } 168 169 certList := CertificateList{ 170 Raw: derBytes, 171 TBSCertList: TBSCertList{ 172 Raw: pkixList.TBSCertList.Raw, 173 Version: pkixList.TBSCertList.Version, 174 Signature: pkixList.TBSCertList.Signature, 175 Issuer: pkixList.TBSCertList.Issuer, 176 ThisUpdate: pkixList.TBSCertList.ThisUpdate, 177 NextUpdate: pkixList.TBSCertList.NextUpdate, 178 RevokedCertificates: revokedCerts, 179 Extensions: pkixList.TBSCertList.Extensions, 180 CRLNumber: -1, 181 BaseCRLNumber: -1, 182 }, 183 SignatureAlgorithm: pkixList.SignatureAlgorithm, 184 SignatureValue: pkixList.SignatureValue, 185 } 186 187 // Now crack out extensions. 188 for _, e := range certList.TBSCertList.Extensions { 189 if expectCritical, present := listExtCritical[e.Id.String()]; present { 190 if e.Critical && !expectCritical { 191 errs.AddID(ErrUnexpectedlyCriticalCertListExtension, e.Id) 192 } else if !e.Critical && expectCritical { 193 errs.AddID(ErrUnexpectedlyNonCriticalCertListExtension, e.Id) 194 } 195 } 196 switch { 197 case e.Id.Equal(OIDExtensionAuthorityKeyId): 198 // RFC 5280 s5.2.1 199 var a authKeyId 200 if rest, err := asn1.Unmarshal(e.Value, &a); err != nil { 201 errs.AddID(ErrInvalidCertListAuthKeyID, err) 202 } else if len(rest) != 0 { 203 errs.AddID(ErrTrailingCertListAuthKeyID) 204 } 205 certList.TBSCertList.AuthorityKeyID = a.Id 206 case e.Id.Equal(OIDExtensionIssuerAltName): 207 // RFC 5280 s5.2.2 208 if err := parseGeneralNames(e.Value, &certList.TBSCertList.IssuerAltNames); err != nil { 209 errs.AddID(ErrInvalidCertListIssuerAltName, err) 210 } 211 case e.Id.Equal(OIDExtensionCRLNumber): 212 // RFC 5280 s5.2.3 213 if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.CRLNumber); err != nil { 214 errs.AddID(ErrInvalidCertListCRLNumber, err) 215 } else if len(rest) != 0 { 216 errs.AddID(ErrTrailingCertListCRLNumber) 217 } 218 if certList.TBSCertList.CRLNumber < 0 { 219 errs.AddID(ErrNegativeCertListCRLNumber, certList.TBSCertList.CRLNumber) 220 } 221 case e.Id.Equal(OIDExtensionDeltaCRLIndicator): 222 // RFC 5280 s5.2.4 223 if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.BaseCRLNumber); err != nil { 224 errs.AddID(ErrInvalidCertListDeltaCRL, err) 225 } else if len(rest) != 0 { 226 errs.AddID(ErrTrailingCertListDeltaCRL) 227 } 228 if certList.TBSCertList.BaseCRLNumber < 0 { 229 errs.AddID(ErrNegativeCertListDeltaCRL, certList.TBSCertList.BaseCRLNumber) 230 } 231 case e.Id.Equal(OIDExtensionIssuingDistributionPoint): 232 parseIssuingDistributionPoint(e.Value, &certList.TBSCertList.IssuingDistributionPoint, &certList.TBSCertList.IssuingDPFullNames, &errs) 233 case e.Id.Equal(OIDExtensionFreshestCRL): 234 // RFC 5280 s5.2.6 235 if err := parseDistributionPoints(e.Value, &certList.TBSCertList.FreshestCRLDistributionPoint); err != nil { 236 errs.AddID(ErrInvalidCertListFreshestCRL, err) 237 return nil, err 238 } 239 case e.Id.Equal(OIDExtensionAuthorityInfoAccess): 240 // RFC 5280 s5.2.7 241 var aia []authorityInfoAccess 242 if rest, err := asn1.Unmarshal(e.Value, &aia); err != nil { 243 errs.AddID(ErrInvalidCertListAuthInfoAccess, err) 244 } else if len(rest) != 0 { 245 errs.AddID(ErrTrailingCertListAuthInfoAccess) 246 } 247 248 for _, v := range aia { 249 // GeneralName: uniformResourceIdentifier [6] IA5String 250 if v.Location.Tag != tagURI { 251 continue 252 } 253 switch { 254 case v.Method.Equal(OIDAuthorityInfoAccessOCSP): 255 certList.TBSCertList.OCSPServer = append(certList.TBSCertList.OCSPServer, string(v.Location.Bytes)) 256 case v.Method.Equal(OIDAuthorityInfoAccessIssuers): 257 certList.TBSCertList.IssuingCertificateURL = append(certList.TBSCertList.IssuingCertificateURL, string(v.Location.Bytes)) 258 } 259 // TODO(drysdale): cope with more possibilities 260 } 261 default: 262 if e.Critical { 263 errs.AddID(ErrUnhandledCriticalCertListExtension, e.Id) 264 } 265 } 266 } 267 268 if errs.Fatal() { 269 return nil, &errs 270 } 271 if errs.Empty() { 272 return &certList, nil 273 } 274 return &certList, &errs 275} 276 277func parseIssuingDistributionPoint(data []byte, idp *IssuingDistributionPoint, name *GeneralNames, errs *Errors) { 278 // RFC 5280 s5.2.5 279 if rest, err := asn1.Unmarshal(data, idp); err != nil { 280 errs.AddID(ErrInvalidCertListIssuingDP, err) 281 } else if len(rest) != 0 { 282 errs.AddID(ErrTrailingCertListIssuingDP) 283 } 284 285 typeCount := 0 286 if idp.OnlyContainsUserCerts { 287 typeCount++ 288 } 289 if idp.OnlyContainsCACerts { 290 typeCount++ 291 } 292 if idp.OnlyContainsAttributeCerts { 293 typeCount++ 294 } 295 if typeCount > 1 { 296 errs.AddID(ErrCertListIssuingDPMultipleTypes, idp.OnlyContainsUserCerts, idp.OnlyContainsCACerts, idp.OnlyContainsAttributeCerts) 297 } 298 for _, fn := range idp.DistributionPoint.FullName { 299 if _, err := parseGeneralName(fn.FullBytes, name, false); err != nil { 300 errs.AddID(ErrCertListIssuingDPInvalidFullName, err) 301 } 302 } 303} 304 305// RevokedCertificate represents the unnamed ASN.1 structure that makes up the 306// revokedCertificates member of the TBSCertList structure from RFC 5280, s5.1. 307// It has the same content as pkix.RevokedCertificate but the extensions are 308// included in a parsed format. 309type RevokedCertificate struct { 310 pkix.RevokedCertificate 311 // Cracked out extensions: 312 RevocationReason RevocationReasonCode 313 InvalidityDate time.Time 314 Issuer GeneralNames 315} 316 317func parseRevokedCertificate(pkixRevoked pkix.RevokedCertificate, errs *Errors) *RevokedCertificate { 318 result := RevokedCertificate{RevokedCertificate: pkixRevoked} 319 for _, e := range pkixRevoked.Extensions { 320 if expectCritical, present := certExtCritical[e.Id.String()]; present { 321 if e.Critical && !expectCritical { 322 errs.AddID(ErrUnexpectedlyCriticalRevokedCertExtension, e.Id) 323 } else if !e.Critical && expectCritical { 324 errs.AddID(ErrUnexpectedlyNonCriticalRevokedCertExtension, e.Id) 325 } 326 } 327 switch { 328 case e.Id.Equal(OIDExtensionCRLReasons): 329 // RFC 5280, s5.3.1 330 var reason asn1.Enumerated 331 if rest, err := asn1.Unmarshal(e.Value, &reason); err != nil { 332 errs.AddID(ErrInvalidRevocationReason, err) 333 } else if len(rest) != 0 { 334 errs.AddID(ErrTrailingRevocationReason) 335 } 336 result.RevocationReason = RevocationReasonCode(reason) 337 case e.Id.Equal(OIDExtensionInvalidityDate): 338 // RFC 5280, s5.3.2 339 if rest, err := asn1.Unmarshal(e.Value, &result.InvalidityDate); err != nil { 340 errs.AddID(ErrInvalidRevocationInvalidityDate, err) 341 } else if len(rest) != 0 { 342 errs.AddID(ErrTrailingRevocationInvalidityDate) 343 } 344 case e.Id.Equal(OIDExtensionCertificateIssuer): 345 // RFC 5280, s5.3.3 346 if err := parseGeneralNames(e.Value, &result.Issuer); err != nil { 347 errs.AddID(ErrInvalidRevocationIssuer, err) 348 } 349 default: 350 if e.Critical { 351 errs.AddID(ErrUnhandledCriticalRevokedCertExtension, e.Id) 352 } 353 } 354 } 355 return &result 356} 357 358// CheckCertificateListSignature checks that the signature in crl is from c. 359func (c *Certificate) CheckCertificateListSignature(crl *CertificateList) error { 360 algo := SignatureAlgorithmFromAI(crl.SignatureAlgorithm) 361 return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign()) 362} 363