// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package core

import (
	"crypto/subtle"
	"crypto/x509"
	"encoding/asn1"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"math/big"
	"net"
	"strings"
	"time"

	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
	"github.com/letsencrypt/boulder/probs"
)

// AcmeStatus defines the state of a given authorization
type AcmeStatus string

// AcmeResource values identify different types of ACME resources
type AcmeResource string

// Buffer is a variable-length collection of bytes
type Buffer []byte

// IdentifierType defines the available identification mechanisms for domains
type IdentifierType string

// OCSPStatus defines the state of OCSP for a domain
type OCSPStatus string

// These statuses are the states of authorizations
const (
	StatusUnknown    = AcmeStatus("unknown")    // Unknown status; the default
	StatusPending    = AcmeStatus("pending")    // In process; client has next action
	StatusProcessing = AcmeStatus("processing") // In process; server has next action
	StatusValid      = AcmeStatus("valid")      // Validation succeeded
	StatusInvalid    = AcmeStatus("invalid")    // Validation failed
	StatusRevoked    = AcmeStatus("revoked")    // Object no longer valid
)

// These types are the available identification mechanisms
const (
	IdentifierDNS = IdentifierType("dns")
)

// The types of ACME resources
const (
	ResourceNewReg       = AcmeResource("new-reg")
	ResourceNewAuthz     = AcmeResource("new-authz")
	ResourceNewCert      = AcmeResource("new-cert")
	ResourceRevokeCert   = AcmeResource("revoke-cert")
	ResourceRegistration = AcmeResource("reg")
	ResourceChallenge    = AcmeResource("challenge")
)

// These status are the states of OCSP
const (
	OCSPStatusGood    = OCSPStatus("good")
	OCSPStatusRevoked = OCSPStatus("revoked")
)

// These types are the available challenges
const (
	ChallengeTypeSimpleHTTP = "simpleHttp"
	ChallengeTypeDVSNI      = "dvsni"
	ChallengeTypeHTTP01     = "http-01"
	ChallengeTypeTLSSNI01   = "tls-sni-01"
	ChallengeTypeDNS01      = "dns-01"
)

// ValidChallenge tests whether the provided string names a known challenge
func ValidChallenge(name string) bool {
	switch name {
	// TODO(#894): Delete these lines
	case ChallengeTypeSimpleHTTP:
		fallthrough
	case ChallengeTypeDVSNI:
		fallthrough

	case ChallengeTypeHTTP01:
		fallthrough
	case ChallengeTypeTLSSNI01:
		fallthrough
	case ChallengeTypeDNS01:
		return true

	default:
		return false
	}
}

// TLSSNISuffix is appended to pseudo-domain names in DVSNI challenges
const TLSSNISuffix = "acme.invalid"

// DNSPrefix is attached to DNS names in DNS challenges
const DNSPrefix = "_acme-challenge"

// An AcmeIdentifier encodes an identifier that can
// be validated by ACME.  The protocol allows for different
// types of identifier to be supported (DNS names, IP
// addresses, etc.), but currently we only support
// domain names.
type AcmeIdentifier struct {
	Type  IdentifierType `json:"type"`  // The type of identifier being encoded
	Value string         `json:"value"` // The identifier itself
}

// CertificateRequest is just a CSR
//
// This data is unmarshalled from JSON by way of rawCertificateRequest, which
// represents the actual structure received from the client.
type CertificateRequest struct {
	CSR   *x509.CertificateRequest // The CSR
	Bytes []byte                   // The original bytes of the CSR, for logging.
}

type rawCertificateRequest struct {
	CSR JSONBuffer `json:"csr"` // The encoded CSR
}

// UnmarshalJSON provides an implementation for decoding CertificateRequest objects.
func (cr *CertificateRequest) UnmarshalJSON(data []byte) error {
	var raw rawCertificateRequest
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}

	csr, err := x509.ParseCertificateRequest(raw.CSR)
	if err != nil {
		return err
	}

	cr.CSR = csr
	cr.Bytes = raw.CSR
	return nil
}

