2021-02-28 18:09:06 +00:00
package acme
2019-05-27 00:41:10 +00:00
import (
2021-05-28 14:40:46 +00:00
"bytes"
2019-05-27 00:41:10 +00:00
"context"
"crypto/x509"
"encoding/json"
2021-05-28 14:40:46 +00:00
"net"
2020-01-28 23:34:01 +00:00
"sort"
"strings"
2019-05-27 00:41:10 +00:00
"time"
"github.com/smallstep/certificates/authority/provisioner"
2020-08-05 23:02:46 +00:00
"go.step.sm/crypto/x509util"
2019-05-27 00:41:10 +00:00
)
2021-03-01 06:49:20 +00:00
// Identifier encodes the type that an order pertains to.
type Identifier struct {
Type string ` json:"type" `
Value string ` json:"value" `
}
2019-05-27 00:41:10 +00:00
// Order contains order metadata for the ACME protocol order type.
type Order struct {
2021-03-25 20:46:51 +00:00
ID string ` json:"id" `
2021-03-29 19:04:14 +00:00
AccountID string ` json:"-" `
ProvisionerID string ` json:"-" `
2021-03-25 20:46:51 +00:00
Status Status ` json:"status" `
2021-03-30 05:58:26 +00:00
ExpiresAt time . Time ` json:"expires" `
2021-03-25 20:46:51 +00:00
Identifiers [ ] Identifier ` json:"identifiers" `
2021-03-30 05:58:26 +00:00
NotBefore time . Time ` json:"notBefore" `
NotAfter time . Time ` json:"notAfter" `
2021-03-25 20:46:51 +00:00
Error * Error ` json:"error,omitempty" `
AuthorizationIDs [ ] string ` json:"-" `
AuthorizationURLs [ ] string ` json:"authorizations" `
FinalizeURL string ` json:"finalize" `
CertificateID string ` json:"-" `
CertificateURL string ` json:"certificate,omitempty" `
2019-05-27 00:41:10 +00:00
}
// ToLog enables response logging.
func ( o * Order ) ToLog ( ) ( interface { } , error ) {
b , err := json . Marshal ( o )
if err != nil {
2021-03-05 07:10:46 +00:00
return nil , WrapErrorISE ( err , "error marshaling order for logging" )
2019-05-27 00:41:10 +00:00
}
return string ( b ) , nil
}
2021-02-28 01:05:37 +00:00
// UpdateStatus updates the ACME Order Status if necessary.
// Changes to the order are saved using the database interface.
func ( o * Order ) UpdateStatus ( ctx context . Context , db DB ) error {
2021-03-12 08:16:48 +00:00
now := clock . Now ( )
2019-05-27 00:41:10 +00:00
switch o . Status {
case StatusInvalid :
2021-02-28 01:05:37 +00:00
return nil
2019-05-27 00:41:10 +00:00
case StatusValid :
2021-02-28 01:05:37 +00:00
return nil
2019-05-27 00:41:10 +00:00
case StatusReady :
2021-02-28 01:05:37 +00:00
// Check expiry
2021-03-19 21:37:45 +00:00
if now . After ( o . ExpiresAt ) {
2021-02-28 01:05:37 +00:00
o . Status = StatusInvalid
2021-03-01 07:33:18 +00:00
o . Error = NewError ( ErrorMalformedType , "order has expired" )
2019-05-27 00:41:10 +00:00
break
}
2021-02-28 01:05:37 +00:00
return nil
2019-05-27 00:41:10 +00:00
case StatusPending :
2021-02-28 01:05:37 +00:00
// Check expiry
2021-03-19 21:37:45 +00:00
if now . After ( o . ExpiresAt ) {
2021-02-28 01:05:37 +00:00
o . Status = StatusInvalid
2021-03-01 07:33:18 +00:00
o . Error = NewError ( ErrorMalformedType , "order has expired" )
2019-05-27 00:41:10 +00:00
break
}
2021-03-01 06:49:20 +00:00
var count = map [ Status ] int {
2019-05-27 00:41:10 +00:00
StatusValid : 0 ,
StatusInvalid : 0 ,
StatusPending : 0 ,
}
2021-03-03 23:16:25 +00:00
for _ , azID := range o . AuthorizationIDs {
2021-02-28 01:05:37 +00:00
az , err := db . GetAuthorization ( ctx , azID )
2019-05-27 00:41:10 +00:00
if err != nil {
2021-03-24 23:50:35 +00:00
return WrapErrorISE ( err , "error getting authorization ID %s" , azID )
2019-05-27 00:41:10 +00:00
}
2021-03-01 06:49:20 +00:00
if err = az . UpdateStatus ( ctx , db ) ; err != nil {
2021-03-24 23:50:35 +00:00
return WrapErrorISE ( err , "error updating authorization ID %s" , azID )
2019-05-27 00:41:10 +00:00
}
2021-02-28 01:05:37 +00:00
st := az . Status
2019-05-27 00:41:10 +00:00
count [ st ] ++
}
switch {
case count [ StatusInvalid ] > 0 :
2021-02-28 01:05:37 +00:00
o . Status = StatusInvalid
2020-05-28 21:58:35 +00:00
// No change in the order status, so just return the order as is -
// without writing any changes.
2019-05-27 00:41:10 +00:00
case count [ StatusPending ] > 0 :
2021-02-28 01:05:37 +00:00
return nil
2020-05-28 21:58:35 +00:00
2021-03-03 23:16:25 +00:00
case count [ StatusValid ] == len ( o . AuthorizationIDs ) :
2021-02-28 01:05:37 +00:00
o . Status = StatusReady
2020-05-28 21:58:35 +00:00
2019-05-27 00:41:10 +00:00
default :
2021-03-03 23:16:25 +00:00
return NewErrorISE ( "unexpected authz status" )
2019-05-27 00:41:10 +00:00
}
default :
2021-03-03 23:16:25 +00:00
return NewErrorISE ( "unrecognized order status: %s" , o . Status )
2019-05-27 00:41:10 +00:00
}
2021-03-24 23:50:35 +00:00
if err := db . UpdateOrder ( ctx , o ) ; err != nil {
return WrapErrorISE ( err , "error updating order" )
}
return nil
2019-05-27 00:41:10 +00:00
}
2021-03-01 06:49:20 +00:00
// Finalize signs a certificate if the necessary conditions for Order completion
2019-05-27 00:41:10 +00:00
// have been met.
2021-03-05 07:10:46 +00:00
func ( o * Order ) Finalize ( ctx context . Context , db DB , csr * x509 . CertificateRequest , auth CertificateAuthority , p Provisioner ) error {
2021-03-01 06:49:20 +00:00
if err := o . UpdateStatus ( ctx , db ) ; err != nil {
return err
2019-05-27 00:41:10 +00:00
}
2020-10-20 23:18:16 +00:00
2019-05-27 00:41:10 +00:00
switch o . Status {
case StatusInvalid :
2021-03-01 07:33:18 +00:00
return NewError ( ErrorOrderNotReadyType , "order %s has been abandoned" , o . ID )
2019-05-27 00:41:10 +00:00
case StatusValid :
2021-02-28 01:05:37 +00:00
return nil
2019-05-27 00:41:10 +00:00
case StatusPending :
2021-03-01 07:33:18 +00:00
return NewError ( ErrorOrderNotReadyType , "order %s is not ready" , o . ID )
2019-05-27 00:41:10 +00:00
case StatusReady :
break
default :
2021-03-03 23:16:25 +00:00
return NewErrorISE ( "unexpected status %s for order %s" , o . Status , o . ID )
2019-05-27 00:41:10 +00:00
}
2021-05-28 14:40:46 +00:00
// canonicalize the CSR to allow for comparison
csr = canonicalize ( csr )
2020-07-16 00:30:29 +00:00
2021-05-28 14:40:46 +00:00
// retrieve the requested SANs for the Order
sans , err := o . sans ( csr )
if err != nil {
return WrapErrorISE ( err , "error determining SANs for the CSR" )
2020-06-23 18:10:45 +00:00
}
2019-05-27 00:41:10 +00:00
// Get authorizations from the ACME provisioner.
2021-03-01 06:49:20 +00:00
ctx = provisioner . NewContextWithMethod ( ctx , provisioner . SignMethod )
2019-05-27 00:41:10 +00:00
signOps , err := p . AuthorizeSign ( ctx , "" )
if err != nil {
2021-03-05 07:10:46 +00:00
return WrapErrorISE ( err , "error retrieving authorization options from ACME provisioner" )
2019-05-27 00:41:10 +00:00
}
2020-07-16 00:30:29 +00:00
// Template data
data := x509util . NewTemplateData ( )
data . SetCommonName ( csr . Subject . CommonName )
data . Set ( x509util . SANsKey , sans )
templateOptions , err := provisioner . TemplateOptions ( p . GetOptions ( ) , data )
if err != nil {
2021-03-05 07:10:46 +00:00
return WrapErrorISE ( err , "error creating template options from ACME provisioner" )
2020-07-16 00:30:29 +00:00
}
signOps = append ( signOps , templateOptions )
2021-03-01 06:49:20 +00:00
// Sign a new certificate.
2020-07-23 01:24:45 +00:00
certChain , err := auth . Sign ( csr , provisioner . SignOptions {
2021-03-03 23:16:25 +00:00
NotBefore : provisioner . NewTimeDuration ( o . NotBefore ) ,
NotAfter : provisioner . NewTimeDuration ( o . NotAfter ) ,
2019-05-27 00:41:10 +00:00
} , signOps ... )
if err != nil {
2021-03-05 07:10:46 +00:00
return WrapErrorISE ( err , "error signing certificate for order %s" , o . ID )
2019-05-27 00:41:10 +00:00
}
2021-03-01 06:49:20 +00:00
cert := & Certificate {
2019-05-27 00:41:10 +00:00
AccountID : o . AccountID ,
OrderID : o . ID ,
2019-10-09 19:57:12 +00:00
Leaf : certChain [ 0 ] ,
Intermediates : certChain [ 1 : ] ,
2021-03-01 06:49:20 +00:00
}
if err := db . CreateCertificate ( ctx , cert ) ; err != nil {
2021-03-25 03:07:21 +00:00
return WrapErrorISE ( err , "error creating certificate for order %s" , o . ID )
2019-05-27 00:41:10 +00:00
}
2021-03-05 07:10:46 +00:00
o . CertificateID = cert . ID
2021-02-28 01:05:37 +00:00
o . Status = StatusValid
2021-03-25 03:07:21 +00:00
if err = db . UpdateOrder ( ctx , o ) ; err != nil {
return WrapErrorISE ( err , "error updating order %s" , o . ID )
}
return nil
2019-05-27 00:41:10 +00:00
}
2020-01-28 23:34:01 +00:00
2021-05-28 14:40:46 +00:00
func ( o * Order ) sans ( csr * x509 . CertificateRequest ) ( [ ] x509util . SubjectAlternativeName , error ) {
var sans [ ] x509util . SubjectAlternativeName
// order the DNS names and IP addresses, so that they can be compared against the canonicalized CSR
orderNames := make ( [ ] string , len ( o . Identifiers ) )
orderIPs := make ( [ ] net . IP , len ( o . Identifiers ) )
for i , n := range o . Identifiers {
switch n . Type {
case "dns" :
orderNames [ i ] = n . Value
case "ip" :
orderIPs [ i ] = net . ParseIP ( n . Value ) // NOTE: this assumes are all valid IPs or will result in nil entries
default :
return sans , NewErrorISE ( "unsupported identifier type in order: %s" , n . Type )
}
}
orderNames = uniqueSortedLowerNames ( orderNames )
orderIPs = uniqueSortedIPs ( orderIPs )
// TODO: limit what IP addresses can be used? Only private? Only certain ranges
// based on configuration? Public vs. private range? That logic should be configurable somewhere.
// TODO: ensure that DNSNames indeed MUST NEVER have an IP
// TODO: only allow IP based identifier based on configuration?
2021-05-28 22:19:14 +00:00
// TODO: validation of the input (if IP; should be valid IPv4/v6; Incoming request should have Host header set / ALPN IN-ADDR.ARPA)
// TODO: limit the IP address identifier to a single IP address? RFC _can_ be read like that, but there can be multiple identifiers, of course
2021-05-28 14:40:46 +00:00
// Determine if DNS names or IPs should be processed.
// At this time, orders in which DNS names and IPs are mixed are not supported. // TODO: ensure that's OK and/or should we support more, RFC-wise
shouldProcessIPAddresses := len ( csr . DNSNames ) == 0 && len ( orderIPs ) != 0 // TODO: verify that this logic is OK and sufficient
if shouldProcessIPAddresses {
// Validate identifier IPs against CSR alternative names (IPs).
if len ( csr . IPAddresses ) != len ( orderIPs ) {
return sans , NewError ( ErrorBadCSRType , "CSR IPs do not match identifiers exactly: " +
"CSR IPs = %v, Order IPs = %v" , csr . IPAddresses , orderIPs )
}
sans = make ( [ ] x509util . SubjectAlternativeName , len ( csr . IPAddresses ) )
for i := range csr . IPAddresses {
if ! ipsAreEqual ( csr . IPAddresses [ i ] , orderIPs [ i ] ) {
return sans , NewError ( ErrorBadCSRType , "CSR IPs do not match identifiers exactly: " +
"CSR IPs = %v, Order IPs = %v" , csr . IPAddresses , orderIPs )
}
sans [ i ] = x509util . SubjectAlternativeName {
Type : x509util . IPType ,
Value : csr . IPAddresses [ i ] . String ( ) ,
}
}
} else {
// Validate identifier names against CSR alternative names.
//
// Note that with certificate templates we are not going to check for the
// absence of other SANs as they will only be set if the templates allows
// them.
if len ( csr . DNSNames ) != len ( orderNames ) {
return sans , NewError ( ErrorBadCSRType , "CSR names do not match identifiers exactly: " +
"CSR names = %v, Order names = %v" , csr . DNSNames , orderNames )
}
sans = make ( [ ] x509util . SubjectAlternativeName , len ( csr . DNSNames ) )
for i := range csr . DNSNames {
if csr . DNSNames [ i ] != orderNames [ i ] {
return sans , NewError ( ErrorBadCSRType , "CSR names do not match identifiers exactly: " +
"CSR names = %v, Order names = %v" , csr . DNSNames , orderNames )
}
sans [ i ] = x509util . SubjectAlternativeName {
Type : x509util . DNSType ,
Value : csr . DNSNames [ i ] ,
}
}
}
return sans , nil
}
func canonicalize ( csr * x509 . CertificateRequest ) ( canonicalized * x509 . CertificateRequest ) {
// for clarity only; we're operating on the same object by pointer
canonicalized = csr
// RFC8555: The CSR MUST indicate the exact same set of requested
// identifiers as the initial newOrder request. Identifiers of type "dns"
// MUST appear either in the commonName portion of the requested subject
// name or in an extensionRequest attribute [RFC2985] requesting a
// subjectAltName extension, or both.
if csr . Subject . CommonName != "" {
canonicalized . DNSNames = append ( csr . DNSNames , csr . Subject . CommonName )
}
canonicalized . DNSNames = uniqueSortedLowerNames ( csr . DNSNames )
canonicalized . IPAddresses = uniqueSortedIPs ( csr . IPAddresses ) // TODO: sorting and setting this value MAY result in different values in CSR (and probably also ending up in cert); is that behavior wanted?
return canonicalized
}
// ipsAreEqual compares IPs to be equal. IPv6 representations of IPv4
// adresses are NOT considered equal to the IPv4 address in this case.
// Both IPs should be the same version AND equal to each other.
func ipsAreEqual ( x , y net . IP ) bool {
if isIPv4 ( x ) && isIPv4 ( y ) {
return x . Equal ( y )
}
return x . Equal ( y )
}
// isIPv4 returns if an IP is IPv4 or not.
func isIPv4 ( ip net . IP ) bool {
return ip . To4 ( ) != nil
}
2021-02-28 01:05:37 +00:00
// uniqueSortedLowerNames returns the set of all unique names in the input after all
2020-01-28 23:34:01 +00:00
// of them are lowercased. The returned names will be in their lowercased form
// and sorted alphabetically.
2021-02-28 01:05:37 +00:00
func uniqueSortedLowerNames ( names [ ] string ) ( unique [ ] string ) {
2020-01-28 23:34:01 +00:00
nameMap := make ( map [ string ] int , len ( names ) )
for _ , name := range names {
nameMap [ strings . ToLower ( name ) ] = 1
}
unique = make ( [ ] string , 0 , len ( nameMap ) )
for name := range nameMap {
unique = append ( unique , name )
}
sort . Strings ( unique )
return
}
2021-05-28 14:40:46 +00:00
// uniqueSortedIPs returns the set of all unique net.IPs in the input. They
// are sorted by their bytes (octet) representation.
func uniqueSortedIPs ( ips [ ] net . IP ) ( unique [ ] net . IP ) {
type entry struct {
ip net . IP
}
ipEntryMap := make ( map [ string ] entry , len ( ips ) )
for _ , ip := range ips {
ipEntryMap [ ip . String ( ) ] = entry { ip : ip }
}
unique = make ( [ ] net . IP , 0 , len ( ipEntryMap ) )
for _ , entry := range ipEntryMap {
unique = append ( unique , entry . ip )
}
sort . Slice ( unique , func ( i , j int ) bool {
return bytes . Compare ( unique [ i ] , unique [ j ] ) < 0
} )
return
}