package pki

import (
	"context"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"

	"golang.org/x/crypto/ed25519"

	"github.com/hashicorp/vault/sdk/helper/certutil"

	"github.com/hashicorp/vault/sdk/framework"
	"github.com/hashicorp/vault/sdk/helper/errutil"
	"github.com/hashicorp/vault/sdk/logical"
)

func pathGenerateRoot(b *backend) *framework.Path {
	return buildPathGenerateRoot(b, "root/generate/"+framework.GenericNameRegex("exported"))
}

func pathDeleteRoot(b *backend) *framework.Path {
	ret := &framework.Path{
		Pattern: "root",
		Operations: map[logical.Operation]framework.OperationHandler{
			logical.DeleteOperation: &framework.PathOperation{
				Callback: b.pathCADeleteRoot,
				// Read more about why these flags are set in backend.go
				ForwardPerformanceStandby:   true,
				ForwardPerformanceSecondary: true,
			},
		},

		HelpSynopsis:    pathDeleteRootHelpSyn,
		HelpDescription: pathDeleteRootHelpDesc,
	}

	return ret
}

func (b *backend) pathCADeleteRoot(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
	// Since we're planning on updating issuers here, grab the lock so we've
	// got a consistent view.
	b.issuersLock.Lock()
	defer b.issuersLock.Unlock()

	sc := b.makeStorageContext(ctx, req.Storage)
	if !b.useLegacyBundleCaStorage() {
		issuers, err := sc.listIssuers()
		if err != nil {
			return nil, err
		}

		keys, err := sc.listKeys()
		if err != nil {
			return nil, err
		}

		// Delete all issuers and keys. Ignore deleting the default since we're
		// explicitly deleting everything.
		for _, issuer := range issuers {
			if _, err = sc.deleteIssuer(issuer); err != nil {
				return nil, err
			}
		}
		for _, key := range keys {
			if _, err = sc.deleteKey(key); err != nil {
				return nil, err
			}
		}
	}

	// Delete legacy CA bundle and its backup, if any.
	if err := req.Storage.Delete(ctx, legacyCertBundlePath); err != nil {
		return nil, err
	}

	if err := req.Storage.Delete(ctx, legacyCertBundleBackupPath); err != nil {
		return nil, err
	}

	// Delete legacy CRL bundle.
	if err := req.Storage.Delete(ctx, legacyCRLPath); err != nil {
		return nil, err
	}

	// Return a warning about preferring to delete issuers and keys
	// explicitly versus deleting everything.
	resp := &logical.Response{}
	resp.AddWarning("DELETE /root deletes all keys and issuers; prefer the new DELETE /key/:key_ref and DELETE /issuer/:issuer_ref for finer granularity, unless removal of all keys and issuers is desired.")
	return resp, nil
}