// MarshalJSON provides an implementation for encoding CertificateRequest objects.
func (cr CertificateRequest) MarshalJSON() ([]byte, error) {
	return json.Marshal(rawCertificateRequest{
		CSR: cr.CSR.Raw,
	})
}

// Registration objects represent non-public metadata attached
// to account keys.
type Registration struct {
	// Unique identifier
	ID int64 `json:"id" db:"id"`

	// Account key to which the details are attached
	Key jose.JsonWebKey `json:"key"`

	// Contact URIs
	Contact []*AcmeURL `json:"contact,omitempty"`

	// Agreement with terms of service
	Agreement string `json:"agreement,omitempty"`

	// InitialIP is the IP address from which the registration was created
	InitialIP net.IP `json:"initialIp"`

	// CreatedAt is the time the registration was created.
	CreatedAt time.Time `json:"createdAt"`
}

// MergeUpdate copies a subset of information from the input Registration
// into this one.
func (r *Registration) MergeUpdate(input Registration) {
	if len(input.Contact) > 0 {
		r.Contact = input.Contact
	}

	if len(input.Agreement) > 0 {
		r.Agreement = input.Agreement
	}
}

// ValidationRecord represents a validation attempt against a specific URL/hostname
// and the IP addresses that were resolved and used
type ValidationRecord struct {
	// SimpleHTTP only
	URL string `json:"url,omitempty"`

	// Shared
	Hostname          string   `json:"hostname"`
	Port              string   `json:"port"`
	AddressesResolved []net.IP `json:"addressesResolved"`
	AddressUsed       net.IP   `json:"addressUsed"`
}

// KeyAuthorization represents a domain holder's authorization for a
// specific account key to satisfy a specific challenge.
type KeyAuthorization struct {
	Token      string
	Thumbprint string
}

// NewKeyAuthorization computes the thumbprint and assembles the object
func NewKeyAuthorization(token string, key *jose.JsonWebKey) (KeyAuthorization, error) {
	if key == nil {
		return KeyAuthorization{}, fmt.Errorf("Cannot authorize a nil key")
	}

	thumbprint, err := Thumbprint(key)
	if err != nil {
		return KeyAuthorization{}, err
	}

	return KeyAuthorization{
		Token:      token,
		Thumbprint: thumbprint,
	}, nil
}

// NewKeyAuthorizationFromString parses the string and composes a key authorization struct
func NewKeyAuthorizationFromString(input string) (ka KeyAuthorization, err error) {
	parts := strings.Split(input, ".")
	if len(parts) != 2 {
		err = fmt.Errorf("Invalid key authorization: %d parts", len(parts))
		return
	} else if !LooksLikeAToken(parts[0]) {
		err = fmt.Errorf("Invalid key authorization: malformed token")
		return
	} else if !LooksLikeAToken(parts[1]) {
		// Thumbprints have the same syntax as tokens in boulder
		// Both are base64-encoded and 32 octets
		err = fmt.Errorf("Invalid key authorization: malformed key thumbprint")
		return
	}

	ka = KeyAuthorization{
		Token:      parts[0],
		Thumbprint: parts[1],
	}
	return
}

// String produces the string representation of a key authorization
func (ka KeyAuthorization) String() string {
	return ka.Token + "." + ka.Thumbprint
}

// Match determines whether this KeyAuthorization matches the given token and key
func (ka KeyAuthorization) Match(token string, key *jose.JsonWebKey) bool {
	if key == nil {
		return false
	}

	thumbprint, err := Thumbprint(key)
	if err != nil {
		return false
	}

	tokensEqual := subtle.ConstantTimeCompare([]byte(token), []byte(ka.Token))
	thumbprintsEqual := subtle.ConstantTimeCompare([]byte(thumbprint), []byte(ka.Thumbprint))

	return tokensEqual == 1 && thumbprintsEqual == 1
}

