blob: e7044416398009adaf59fa2ce0c312a7c08694f2 [file] [log] [blame]
// 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())
}