// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package pki

import (
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"net"
	"net/http"
	"sort"
	"strings"
	"time"

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

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

	"github.com/hashicorp/vault/sdk/framework"
	"github.com/hashicorp/vault/sdk/logical"
	"golang.org/x/net/idna"
)

var maxAcmeCertTTL = 90 * (24 * time.Hour)

func pathAcmeListOrders(b *backend) []*framework.Path {
	return buildAcmeFrameworkPaths(b, patternAcmeListOrders, "/orders")
}

func pathAcmeGetOrder(b *backend) []*framework.Path {
	return buildAcmeFrameworkPaths(b, patternAcmeGetOrder, "/order/"+uuidNameRegex("order_id"))
}

func pathAcmeNewOrder(b *backend) []*framework.Path {
	return buildAcmeFrameworkPaths(b, patternAcmeNewOrder, "/new-order")
}

func pathAcmeFinalizeOrder(b *backend) []*framework.Path {
	return buildAcmeFrameworkPaths(b, patternAcmeFinalizeOrder, "/order/"+uuidNameRegex("order_id")+"/finalize")
}

func pathAcmeFetchOrderCert(b *backend) []*framework.Path {
	return buildAcmeFrameworkPaths(b, patternAcmeFetchOrderCert, "/order/"+uuidNameRegex("order_id")+"/cert")
}

func patternAcmeNewOrder(b *backend, pattern string) *framework.Path {
	fields := map[string]*framework.FieldSchema{}
	addFieldsForACMEPath(fields, pattern)
	addFieldsForACMERequest(fields)

	return &framework.Path{
		Pattern: pattern,
		Fields:  fields,
		Operations: map[logical.Operation]framework.OperationHandler{
			logical.UpdateOperation: &framework.PathOperation{
				Callback:                    b.acmeAccountRequiredWrapper(b.acmeNewOrderHandler),
				ForwardPerformanceSecondary: false,
				ForwardPerformanceStandby:   true,
			},
		},

		HelpSynopsis:    pathAcmeHelpSync,
		HelpDescription: pathAcmeHelpDesc,
	}
}

func patternAcmeListOrders(b *backend, pattern string) *framework.Path {
	fields := map[string]*framework.FieldSchema{}
	addFieldsForACMEPath(fields, pattern)
	addFieldsForACMERequest(fields)

	return &framework.Path{
		Pattern: pattern,
		Fields:  fields,
		Operations: map[logical.Operation]framework.OperationHandler{
			logical.UpdateOperation: &framework.PathOperation{
				Callback:                    b.acmeAccountRequiredWrapper(b.acmeListOrdersHandler),
				ForwardPerformanceSecondary: false,
				ForwardPerformanceStandby:   true,
			},
		},

		HelpSynopsis:    pathAcmeHelpSync,
		HelpDescription: pathAcmeHelpDesc,
	}
}

func patternAcmeGetOrder(b *backend, pattern string) *framework.Path {
	fields := map[string]*framework.FieldSchema{}
	addFieldsForACMEPath(fields, pattern)
	addFieldsForACMERequest(fields)
	addFieldsForACMEOrder(fields)

	return &framework.Path{
		Pattern: pattern,
		Fields:  fields,
		Operations: map[logical.Operation]framework.OperationHandler{
			logical.UpdateOperation: &framework.PathOperation{
				Callback:                    b.acmeAccountRequiredWrapper(b.acmeGetOrderHandler),
				ForwardPerformanceSecondary: false,
				ForwardPerformanceStandby:   true,
			},
		},

		HelpSynopsis:    pathAcmeHelpSync,
		HelpDescription: pathAcmeHelpDesc,
	}
}

func patternAcmeFinalizeOrder(b *backend, pattern string) *framework.Path {
	fields := map[string]*framework.FieldSchema{}
	addFieldsForACMEPath(fields, pattern)
	addFieldsForACMERequest(fields)
	addFieldsForACMEOrder(fields)

	return &framework.Path{
		Pattern: pattern,
		Fields:  fields,
		Operations: map[logical.Operation]framework.OperationHandler{
			logical.UpdateOperation: &framework.PathOperation{
				Callback:                    b.acmeAccountRequiredWrapper(b.acmeFinalizeOrderHandler),
				ForwardPerformanceSecondary: false,
				ForwardPerformanceStandby:   true,
			},
		},

		HelpSynopsis:    pathAcmeHelpSync,
		HelpDescription: pathAcmeHelpDesc,
	}
}

func patternAcmeFetchOrderCert(b *backend, pattern string) *framework.Path {
	fields := map[string]*framework.FieldSchema{}
	addFieldsForACMEPath(fields, pattern)
	addFieldsForACMERequest(fields)
	addFieldsForACMEOrder(fields)

	return &framework.Path{
		Pattern: pattern,
		Fields:  fields,
		Operations: map[logical.Operation]framework.OperationHandler{
			logical.UpdateOperation: &framework.PathOperation{
				Callback:                    b.acmeAccountRequiredWrapper(b.acmeFetchCertOrderHandler),
				ForwardPerformanceSecondary: false,
				ForwardPerformanceStandby:   true,
			},
		},

		HelpSynopsis:    pathAcmeHelpSync,
		HelpDescription: pathAcmeHelpDesc,
	}
}