// MarshalJSON packs a key authorization into its string representation
func (ka KeyAuthorization) MarshalJSON() (result []byte, err error) {
	return json.Marshal(ka.String())
}

// UnmarshalJSON unpacks a key authorization from a string
func (ka *KeyAuthorization) UnmarshalJSON(data []byte) (err error) {
	var str string
	err = json.Unmarshal(data, &str)
	if err != nil {
		return err
	}

	parsed, err := NewKeyAuthorizationFromString(str)
	if err != nil {
		return err
	}

	*ka = parsed
	return
}

// Challenge is an aggregate of all data needed for any challenges.
//
// Rather than define individual types for different types of
// challenge, we just throw all the elements into one bucket,
// together with the common metadata elements.
type Challenge struct {
	ID int64 `json:"id,omitempty"`

	// The type of challenge
	Type string `json:"type"`

	// The status of this challenge
	Status AcmeStatus `json:"status,omitempty"`

	// Contains the error that occured during challenge validation, if any
	Error *probs.ProblemDetails `json:"error,omitempty"`

	// If successful, the time at which this challenge
	// was completed by the server.
	Validated *time.Time `json:"validated,omitempty"`

	// A URI to which a response can be POSTed
	URI string `json:"uri"`

	// Used by simpleHttp, http-00, tls-sni-00, and dns-00 challenges
	Token string `json:"token,omitempty"`

	// Used by simpleHttp challenges
	TLS *bool `json:"tls,omitempty"`

	// Used by dvsni challenges
	Validation *jose.JsonWebSignature `json:"validation,omitempty"`

	// Used by http-00, tls-sni-00, and dns-00 challenges
	KeyAuthorization *KeyAuthorization `json:"keyAuthorization,omitempty"`

	// Contains information about URLs used or redirected to and IPs resolved and
	// used
	ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"`

	// The account key used to create this challenge.  This is not part of the
	// spec, but clients are required to ignore unknown fields, so it's harmless
	// to include.
	//
	// Boulder needs to remember what key was used to create a challenge in order
	// to prevent an attacker from re-using a validation signature with a different,
	// unauthorized key. See:
	//   https://mailarchive.ietf.org/arch/msg/acme/F71iz6qq1o_QPVhJCV4dqWf-4Yc
	AccountKey *jose.JsonWebKey `json:"accountKey,omitempty"`
}

// RecordsSane checks the sanity of a ValidationRecord object before sending it
// back to the RA to be stored.
func (ch Challenge) RecordsSane() bool {
	if ch.ValidationRecord == nil || len(ch.ValidationRecord) == 0 {
		return false
	}

	switch ch.Type {
	case ChallengeTypeSimpleHTTP:
		// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this case
		fallthrough
	case ChallengeTypeHTTP01:
		for _, rec := range ch.ValidationRecord {
			if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil ||
				len(rec.AddressesResolved) == 0 {
				return false
			}
		}
	case ChallengeTypeDVSNI:
		// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this case
		fallthrough
	case ChallengeTypeTLSSNI01:
		if len(ch.ValidationRecord) > 1 {
			return false
		}
		if ch.ValidationRecord[0].URL != "" {
			return false
		}
		if ch.ValidationRecord[0].Hostname == "" || ch.ValidationRecord[0].Port == "" ||
			ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
			return false
		}
	case ChallengeTypeDNS01:
		// Nothing for now
	}

	return true
}

// isLegacy returns true if the challenge is of a legacy type (i.e., one defined
// before draft-ietf-acme-acme-00)
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
func (ch Challenge) isLegacy() bool {
	return (ch.Type == ChallengeTypeSimpleHTTP) ||
		(ch.Type == ChallengeTypeDVSNI)
}

