Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
13 KiB
365 lines
13 KiB
// 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" |
|
) |
|
|
|
// OID values for CRL extensions (TBSCertList.Extensions), RFC 5280 s5.2. |
|
var ( |
|
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 |
|
var ( |
|
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 []accessDescription |
|
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()) |
|
}
|
|
|