func addFieldsForACMEOrder(fields map[string]*framework.FieldSchema) {
	fields["order_id"] = &framework.FieldSchema{
		Type:        framework.TypeString,
		Description: `The ACME order identifier to fetch`,
		Required:    true,
	}
}

func (b *backend) acmeFetchCertOrderHandler(ac *acmeContext, _ *logical.Request, fields *framework.FieldData, uc *jwsCtx, data map[string]interface{}, _ *acmeAccount) (*logical.Response, error) {
	orderId := fields.Get("order_id").(string)

	order, err := b.acmeState.LoadOrder(ac, uc, orderId)
	if err != nil {
		return nil, err
	}

	if order.Status != ACMEOrderValid {
		return nil, fmt.Errorf("%w: order is status %s, needs to be in valid state", ErrOrderNotReady, order.Status)
	}

	if len(order.IssuerId) == 0 || len(order.CertificateSerialNumber) == 0 {
		return nil, fmt.Errorf("order is missing required fields to load certificate")
	}

	certEntry, err := fetchCertBySerial(ac.sc, "certs/", order.CertificateSerialNumber)
	if err != nil {
		return nil, fmt.Errorf("failed reading certificate %s from storage: %w", order.CertificateSerialNumber, err)
	}
	if certEntry == nil || len(certEntry.Value) == 0 {
		return nil, fmt.Errorf("missing certificate %s from storage", order.CertificateSerialNumber)
	}

	cert, err := x509.ParseCertificate(certEntry.Value)
	if err != nil {
		return nil, fmt.Errorf("failed parsing certificate %s: %w", order.CertificateSerialNumber, err)
	}

	issuer, err := ac.sc.fetchIssuerById(order.IssuerId)
	if err != nil {
		return nil, fmt.Errorf("failed loading certificate issuer %s from storage: %w", order.IssuerId, err)
	}

	allPems, err := func() ([]byte, error) {
		leafPEM := pem.EncodeToMemory(&pem.Block{
			Type:  "CERTIFICATE",
			Bytes: cert.Raw,
		})

		chains := []byte(issuer.Certificate)
		for _, chainVal := range issuer.CAChain {
			if chainVal == issuer.Certificate {
				continue
			}
			chains = append(chains, []byte(chainVal)...)
		}

		return append(leafPEM, chains...), nil
	}()
	if err != nil {
		return nil, fmt.Errorf("failed encoding certificate ca chain: %w", err)
	}

	return &logical.Response{
		Data: map[string]interface{}{
			logical.HTTPContentType: "application/pem-certificate-chain",
			logical.HTTPStatusCode:  http.StatusOK,
			logical.HTTPRawBody:     allPems,
		},
	}, nil
}

func (b *backend) acmeFinalizeOrderHandler(ac *acmeContext, _ *logical.Request, fields *framework.FieldData, uc *jwsCtx, data map[string]interface{}, account *acmeAccount) (*logical.Response, error) {
	orderId := fields.Get("order_id").(string)

	csr, err := parseCsrFromFinalize(data)
	if err != nil {
		return nil, err
	}

	order, err := b.acmeState.LoadOrder(ac, uc, orderId)
	if err != nil {
		return nil, err
	}

	order.Status, err = computeOrderStatus(ac, uc, order)
	if err != nil {
		return nil, err
	}

	if order.Status != ACMEOrderReady {
		return nil, fmt.Errorf("%w: order is status %s, needs to be in ready state", ErrOrderNotReady, order.Status)
	}

	now := time.Now()
	if !order.Expires.IsZero() && now.After(order.Expires) {
		return nil, fmt.Errorf("%w: order %s is expired", ErrMalformed, orderId)
	}

	if err = validateCsrMatchesOrder(csr, order); err != nil {
		return nil, err
	}

	if err = validateCsrNotUsingAccountKey(csr, uc); err != nil {
		return nil, err
	}

	signedCertBundle, issuerId, err := issueCertFromCsr(ac, csr)
	if err != nil {
		return nil, err
	}

	hyphenSerialNumber := normalizeSerialFromBigInt(signedCertBundle.Certificate.SerialNumber)
	err = storeCertificate(ac.sc, signedCertBundle)
	if err != nil {
		return nil, err
	}

	if err := b.acmeState.TrackIssuedCert(ac, order.AccountId, hyphenSerialNumber, order.OrderId); err != nil {
		b.Logger().Warn("orphaned generated ACME certificate due to error saving account->cert->order reference", "serial_number", hyphenSerialNumber, "error", err)
		return nil, err
	}

	order.Status = ACMEOrderValid
	order.CertificateSerialNumber = hyphenSerialNumber
	order.CertificateExpiry = signedCertBundle.Certificate.NotAfter
	order.IssuerId = issuerId

	err = b.acmeState.SaveOrder(ac, order)
	if err != nil {
		b.Logger().Warn("orphaned generated ACME certificate due to error saving order", "serial_number", hyphenSerialNumber, "error", err)
		return nil, fmt.Errorf("failed saving updated order: %w", err)
	}

	if err := b.doTrackBilling(ac.sc.Context, order.Identifiers); err != nil {
		b.Logger().Error("failed to track billing for order", "order", orderId, "error", err)
		err = nil
	}

	return formatOrderResponse(ac, order), nil
}

