package tls

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"math/big"
	"time"
)

type (
	// CA provides a certificate authority for TLS-enabled installs.
	// Issuing certificates concurrently is not supported.
	CA struct {
		// Cred contains the CA's credentials.
		Cred Cred

		// Validity configures the NotBefore and NotAfter parameters for certificates
		// issued by this CA.
		//
		// Currently this is used for the CA's validity too, but nothing should
		// assume that the CA's validity period is the same as issued certificates'
		// validity.
		Validity Validity

		// nextSerialNumber is the serial number of the next certificate to issue.
		// Serial numbers must not be reused.
		//
		// It is assumed there is only one instance of CA and it is assumed that a
		// given CA object isn't requested to issue certificates concurrently.
		//
		// For now we do not attempt to meet CABForum requirements (e.g. regarding
		// randomness).
		nextSerialNumber uint64

		// firstCrtExpiration is the time when the first expiration of a certificate
		// in the trust chain occurs
		firstCrtExpiration time.Time
	}

	// Validity configures the expiry times of issued certificates.
	Validity struct {
		// Validity is the duration for which issued certificates are valid. This
		// is approximately cert.NotAfter - cert.NotBefore with some additional
		// allowance for clock skew.
		//
		// Currently this is used for the CA's validity too, but nothing should
		// assume that the CA's validity period is the same as issued certificates'
		// validity.
		Lifetime time.Duration

		// ClockSkewAllowance is the maximum supported clock skew. Everything that
		// processes the certificates must have a system clock that is off by no
		// more than this allowance in either direction.
		ClockSkewAllowance time.Duration

		// ValidFrom is the point in time from which the certificate is valid.
		// This is cert.NotBefore with some clock skew allowance.
		ValidFrom *time.Time
	}

	// Issuer implementors signs certificate requests.
	Issuer interface {
		IssueEndEntityCrt(*x509.CertificateRequest) (Crt, error)
	}
)

const (
	// DefaultLifetime configures certificate validity.
	//
	// Initially all certificates will be valid for one year.
	//
	// TODO: Shorten the validity duration of CA and end-entity certificates downward.
	DefaultLifetime = (24 * 365) * time.Hour

	// DefaultClockSkewAllowance indicates the maximum allowed difference in clocks
	// in the network.
	//
	// TODO: make it tunable.
	//
	// TODO: Reconsider how this interacts with the similar logic in the webpki
	// verifier; since both are trying to account for clock skew, there is
	// somewhat of an over-correction.
	DefaultClockSkewAllowance = 10 * time.Second
)

// Finds the time at which the first certificate
// from the chain will expire
func findFirstExpiration(cred *Cred) time.Time {
	firstExpiration := cred.Certificate.NotAfter
	for _, c := range cred.TrustChain {
		if c.NotAfter.Before(firstExpiration) {
			firstExpiration = c.NotAfter
		}
	}
	return firstExpiration
}

// NewCA initializes a new CA with default settings.
func NewCA(cred Cred, validity Validity) *CA {
	return &CA{cred, validity, uint64(1), findFirstExpiration(&cred)}
}

func init() {
	// Assert that the struct implements the interface.
	var _ Issuer = &CA{}
}

// CreateRootCA configures a new root CA with the given settings
func CreateRootCA(
	name string,
	key *ecdsa.PrivateKey,
	validity Validity,
) (*CA, error) {
	// Configure the root certificate.
	t := createTemplate(1, &key.PublicKey, validity)
	t.Subject = pkix.Name{CommonName: name}
	t.IsCA = true
	t.MaxPathLen = -1
	t.BasicConstraintsValid = true
	t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign

	// Self-sign the root certificate.
	crtb, err := x509.CreateCertificate(rand.Reader, t, t, key.Public(), key)
	if err != nil {
		return nil, err
	}
	c, err := x509.ParseCertificate(crtb)
	if err != nil {
		return nil, err
	}

	// The Crt has an empty TrustChain because it's at the root.
	cred := validCredOrPanic(key, Crt{Certificate: c})
	ca := NewCA(cred, validity)
	ca.nextSerialNumber++ // Because we've already created the root cert.
	return ca, nil
}

// GenerateKey creates a new P-256 ECDSA private key from the default random
// source.
func GenerateKey() (*ecdsa.PrivateKey, error) {
	return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}

// GenerateRootCAWithDefaults generates a new root CA with default settings.
func GenerateRootCAWithDefaults(name string) (*CA, error) {
	// Generate a new root key.
	key, err := GenerateKey()
	if err != nil {
		return nil, err
	}

	return CreateRootCA(name, key, Validity{})
}