// legacyIsSane performs sanity checks for legacy challenge types, which have
// a different structure / logic than current challenges.
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
func (ch Challenge) legacyIsSane(completed bool) bool {
	if ch.Status != StatusPending {
		return false
	}

	if ch.AccountKey == nil {
		return false
	}

	switch ch.Type {
	case ChallengeTypeSimpleHTTP:
		// check extra fields aren't used
		if ch.Validation != nil {
			return false
		}

		if completed && ch.TLS == nil {
			return false
		}

		// check token is present, corrent length, and contains b64 encoded string
		if ch.Token == "" || len(ch.Token) != 43 {
			return false
		}
		if _, err := B64dec(ch.Token); err != nil {
			return false
		}
	case ChallengeTypeDVSNI:
		// check extra fields aren't used
		if ch.TLS != nil {
			return false
		}

		// check token is present, corrent length, and contains b64 encoded string
		if ch.Token == "" || len(ch.Token) != 43 {
			return false
		}
		if _, err := B64dec(ch.Token); err != nil {
			return false
		}

		// If completed, check that there's a validation object
		if completed && ch.Validation == nil {
			return false
		}
	default:
		return false
	}

	return true
}

// legacyMergeResponse copies a subset of client-provided data to the current Challenge.
// Note: This method does not update the challenge on the left side of the '.'
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
func (ch Challenge) legacyMergeResponse(resp Challenge) Challenge {
	switch ch.Type {
	case ChallengeTypeSimpleHTTP:
		// For simpleHttp, only "tls" is client-provided
		// If "tls" is not provided, default to "true"
		if resp.TLS != nil {
			ch.TLS = resp.TLS
		} else {
			ch.TLS = new(bool)
			*ch.TLS = true
		}

	case ChallengeTypeDVSNI:
		// For dvsni and dns, only "validation" is client-provided
		if resp.Validation != nil {
			ch.Validation = resp.Validation
		}
	}

	return ch
}

// IsSane checks the sanity of a challenge object before issued to the client
// (completed = false) and before validation (completed = true).
func (ch Challenge) IsSane(completed bool) bool {
	// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this branch
	if ch.isLegacy() {
		return ch.legacyIsSane(completed)
	}

	if ch.Status != StatusPending {
		return false
	}

	// There always needs to be an account key and token
	if ch.AccountKey == nil || !LooksLikeAToken(ch.Token) {
		return false
	}

	// Before completion, the key authorization field should be empty
	if !completed && ch.KeyAuthorization != nil {
		return false
	}

	// If the challenge is completed, then there should be a key authorization,
	// and it should match the challenge.
	if completed {
		if ch.KeyAuthorization == nil {
			return false
		}

		if !ch.KeyAuthorization.Match(ch.Token, ch.AccountKey) {
			return false
		}
	}

	return true
}

// MergeResponse copies a subset of client-provided data to the current Challenge.
// Note: This method does not update the challenge on the left side of the '.'
func (ch Challenge) MergeResponse(resp Challenge) Challenge {
	// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this branch
	if ch.isLegacy() {
		return ch.legacyMergeResponse(resp)
	}

	// The only client-provided field is the key authorization, and all current
	// challenge types use it.
	switch ch.Type {
	case ChallengeTypeHTTP01:
		fallthrough
	case ChallengeTypeTLSSNI01:
		fallthrough
	case ChallengeTypeDNS01:
		ch.KeyAuthorization = resp.KeyAuthorization
	}

	return ch
}