func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	// Since we're planning on updating issuers here, grab the lock so we've
	// got a consistent view.
	b.issuersLock.Lock()
	defer b.issuersLock.Unlock()

	var err error

	if b.useLegacyBundleCaStorage() {
		return logical.ErrorResponse("Can not create root CA until migration has completed"), nil
	}

	sc := b.makeStorageContext(ctx, req.Storage)

	exported, format, role, errorResp := getGenerationParams(sc, data)
	if errorResp != nil {
		return errorResp, nil
	}

	maxPathLengthIface, ok := data.GetOk("max_path_length")
	if ok {
		maxPathLength := maxPathLengthIface.(int)
		role.MaxPathLength = &maxPathLength
	}

	issuerName, err := getIssuerName(sc, data)
	if err != nil {
		return logical.ErrorResponse(err.Error()), nil
	}
	// Handle the aliased path specifying the new issuer name as "next", but
	// only do it if its not in use.
	if strings.HasPrefix(req.Path, "root/rotate/") && len(issuerName) == 0 {
		// err is nil when the issuer name is in use.
		_, err = sc.resolveIssuerReference("next")
		if err != nil {
			issuerName = "next"
		}
	}

	keyName, err := getKeyName(sc, data)
	if err != nil {
		return logical.ErrorResponse(err.Error()), nil
	}

	input := &inputBundle{
		req:     req,
		apiData: data,
		role:    role,
	}
	parsedBundle, warnings, err := generateCert(sc, input, nil, true, b.Backend.GetRandomReader())
	if err != nil {
		switch err.(type) {
		case errutil.UserError:
			return logical.ErrorResponse(err.Error()), nil
		default:
			return nil, err
		}
	}

	cb, err := parsedBundle.ToCertBundle()
	if err != nil {
		return nil, fmt.Errorf("error converting raw cert bundle to cert bundle: %w", err)
	}

	resp := &logical.Response{
		Data: map[string]interface{}{
			"expiration":    int64(parsedBundle.Certificate.NotAfter.Unix()),
			"serial_number": cb.SerialNumber,
		},
	}

	if len(parsedBundle.Certificate.RawSubject) <= 2 {
		// Strictly a subject is a SEQUENCE of SETs of SEQUENCES.
		//
		// The outer SEQUENCE is preserved, having byte value 30 00.
		//
		// Because of the tag and the length encoding each taking up
		// at least one byte, it is impossible to have a non-empty
		// subject in two or fewer bytes. We're also not here to validate
		// our certificate's ASN.1 content, so let's just assume it holds
		// and move on.
		resp.AddWarning("This issuer certificate was generated without a Subject; this makes it likely that issuing leaf certs with this certificate will cause TLS validation libraries to reject this certificate.")
	}

	if len(parsedBundle.Certificate.OCSPServer) == 0 && len(parsedBundle.Certificate.IssuingCertificateURL) == 0 && len(parsedBundle.Certificate.CRLDistributionPoints) == 0 {
		// If the operator hasn't configured any of the URLs prior to
		// generating this issuer, we should add a warning to the response,
		// informing them they might want to do so prior to issuing leaves.
		resp.AddWarning("This mount hasn't configured any authority information access (AIA) fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls or the newly generated issuer with this information.")
	}

	switch format {
	case "pem":
		resp.Data["certificate"] = cb.Certificate
		resp.Data["issuing_ca"] = cb.Certificate
		if exported {
			resp.Data["private_key"] = cb.PrivateKey
			resp.Data["private_key_type"] = cb.PrivateKeyType
		}

	case "pem_bundle":
		resp.Data["issuing_ca"] = cb.Certificate

		if exported {
			resp.Data["private_key"] = cb.PrivateKey
			resp.Data["private_key_type"] = cb.PrivateKeyType
			resp.Data["certificate"] = fmt.Sprintf("%s\n%s", cb.PrivateKey, cb.Certificate)
		} else {
			resp.Data["certificate"] = cb.Certificate
		}

	case "der":
		resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
		resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
		if exported {
			resp.Data["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes)
			resp.Data["private_key_type"] = cb.PrivateKeyType
		}
	default:
		return nil, fmt.Errorf("unsupported format argument: %s", format)
	}

	if data.Get("private_key_format").(string) == "pkcs8" {
		err = convertRespToPKCS8(resp)
		if err != nil {
			return nil, err
		}
	}

	// Store it as the CA bundle
	myIssuer, myKey, err := sc.writeCaBundle(cb, issuerName, keyName)
	if err != nil {
		return nil, err
	}
	resp.Data["issuer_id"] = myIssuer.ID
	resp.Data["issuer_name"] = myIssuer.Name
	resp.Data["key_id"] = myKey.ID
	resp.Data["key_name"] = myKey.Name

	// The one time that it is safe (and good) to copy the
	// SignatureAlgorithm field off the certificate (for the purposes of
	// detecting PSS support) is when we've freshly generated it AND it
	// is a root (exactly this endpoint).
	//
	// For intermediates, this doesn't hold (not this endpoint) as that
	// reflects the parent key's preferences. For imports, this doesn't
	// hold as the old system might've allowed other signature types that
	// the new system (whether Vault or a managed key) doesn't.
	//
	// Previously we did this conditionally on whether or not PSS was in
	// use. This is insufficient as some cloud KMS providers (namely, GCP)
	// restrict the key to a single signature algorithm! So e.g., a RSA 3072
	// key MUST use SHA-384 as the hash algorithm. Thus we pull in the
	// RevocationSigAlg unconditionally on roots now.
	myIssuer.RevocationSigAlg = parsedBundle.Certificate.SignatureAlgorithm
	if err := sc.writeIssuer(myIssuer); err != nil {
		return nil, fmt.Errorf("unable to store PSS-updated issuer: %w", err)
	}

	// Also store it as just the certificate identified by serial number, so it
	// can be revoked
	key := "certs/" + normalizeSerial(cb.SerialNumber)
	certsCounted := b.certsCounted.Load()
	err = req.Storage.Put(ctx, &logical.StorageEntry{
		Key:   key,
		Value: parsedBundle.CertificateBytes,
	})
	if err != nil {
		return nil, fmt.Errorf("unable to store certificate locally: %w", err)
	}
	b.incrementTotalCertificatesCount(certsCounted, key)

	// Build a fresh CRL
	err = b.crlBuilder.rebuild(sc, true)
	if err != nil {
		return nil, err
	}

	if parsedBundle.Certificate.MaxPathLen == 0 {
		resp.AddWarning("Max path length of the generated certificate is zero. This certificate cannot be used to issue intermediate CA certificates.")
	}

	// Check whether we need to update our default issuer configuration.
	config, err := sc.getIssuersConfig()
	if err != nil {
		resp.AddWarning("Unable to fetch default issuers configuration to update default issuer if necessary: " + err.Error())
	} else if config.DefaultFollowsLatestIssuer {
		if err := sc.updateDefaultIssuerId(myIssuer.ID); err != nil {
			resp.AddWarning("Unable to update this new root as the default issuer: " + err.Error())
		}
	}

	resp = addWarnings(resp, warnings)

	return resp, nil
}