// GenerateCA generates a new intermdiary CA.
func (ca *CA) GenerateCA(name string, maxPathLen int) (*CA, error) {
	key, err := GenerateKey()
	if err != nil {
		return nil, err
	}

	t := ca.createTemplate(&key.PublicKey)
	t.Subject = pkix.Name{CommonName: name}
	t.IsCA = true
	t.MaxPathLen = maxPathLen
	t.MaxPathLenZero = true // 0-values are actually 0
	t.BasicConstraintsValid = true
	t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
	crt, err := ca.Cred.SignCrt(t)
	if err != nil {
		return nil, err
	}

	return NewCA(validCredOrPanic(key, crt), ca.Validity), nil
}

// GenerateEndEntityCred creates a new certificate that is valid for the
// given DNS name, generating a new keypair for it.
func (ca *CA) GenerateEndEntityCred(dnsName string) (*Cred, error) {
	key, err := GenerateKey()
	if err != nil {
		return nil, err
	}

	csr := x509.CertificateRequest{
		Subject:   pkix.Name{CommonName: dnsName},
		DNSNames:  []string{dnsName},
		PublicKey: &key.PublicKey,
	}
	crt, err := ca.IssueEndEntityCrt(&csr)
	if err != nil {
		return nil, err
	}

	c := validCredOrPanic(key, crt)
	return &c, nil
}

// IssueEndEntityCrt creates a new certificate that is valid for the
// given DNS name, generating a new keypair for it.
func (ca *CA) IssueEndEntityCrt(csr *x509.CertificateRequest) (Crt, error) {
	pubkey, ok := csr.PublicKey.(*ecdsa.PublicKey)
	if !ok {
		return Crt{}, fmt.Errorf("CSR must contain an ECDSA public key: %+v", csr.PublicKey)
	}

	t := ca.createTemplate(pubkey)
	t.Issuer = ca.Cred.Crt.Certificate.Subject
	t.Subject = csr.Subject
	t.Extensions = csr.Extensions
	t.ExtraExtensions = csr.ExtraExtensions
	t.DNSNames = csr.DNSNames
	t.EmailAddresses = csr.EmailAddresses
	t.IPAddresses = csr.IPAddresses
	t.URIs = csr.URIs

	return ca.Cred.SignCrt(t)
}

// createTemplate returns a certificate t for a non-CA certificate with
// no subject name, no subjectAltNames. The t can then be modified into
// a (root) CA t or an end-entity t by the caller.
func (ca *CA) createTemplate(pubkey *ecdsa.PublicKey) *x509.Certificate {
	c := createTemplate(ca.nextSerialNumber, pubkey, ca.Validity)
	ca.nextSerialNumber++
	// if our trust chain contains a certificate that expires
	// sooner than the one we intend to issue, we clamp the
	// NotAfter time of our newly issued certificate. That ensures
	// the proxy will request a new cert before any of the
	// certs in the chain are expired.
	if ca.firstCrtExpiration.Before(c.NotAfter) {
		c.NotAfter = ca.firstCrtExpiration
	}
	return c
}

// createTemplate returns a certificate t for a non-CA certificate with
// no subject name, no subjectAltNames. The t can then be modified into
// a (root) CA t or an end-entity t by the caller.
func createTemplate(
	serialNumber uint64,
	k *ecdsa.PublicKey,
	v Validity,
) *x509.Certificate {
	// ECDSA is used instead of RSA because ECDSA key generation is
	// straightforward and fast whereas RSA key generation is extremely slow
	// and error-prone.
	//
	// CA certificates are signed with the same algorithm as end-entity
	// certificates because they are relatively short-lived, because using one
	// algorithm minimizes exposure to implementation flaws, and to speed up
	// signature verification time.
	//
	// SHA-256 is used because any larger digest would be truncated to 256 bits
	// anyway since a P-256 scalar is only 256 bits long.
	const SignatureAlgorithm = x509.ECDSAWithSHA256

	if v.ValidFrom == nil {
		now := time.Now()
		v.ValidFrom = &now
	}
	notBefore, notAfter := v.Window(*v.ValidFrom)

	return &x509.Certificate{
		SerialNumber:       big.NewInt(int64(serialNumber)),
		SignatureAlgorithm: SignatureAlgorithm,
		NotBefore:          notBefore,
		NotAfter:           notAfter,
		PublicKey:          k,
		KeyUsage:           x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage: []x509.ExtKeyUsage{
			x509.ExtKeyUsageServerAuth,
			x509.ExtKeyUsageClientAuth,
		},
	}
}

// Window returns the time window for which a certificate should be valid.
func (v *Validity) Window(t time.Time) (time.Time, time.Time) {
	life := v.Lifetime
	if life == 0 {
		life = DefaultLifetime
	}
	skew := v.ClockSkewAllowance
	if skew == 0 {
		skew = DefaultClockSkewAllowance
	}
	start := t.Add(-skew)
	end := t.Add(life).Add(skew)
	return start, end
}