// Authorization represents the authorization of an account key holder
// to act on behalf of a domain.  This struct is intended to be used both
// internally and for JSON marshaling on the wire.  Any fields that should be
// suppressed on the wire (e.g., ID, regID) must be made empty before marshaling.
type Authorization struct {
	// An identifier for this authorization, unique across
	// authorizations and certificates within this instance.
	ID string `json:"id,omitempty" db:"id"`

	// The identifier for which authorization is being given
	Identifier AcmeIdentifier `json:"identifier,omitempty" db:"identifier"`

	// The registration ID associated with the authorization
	RegistrationID int64 `json:"regId,omitempty" db:"registrationID"`

	// The status of the validation of this authorization
	Status AcmeStatus `json:"status,omitempty" db:"status"`

	// The date after which this authorization will be no
	// longer be considered valid. Note: a certificate may be issued even on the
	// last day of an authorization's lifetime. The last day for which someone can
	// hold a valid certificate based on an authorization is authorization
	// lifetime + certificate lifetime.
	Expires *time.Time `json:"expires,omitempty" db:"expires"`

	// An array of challenges objects used to validate the
	// applicant's control of the identifier.  For authorizations
	// in process, these are challenges to be fulfilled; for
	// final authorizations, they describe the evidence that
	// the server used in support of granting the authorization.
	Challenges []Challenge `json:"challenges,omitempty" db:"-"`

	// The server may suggest combinations of challenges if it
	// requires more than one challenge to be completed.
	Combinations [][]int `json:"combinations,omitempty" db:"combinations"`
}

// FindChallenge will look for the given challenge inside this authorization. If
// found, it will return the index of that challenge within the Authorization's
// Challenges array. Otherwise it will return -1.
func (authz *Authorization) FindChallenge(challengeID int64) int {
	for i, c := range authz.Challenges {
		if c.ID == challengeID {
			return i
		}
	}
	return -1
}

// JSONBuffer fields get encoded and decoded JOSE-style, in base64url encoding
// with stripped padding.
type JSONBuffer []byte

// URL-safe base64 encode that strips padding
func base64URLEncode(data []byte) string {
	var result = base64.URLEncoding.EncodeToString(data)
	return strings.TrimRight(result, "=")
}

// URL-safe base64 decoder that adds padding
func base64URLDecode(data string) ([]byte, error) {
	var missing = (4 - len(data)%4) % 4
	data += strings.Repeat("=", missing)
	return base64.URLEncoding.DecodeString(data)
}

// MarshalJSON encodes a JSONBuffer for transmission.
func (jb JSONBuffer) MarshalJSON() (result []byte, err error) {
	return json.Marshal(base64URLEncode(jb))
}

// UnmarshalJSON decodes a JSONBuffer to an object.
func (jb *JSONBuffer) UnmarshalJSON(data []byte) (err error) {
	var str string
	err = json.Unmarshal(data, &str)
	if err != nil {
		return err
	}
	*jb, err = base64URLDecode(str)
	return
}

// Certificate objects are entirely internal to the server.  The only
// thing exposed on the wire is the certificate itself.
type Certificate struct {
	RegistrationID int64 `db:"registrationID"`

	Serial  string    `db:"serial"`
	Digest  string    `db:"digest"`
	DER     []byte    `db:"der"`
	Issued  time.Time `db:"issued"`
	Expires time.Time `db:"expires"`
}

// IdentifierData holds information about what certificates are known for a
// given identifier. This is used to present Proof of Posession challenges in
// the case where a certificate already exists. The DB table holding
// IdentifierData rows contains information about certs issued by Boulder and
// also information about certs observed from third parties.
type IdentifierData struct {
	ReversedName string `db:"reversedName"` // The label-wise reverse of an identifier, e.g. com.example or com.example.*
	CertSHA1     string `db:"certSHA1"`     // The hex encoding of the SHA-1 hash of a cert containing the identifier
}

// ExternalCert holds information about certificates issued by other CAs,
// obtained through Certificate Transparency, the SSL Observatory, or scans.io.
type ExternalCert struct {
	SHA1     string    `db:"sha1"`       // The hex encoding of the SHA-1 hash of this cert
	Issuer   string    `db:"issuer"`     // The Issuer field of this cert
	Subject  string    `db:"subject"`    // The Subject field of this cert
	NotAfter time.Time `db:"notAfter"`   // Date after which this cert should be considered invalid
	SPKI     []byte    `db:"spki"`       // The hex encoding of the certificate's SubjectPublicKeyInfo in DER form
	Valid    bool      `db:"valid"`      // Whether this certificate was valid at LastUpdated time
	EV       bool      `db:"ev"`         // Whether this cert was EV valid
	CertDER  []byte    `db:"rawDERCert"` // DER (binary) encoding of the raw certificate
}