func computeOrderStatus(ac *acmeContext, uc *jwsCtx, order *acmeOrder) (ACMEOrderStatusType, error) {
	// If we reached a final stage, no use computing anything else
	if order.Status == ACMEOrderInvalid || order.Status == ACMEOrderValid {
		return order.Status, nil
	}

	// We aren't in a final state yet, check for expiry
	if time.Now().After(order.Expires) {
		return ACMEOrderInvalid, nil
	}

	// Intermediary steps passed authorizations should short circuit us as well
	if order.Status == ACMEOrderReady || order.Status == ACMEOrderProcessing {
		return order.Status, nil
	}

	// If we have no authorizations attached to the order, nothing to compute either
	if len(order.AuthorizationIds) == 0 {
		return ACMEOrderPending, nil
	}

	anyFailed := false
	allPassed := true
	for _, authId := range order.AuthorizationIds {
		authorization, err := ac.getAcmeState().LoadAuthorization(ac, uc, authId)
		if err != nil {
			return order.Status, fmt.Errorf("failed loading authorization: %s: %w", authId, err)
		}

		if authorization.Status == ACMEAuthorizationPending {
			allPassed = false
			continue
		}

		if authorization.Status != ACMEAuthorizationValid {
			// Per RFC 8555 - 7.1.6. Status Changes
			// The order also moves to the "invalid" state if it expires or
			// one of its authorizations enters a final state other than
			// "valid" ("expired", "revoked", or "deactivated").
			allPassed = false
			anyFailed = true
			break
		}
	}

	if anyFailed {
		return ACMEOrderInvalid, nil
	}

	if allPassed {
		return ACMEOrderReady, nil
	}

	// The order has not expired, no authorizations have yet to be marked as failed
	// nor have we passed them all.
	return ACMEOrderPending, nil
}

func validateCsrNotUsingAccountKey(csr *x509.CertificateRequest, uc *jwsCtx) error {
	csrKey := csr.PublicKey
	userKey := uc.Key.Public().Key

	sameKey, err := certutil.ComparePublicKeysAndType(csrKey, userKey)
	if err != nil {
		return err
	}

	if sameKey {
		return fmt.Errorf("%w: certificate public key must not match account key", ErrBadCSR)
	}

	return nil
}

func validateCsrMatchesOrder(csr *x509.CertificateRequest, order *acmeOrder) error {
	csrDNSIdentifiers, csrIPIdentifiers := getIdentifiersFromCSR(csr)
	orderDNSIdentifiers := strutil.RemoveDuplicates(order.getIdentifierDNSValues(), true)
	orderIPIdentifiers := removeDuplicatesAndSortIps(order.getIdentifierIPValues())

	if len(orderDNSIdentifiers) == 0 && len(orderIPIdentifiers) == 0 {
		return fmt.Errorf("%w: order did not include any identifiers", ErrServerInternal)
	}

	if len(orderDNSIdentifiers) != len(csrDNSIdentifiers) {
		return fmt.Errorf("%w: Order (%v) and CSR (%v) mismatch on number of DNS identifiers", ErrBadCSR, len(orderDNSIdentifiers), len(csrDNSIdentifiers))
	}

	if len(orderIPIdentifiers) != len(csrIPIdentifiers) {
		return fmt.Errorf("%w: Order (%v) and CSR (%v) mismatch on number of IP identifiers", ErrBadCSR, len(orderIPIdentifiers), len(csrIPIdentifiers))
	}

	for i, identifier := range orderDNSIdentifiers {
		if identifier != csrDNSIdentifiers[i] {
			return fmt.Errorf("%w: CSR is missing order DNS identifier %s", ErrBadCSR, identifier)
		}
	}

	for i, identifier := range orderIPIdentifiers {
		if !identifier.Equal(csrIPIdentifiers[i]) {
			return fmt.Errorf("%w: CSR is missing order IP identifier %s", ErrBadCSR, identifier.String())
		}
	}

	// Since we do not support NotBefore/NotAfter dates at this time no need to validate CSR/Order match.

	return nil
}

func (b *backend) validateIdentifiersAgainstRole(role *roleEntry, identifiers []*ACMEIdentifier) error {
	for _, identifier := range identifiers {
		switch identifier.Type {
		case ACMEDNSIdentifier:
			data := &inputBundle{
				role:    role,
				req:     &logical.Request{},
				apiData: &framework.FieldData{},
			}

			if validateNames(b, data, []string{identifier.OriginalValue}) != "" {
				return fmt.Errorf("%w: role (%s) will not issue certificate for name %v",
					ErrRejectedIdentifier, role.Name, identifier.OriginalValue)
			}
		case ACMEIPIdentifier:
			if !role.AllowIPSANs {
				return fmt.Errorf("%w: role (%s) does not allow IP sans, so cannot issue certificate for %v",
					ErrRejectedIdentifier, role.Name, identifier.OriginalValue)
			}
		default:
			return fmt.Errorf("unknown type of identifier: %v for %v", identifier.Type, identifier.OriginalValue)
		}
	}

	return nil
}

