// 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())
}