// CertificateStatus structs are internal to the server. They represent the
// latest data about the status of the certificate, required for OCSP updating
// and for validating that the subscriber has accepted the certificate.
type CertificateStatus struct {
	Serial string `db:"serial"`

	// subscriberApproved: true iff the subscriber has posted back to the server
	//   that they accept the certificate, otherwise 0.
	SubscriberApproved bool `db:"subscriberApproved"`

	// status: 'good' or 'revoked'. Note that good, expired certificates remain
	//   with status 'good' but don't necessarily get fresh OCSP responses.
	Status OCSPStatus `db:"status"`

	// ocspLastUpdated: The date and time of the last time we generated an OCSP
	//   response. If we have never generated one, this has the zero value of
	//   time.Time, i.e. Jan 1 1970.
	OCSPLastUpdated time.Time `db:"ocspLastUpdated"`

	// revokedDate: If status is 'revoked', this is the date and time it was
	//   revoked. Otherwise it has the zero value of time.Time, i.e. Jan 1 1970.
	RevokedDate time.Time `db:"revokedDate"`

	// revokedReason: If status is 'revoked', this is the reason code for the
	//   revocation. Otherwise it is zero (which happens to be the reason
	//   code for 'unspecified').
	RevokedReason RevocationCode `db:"revokedReason"`

	LastExpirationNagSent time.Time `db:"lastExpirationNagSent"`

	// The encoded and signed OCSP response.
	OCSPResponse []byte `db:"ocspResponse"`

	LockCol int64 `json:"-"`
}

// OCSPResponse is a (large) table of OCSP responses. This contains all
// historical OCSP responses we've signed, is append-only, and is likely to get
// quite large.
// It must be administratively truncated outside of Boulder.
type OCSPResponse struct {
	ID int `db:"id"`

	// serial: Same as certificate serial.
	Serial string `db:"serial"`

	// createdAt: The date the response was signed.
	CreatedAt time.Time `db:"createdAt"`

	// response: The encoded and signed CRL.
	Response []byte `db:"response"`
}

// CRL is a large table of signed CRLs. This contains all historical CRLs
// we've signed, is append-only, and is likely to get quite large.
// It must be administratively truncated outside of Boulder.
type CRL struct {
	// serial: Same as certificate serial.
	Serial string `db:"serial"`

	// createdAt: The date the CRL was signed.
	CreatedAt time.Time `db:"createdAt"`

	// crl: The encoded and signed CRL.
	CRL string `db:"crl"`
}

// DeniedCSR is a list of names we deny issuing.
type DeniedCSR struct {
	ID int `db:"id"`

	Names string `db:"names"`
}

// OCSPSigningRequest is a transfer object representing an OCSP Signing Request
type OCSPSigningRequest struct {
	CertDER   []byte
	Status    string
	Reason    RevocationCode
	RevokedAt time.Time
}

// SignedCertificateTimestamp represents objects used by Certificate Transparency
// to demonstrate that a certificate was submitted to a CT log. See RFC 6962.
type SignedCertificateTimestamp struct {
	ID int `db:"id"`
	// The version of the protocol to which the SCT conforms
	SCTVersion uint8 `db:"sctVersion"`
	// the SHA-256 hash of the log's public key, calculated over
	// the DER encoding of the key represented as SubjectPublicKeyInfo.
	LogID string `db:"logID"`
	// Timestamp (in ms since unix epoc) at which the SCT was issued
	Timestamp uint64 `db:"timestamp"`
	// For future extensions to the protocol
	Extensions []byte `db:"extensions"`
	// The Log's signature for this SCT
	Signature []byte `db:"signature"`

	// The serial of the certificate this SCT is for
	CertificateSerial string `db:"certificateSerial"`

	LockCol int64
}

