2019-03-05 01:58:20 +00:00
package provisioner
import (
2019-08-27 00:52:49 +00:00
"crypto/ecdsa"
"crypto/rsa"
2019-03-05 01:58:20 +00:00
"crypto/x509"
2019-03-06 22:58:46 +00:00
"crypto/x509/pkix"
"encoding/asn1"
"net"
"reflect"
"time"
2019-03-05 01:58:20 +00:00
"github.com/pkg/errors"
"github.com/smallstep/cli/crypto/x509util"
2019-08-27 00:52:49 +00:00
"golang.org/x/crypto/ed25519"
2019-03-05 01:58:20 +00:00
)
2020-01-03 01:48:28 +00:00
// Options contains the options that can be passed to the Sign method. Backdate
// is automatically filled and can only be configured in the CA.
2019-03-07 23:14:18 +00:00
type Options struct {
2020-01-03 01:48:28 +00:00
NotAfter TimeDuration ` json:"notAfter" `
NotBefore TimeDuration ` json:"notBefore" `
Backdate time . Duration ` json:"-" `
2019-03-07 23:14:18 +00:00
}
2019-03-05 01:58:20 +00:00
// SignOption is the interface used to collect all extra options used in the
// Sign method.
type SignOption interface { }
// CertificateValidator is the interface used to validate a X.509 certificate.
type CertificateValidator interface {
SignOption
2019-12-20 21:30:05 +00:00
Valid ( cert * x509 . Certificate , o Options ) error
2019-03-05 01:58:20 +00:00
}
// CertificateRequestValidator is the interface used to validate a X.509
// certificate request.
type CertificateRequestValidator interface {
SignOption
2019-03-06 22:58:46 +00:00
Valid ( req * x509 . CertificateRequest ) error
2019-03-05 01:58:20 +00:00
}
2019-03-07 23:14:18 +00:00
// ProfileModifier is the interface used to add custom options to the profile
2019-03-05 01:58:20 +00:00
// constructor. The options are used to modify the final certificate.
2019-03-07 23:14:18 +00:00
type ProfileModifier interface {
2019-03-05 01:58:20 +00:00
SignOption
2019-03-08 00:04:56 +00:00
Option ( o Options ) x509util . WithOption
2019-03-05 01:58:20 +00:00
}
2019-03-07 23:14:18 +00:00
// profileWithOption is a wrapper against x509util.WithOption to conform the
// interface.
2019-03-06 22:58:46 +00:00
type profileWithOption x509util . WithOption
2019-03-07 23:14:18 +00:00
func ( v profileWithOption ) Option ( Options ) x509util . WithOption {
2019-03-06 22:58:46 +00:00
return x509util . WithOption ( v )
}
2019-03-05 01:58:20 +00:00
// emailOnlyIdentity is a CertificateRequestValidator that checks that the only
// SAN provided is the given email address.
type emailOnlyIdentity string
func ( e emailOnlyIdentity ) Valid ( req * x509 . CertificateRequest ) error {
switch {
case len ( req . DNSNames ) > 0 :
return errors . New ( "certificate request cannot contain DNS names" )
case len ( req . IPAddresses ) > 0 :
return errors . New ( "certificate request cannot contain IP addresses" )
case len ( req . URIs ) > 0 :
return errors . New ( "certificate request cannot contain URIs" )
case len ( req . EmailAddresses ) == 0 :
return errors . New ( "certificate request does not contain any email address" )
case len ( req . EmailAddresses ) > 1 :
2019-08-27 00:52:49 +00:00
return errors . New ( "certificate request contains too many email addresses" )
2019-03-11 20:25:19 +00:00
case req . EmailAddresses [ 0 ] == "" :
return errors . New ( "certificate request cannot contain an empty email address" )
2019-03-05 01:58:20 +00:00
case req . EmailAddresses [ 0 ] != string ( e ) :
2019-03-06 22:58:46 +00:00
return errors . Errorf ( "certificate request does not contain the valid email address, got %s, want %s" , req . EmailAddresses [ 0 ] , e )
2019-03-05 01:58:20 +00:00
default :
return nil
}
}
2019-03-06 22:58:46 +00:00
2019-08-27 00:52:49 +00:00
// defaultPublicKeyValidator validates the public key of a certificate request.
type defaultPublicKeyValidator struct { }
// Valid checks that certificate request common name matches the one configured.
func ( v defaultPublicKeyValidator ) Valid ( req * x509 . CertificateRequest ) error {
switch k := req . PublicKey . ( type ) {
case * rsa . PublicKey :
if k . Size ( ) < 256 {
return errors . New ( "rsa key in CSR must be at least 2048 bits (256 bytes)" )
}
case * ecdsa . PublicKey , ed25519 . PublicKey :
default :
return errors . Errorf ( "unrecognized public key of type '%T' in CSR" , k )
}
return nil
}
2019-03-06 22:58:46 +00:00
// commonNameValidator validates the common name of a certificate request.
type commonNameValidator string
// Valid checks that certificate request common name matches the one configured.
func ( v commonNameValidator ) Valid ( req * x509 . CertificateRequest ) error {
if req . Subject . CommonName == "" {
return errors . New ( "certificate request cannot contain an empty common name" )
}
if req . Subject . CommonName != string ( v ) {
2019-12-20 21:30:05 +00:00
return errors . Errorf ( "certificate request does not contain the valid common name; requested common name = %s, token subject = %s" , req . Subject . CommonName , v )
2019-03-06 22:58:46 +00:00
}
return nil
}
2019-07-15 22:52:36 +00:00
// commonNameSliceValidator validates thats the common name of a certificate request is present in the slice.
type commonNameSliceValidator [ ] string
func ( v commonNameSliceValidator ) Valid ( req * x509 . CertificateRequest ) error {
if req . Subject . CommonName == "" {
return errors . New ( "certificate request cannot contain an empty common name" )
}
for _ , cn := range v {
if req . Subject . CommonName == cn {
return nil
}
}
return errors . Errorf ( "certificate request does not contain the valid common name, got %s, want %s" , req . Subject . CommonName , v )
}
2019-03-06 22:58:46 +00:00
// dnsNamesValidator validates the DNS names SAN of a certificate request.
type dnsNamesValidator [ ] string
2019-03-09 02:09:35 +00:00
// Valid checks that certificate request DNS Names match those configured in
// the bootstrap (token) flow.
2019-03-06 22:58:46 +00:00
func ( v dnsNamesValidator ) Valid ( req * x509 . CertificateRequest ) error {
want := make ( map [ string ] bool )
for _ , s := range v {
want [ s ] = true
}
got := make ( map [ string ] bool )
for _ , s := range req . DNSNames {
got [ s ] = true
}
if ! reflect . DeepEqual ( want , got ) {
return errors . Errorf ( "certificate request does not contain the valid DNS names - got %v, want %v" , req . DNSNames , v )
}
return nil
}
// ipAddressesValidator validates the IP addresses SAN of a certificate request.
type ipAddressesValidator [ ] net . IP
2019-03-09 02:09:35 +00:00
// Valid checks that certificate request IP Addresses match those configured in
// the bootstrap (token) flow.
2019-03-06 22:58:46 +00:00
func ( v ipAddressesValidator ) Valid ( req * x509 . CertificateRequest ) error {
want := make ( map [ string ] bool )
for _ , ip := range v {
want [ ip . String ( ) ] = true
}
got := make ( map [ string ] bool )
for _ , ip := range req . IPAddresses {
got [ ip . String ( ) ] = true
}
if ! reflect . DeepEqual ( want , got ) {
return errors . Errorf ( "IP Addresses claim failed - got %v, want %v" , req . IPAddresses , v )
}
return nil
}
2019-08-23 19:09:16 +00:00
// emailAddressesValidator validates the email address SANs of a certificate request.
type emailAddressesValidator [ ] string
// Valid checks that certificate request IP Addresses match those configured in
// the bootstrap (token) flow.
func ( v emailAddressesValidator ) Valid ( req * x509 . CertificateRequest ) error {
want := make ( map [ string ] bool )
for _ , s := range v {
want [ s ] = true
}
got := make ( map [ string ] bool )
for _ , s := range req . EmailAddresses {
got [ s ] = true
}
if ! reflect . DeepEqual ( want , got ) {
return errors . Errorf ( "certificate request does not contain the valid Email Addresses - got %v, want %v" , req . EmailAddresses , v )
}
return nil
}
2019-09-05 01:31:09 +00:00
// profileDefaultDuration is a wrapper against x509util.WithOption to conform
// the SignOption interface.
type profileDefaultDuration time . Duration
func ( v profileDefaultDuration ) Option ( so Options ) x509util . WithOption {
2020-01-03 01:48:28 +00:00
var backdate time . Duration
2019-09-05 01:31:09 +00:00
notBefore := so . NotBefore . Time ( )
if notBefore . IsZero ( ) {
2020-01-06 20:19:00 +00:00
notBefore = now ( )
2020-01-03 01:48:28 +00:00
backdate = - 1 * so . Backdate
2019-09-05 01:31:09 +00:00
}
notAfter := so . NotAfter . RelativeTime ( notBefore )
2020-01-03 01:48:28 +00:00
return func ( p x509util . Profile ) error {
fn := x509util . WithNotBeforeAfterDuration ( notBefore , notAfter , time . Duration ( v ) )
if err := fn ( p ) ; err != nil {
return err
}
crt := p . Subject ( )
crt . NotBefore = crt . NotBefore . Add ( backdate )
return nil
}
2019-09-05 01:31:09 +00:00
}
// profileLimitDuration is an x509 profile option that modifies an x509 validity
// period according to an imposed expiration time.
type profileLimitDuration struct {
def time . Duration
notAfter time . Time
}
// Option returns an x509util option that limits the validity period of a
// certificate to one that is superficially imposed.
func ( v profileLimitDuration ) Option ( so Options ) x509util . WithOption {
return func ( p x509util . Profile ) error {
2020-01-03 01:48:28 +00:00
var backdate time . Duration
2019-09-05 01:31:09 +00:00
n := now ( )
notBefore := so . NotBefore . Time ( )
if notBefore . IsZero ( ) {
notBefore = n
2020-01-03 01:48:28 +00:00
backdate = - 1 * so . Backdate
2019-09-05 01:31:09 +00:00
}
if notBefore . After ( v . notAfter ) {
return errors . Errorf ( "provisioning credential expiration (%s) is before " +
"requested certificate notBefore (%s)" , v . notAfter , notBefore )
}
notAfter := so . NotAfter . RelativeTime ( notBefore )
if notAfter . After ( v . notAfter ) {
return errors . Errorf ( "provisioning credential expiration (%s) is before " +
"requested certificate notAfter (%s)" , v . notAfter , notBefore )
}
if notAfter . IsZero ( ) {
t := notBefore . Add ( v . def )
if t . After ( v . notAfter ) {
notAfter = v . notAfter
} else {
notAfter = t
}
}
crt := p . Subject ( )
2020-01-03 01:48:28 +00:00
crt . NotBefore = notBefore . Add ( backdate )
2019-09-05 01:31:09 +00:00
crt . NotAfter = notAfter
return nil
}
}
// validityValidator validates the certificate validity settings.
2019-03-06 22:58:46 +00:00
type validityValidator struct {
min time . Duration
max time . Duration
}
2019-03-07 01:30:14 +00:00
// newValidityValidator return a new validity validator.
func newValidityValidator ( min , max time . Duration ) * validityValidator {
return & validityValidator { min : min , max : max }
}
2019-09-05 01:31:09 +00:00
// Valid validates the certificate validity settings (notBefore/notAfter) and
// and total duration.
2019-12-20 21:30:05 +00:00
func ( v * validityValidator ) Valid ( cert * x509 . Certificate , o Options ) error {
2019-03-06 22:58:46 +00:00
var (
2019-12-20 21:30:05 +00:00
na = cert . NotAfter . Truncate ( time . Second )
nb = cert . NotBefore . Truncate ( time . Second )
2020-01-04 02:22:02 +00:00
now = time . Now ( ) . Truncate ( time . Second )
2019-03-06 22:58:46 +00:00
)
2020-01-04 02:22:02 +00:00
2019-12-20 21:30:05 +00:00
d := na . Sub ( nb )
2019-03-06 22:58:46 +00:00
if na . Before ( now ) {
2019-12-20 21:30:05 +00:00
return errors . Errorf ( "notAfter cannot be in the past; na=%v" , na )
2019-03-06 22:58:46 +00:00
}
if na . Before ( nb ) {
2019-12-20 21:30:05 +00:00
return errors . Errorf ( "notAfter cannot be before notBefore; na=%v, nb=%v" , na , nb )
2019-03-06 22:58:46 +00:00
}
if d < v . min {
return errors . Errorf ( "requested duration of %v is less than the authorized minimum certificate duration of %v" ,
d , v . min )
}
2019-12-20 21:30:05 +00:00
// NOTE: this check is not "technically correct". We're allowing the max
// duration of a cert to be "max + backdate" and not all certificates will
// be backdated (e.g. if a user passes the NotBefore value then we do not
// apply a backdate). This is good enough.
if d > v . max + o . Backdate {
2019-03-06 22:58:46 +00:00
return errors . Errorf ( "requested duration of %v is more than the authorized maximum certificate duration of %v" ,
d , v . max )
}
return nil
}
var (
stepOIDRoot = asn1 . ObjectIdentifier { 1 , 3 , 6 , 1 , 4 , 1 , 37476 , 9000 , 64 }
stepOIDProvisioner = append ( asn1 . ObjectIdentifier ( nil ) , append ( stepOIDRoot , 1 ) ... )
)
type stepProvisionerASN1 struct {
2019-07-15 22:52:36 +00:00
Type int
Name [ ] byte
CredentialID [ ] byte
KeyValuePairs [ ] string ` asn1:"optional,omitempty" `
2019-03-06 22:58:46 +00:00
}
type provisionerExtensionOption struct {
2019-07-15 22:52:36 +00:00
Type int
Name string
CredentialID string
KeyValuePairs [ ] string
2019-03-06 22:58:46 +00:00
}
2019-07-15 22:52:36 +00:00
func newProvisionerExtensionOption ( typ Type , name , credentialID string , keyValuePairs ... string ) * provisionerExtensionOption {
2019-03-06 22:58:46 +00:00
return & provisionerExtensionOption {
2019-07-15 22:52:36 +00:00
Type : int ( typ ) ,
Name : name ,
CredentialID : credentialID ,
KeyValuePairs : keyValuePairs ,
2019-03-06 22:58:46 +00:00
}
}
2019-03-08 00:04:56 +00:00
func ( o * provisionerExtensionOption ) Option ( Options ) x509util . WithOption {
2019-03-06 22:58:46 +00:00
return func ( p x509util . Profile ) error {
crt := p . Subject ( )
2019-07-15 22:52:36 +00:00
ext , err := createProvisionerExtension ( o . Type , o . Name , o . CredentialID , o . KeyValuePairs ... )
2019-03-06 22:58:46 +00:00
if err != nil {
2019-03-08 20:19:44 +00:00
return err
2019-03-06 22:58:46 +00:00
}
2019-03-08 20:19:44 +00:00
crt . ExtraExtensions = append ( crt . ExtraExtensions , ext )
2019-03-06 22:58:46 +00:00
return nil
}
}
2019-03-08 20:19:44 +00:00
2019-07-15 22:52:36 +00:00
func createProvisionerExtension ( typ int , name , credentialID string , keyValuePairs ... string ) ( pkix . Extension , error ) {
2019-03-08 20:19:44 +00:00
b , err := asn1 . Marshal ( stepProvisionerASN1 {
2019-07-15 22:52:36 +00:00
Type : typ ,
Name : [ ] byte ( name ) ,
CredentialID : [ ] byte ( credentialID ) ,
KeyValuePairs : keyValuePairs ,
2019-03-08 20:19:44 +00:00
} )
if err != nil {
return pkix . Extension { } , errors . Wrapf ( err , "error marshaling provisioner extension" )
}
return pkix . Extension {
Id : stepOIDProvisioner ,
Critical : false ,
Value : b ,
} , nil
}
2019-03-12 01:47:57 +00:00
func init ( ) {
2019-03-23 01:55:28 +00:00
// Avoid dead-code warning in profileWithOption
2019-03-12 01:47:57 +00:00
_ = profileWithOption ( nil )
}