func getIdentifiersFromCSR(csr *x509.CertificateRequest) ([]string, []net.IP) {
	dnsIdentifiers := append([]string(nil), csr.DNSNames...)
	ipIdentifiers := append([]net.IP(nil), csr.IPAddresses...)

	if csr.Subject.CommonName != "" {
		ip := net.ParseIP(csr.Subject.CommonName)
		if ip != nil {
			ipIdentifiers = append(ipIdentifiers, ip)
		} else {
			dnsIdentifiers = append(dnsIdentifiers, csr.Subject.CommonName)
		}
	}

	return strutil.RemoveDuplicates(dnsIdentifiers, true), removeDuplicatesAndSortIps(ipIdentifiers)
}

func removeDuplicatesAndSortIps(ipIdentifiers []net.IP) []net.IP {
	var uniqueIpIdentifiers []net.IP
	for _, ip := range ipIdentifiers {
		found := false
		for _, curIp := range uniqueIpIdentifiers {
			if curIp.Equal(ip) {
				found = true
			}
		}

		if !found {
			uniqueIpIdentifiers = append(uniqueIpIdentifiers, ip)
		}
	}

	sort.Slice(uniqueIpIdentifiers, func(i, j int) bool {
		return uniqueIpIdentifiers[i].String() < uniqueIpIdentifiers[j].String()
	})
	return uniqueIpIdentifiers
}

func storeCertificate(sc *storageContext, signedCertBundle *certutil.ParsedCertBundle) error {
	hyphenSerialNumber := normalizeSerialFromBigInt(signedCertBundle.Certificate.SerialNumber)
	key := "certs/" + hyphenSerialNumber
	certsCounted := sc.Backend.certsCounted.Load()
	err := sc.Storage.Put(sc.Context, &logical.StorageEntry{
		Key:   key,
		Value: signedCertBundle.CertificateBytes,
	})
	if err != nil {
		return fmt.Errorf("unable to store certificate locally: %w", err)
	}
	sc.Backend.ifCountEnabledIncrementTotalCertificatesCount(certsCounted, key)
	return nil
}

func maybeAugmentReqDataWithSuitableCN(ac *acmeContext, csr *x509.CertificateRequest, data *framework.FieldData) {
	// Role doesn't require a CN, so we don't care.
	if !ac.role.RequireCN {
		return
	}

	// CSR contains a CN, so use that one.
	if csr.Subject.CommonName != "" {
		return
	}

	// Choose a CN in the order wildcard -> DNS -> IP -> fail.
	for _, name := range csr.DNSNames {
		if strings.Contains(name, "*") {
			data.Raw["common_name"] = name
			return
		}
	}
	if len(csr.DNSNames) > 0 {
		data.Raw["common_name"] = csr.DNSNames[0]
		return
	}
	if len(csr.IPAddresses) > 0 {
		data.Raw["common_name"] = csr.IPAddresses[0].String()
		return
	}
}