func (b *backend) pathIssuerSignIntermediate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	var err error

	issuerName := getIssuerRef(data)
	if len(issuerName) == 0 {
		return logical.ErrorResponse("missing issuer reference"), nil
	}

	format := getFormat(data)
	if format == "" {
		return logical.ErrorResponse(
			`The "format" path parameter must be "pem" or "der"`,
		), nil
	}

	role := &roleEntry{
		OU:                        data.Get("ou").([]string),
		Organization:              data.Get("organization").([]string),
		Country:                   data.Get("country").([]string),
		Locality:                  data.Get("locality").([]string),
		Province:                  data.Get("province").([]string),
		StreetAddress:             data.Get("street_address").([]string),
		PostalCode:                data.Get("postal_code").([]string),
		TTL:                       time.Duration(data.Get("ttl").(int)) * time.Second,
		AllowLocalhost:            true,
		AllowAnyName:              true,
		AllowIPSANs:               true,
		AllowWildcardCertificates: new(bool),
		EnforceHostnames:          false,
		KeyType:                   "any",
		SignatureBits:             data.Get("signature_bits").(int),
		UsePSS:                    data.Get("use_pss").(bool),
		AllowedOtherSANs:          []string{"*"},
		AllowedSerialNumbers:      []string{"*"},
		AllowedURISANs:            []string{"*"},
		NotAfter:                  data.Get("not_after").(string),
		NotBeforeDuration:         time.Duration(data.Get("not_before_duration").(int)) * time.Second,
		CNValidations:             []string{"disabled"},
	}
	*role.AllowWildcardCertificates = true

	if cn := data.Get("common_name").(string); len(cn) == 0 {
		role.UseCSRCommonName = true
	}

	var caErr error
	sc := b.makeStorageContext(ctx, req.Storage)
	signingBundle, caErr := sc.fetchCAInfo(issuerName, IssuanceUsage)
	if caErr != nil {
		switch caErr.(type) {
		case errutil.UserError:
			return nil, errutil.UserError{Err: fmt.Sprintf(
				"could not fetch the CA certificate (was one set?): %s", caErr)}
		default:
			return nil, errutil.InternalError{Err: fmt.Sprintf(
				"error fetching CA certificate: %s", caErr)}
		}
	}

	// Since we are signing an intermediate, we explicitly want to override
	// the leaf NotAfterBehavior to permit issuing intermediates longer than
	// the life of this issuer.
	signingBundle.LeafNotAfterBehavior = certutil.PermitNotAfterBehavior

	useCSRValues := data.Get("use_csr_values").(bool)

	maxPathLengthIface, ok := data.GetOk("max_path_length")
	if ok {
		maxPathLength := maxPathLengthIface.(int)
		role.MaxPathLength = &maxPathLength
	}

	input := &inputBundle{
		req:     req,
		apiData: data,
		role:    role,
	}
	parsedBundle, warnings, err := signCert(b, input, signingBundle, true, useCSRValues)
	if err != nil {
		switch err.(type) {
		case errutil.UserError:
			return logical.ErrorResponse(err.Error()), nil
		default:
			return nil, errutil.InternalError{Err: fmt.Sprintf(
				"error signing cert: %s", err)}
		}
	}

	if err := parsedBundle.Verify(); err != nil {
		return nil, fmt.Errorf("verification of parsed bundle failed: %w", err)
	}

	signingCB, err := signingBundle.ToCertBundle()
	if err != nil {
		return nil, fmt.Errorf("error converting raw signing bundle to cert bundle: %w", err)
	}

	cb, err := parsedBundle.ToCertBundle()
	if err != nil {
		return nil, fmt.Errorf("error converting raw cert bundle to cert bundle: %w", err)
	}

	resp := &logical.Response{
		Data: map[string]interface{}{
			"expiration":    int64(parsedBundle.Certificate.NotAfter.Unix()),
			"serial_number": cb.SerialNumber,
		},
	}

	if signingBundle.Certificate.NotAfter.Before(parsedBundle.Certificate.NotAfter) {
		resp.AddWarning("The expiration time for the signed certificate is after the CA's expiration time. If the new certificate is not treated as a root, validation paths with the certificate past the issuing CA's expiration time will fail.")
	}

	if len(parsedBundle.Certificate.RawSubject) <= 2 {
		// Strictly a subject is a SEQUENCE of SETs of SEQUENCES.
		//
		// The outer SEQUENCE is preserved, having byte value 30 00.
		//
		// Because of the tag and the length encoding each taking up
		// at least one byte, it is impossible to have a non-empty
		// subject in two or fewer bytes. We're also not here to validate
		// our certificate's ASN.1 content, so let's just assume it holds
		// and move on.
		resp.AddWarning("This issuer certificate was generated without a Subject; this makes it likely that issuing leaf certs with this certificate will cause TLS validation libraries to reject this certificate.")
	}

	if len(parsedBundle.Certificate.OCSPServer) == 0 && len(parsedBundle.Certificate.IssuingCertificateURL) == 0 && len(parsedBundle.Certificate.CRLDistributionPoints) == 0 {
		// If the operator hasn't configured any of the URLs prior to
		// generating this issuer, we should add a warning to the response,
		// informing them they might want to do so prior to issuing leaves.
		resp.AddWarning("This mount hasn't configured any authority information access (AIA) fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls or the newly generated issuer with this information.")
	}

	caChain := append([]string{cb.Certificate}, cb.CAChain...)

	switch format {
	case "pem":
		resp.Data["certificate"] = cb.Certificate
		resp.Data["issuing_ca"] = signingCB.Certificate
		resp.Data["ca_chain"] = caChain

	case "pem_bundle":
		resp.Data["certificate"] = cb.ToPEMBundle()
		resp.Data["issuing_ca"] = signingCB.Certificate
		resp.Data["ca_chain"] = caChain

	case "der":
		resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
		resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(signingBundle.CertificateBytes)

		var derCaChain []string
		derCaChain = append(derCaChain, base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes))
		for _, caCert := range parsedBundle.CAChain {
			derCaChain = append(derCaChain, base64.StdEncoding.EncodeToString(caCert.Bytes))
		}
		resp.Data["ca_chain"] = derCaChain

	default:
		return nil, fmt.Errorf("unsupported format argument: %s", format)
	}

	key := "certs/" + normalizeSerial(cb.SerialNumber)
	certsCounted := b.certsCounted.Load()
	err = req.Storage.Put(ctx, &logical.StorageEntry{
		Key:   key,
		Value: parsedBundle.CertificateBytes,
	})
	if err != nil {
		return nil, fmt.Errorf("unable to store certificate locally: %w", err)
	}
	b.incrementTotalCertificatesCount(certsCounted, key)

	if parsedBundle.Certificate.MaxPathLen == 0 {
		resp.AddWarning("Max path length of the signed certificate is zero. This certificate cannot be used to issue intermediate CA certificates.")
	}

	resp = addWarnings(resp, warnings)

	return resp, nil
}