// RPCSignedCertificateTimestamp is a wrapper around SignedCertificateTimestamp
// so that it can be passed through the RPC layer properly. Without this wrapper
// the UnmarshalJSON method below will be used when marshaling/unmarshaling the
// object, which is not what we want as it is not symmetrical (as it is intended
// to unmarshal a rawSignedCertificateTimestamp into a SignedCertificateTimestamp)
type RPCSignedCertificateTimestamp SignedCertificateTimestamp

type rawSignedCertificateTimestamp struct {
	Version    uint8  `json:"sct_version"`
	LogID      string `json:"id"`
	Timestamp  uint64 `json:"timestamp"`
	Signature  string `json:"signature"`
	Extensions string `json:"extensions"`
}

// UnmarshalJSON parses the add-chain response from a CT log. It fills all of
// the fields in the SignedCertificateTimestamp struct except for ID and
// CertificateSerial, which are used for local recordkeeping in the Boulder DB.
func (sct *SignedCertificateTimestamp) UnmarshalJSON(data []byte) error {
	var err error
	var rawSCT rawSignedCertificateTimestamp
	if err = json.Unmarshal(data, &rawSCT); err != nil {
		return fmt.Errorf("Failed to unmarshal SCT receipt, %s", err)
	}
	sct.LogID = rawSCT.LogID
	if err != nil {
		return fmt.Errorf("Failed to decode log ID, %s", err)
	}
	sct.Signature, err = base64.StdEncoding.DecodeString(rawSCT.Signature)
	if err != nil {
		return fmt.Errorf("Failed to decode SCT signature, %s", err)
	}
	sct.Extensions, err = base64.StdEncoding.DecodeString(rawSCT.Extensions)
	if err != nil {
		return fmt.Errorf("Failed to decode SCT extensions, %s", err)
	}
	sct.SCTVersion = rawSCT.Version
	sct.Timestamp = rawSCT.Timestamp
	return nil
}

const (
	sctHashSHA256 = 4
	sctSigECDSA   = 3
)

// CheckSignature validates that the returned SCT signature is a valid SHA256 +
// ECDSA signature but does not verify that a specific public key signed it.
func (sct *SignedCertificateTimestamp) CheckSignature() error {
	if len(sct.Signature) < 4 {
		return errors.New("SCT signature is truncated")
	}
	// Since all of the known logs currently only use SHA256 hashes and ECDSA
	// keys, only allow those
	if sct.Signature[0] != sctHashSHA256 {
		return fmt.Errorf("Unsupported SCT hash function [%d]", sct.Signature[0])
	}
	if sct.Signature[1] != sctSigECDSA {
		return fmt.Errorf("Unsupported SCT signature algorithm [%d]", sct.Signature[1])
	}

	var ecdsaSig struct {
		R, S *big.Int
	}
	// Ignore the two length bytes and attempt to unmarshal the signature directly
	signatureBytes := sct.Signature[4:]
	signatureBytes, err := asn1.Unmarshal(signatureBytes, &ecdsaSig)
	if err != nil {
		return fmt.Errorf("Failed to parse SCT signature, %s", err)
	}
	if len(signatureBytes) > 0 {
		return fmt.Errorf("Trailing garbage after signature")
	}

	return nil
}

// RevocationCode is used to specify a certificate revocation reason
type RevocationCode int

// RevocationReasons provides a map from reason code to string explaining the
// code
var RevocationReasons = map[RevocationCode]string{
	0: "unspecified",
	1: "keyCompromise",
	2: "cACompromise",
	3: "affiliationChanged",
	4: "superseded",
	5: "cessationOfOperation",
	6: "certificateHold",
	// 7 is unused
	8:  "removeFromCRL", // needed?
	9:  "privilegeWithdrawn",
	10: "aAcompromise",
}