func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, issuerID, error) {
	pemBlock := &pem.Block{
		Type:    "CERTIFICATE REQUEST",
		Headers: nil,
		Bytes:   csr.Raw,
	}
	pemCsr := string(pem.EncodeToMemory(pemBlock))

	data := &framework.FieldData{
		Raw: map[string]interface{}{
			"csr": pemCsr,
		},
		Schema: getCsrSignVerbatimSchemaFields(),
	}

	// XXX: Usability hack: by default, minimalist roles have require_cn=true,
	// but some ACME clients do not provision one in the certificate as modern
	// (TLS) clients are mostly verifying against server's DNS SANs.
	maybeAugmentReqDataWithSuitableCN(ac, csr, data)

	signingBundle, issuerId, err := ac.sc.fetchCAInfoWithIssuer(ac.issuer.ID.String(), IssuanceUsage)
	if err != nil {
		return nil, "", fmt.Errorf("failed loading CA %s: %w", ac.issuer.ID.String(), err)
	}

	// ACME issued cert will override the TTL values to truncate to the issuer's
	// expiration if we go beyond, no matter the setting
	if signingBundle.LeafNotAfterBehavior == certutil.ErrNotAfterBehavior {
		signingBundle.LeafNotAfterBehavior = certutil.TruncateNotAfterBehavior
	}

	input := &inputBundle{
		req:     &logical.Request{},
		apiData: data,
		role:    ac.role,
	}

	normalNotAfter, _, err := getCertificateNotAfter(ac.sc.Backend, input, signingBundle)
	if err != nil {
		return nil, "", fmt.Errorf("failed computing certificate TTL from role/mount: %v: %w", err, ErrMalformed)
	}

	// Force a maximum 90 day TTL or lower for ACME
	if time.Now().Add(maxAcmeCertTTL).Before(normalNotAfter) {
		input.apiData.Raw["ttl"] = maxAcmeCertTTL
	}

	if csr.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm || csr.PublicKey == nil {
		return nil, "", fmt.Errorf("%w: Refusing to sign CSR with empty PublicKey", ErrBadCSR)
	}

	// UseCSRValues as defined in certutil/helpers.go accepts the following
	// fields off of the CSR:
	//
	// 1. Subject fields,
	// 2. SANs,
	// 3. Extensions (except for a BasicConstraint extension)
	//
	// Because we have stricter validation of subject parameters, and no way
	// to validate or allow extensions, we do not wish to use the CSR's
	// parameters for these values. If a CSR sets, e.g., an organizational
	// unit, we have no way of validating this (via ACME here, without perhaps
	// an external policy engine), and thus should not be setting it on our
	// final issued certificate.
	parsedBundle, _, err := signCert(ac.sc.Backend, input, signingBundle, false /* is_ca=false */, false /* use_csr_values */)
	if err != nil {
		return nil, "", fmt.Errorf("%w: refusing to sign CSR: %s", ErrBadCSR, err.Error())
	}

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

	// We only allow ServerAuth key usage from ACME issued certs
	// when configuration does not allow usage of ExtKeyusage field.
	config, err := ac.sc.Backend.acmeState.getConfigWithUpdate(ac.sc)
	if err != nil {
		return nil, "", fmt.Errorf("failed to fetch ACME configuration: %w", err)
	}

	if !config.AllowRoleExtKeyUsage {
		for _, usage := range parsedBundle.Certificate.ExtKeyUsage {
			if usage != x509.ExtKeyUsageServerAuth {
				return nil, "", fmt.Errorf("%w: ACME certs only allow ServerAuth key usage", ErrBadCSR)
			}
		}
	}

	return parsedBundle, issuerId, err
}

func parseCsrFromFinalize(data map[string]interface{}) (*x509.CertificateRequest, error) {
	csrInterface, present := data["csr"]
	if !present {
		return nil, fmt.Errorf("%w: missing csr in payload", ErrMalformed)
	}

	base64Csr, ok := csrInterface.(string)
	if !ok {
		return nil, fmt.Errorf("%w: csr in payload not the expected type: %T", ErrMalformed, csrInterface)
	}

	derCsr, err := base64.RawURLEncoding.DecodeString(base64Csr)
	if err != nil {
		return nil, fmt.Errorf("%w: failed base64 decoding csr: %s", ErrMalformed, err.Error())
	}

	csr, err := x509.ParseCertificateRequest(derCsr)
	if err != nil {
		return nil, fmt.Errorf("%w: failed to parse csr: %s", ErrMalformed, err.Error())
	}

	if csr.PublicKey == nil || csr.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm {
		return nil, fmt.Errorf("%w: failed to parse csr no public key info or unknown key algorithm used", ErrBadCSR)
	}

	for _, ext := range csr.Extensions {
		if ext.Id.Equal(certutil.ExtensionBasicConstraintsOID) {
			isCa, _, err := certutil.ParseBasicConstraintExtension(ext)
			if err != nil {
				return nil, fmt.Errorf("%w: refusing to accept CSR with Basic Constraints extension: %v", ErrBadCSR, err.Error())
			}

			if isCa {
				return nil, fmt.Errorf("%w: refusing to accept CSR with Basic Constraints extension with CA set to true", ErrBadCSR)
			}
		}
	}

	return csr, nil
}

func (b *backend) acmeGetOrderHandler(ac *acmeContext, _ *logical.Request, fields *framework.FieldData, uc *jwsCtx, _ map[string]interface{}, _ *acmeAccount) (*logical.Response, error) {
	orderId := fields.Get("order_id").(string)

	order, err := b.acmeState.LoadOrder(ac, uc, orderId)
	if err != nil {
		return nil, err
	}

	order.Status, err = computeOrderStatus(ac, uc, order)
	if err != nil {
		return nil, err
	}

	// Per RFC 8555 -> 7.1.3.  Order Objects
	// For final orders (in the "valid" or "invalid" state), the authorizations that were completed.
	//
	// Otherwise, for "pending" orders we will return our list as it was originally saved.
	requiresFiltering := order.Status == ACMEOrderValid || order.Status == ACMEOrderInvalid
	if requiresFiltering {
		filteredAuthorizationIds := []string{}

		for _, authId := range order.AuthorizationIds {
			authorization, err := b.acmeState.LoadAuthorization(ac, uc, authId)
			if err != nil {
				return nil, err
			}

			if (order.Status == ACMEOrderInvalid || order.Status == ACMEOrderValid) &&
				authorization.Status == ACMEAuthorizationValid {
				filteredAuthorizationIds = append(filteredAuthorizationIds, authId)
			}
		}

		order.AuthorizationIds = filteredAuthorizationIds
	}

	return formatOrderResponse(ac, order), nil
}

