| // Copyright 2017 Google Inc. All Rights Reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package x509 |
| |
| import ( |
| "bytes" |
| "encoding/pem" |
| "time" |
| |
| "github.com/google/certificate-transparency-go/asn1" |
| "github.com/google/certificate-transparency-go/x509/pkix" |
| ) |
| |
| var ( |
| // OID values for CRL extensions (TBSCertList.Extensions), RFC 5280 s5.2. |
| OIDExtensionCRLNumber = asn1.ObjectIdentifier{2, 5, 29, 20} |
| OIDExtensionDeltaCRLIndicator = asn1.ObjectIdentifier{2, 5, 29, 27} |
| OIDExtensionIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28} |
| // OID values for CRL entry extensions (RevokedCertificate.Extensions), RFC 5280 s5.3 |
| OIDExtensionCRLReasons = asn1.ObjectIdentifier{2, 5, 29, 21} |
| OIDExtensionInvalidityDate = asn1.ObjectIdentifier{2, 5, 29, 24} |
| OIDExtensionCertificateIssuer = asn1.ObjectIdentifier{2, 5, 29, 29} |
| ) |
| |
| // RevocationReasonCode represents the reason for a certificate revocation; see RFC 5280 s5.3.1. |
| type RevocationReasonCode asn1.Enumerated |
| |
| // RevocationReasonCode values. |
| var ( |
| Unspecified = RevocationReasonCode(0) |
| KeyCompromise = RevocationReasonCode(1) |
| CACompromise = RevocationReasonCode(2) |
| AffiliationChanged = RevocationReasonCode(3) |
| Superseded = RevocationReasonCode(4) |
| CessationOfOperation = RevocationReasonCode(5) |
| CertificateHold = RevocationReasonCode(6) |
| RemoveFromCRL = RevocationReasonCode(8) |
| PrivilegeWithdrawn = RevocationReasonCode(9) |
| AACompromise = RevocationReasonCode(10) |
| ) |
| |
| // ReasonFlag holds a bitmask of applicable revocation reasons, from RFC 5280 s4.2.1.13 |
| type ReasonFlag int |
| |
| // ReasonFlag values. |
| const ( |
| UnusedFlag ReasonFlag = 1 << iota |
| KeyCompromiseFlag |
| CACompromiseFlag |
| AffiliationChangedFlag |
| SupersededFlag |
| CessationOfOperationFlag |
| CertificateHoldFlag |
| PrivilegeWithdrawnFlag |
| AACompromiseFlag |
| ) |
| |
| // CertificateList represents the ASN.1 structure of the same name from RFC 5280, s5.1. |
| // It has the same content as pkix.CertificateList, but the contents include parsed versions |
| // of any extensions. |
| type CertificateList struct { |
| Raw asn1.RawContent |
| TBSCertList TBSCertList |
| SignatureAlgorithm pkix.AlgorithmIdentifier |
| SignatureValue asn1.BitString |
| } |
| |
| // ExpiredAt reports whether now is past the expiry time of certList. |
| func (certList *CertificateList) ExpiredAt(now time.Time) bool { |
| return now.After(certList.TBSCertList.NextUpdate) |
| } |
| |
| // Indication of whether extensions need to be critical or non-critical. Extensions that |
| // can be either are omitted from the map. |
| var listExtCritical = map[string]bool{ |
| // From RFC 5280... |
| OIDExtensionAuthorityKeyId.String(): false, // s5.2.1 |
| OIDExtensionIssuerAltName.String(): false, // s5.2.2 |
| OIDExtensionCRLNumber.String(): false, // s5.2.3 |
| OIDExtensionDeltaCRLIndicator.String(): true, // s5.2.4 |
| OIDExtensionIssuingDistributionPoint.String(): true, // s5.2.5 |
| OIDExtensionFreshestCRL.String(): false, // s5.2.6 |
| OIDExtensionAuthorityInfoAccess.String(): false, // s5.2.7 |
| } |
| |
| var certExtCritical = map[string]bool{ |
| // From RFC 5280... |
| OIDExtensionCRLReasons.String(): false, // s5.3.1 |
| OIDExtensionInvalidityDate.String(): false, // s5.3.2 |
| OIDExtensionCertificateIssuer.String(): true, // s5.3.3 |
| } |
| |
| // IssuingDistributionPoint represents the ASN.1 structure of the same |
| // name |
| type IssuingDistributionPoint struct { |
| DistributionPoint distributionPointName `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"` |
| } |
| |
| // TBSCertList represents the ASN.1 structure of the same name from RFC |
| // 5280, section 5.1. It has the same content as pkix.TBSCertificateList |
| // but the extensions are included in a parsed format. |
| type TBSCertList struct { |
| Raw asn1.RawContent |
| Version int |
| Signature pkix.AlgorithmIdentifier |
| Issuer pkix.RDNSequence |
| ThisUpdate time.Time |
| NextUpdate time.Time |
| RevokedCertificates []*RevokedCertificate |
| Extensions []pkix.Extension |
| // Cracked out extensions: |
| AuthorityKeyID []byte |
| IssuerAltNames GeneralNames |
| CRLNumber int |
| BaseCRLNumber int // -1 if no delta CRL present |
| IssuingDistributionPoint IssuingDistributionPoint |
| IssuingDPFullNames GeneralNames |
| FreshestCRLDistributionPoint []string |
| OCSPServer []string |
| IssuingCertificateURL []string |
| } |
| |
| // ParseCertificateList parses a CertificateList (e.g. a CRL) from the given |
| // bytes. It's often the case that PEM encoded CRLs will appear where they |
| // should be DER encoded, so this function will transparently handle PEM |
| // encoding as long as there isn't any leading garbage. |
| func ParseCertificateList(clBytes []byte) (*CertificateList, error) { |
| if bytes.HasPrefix(clBytes, pemCRLPrefix) { |
| block, _ := pem.Decode(clBytes) |
| if block != nil && block.Type == pemType { |
| clBytes = block.Bytes |
| } |
| } |
| return ParseCertificateListDER(clBytes) |
| } |
| |
| // ParseCertificateListDER parses a DER encoded CertificateList from the given bytes. |
| // For non-fatal errors, this function returns both an error and a CertificateList |
| // object. |
| func ParseCertificateListDER(derBytes []byte) (*CertificateList, error) { |
| var errs Errors |
| // First parse the DER into the pkix structures. |
| pkixList := new(pkix.CertificateList) |
| if rest, err := asn1.Unmarshal(derBytes, pkixList); err != nil { |
| errs.AddID(ErrInvalidCertList, err) |
| return nil, &errs |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingCertList) |
| return nil, &errs |
| } |
| |
| // Transcribe the revoked certs but crack out extensions. |
| revokedCerts := make([]*RevokedCertificate, len(pkixList.TBSCertList.RevokedCertificates)) |
| for i, pkixRevoked := range pkixList.TBSCertList.RevokedCertificates { |
| revokedCerts[i] = parseRevokedCertificate(pkixRevoked, &errs) |
| if revokedCerts[i] == nil { |
| return nil, &errs |
| } |
| } |
| |
| certList := CertificateList{ |
| Raw: derBytes, |
| TBSCertList: TBSCertList{ |
| Raw: pkixList.TBSCertList.Raw, |
| Version: pkixList.TBSCertList.Version, |
| Signature: pkixList.TBSCertList.Signature, |
| Issuer: pkixList.TBSCertList.Issuer, |
| ThisUpdate: pkixList.TBSCertList.ThisUpdate, |
| NextUpdate: pkixList.TBSCertList.NextUpdate, |
| RevokedCertificates: revokedCerts, |
| Extensions: pkixList.TBSCertList.Extensions, |
| CRLNumber: -1, |
| BaseCRLNumber: -1, |
| }, |
| SignatureAlgorithm: pkixList.SignatureAlgorithm, |
| SignatureValue: pkixList.SignatureValue, |
| } |
| |
| // Now crack out extensions. |
| for _, e := range certList.TBSCertList.Extensions { |
| if expectCritical, present := listExtCritical[e.Id.String()]; present { |
| if e.Critical && !expectCritical { |
| errs.AddID(ErrUnexpectedlyCriticalCertListExtension, e.Id) |
| } else if !e.Critical && expectCritical { |
| errs.AddID(ErrUnexpectedlyNonCriticalCertListExtension, e.Id) |
| } |
| } |
| switch { |
| case e.Id.Equal(OIDExtensionAuthorityKeyId): |
| // RFC 5280 s5.2.1 |
| var a authKeyId |
| if rest, err := asn1.Unmarshal(e.Value, &a); err != nil { |
| errs.AddID(ErrInvalidCertListAuthKeyID, err) |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingCertListAuthKeyID) |
| } |
| certList.TBSCertList.AuthorityKeyID = a.Id |
| case e.Id.Equal(OIDExtensionIssuerAltName): |
| // RFC 5280 s5.2.2 |
| if err := parseGeneralNames(e.Value, &certList.TBSCertList.IssuerAltNames); err != nil { |
| errs.AddID(ErrInvalidCertListIssuerAltName, err) |
| } |
| case e.Id.Equal(OIDExtensionCRLNumber): |
| // RFC 5280 s5.2.3 |
| if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.CRLNumber); err != nil { |
| errs.AddID(ErrInvalidCertListCRLNumber, err) |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingCertListCRLNumber) |
| } |
| if certList.TBSCertList.CRLNumber < 0 { |
| errs.AddID(ErrNegativeCertListCRLNumber, certList.TBSCertList.CRLNumber) |
| } |
| case e.Id.Equal(OIDExtensionDeltaCRLIndicator): |
| // RFC 5280 s5.2.4 |
| if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.BaseCRLNumber); err != nil { |
| errs.AddID(ErrInvalidCertListDeltaCRL, err) |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingCertListDeltaCRL) |
| } |
| if certList.TBSCertList.BaseCRLNumber < 0 { |
| errs.AddID(ErrNegativeCertListDeltaCRL, certList.TBSCertList.BaseCRLNumber) |
| } |
| case e.Id.Equal(OIDExtensionIssuingDistributionPoint): |
| parseIssuingDistributionPoint(e.Value, &certList.TBSCertList.IssuingDistributionPoint, &certList.TBSCertList.IssuingDPFullNames, &errs) |
| case e.Id.Equal(OIDExtensionFreshestCRL): |
| // RFC 5280 s5.2.6 |
| if err := parseDistributionPoints(e.Value, &certList.TBSCertList.FreshestCRLDistributionPoint); err != nil { |
| errs.AddID(ErrInvalidCertListFreshestCRL, err) |
| return nil, err |
| } |
| case e.Id.Equal(OIDExtensionAuthorityInfoAccess): |
| // RFC 5280 s5.2.7 |
| var aia []authorityInfoAccess |
| if rest, err := asn1.Unmarshal(e.Value, &aia); err != nil { |
| errs.AddID(ErrInvalidCertListAuthInfoAccess, err) |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingCertListAuthInfoAccess) |
| } |
| |
| for _, v := range aia { |
| // GeneralName: uniformResourceIdentifier [6] IA5String |
| if v.Location.Tag != tagURI { |
| continue |
| } |
| switch { |
| case v.Method.Equal(OIDAuthorityInfoAccessOCSP): |
| certList.TBSCertList.OCSPServer = append(certList.TBSCertList.OCSPServer, string(v.Location.Bytes)) |
| case v.Method.Equal(OIDAuthorityInfoAccessIssuers): |
| certList.TBSCertList.IssuingCertificateURL = append(certList.TBSCertList.IssuingCertificateURL, string(v.Location.Bytes)) |
| } |
| // TODO(drysdale): cope with more possibilities |
| } |
| default: |
| if e.Critical { |
| errs.AddID(ErrUnhandledCriticalCertListExtension, e.Id) |
| } |
| } |
| } |
| |
| if errs.Fatal() { |
| return nil, &errs |
| } |
| if errs.Empty() { |
| return &certList, nil |
| } |
| return &certList, &errs |
| } |
| |
| func parseIssuingDistributionPoint(data []byte, idp *IssuingDistributionPoint, name *GeneralNames, errs *Errors) { |
| // RFC 5280 s5.2.5 |
| if rest, err := asn1.Unmarshal(data, idp); err != nil { |
| errs.AddID(ErrInvalidCertListIssuingDP, err) |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingCertListIssuingDP) |
| } |
| |
| typeCount := 0 |
| if idp.OnlyContainsUserCerts { |
| typeCount++ |
| } |
| if idp.OnlyContainsCACerts { |
| typeCount++ |
| } |
| if idp.OnlyContainsAttributeCerts { |
| typeCount++ |
| } |
| if typeCount > 1 { |
| errs.AddID(ErrCertListIssuingDPMultipleTypes, idp.OnlyContainsUserCerts, idp.OnlyContainsCACerts, idp.OnlyContainsAttributeCerts) |
| } |
| for _, fn := range idp.DistributionPoint.FullName { |
| if _, err := parseGeneralName(fn.FullBytes, name, false); err != nil { |
| errs.AddID(ErrCertListIssuingDPInvalidFullName, err) |
| } |
| } |
| } |
| |
| // RevokedCertificate represents the unnamed ASN.1 structure that makes up the |
| // revokedCertificates member of the TBSCertList structure from RFC 5280, s5.1. |
| // It has the same content as pkix.RevokedCertificate but the extensions are |
| // included in a parsed format. |
| type RevokedCertificate struct { |
| pkix.RevokedCertificate |
| // Cracked out extensions: |
| RevocationReason RevocationReasonCode |
| InvalidityDate time.Time |
| Issuer GeneralNames |
| } |
| |
| func parseRevokedCertificate(pkixRevoked pkix.RevokedCertificate, errs *Errors) *RevokedCertificate { |
| result := RevokedCertificate{RevokedCertificate: pkixRevoked} |
| for _, e := range pkixRevoked.Extensions { |
| if expectCritical, present := certExtCritical[e.Id.String()]; present { |
| if e.Critical && !expectCritical { |
| errs.AddID(ErrUnexpectedlyCriticalRevokedCertExtension, e.Id) |
| } else if !e.Critical && expectCritical { |
| errs.AddID(ErrUnexpectedlyNonCriticalRevokedCertExtension, e.Id) |
| } |
| } |
| switch { |
| case e.Id.Equal(OIDExtensionCRLReasons): |
| // RFC 5280, s5.3.1 |
| var reason asn1.Enumerated |
| if rest, err := asn1.Unmarshal(e.Value, &reason); err != nil { |
| errs.AddID(ErrInvalidRevocationReason, err) |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingRevocationReason) |
| } |
| result.RevocationReason = RevocationReasonCode(reason) |
| case e.Id.Equal(OIDExtensionInvalidityDate): |
| // RFC 5280, s5.3.2 |
| if rest, err := asn1.Unmarshal(e.Value, &result.InvalidityDate); err != nil { |
| errs.AddID(ErrInvalidRevocationInvalidityDate, err) |
| } else if len(rest) != 0 { |
| errs.AddID(ErrTrailingRevocationInvalidityDate) |
| } |
| case e.Id.Equal(OIDExtensionCertificateIssuer): |
| // RFC 5280, s5.3.3 |
| if err := parseGeneralNames(e.Value, &result.Issuer); err != nil { |
| errs.AddID(ErrInvalidRevocationIssuer, err) |
| } |
| default: |
| if e.Critical { |
| errs.AddID(ErrUnhandledCriticalRevokedCertExtension, e.Id) |
| } |
| } |
| } |
| return &result |
| } |
| |
| // CheckCertificateListSignature checks that the signature in crl is from c. |
| func (c *Certificate) CheckCertificateListSignature(crl *CertificateList) error { |
| algo := SignatureAlgorithmFromAI(crl.SignatureAlgorithm) |
| return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign()) |
| } |