2023-04-14 18:12:31 +00:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2023-04-14 14:54:48 +00:00
package pki
import (
2023-04-21 13:38:06 +00:00
"crypto/x509"
"encoding/base64"
"encoding/pem"
2023-04-14 14:54:48 +00:00
"fmt"
2023-04-21 13:38:06 +00:00
"net"
2023-04-14 14:54:48 +00:00
"net/http"
2023-04-21 13:38:06 +00:00
"sort"
2023-04-14 14:54:48 +00:00
"time"
2023-04-21 13:38:06 +00:00
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/helper/certutil"
2023-04-14 14:54:48 +00:00
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
"golang.org/x/net/idna"
)
2023-04-14 18:48:33 +00:00
func pathAcmeListOrders ( b * backend ) [ ] * framework . Path {
return buildAcmeFrameworkPaths ( b , patternAcmeListOrders , "/orders" )
2023-04-14 14:54:48 +00:00
}
2023-04-14 18:48:33 +00:00
func pathAcmeGetOrder ( b * backend ) [ ] * framework . Path {
return buildAcmeFrameworkPaths ( b , patternAcmeGetOrder , "/order/" + uuidNameRegex ( "order_id" ) )
2023-04-14 14:54:48 +00:00
}
2023-04-14 18:48:33 +00:00
func pathAcmeNewOrder ( b * backend ) [ ] * framework . Path {
return buildAcmeFrameworkPaths ( b , patternAcmeNewOrder , "/new-order" )
2023-04-14 14:54:48 +00:00
}
2023-04-21 13:38:06 +00:00
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" )
}
2023-04-14 14:54:48 +00:00
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 : "" ,
HelpDescription : "" ,
}
}
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 : "" ,
HelpDescription : "" ,
}
}
func patternAcmeGetOrder ( b * backend , pattern string ) * framework . Path {
fields := map [ string ] * framework . FieldSchema { }
addFieldsForACMEPath ( fields , pattern )
addFieldsForACMERequest ( fields )
2023-04-21 13:38:06 +00:00
addFieldsForACMEOrder ( fields )
2023-04-14 14:54:48 +00:00
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 : "" ,
HelpDescription : "" ,
}
}
2023-04-21 13:38:06 +00:00
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 : "" ,
HelpDescription : "" ,
}
}
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 : "" ,
HelpDescription : "" ,
}
}
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
}
if order . Status == ACMEOrderPending {
// Lets see if we can update our order status to ready if all the authorizations have been completed.
if requiredAuthorizationsCompleted ( b , ac , uc , order ) {
order . Status = ACMEOrderReady
}
}
if order . Status != ACMEOrderReady {
return nil , fmt . Errorf ( "%w: order is status %s, needs to be in ready state" , ErrOrderNotReady , order . Status )
}
if ! order . Expires . IsZero ( ) && time . 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
}
2023-04-25 20:48:30 +00:00
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
}
2023-04-21 13:38:06 +00:00
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 )
}
return formatOrderResponse ( ac , order ) , nil
}
func requiredAuthorizationsCompleted ( b * backend , ac * acmeContext , uc * jwsCtx , order * acmeOrder ) bool {
if len ( order . AuthorizationIds ) == 0 {
return false
}
for _ , authId := range order . AuthorizationIds {
authorization , err := b . acmeState . LoadAuthorization ( ac , uc , authId )
if err != nil {
return false
}
if authorization . Status != ACMEAuthorizationValid {
return false
}
}
return true
}
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 ) {
2023-04-21 16:54:19 +00:00
return fmt . Errorf ( "%w: Order (%v) and CSR (%v) mismatch on number of DNS identifiers" , ErrBadCSR , len ( orderDNSIdentifiers ) , len ( csrDNSIdentifiers ) )
2023-04-21 13:38:06 +00:00
}
if len ( orderIPIdentifiers ) != len ( csrIPIdentifiers ) {
2023-04-21 16:54:19 +00:00
return fmt . Errorf ( "%w: Order (%v) and CSR (%v) mismatch on number of IP identifiers" , ErrBadCSR , len ( orderIPIdentifiers ) , len ( csrIPIdentifiers ) )
2023-04-21 13:38:06 +00:00
}
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 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 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 ) )
2023-04-25 13:29:07 +00:00
data := & framework . FieldData {
Raw : map [ string ] interface { } {
"csr" : pemCsr ,
} ,
Schema : getCsrSignVerbatimSchemaFields ( ) ,
}
signingBundle , issuerId , err := ac . sc . fetchCAInfoWithIssuer ( ac . issuer . ID . String ( ) , IssuanceUsage )
2023-04-21 13:38:06 +00:00
if err != nil {
2023-04-25 13:29:07 +00:00
return nil , "" , fmt . Errorf ( "failed loading CA %s: %w" , ac . issuer . ID . String ( ) , err )
2023-04-21 13:38:06 +00:00
}
input := & inputBundle {
2023-04-25 13:29:07 +00:00
req : & logical . Request { } ,
apiData : data ,
role : ac . role ,
2023-04-21 13:38:06 +00:00
}
if csr . PublicKeyAlgorithm == x509 . UnknownPublicKeyAlgorithm || csr . PublicKey == nil {
return nil , "" , fmt . Errorf ( "%w: Refusing to sign CSR with empty PublicKey" , ErrBadCSR )
}
parsedBundle , _ , err := signCert ( ac . sc . Backend , input , signingBundle , false , true )
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 )
}
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 )
}
return csr , nil
}
func ( b * backend ) acmeGetOrderHandler ( ac * acmeContext , _ * logical . Request , fields * framework . FieldData , uc * jwsCtx , _ map [ string ] interface { } , _ * acmeAccount ) ( * logical . Response , error ) {
2023-04-14 14:54:48 +00:00
orderId := fields . Get ( "order_id" ) . ( string )
order , err := b . acmeState . LoadOrder ( ac , uc , orderId )
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 , 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
}
2023-04-21 13:38:06 +00:00
func ( b * backend ) acmeNewOrderHandler ( ac * acmeContext , _ * logical . Request , _ * framework . FieldData , _ * jwsCtx , data map [ string ] interface { } , account * acmeAccount ) ( * logical . Response , error ) {
2023-04-14 14:54:48 +00:00
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
}
2023-04-21 13:38:06 +00:00
if ! notBefore . IsZero ( ) || ! notAfter . IsZero ( ) {
return nil , fmt . Errorf ( "%w: NotBefore and NotAfter are not supported" , ErrMalformed )
}
2023-04-14 14:54:48 +00:00
err = validateAcmeProvidedOrderDates ( notBefore , notAfter )
if err != nil {
return nil , err
}
// TODO: Implement checks against role here.
// 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 {
2023-04-21 13:38:06 +00:00
authz , err := generateAuthorization ( account , identifier )
2023-04-17 19:23:04 +00:00
if err != nil {
return nil , fmt . Errorf ( "error generating authorizations: %w" , err )
}
2023-04-14 14:54:48 +00:00
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 ,
2023-04-21 13:38:06 +00:00
Expires : time . Now ( ) . Add ( 24 * time . Hour ) , // TODO: Readjust this based on authz and/or config
2023-04-14 14:54:48 +00:00
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 {
2023-04-17 17:52:54 +00:00
authorizationUrls = append ( authorizationUrls , buildAuthorizationUrl ( acmeCtx , authId ) )
2023-04-14 14:54:48 +00:00
}
resp := & logical . Response {
Data : map [ string ] interface { } {
2023-04-21 13:38:06 +00:00
"status" : order . Status ,
"expires" : order . Expires . Format ( time . RFC3339 ) ,
2023-04-14 14:54:48 +00:00
"identifiers" : order . 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
}
2023-04-17 17:52:54 +00:00
func buildAuthorizationUrl ( acmeCtx * acmeContext , authId string ) string {
return acmeCtx . baseUrl . JoinPath ( "authorization" , authId ) . String ( )
}
2023-04-14 14:54:48 +00:00
func buildOrderUrl ( acmeCtx * acmeContext , orderId string ) string {
2023-04-17 17:52:54 +00:00
return acmeCtx . baseUrl . JoinPath ( "order" , orderId ) . String ( )
2023-04-14 14:54:48 +00:00
}
2023-04-21 13:38:06 +00:00
func generateAuthorization ( acct * acmeAccount , identifier * ACMEIdentifier ) ( * ACMEAuthorization , error ) {
2023-04-17 17:52:54 +00:00
authId := genUuid ( )
2023-04-21 16:54:19 +00:00
// 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 }
if identifier . Type == ACMEIPIdentifier {
allowedChallenges = [ ] ACMEChallengeType { ACMEHTTPChallenge }
} else if identifier . IsWildcard {
allowedChallenges = [ ] ACMEChallengeType { ACMEDNSChallenge }
}
2023-04-17 19:23:04 +00:00
var challenges [ ] * ACMEChallenge
2023-04-21 16:54:19 +00:00
for _ , challengeType := range allowedChallenges {
2023-04-17 19:23:04 +00:00
token , err := getACMEToken ( )
if err != nil {
return nil , err
}
2023-04-17 17:52:54 +00:00
2023-04-17 19:23:04 +00:00
challenge := & ACMEChallenge {
Type : challengeType ,
Status : ACMEChallengePending ,
ChallengeFields : map [ string ] interface { } {
"token" : token ,
} ,
}
challenges = append ( challenges , challenge )
2023-04-14 14:54:48 +00:00
}
return & ACMEAuthorization {
2023-04-17 17:52:54 +00:00
Id : authId ,
2023-04-14 14:54:48 +00:00
AccountId : acct . KeyId ,
Identifier : identifier ,
Status : ACMEAuthorizationPending ,
Expires : "" , // only populated when it switches to valid.
Challenges : challenges ,
2023-04-21 16:54:19 +00:00
Wildcard : identifier . IsWildcard ,
2023-04-17 19:23:04 +00:00
} , nil
2023-04-14 14:54:48 +00:00
}
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 )
}
2023-04-21 16:54:19 +00:00
identifier := & ACMEIdentifier {
Value : valueStr ,
OriginalValue : valueStr ,
}
2023-04-21 13:38:06 +00:00
switch typeStr {
case string ( ACMEIPIdentifier ) :
2023-04-21 16:54:19 +00:00
identifier . Type = ACMEIPIdentifier
2023-04-21 13:38:06 +00:00
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 ) :
2023-04-21 16:54:19 +00:00
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.
2023-04-21 13:38:06 +00:00
p := idna . New ( idna . ValidateForRegistration ( ) )
2023-04-21 16:54:19 +00:00
converted , err := p . ToASCII ( identifier . Value )
2023-04-21 13:38:06 +00:00
if err != nil {
return nil , fmt . Errorf ( "value argument (%s) failed validation: %s: %w" , valueStr , err . Error ( ) , ErrMalformed )
}
2023-04-21 16:54:19 +00:00
// 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 )
}
2023-04-21 13:38:06 +00:00
default :
return nil , fmt . Errorf ( "unsupported identifier type %s: %w" , typeStr , ErrUnsupportedIdentifier )
}
2023-04-21 16:54:19 +00:00
identifiers = append ( identifiers , identifier )
2023-04-14 14:54:48 +00:00
}
return identifiers , nil
}