func (b *backend) acmeListOrdersHandler(ac *acmeContext, _ *logical.Request, _ *framework.FieldData, uc *jwsCtx, _ map[string]interface{}, acct *acmeAccount) (*logical.Response, error) {
	orderIds, err := b.acmeState.ListOrderIds(ac.sc, acct.KeyId)
	if err != nil {
		return nil, err
	}

	orderUrls := []string{}
	for _, orderId := range orderIds {
		order, err := b.acmeState.LoadOrder(ac, uc, orderId)
		if err != nil {
			return nil, err
		}

		if order.Status == ACMEOrderInvalid {
			// Per RFC8555 -> 7.1.2.1 - Orders List
			// The server SHOULD include pending orders and SHOULD NOT
			// include orders that are invalid in the array of URLs.
			continue
		}

		orderUrls = append(orderUrls, buildOrderUrl(ac, orderId))
	}

	resp := &logical.Response{
		Data: map[string]interface{}{
			"orders": orderUrls,
		},
	}

	return resp, nil
}

func (b *backend) acmeNewOrderHandler(ac *acmeContext, _ *logical.Request, _ *framework.FieldData, _ *jwsCtx, data map[string]interface{}, account *acmeAccount) (*logical.Response, error) {
	identifiers, err := parseOrderIdentifiers(data)
	if err != nil {
		return nil, err
	}

	notBefore, err := parseOptRFC3339Field(data, "notBefore")
	if err != nil {
		return nil, err
	}

	notAfter, err := parseOptRFC3339Field(data, "notAfter")
	if err != nil {
		return nil, err
	}

	if !notBefore.IsZero() || !notAfter.IsZero() {
		return nil, fmt.Errorf("%w: NotBefore and NotAfter are not supported", ErrMalformed)
	}

	err = validateAcmeProvidedOrderDates(notBefore, notAfter)
	if err != nil {
		return nil, err
	}

	err = b.validateIdentifiersAgainstRole(ac.role, identifiers)
	if err != nil {
		return nil, err
	}

	// Per RFC 8555 -> 7.1.3. Order Objects
	// For pending orders, the authorizations that the client needs to complete before the
	// requested certificate can be issued (see Section 7.5), including
	// unexpired authorizations that the client has completed in the past
	// for identifiers specified in the order.
	//
	// Since we are generating all authorizations here, there is no need to filter them out
	// IF/WHEN we support pre-authz workflows and associate existing authorizations to this
	// order they will need filtering.
	var authorizations []*ACMEAuthorization
	var authorizationIds []string
	for _, identifier := range identifiers {
		authz, err := generateAuthorization(account, identifier)
		if err != nil {
			return nil, fmt.Errorf("error generating authorizations: %w", err)
		}
		authorizations = append(authorizations, authz)

		err = b.acmeState.SaveAuthorization(ac, authz)
		if err != nil {
			return nil, fmt.Errorf("failed storing authorization: %w", err)
		}

		authorizationIds = append(authorizationIds, authz.Id)
	}

	order := &acmeOrder{
		OrderId:          genUuid(),
		AccountId:        account.KeyId,
		Status:           ACMEOrderPending,
		Expires:          time.Now().Add(24 * time.Hour), // TODO: Readjust this based on authz and/or config
		Identifiers:      identifiers,
		AuthorizationIds: authorizationIds,
	}

	err = b.acmeState.SaveOrder(ac, order)
	if err != nil {
		return nil, fmt.Errorf("failed storing order: %w", err)
	}

	resp := formatOrderResponse(ac, order)

	// Per RFC 8555 Section 7.4. Applying for Certificate Issuance:
	//
	// > If the server is willing to issue the requested certificate, it
	// > responds with a 201 (Created) response.
	resp.Data[logical.HTTPStatusCode] = http.StatusCreated
	return resp, nil
}

func validateAcmeProvidedOrderDates(notBefore time.Time, notAfter time.Time) error {
	if !notBefore.IsZero() && !notAfter.IsZero() {
		if notBefore.Equal(notAfter) {
			return fmt.Errorf("%w: provided notBefore and notAfter dates can not be equal", ErrMalformed)
		}

		if notBefore.After(notAfter) {
			return fmt.Errorf("%w: provided notBefore can not be greater than notAfter", ErrMalformed)
		}
	}

	if !notAfter.IsZero() {
		if time.Now().After(notAfter) {
			return fmt.Errorf("%w: provided notAfter can not be in the past", ErrMalformed)
		}
	}

	return nil
}

func formatOrderResponse(acmeCtx *acmeContext, order *acmeOrder) *logical.Response {
	baseOrderUrl := buildOrderUrl(acmeCtx, order.OrderId)

	var authorizationUrls []string
	for _, authId := range order.AuthorizationIds {
		authorizationUrls = append(authorizationUrls, buildAuthorizationUrl(acmeCtx, authId))
	}

	var identifiers []map[string]interface{}
	for _, identifier := range order.Identifiers {
		identifiers = append(identifiers, identifier.NetworkMarshal( /* use original value */ true))
	}

	resp := &logical.Response{
		Data: map[string]interface{}{
			"status":         order.Status,
			"expires":        order.Expires.Format(time.RFC3339),
			"identifiers":    identifiers,
			"authorizations": authorizationUrls,
			"finalize":       baseOrderUrl + "/finalize",
		},
		Headers: map[string][]string{
			"Location": {baseOrderUrl},
		},
	}

	// Only reply with the certificate URL if we are in a valid order state.
	if order.Status == ACMEOrderValid {
		resp.Data["certificate"] = baseOrderUrl + "/cert"
	}

	return resp
}