func (b *backend) pathIssuerSignSelfIssued(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	var err error

	issuerName := getIssuerRef(data)
	if len(issuerName) == 0 {
		return logical.ErrorResponse("missing issuer reference"), nil
	}

	certPem := data.Get("certificate").(string)
	block, _ := pem.Decode([]byte(certPem))
	if block == nil || len(block.Bytes) == 0 {
		return logical.ErrorResponse("certificate could not be PEM-decoded"), nil
	}
	certs, err := x509.ParseCertificates(block.Bytes)
	if err != nil {
		return logical.ErrorResponse(fmt.Sprintf("error parsing certificate: %s", err)), nil
	}
	if len(certs) != 1 {
		return logical.ErrorResponse(fmt.Sprintf("%d certificates found in PEM file, expected 1", len(certs))), nil
	}

	cert := certs[0]
	if !cert.IsCA {
		return logical.ErrorResponse("given certificate is not a CA certificate"), nil
	}
	if !reflect.DeepEqual(cert.Issuer, cert.Subject) {
		return logical.ErrorResponse("given certificate is not self-issued"), nil
	}

	var caErr error
	sc := b.makeStorageContext(ctx, req.Storage)
	signingBundle, caErr := sc.fetchCAInfo(issuerName, IssuanceUsage)
	if caErr != nil {
		switch caErr.(type) {
		case errutil.UserError:
			return nil, errutil.UserError{Err: fmt.Sprintf(
				"could not fetch the CA certificate (was one set?): %s", caErr)}
		default:
			return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching CA certificate: %s", caErr)}
		}
	}

	signingCB, err := signingBundle.ToCertBundle()
	if err != nil {
		return nil, fmt.Errorf("error converting raw signing bundle to cert bundle: %w", err)
	}

	urls := &certutil.URLEntries{}
	if signingBundle.URLs != nil {
		urls = signingBundle.URLs
	}
	cert.IssuingCertificateURL = urls.IssuingCertificates
	cert.CRLDistributionPoints = urls.CRLDistributionPoints
	cert.OCSPServer = urls.OCSPServers

	// If the requested signature algorithm isn't the same as the signing certificate, and
	// the user has requested a cross-algorithm signature, reset the template's signing algorithm
	// to that of the signing key
	signingPubType, signingAlgorithm, err := publicKeyType(signingBundle.Certificate.PublicKey)
	if err != nil {
		return nil, fmt.Errorf("error determining signing certificate algorithm type: %e", err)
	}
	certPubType, _, err := publicKeyType(cert.PublicKey)
	if err != nil {
		return nil, fmt.Errorf("error determining template algorithm type: %e", err)
	}

	if signingPubType != certPubType {
		b, ok := data.GetOk("require_matching_certificate_algorithms")
		if !ok || !b.(bool) {
			cert.SignatureAlgorithm = signingAlgorithm
		} else {
			return nil, fmt.Errorf("signing certificate's public key algorithm (%s) does not match submitted certificate's (%s), and require_matching_certificate_algorithms is true",
				signingPubType.String(), certPubType.String())
		}
	}

	newCert, err := x509.CreateCertificate(rand.Reader, cert, signingBundle.Certificate, cert.PublicKey, signingBundle.PrivateKey)
	if err != nil {
		return nil, fmt.Errorf("error signing self-issued certificate: %w", err)
	}
	if len(newCert) == 0 {
		return nil, fmt.Errorf("nil cert was created when signing self-issued certificate")
	}
	pemCert := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: newCert,
	})

	return &logical.Response{
		Data: map[string]interface{}{
			"certificate": strings.TrimSpace(string(pemCert)),
			"issuing_ca":  signingCB.Certificate,
		},
	}, nil
}

