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
2021-06-03 20:45:24 +00:00
orderNames := make ( [ ] string , numberOfIdentifierType ( "dns" , o . Identifiers ) )
orderIPs := make ( [ ] net . IP , numberOfIdentifierType ( "ip" , o . Identifiers ) )
indexDNS , indexIP := 0 , 0
for _ , n := range o . Identifiers {
2021-05-28 14:40:46 +00:00
switch n . Type {
case "dns" :
2021-06-03 20:45:24 +00:00
orderNames [ indexDNS ] = n . Value
indexDNS ++
2021-05-28 14:40:46 +00:00
case "ip" :
2021-06-03 20:45:24 +00:00
orderIPs [ indexIP ] = net . ParseIP ( n . Value ) // NOTE: this assumes are all valid IPs at this time; or will result in nil entries
indexIP ++
2021-05-28 14:40:46 +00:00
default :
return sans , NewErrorISE ( "unsupported identifier type in order: %s" , n . Type )
}
}
orderNames = uniqueSortedLowerNames ( orderNames )
orderIPs = uniqueSortedIPs ( orderIPs )
2021-06-03 20:03:21 +00:00
totalNumberOfSANs := len ( csr . DNSNames ) + len ( csr . IPAddresses )
sans = make ( [ ] x509util . SubjectAlternativeName , totalNumberOfSANs )
2021-06-03 20:45:24 +00:00
index := 0
2021-06-03 20:03:21 +00:00
// TODO: limit what IP addresses can be used? Only private? Only certain ranges (i.e. only allow the specific ranges by default, configuration for all?)
// TODO: can DNS already be limited to a certain domain? That would probably be nice to have too, but maybe not as part of this PR
// TODO: if it seems not too big of a change, make consts/enums out of the stringly typed identifiers (challenge types, identifier types)
2021-05-28 14:40:46 +00:00
// based on configuration? Public vs. private range? That logic should be configurable somewhere.
2021-06-03 20:03:21 +00:00
// TODO: only allow IP based identifier based on configuration? Some additional configuration and validation on the provisioner for this case.
// 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.
2021-06-03 20:45:24 +00:00
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 )
}
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 [ index ] = x509util . SubjectAlternativeName {
Type : x509util . DNSType ,
Value : csr . DNSNames [ i ] ,
}
index ++
}
2021-06-03 20:03:21 +00:00
if len ( csr . IPAddresses ) != len ( orderIPs ) {
return sans , NewError ( ErrorBadCSRType , "number of CSR IPs do not match identifiers exactly: " +
"CSR IPs = %v, Order IPs = %v" , csr . IPAddresses , orderIPs )
}
for i := range csr . IPAddresses {
if ! ipsAreEqual ( csr . IPAddresses [ i ] , orderIPs [ i ] ) {
2021-05-28 14:40:46 +00:00
return sans , NewError ( ErrorBadCSRType , "CSR IPs do not match identifiers exactly: " +
"CSR IPs = %v, Order IPs = %v" , csr . IPAddresses , orderIPs )
}
2021-06-03 20:45:24 +00:00
sans [ index ] = x509util . SubjectAlternativeName {
2021-06-03 20:03:21 +00:00
Type : x509util . IPType ,
Value : csr . IPAddresses [ i ] . String ( ) ,
2021-05-28 14:40:46 +00:00
}
2021-06-03 20:45:24 +00:00
index ++
2021-06-03 20:03:21 +00:00
}
2021-06-03 20:45:24 +00:00
return sans , nil
}
2021-06-03 20:03:21 +00:00
2021-06-03 20:45:24 +00:00
// numberOfIdentifierType returns the number of Identifiers that
// are of type typ.
func numberOfIdentifierType ( typ string , ids [ ] Identifier ) int {
c := 0
for _ , id := range ids {
if id . Type == typ {
c ++
2021-05-28 14:40:46 +00:00
}
}
2021-06-03 20:45:24 +00:00
return c
2021-05-28 14:40:46 +00:00
}
2021-06-03 20:45:24 +00:00
// canonicalize canonicalizes a CSR so that it can be compared against an Order
// NOTE: this effectively changes the order of SANs in the CSR, which may be OK,
// but may not be expected.
2021-05-28 14:40:46 +00:00
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 )
2021-06-03 20:03:21 +00:00
canonicalized . IPAddresses = uniqueSortedIPs ( csr . IPAddresses )
2021-05-28 14:40:46 +00:00
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 {
2021-06-03 20:03:21 +00:00
if matchAddrFamily ( x , y ) {
2021-05-28 14:40:46 +00:00
return x . Equal ( y )
}
2021-06-03 20:03:21 +00:00
return false
2021-05-28 14:40:46 +00:00
}
2021-06-03 20:03:21 +00:00
// matchAddrFamily returns if two IPs are both IPv4 OR IPv6
// Implementation taken and adapted from https://golang.org/src/net/ip.go
func matchAddrFamily ( x net . IP , y net . IP ) bool {
return x . To4 ( ) != nil && y . To4 ( ) != nil || x . To16 ( ) != nil && x . To4 ( ) == nil && y . To16 ( ) != nil && y . To4 ( ) == nil
2021-05-28 14:40:46 +00:00
}
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
}