func buildAuthorizationUrl(acmeCtx *acmeContext, authId string) string {
	return acmeCtx.baseUrl.JoinPath("authorization", authId).String()
}

func buildOrderUrl(acmeCtx *acmeContext, orderId string) string {
	return acmeCtx.baseUrl.JoinPath("order", orderId).String()
}

func generateAuthorization(acct *acmeAccount, identifier *ACMEIdentifier) (*ACMEAuthorization, error) {
	authId := genUuid()

	// Certain challenges have certain restrictions: DNS challenges cannot
	// be used to validate IP addresses, and only DNS challenges can be used
	// to validate wildcards.
	allowedChallenges := []ACMEChallengeType{ACMEHTTPChallenge, ACMEDNSChallenge, ACMEALPNChallenge}
	if identifier.Type == ACMEIPIdentifier {
		allowedChallenges = []ACMEChallengeType{ACMEHTTPChallenge}
	} else if identifier.IsWildcard {
		allowedChallenges = []ACMEChallengeType{ACMEDNSChallenge}
	}

	var challenges []*ACMEChallenge
	for _, challengeType := range allowedChallenges {
		token, err := getACMEToken()
		if err != nil {
			return nil, err
		}

		challenge := &ACMEChallenge{
			Type:   challengeType,
			Status: ACMEChallengePending,
			ChallengeFields: map[string]interface{}{
				"token": token,
			},
		}

		challenges = append(challenges, challenge)
	}

	return &ACMEAuthorization{
		Id:         authId,
		AccountId:  acct.KeyId,
		Identifier: identifier,
		Status:     ACMEAuthorizationPending,
		Expires:    "", // only populated when it switches to valid.
		Challenges: challenges,
		Wildcard:   identifier.IsWildcard,
	}, nil
}

func parseOptRFC3339Field(data map[string]interface{}, keyName string) (time.Time, error) {
	var timeVal time.Time
	var err error

	rawBefore, present := data[keyName]
	if present {
		beforeStr, ok := rawBefore.(string)
		if !ok {
			return timeVal, fmt.Errorf("invalid type (%T) for field '%s': %w", rawBefore, keyName, ErrMalformed)
		}
		timeVal, err = time.Parse(time.RFC3339, beforeStr)
		if err != nil {
			return timeVal, fmt.Errorf("failed parsing field '%s' (%s): %s: %w", keyName, rawBefore, err.Error(), ErrMalformed)
		}

		if timeVal.IsZero() {
			return timeVal, fmt.Errorf("provided time value is invalid '%s' (%s): %w", keyName, rawBefore, ErrMalformed)
		}
	}

	return timeVal, nil
}