// Adapted from similar code in https://github.com/golang/go/blob/4a4221e8187189adcc6463d2d96fe2e8da290132/src/crypto/x509/x509.go#L1342,
// may need to be updated in the future.
func publicKeyType(pub crypto.PublicKey) (pubType x509.PublicKeyAlgorithm, sigAlgo x509.SignatureAlgorithm, err error) {
	switch pub := pub.(type) {
	case *rsa.PublicKey:
		pubType = x509.RSA
		sigAlgo = x509.SHA256WithRSA
	case *ecdsa.PublicKey:
		pubType = x509.ECDSA
		switch pub.Curve {
		case elliptic.P224(), elliptic.P256():
			sigAlgo = x509.ECDSAWithSHA256
		case elliptic.P384():
			sigAlgo = x509.ECDSAWithSHA384
		case elliptic.P521():
			sigAlgo = x509.ECDSAWithSHA512
		default:
			err = errors.New("x509: unknown elliptic curve")
		}
	case ed25519.PublicKey:
		pubType = x509.Ed25519
		sigAlgo = x509.PureEd25519
	default:
		err = errors.New("x509: only RSA, ECDSA and Ed25519 keys supported")
	}
	return
}

const pathGenerateRootHelpSyn = `
Generate a new CA certificate and private key used for signing.
`

const pathGenerateRootHelpDesc = `
See the API documentation for more information.
`

const pathDeleteRootHelpSyn = `
Deletes the root CA key to allow a new one to be generated.
`

const pathDeleteRootHelpDesc = `
See the API documentation for more information.
`