func parseOrderIdentifiers(data map[string]interface{}) ([]*ACMEIdentifier, error) {
	rawIdentifiers, present := data["identifiers"]
	if !present {
		return nil, fmt.Errorf("missing required identifiers argument: %w", ErrMalformed)
	}

	listIdentifiers, ok := rawIdentifiers.([]interface{})
	if !ok {
		return nil, fmt.Errorf("invalid type (%T) for field 'identifiers': %w", rawIdentifiers, ErrMalformed)
	}

	var identifiers []*ACMEIdentifier
	for _, rawIdentifier := range listIdentifiers {
		mapIdentifier, ok := rawIdentifier.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("invalid type (%T) for value in 'identifiers': %w", rawIdentifier, ErrMalformed)
		}

		typeVal, present := mapIdentifier["type"]
		if !present {
			return nil, fmt.Errorf("missing type argument for value in 'identifiers': %w", ErrMalformed)
		}
		typeStr, ok := typeVal.(string)
		if !ok {
			return nil, fmt.Errorf("invalid type for type argument (%T) for value in 'identifiers': %w", typeStr, ErrMalformed)
		}

		valueVal, present := mapIdentifier["value"]
		if !present {
			return nil, fmt.Errorf("missing value argument for value in 'identifiers': %w", ErrMalformed)
		}
		valueStr, ok := valueVal.(string)
		if !ok {
			return nil, fmt.Errorf("invalid type for value argument (%T) for value in 'identifiers': %w", valueStr, ErrMalformed)
		}

		if len(valueStr) == 0 {
			return nil, fmt.Errorf("value argument for value in 'identifiers' can not be blank: %w", ErrMalformed)
		}

		identifier := &ACMEIdentifier{
			Value:         valueStr,
			OriginalValue: valueStr,
		}

		switch typeStr {
		case string(ACMEIPIdentifier):
			identifier.Type = ACMEIPIdentifier
			ip := net.ParseIP(valueStr)
			if ip == nil {
				return nil, fmt.Errorf("value argument (%s) failed validation: failed parsing as IP: %w", valueStr, ErrMalformed)
			}
		case string(ACMEDNSIdentifier):
			identifier.Type = ACMEDNSIdentifier

			// This check modifies the identifier if it is a wildcard,
			// removing the non-wildcard portion. We do this before the
			// IP address checks, in case of an attempt to bypass the IP/DNS
			// check via including a leading wildcard (e.g., *.127.0.0.1).
			//
			// Per RFC 8555 Section 7.1.4. Authorization Objects:
			//
			// > Wildcard domain names (with "*" as the first label) MUST NOT
			// > be included in authorization objects.
			if _, _, err := identifier.MaybeParseWildcard(); err != nil {
				return nil, fmt.Errorf("value argument (%s) failed validation: invalid wildcard: %v: %w", valueStr, err, ErrMalformed)
			}

			if isIP := net.ParseIP(identifier.Value); isIP != nil {
				return nil, fmt.Errorf("refusing to accept argument (%s) as DNS type identifier: parsed OK as IP address: %w", valueStr, ErrMalformed)
			}

			// Use the reduced (identifier.Value) in case this was a wildcard
			// domain.
			p := idna.New(idna.ValidateForRegistration())
			converted, err := p.ToASCII(identifier.Value)
			if err != nil {
				return nil, fmt.Errorf("value argument (%s) failed validation: %s: %w", valueStr, err.Error(), ErrMalformed)
			}

			// Per RFC 8555 Section 7.1.4. Authorization Objects:
			//
			// > The domain name MUST be encoded in the form in which it
			// > would appear in a certificate.  That is, it MUST be encoded
			// > according to the rules in Section 7 of [RFC5280]. Servers
			// > MUST verify any identifier values that begin with the
			// > ASCII-Compatible Encoding prefix "xn--" as defined in
			// > [RFC5890] are properly encoded.
			if identifier.Value != converted {
				return nil, fmt.Errorf("value argument (%s) failed IDNA round-tripping to ASCII: %w", valueStr, ErrMalformed)
			}
		default:
			return nil, fmt.Errorf("unsupported identifier type %s: %w", typeStr, ErrUnsupportedIdentifier)
		}

		identifiers = append(identifiers, identifier)
	}

	return identifiers, nil
}

func (b *backend) acmeTidyOrder(sc *storageContext, accountId string, orderPath string, certTidyBuffer time.Duration) (bool, time.Time, error) {
	// First we get the order; note that the orderPath includes the account
	// It's only accessed at acme/orders/<order_id> with the account context
	// It's saved at acme/<account_id>/orders/<orderId>
	entry, err := sc.Storage.Get(sc.Context, orderPath)
	if err != nil {
		return false, time.Time{}, fmt.Errorf("error loading order: %w", err)
	}
	if entry == nil {
		return false, time.Time{}, fmt.Errorf("order does not exist: %w", ErrMalformed)
	}
	var order acmeOrder
	err = entry.DecodeJSON(&order)
	if err != nil {
		return false, time.Time{}, fmt.Errorf("error decoding order: %w", err)
	}

	// Determine whether we should tidy this order
	shouldTidy := false

	// Track either the order expiry or certificate expiry to return to the caller, this
	// can be used to influence the account's expiry
	orderExpiry := order.CertificateExpiry

	// It is faster to check certificate information on the order entry rather than fetch the cert entry to parse:
	if !order.CertificateExpiry.IsZero() {
		// This implies that a certificate exists
		// When a certificate exists, we want to expire and tidy the order when we tidy the certificate:
		if time.Now().After(order.CertificateExpiry.Add(certTidyBuffer)) { // It's time to clean
			shouldTidy = true
		}
	} else {
		// This implies that no certificate exists
		// In this case, we want to expire the order after it has expired (+ some safety buffer)
		if time.Now().After(order.Expires) {
			shouldTidy = true
		}
		orderExpiry = order.Expires
	}
	if shouldTidy == false {
		return shouldTidy, orderExpiry, nil
	}

	// Tidy this Order
	// That includes any certificate acme/<account_id>/orders/orderPath/cert
	// That also includes any related authorizations: acme/<account_id>/authorizations/<auth_id>

	// First Authorizations
	for _, authorizationId := range order.AuthorizationIds {
		err = sc.Storage.Delete(sc.Context, getAuthorizationPath(accountId, authorizationId))
		if err != nil {
			return false, orderExpiry, err
		}
	}

	// Normal Tidy will Take Care of the Certificate, we need to clean up the certificate to account tracker though
	err = sc.Storage.Delete(sc.Context, getAcmeSerialToAccountTrackerPath(accountId, order.CertificateSerialNumber))
	if err != nil {
		return false, orderExpiry, err
	}

	// And Finally, the order:
	err = sc.Storage.Delete(sc.Context, orderPath)
	if err != nil {
		return false, orderExpiry, err
	}
	b.tidyStatusIncDelAcmeOrderCount()

	return true, orderExpiry, nil
}
