2018-10-05 21:48:36 +00:00
package authority
import (
2019-07-29 19:34:27 +00:00
"context"
2020-02-11 22:05:37 +00:00
"crypto"
2018-10-05 21:48:36 +00:00
"crypto/rand"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
2018-10-26 06:49:23 +00:00
"encoding/asn1"
2019-08-27 00:52:49 +00:00
"encoding/pem"
2018-10-05 21:48:36 +00:00
"fmt"
"net/http"
2019-02-15 00:44:36 +00:00
"reflect"
2018-10-05 21:48:36 +00:00
"testing"
"time"
"github.com/pkg/errors"
"github.com/smallstep/assert"
2019-03-08 03:30:17 +00:00
"github.com/smallstep/certificates/authority/provisioner"
2019-03-05 08:07:13 +00:00
"github.com/smallstep/certificates/db"
2019-12-20 21:30:05 +00:00
"github.com/smallstep/certificates/errs"
2018-10-05 21:48:36 +00:00
"github.com/smallstep/cli/crypto/keys"
2019-03-05 08:07:13 +00:00
"github.com/smallstep/cli/crypto/pemutil"
2018-10-05 21:48:36 +00:00
"github.com/smallstep/cli/crypto/tlsutil"
"github.com/smallstep/cli/crypto/x509util"
2019-03-12 01:15:24 +00:00
"github.com/smallstep/cli/jose"
2019-03-05 08:07:13 +00:00
"gopkg.in/square/go-jose.v2/jwt"
2018-10-05 21:48:36 +00:00
)
2019-03-12 01:47:57 +00:00
var (
stepOIDRoot = asn1 . ObjectIdentifier { 1 , 3 , 6 , 1 , 4 , 1 , 37476 , 9000 , 64 }
stepOIDProvisioner = append ( asn1 . ObjectIdentifier ( nil ) , append ( stepOIDRoot , 1 ) ... )
)
const provisionerTypeJWK = 1
type stepProvisionerASN1 struct {
Type int
Name [ ] byte
CredentialID [ ] byte
}
2020-03-31 18:41:36 +00:00
type certificateDurationEnforcer struct {
NotBefore time . Time
NotAfter time . Time
}
func ( m * certificateDurationEnforcer ) Enforce ( cert * x509 . Certificate ) error {
cert . NotBefore = m . NotBefore
cert . NotAfter = m . NotAfter
return nil
}
2019-03-12 01:47:57 +00:00
func withProvisionerOID ( name , kid string ) x509util . WithOption {
return func ( p x509util . Profile ) error {
crt := p . Subject ( )
b , err := asn1 . Marshal ( stepProvisionerASN1 {
Type : provisionerTypeJWK ,
Name : [ ] byte ( name ) ,
CredentialID : [ ] byte ( kid ) ,
} )
if err != nil {
return err
}
crt . ExtraExtensions = append ( crt . ExtraExtensions , pkix . Extension {
Id : stepOIDProvisioner ,
Critical : false ,
Value : b ,
} )
return nil
}
}
2019-02-07 00:26:25 +00:00
func getCSR ( t * testing . T , priv interface { } , opts ... func ( * x509 . CertificateRequest ) ) * x509 . CertificateRequest {
2018-10-05 21:48:36 +00:00
_csr := & x509 . CertificateRequest {
2019-02-07 00:26:25 +00:00
Subject : pkix . Name { CommonName : "smallstep test" } ,
2018-10-05 21:48:36 +00:00
DNSNames : [ ] string { "test.smallstep.com" } ,
}
2019-02-07 00:26:25 +00:00
for _ , opt := range opts {
opt ( _csr )
}
2018-10-05 21:48:36 +00:00
csrBytes , err := x509 . CreateCertificateRequest ( rand . Reader , _csr , priv )
assert . FatalError ( t , err )
csr , err := x509 . ParseCertificateRequest ( csrBytes )
assert . FatalError ( t , err )
return csr
}
2020-06-25 06:25:15 +00:00
func setExtraExtsCSR ( exts [ ] pkix . Extension ) func ( * x509 . CertificateRequest ) {
return func ( csr * x509 . CertificateRequest ) {
csr . ExtraExtensions = exts
}
}
2020-07-21 00:51:33 +00:00
func generateSubjectKeyID ( pub crypto . PublicKey ) ( [ ] byte , error ) {
b , err := x509 . MarshalPKIXPublicKey ( pub )
if err != nil {
return nil , errors . Wrap ( err , "error marshaling public key" )
}
info := struct {
Algorithm pkix . AlgorithmIdentifier
SubjectPublicKey asn1 . BitString
} { }
if _ , err = asn1 . Unmarshal ( b , & info ) ; err != nil {
return nil , errors . Wrap ( err , "error unmarshaling public key" )
}
hash := sha1 . Sum ( info . SubjectPublicKey . Bytes )
return hash [ : ] , nil
}
2020-06-25 06:25:15 +00:00
type basicConstraints struct {
IsCA bool ` asn1:"optional" `
MaxPathLen int ` asn1:"optional,default:-1" `
}
2019-12-20 21:30:05 +00:00
func TestAuthority_Sign ( t * testing . T ) {
2018-10-05 21:48:36 +00:00
pub , priv , err := keys . GenerateDefaultKeyPair ( )
assert . FatalError ( t , err )
a := testAuthority ( t )
assert . FatalError ( t , err )
a . config . AuthorityConfig . Template = & x509util . ASN1DN {
Country : "Tazmania" ,
Organization : "Acme Co" ,
Locality : "Landscapes" ,
Province : "Sudden Cliffs" ,
StreetAddress : "TNT" ,
2018-10-19 05:26:39 +00:00
CommonName : "test.smallstep.com" ,
2018-10-05 21:48:36 +00:00
}
2018-10-19 05:26:39 +00:00
nb := time . Now ( )
2020-07-23 01:24:45 +00:00
signOpts := provisioner . SignOptions {
2019-03-25 19:35:21 +00:00
NotBefore : provisioner . NewTimeDuration ( nb ) ,
NotAfter : provisioner . NewTimeDuration ( nb . Add ( time . Minute * 5 ) ) ,
2020-07-23 02:18:45 +00:00
Backdate : 1 * time . Minute ,
2018-10-19 05:26:39 +00:00
}
2018-10-05 21:48:36 +00:00
2019-03-12 01:15:24 +00:00
// Create a token to get test extra opts.
2019-03-08 03:30:17 +00:00
p := a . config . AuthorityConfig . Provisioners [ 1 ] . ( * provisioner . JWK )
2019-03-12 01:15:24 +00:00
key , err := jose . ParseKey ( "testdata/secrets/step_cli_key_priv.jwk" , jose . WithPassword ( [ ] byte ( "pass" ) ) )
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
token , err := generateToken ( "smallstep test" , "step-cli" , testAudiences . Sign [ 0 ] , [ ] string { "test.smallstep.com" } , time . Now ( ) , key )
2019-03-12 01:15:24 +00:00
assert . FatalError ( t , err )
2019-07-29 19:34:27 +00:00
ctx := provisioner . NewContextWithMethod ( context . Background ( ) , provisioner . SignMethod )
extraOpts , err := a . Authorize ( ctx , token )
2019-03-12 01:15:24 +00:00
assert . FatalError ( t , err )
2018-10-26 06:49:23 +00:00
2018-10-05 21:48:36 +00:00
type signTest struct {
2018-10-19 05:26:39 +00:00
auth * Authority
csr * x509 . CertificateRequest
2020-07-23 01:24:45 +00:00
signOpts provisioner . SignOptions
2019-03-08 03:30:17 +00:00
extraOpts [ ] provisioner . SignOption
2020-03-31 18:41:36 +00:00
notBefore time . Time
notAfter time . Time
2019-12-20 21:30:05 +00:00
err error
code int
2018-10-05 21:48:36 +00:00
}
tests := map [ string ] func ( * testing . T ) * signTest {
2019-03-21 01:11:45 +00:00
"fail invalid signature" : func ( t * testing . T ) * signTest {
2018-10-05 21:48:36 +00:00
csr := getCSR ( t , priv )
2019-03-21 01:11:45 +00:00
csr . Signature = [ ] byte ( "foo" )
2018-10-05 21:48:36 +00:00
return & signTest {
2018-10-26 06:49:23 +00:00
auth : a ,
csr : csr ,
2019-03-21 01:11:45 +00:00
extraOpts : extraOpts ,
2018-10-26 06:49:23 +00:00
signOpts : signOpts ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Sign; invalid certificate request" ) ,
code : http . StatusBadRequest ,
2018-10-05 21:48:36 +00:00
}
} ,
2018-10-19 05:26:39 +00:00
"fail invalid extra option" : func ( t * testing . T ) * signTest {
2018-10-05 21:48:36 +00:00
csr := getCSR ( t , priv )
csr . Raw = [ ] byte ( "foo" )
return & signTest {
2018-10-26 06:49:23 +00:00
auth : a ,
csr : csr ,
2019-02-07 00:26:25 +00:00
extraOpts : append ( extraOpts , "42" ) ,
2018-10-26 06:49:23 +00:00
signOpts : signOpts ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Sign; invalid extra option type string" ) ,
code : http . StatusInternalServerError ,
2018-10-05 21:48:36 +00:00
}
} ,
2018-10-19 05:26:39 +00:00
"fail merge default ASN1DN" : func ( t * testing . T ) * signTest {
2018-10-05 21:48:36 +00:00
_a := testAuthority ( t )
_a . config . AuthorityConfig . Template = nil
csr := getCSR ( t , priv )
return & signTest {
2018-10-26 06:49:23 +00:00
auth : _a ,
csr : csr ,
2019-02-07 00:26:25 +00:00
extraOpts : extraOpts ,
2018-10-26 06:49:23 +00:00
signOpts : signOpts ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Sign: default ASN1DN template cannot be nil" ) ,
2020-07-21 00:51:33 +00:00
code : http . StatusUnauthorized ,
2018-10-05 21:48:36 +00:00
}
} ,
2018-10-19 05:26:39 +00:00
"fail create cert" : func ( t * testing . T ) * signTest {
2018-10-05 21:48:36 +00:00
_a := testAuthority ( t )
2020-02-11 22:05:37 +00:00
_a . x509Signer = nil
2018-10-05 21:48:36 +00:00
csr := getCSR ( t , priv )
return & signTest {
2018-10-26 06:49:23 +00:00
auth : _a ,
csr : csr ,
2019-03-12 01:15:24 +00:00
extraOpts : extraOpts ,
2018-10-26 06:49:23 +00:00
signOpts : signOpts ,
2020-07-21 00:51:33 +00:00
err : errors . New ( "authority.Sign; error creating certificate" ) ,
2019-12-20 21:30:05 +00:00
code : http . StatusInternalServerError ,
2018-10-19 05:26:39 +00:00
}
} ,
"fail provisioner duration claim" : func ( t * testing . T ) * signTest {
csr := getCSR ( t , priv )
2020-07-23 01:24:45 +00:00
_signOpts := provisioner . SignOptions {
2019-03-25 19:35:21 +00:00
NotBefore : provisioner . NewTimeDuration ( nb ) ,
NotAfter : provisioner . NewTimeDuration ( nb . Add ( time . Hour * 25 ) ) ,
2018-10-19 05:26:39 +00:00
}
return & signTest {
2018-10-26 06:49:23 +00:00
auth : a ,
csr : csr ,
2019-02-07 00:26:25 +00:00
extraOpts : extraOpts ,
2018-10-26 06:49:23 +00:00
signOpts : _signOpts ,
2020-01-24 21:42:00 +00:00
err : errors . New ( "authority.Sign: requested duration of 25h0m0s is more than the authorized maximum certificate duration of 24h1m0s" ) ,
2019-12-20 21:30:05 +00:00
code : http . StatusUnauthorized ,
2018-10-05 21:48:36 +00:00
}
} ,
2019-02-07 00:26:25 +00:00
"fail validate sans when adding common name not in claims" : func ( t * testing . T ) * signTest {
csr := getCSR ( t , priv , func ( csr * x509 . CertificateRequest ) {
csr . DNSNames = append ( csr . DNSNames , csr . Subject . CommonName )
} )
return & signTest {
auth : a ,
csr : csr ,
extraOpts : extraOpts ,
signOpts : signOpts ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Sign: certificate request does not contain the valid DNS names - got [test.smallstep.com smallstep test], want [test.smallstep.com]" ) ,
code : http . StatusUnauthorized ,
2019-02-07 00:26:25 +00:00
}
} ,
2019-08-27 00:52:49 +00:00
"fail rsa key too short" : func ( t * testing . T ) * signTest {
shortRSAKeyPEM := ` -- -- - BEGIN CERTIFICATE REQUEST -- -- -
2019-09-05 01:31:09 +00:00
MIIBhDCB7gIBADAZMRcwFQYDVQQDEw5zbWFsbHN0ZXAgdGVzdDCBnzANBgkqhkiG
9 w0BAQEFAAOBjQAwgYkCgYEA5JlgH99HvHHsCD6XTqqYj3bXU2oIlnYGoLVs7IJ4
k205rv5 / YWky2gjdpIv0Tnaf3o57IJ891lB7GiyO5iHIEUv5N9dVzrdUboyzk2uZ
7 JMMNB43CSLB2oNuwJjLeAM / yBzlhRnvpKjrNSfSV + cH54FXdnbFbcTFMStnjqKG
MeECAwEAAaAsMCoGCSqGSIb3DQEJDjEdMBswGQYDVR0RBBIwEIIOc21hbGxzdGVw
IHRlc3QwDQYJKoZIhvcNAQELBQADgYEAKwsbr8Zfcq05DgOoJ //cXMFK1SP8ktRU
N2 ++ E8Ww0Tet9oyNRArqxxS / UyVio63D3wynzRAB25PFGpYG1cN4b81Gv / foFUT6
W5kR63lNVHBHgQmv5mA8YFsfrJHstaz5k727v2LMHEYIf5 / 3i 16 d5zhuxUoaPTYr
ZYtQ9Ot36qc =
2019-08-27 00:52:49 +00:00
-- -- - END CERTIFICATE REQUEST -- -- - `
block , _ := pem . Decode ( [ ] byte ( shortRSAKeyPEM ) )
assert . FatalError ( t , err )
csr , err := x509 . ParseCertificateRequest ( block . Bytes )
assert . FatalError ( t , err )
return & signTest {
auth : a ,
csr : csr ,
extraOpts : extraOpts ,
signOpts : signOpts ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Sign: rsa key in CSR must be at least 2048 bits (256 bytes)" ) ,
code : http . StatusUnauthorized ,
2019-08-27 00:52:49 +00:00
}
} ,
2019-03-05 08:07:13 +00:00
"fail store cert in db" : func ( t * testing . T ) * signTest {
csr := getCSR ( t , priv )
_a := testAuthority ( t )
2019-12-20 21:30:05 +00:00
_a . db = & db . MockAuthDB {
MStoreCertificate : func ( crt * x509 . Certificate ) error {
return errors . New ( "force" )
2019-03-05 08:07:13 +00:00
} ,
}
return & signTest {
auth : _a ,
csr : csr ,
extraOpts : extraOpts ,
signOpts : signOpts ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Sign; error storing certificate in db: force" ) ,
code : http . StatusInternalServerError ,
2019-03-05 08:07:13 +00:00
}
} ,
2020-07-23 02:18:45 +00:00
"fail custom template" : func ( t * testing . T ) * signTest {
csr := getCSR ( t , priv )
testAuthority := testAuthority ( t )
p , ok := testAuthority . provisioners . Load ( "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc" )
if ! ok {
t . Fatal ( "provisioner not found" )
}
p . ( * provisioner . JWK ) . Options = & provisioner . Options {
2020-07-31 00:44:22 +00:00
X509 : & provisioner . X509Options { Template : ` {{ fail "fail message" }} ` } ,
2020-07-23 02:18:45 +00:00
}
testExtraOpts , err := testAuthority . Authorize ( ctx , token )
assert . FatalError ( t , err )
testAuthority . db = & db . MockAuthDB {
MStoreCertificate : func ( crt * x509 . Certificate ) error {
assert . Equals ( t , crt . Subject . CommonName , "smallstep test" )
return nil
} ,
}
return & signTest {
auth : testAuthority ,
csr : csr ,
extraOpts : testExtraOpts ,
signOpts : signOpts ,
err : errors . New ( "fail message" ) ,
code : http . StatusBadRequest ,
}
} ,
2018-10-19 05:26:39 +00:00
"ok" : func ( t * testing . T ) * signTest {
2018-10-05 21:48:36 +00:00
csr := getCSR ( t , priv )
2019-03-05 08:07:13 +00:00
_a := testAuthority ( t )
2019-12-20 21:30:05 +00:00
_a . db = & db . MockAuthDB {
MStoreCertificate : func ( crt * x509 . Certificate ) error {
2019-03-05 08:07:13 +00:00
assert . Equals ( t , crt . Subject . CommonName , "smallstep test" )
return nil
} ,
}
2018-10-05 21:48:36 +00:00
return & signTest {
2018-10-26 06:49:23 +00:00
auth : a ,
csr : csr ,
2019-02-07 00:26:25 +00:00
extraOpts : extraOpts ,
2018-10-26 06:49:23 +00:00
signOpts : signOpts ,
2020-03-31 18:41:36 +00:00
notBefore : signOpts . NotBefore . Time ( ) . Truncate ( time . Second ) ,
notAfter : signOpts . NotAfter . Time ( ) . Truncate ( time . Second ) ,
}
} ,
"ok with enforced modifier" : func ( t * testing . T ) * signTest {
2020-06-25 06:25:15 +00:00
bcExt := pkix . Extension { }
bcExt . Id = asn1 . ObjectIdentifier { 2 , 5 , 29 , 19 }
bcExt . Critical = false
bcExt . Value , err = asn1 . Marshal ( basicConstraints { IsCA : true , MaxPathLen : 4 } )
assert . FatalError ( t , err )
csr := getCSR ( t , priv , setExtraExtsCSR ( [ ] pkix . Extension {
bcExt ,
{ Id : stepOIDProvisioner , Value : [ ] byte ( "foo" ) } ,
{ Id : [ ] int { 1 , 1 , 1 } , Value : [ ] byte ( "bar" ) } } ) )
2020-03-31 18:41:36 +00:00
now := time . Now ( ) . UTC ( )
enforcedExtraOptions := append ( extraOpts , & certificateDurationEnforcer {
NotBefore : now ,
NotAfter : now . Add ( 365 * 24 * time . Hour ) ,
} )
_a := testAuthority ( t )
_a . db = & db . MockAuthDB {
MStoreCertificate : func ( crt * x509 . Certificate ) error {
assert . Equals ( t , crt . Subject . CommonName , "smallstep test" )
return nil
} ,
}
return & signTest {
auth : a ,
csr : csr ,
extraOpts : enforcedExtraOptions ,
signOpts : signOpts ,
notBefore : now . Truncate ( time . Second ) ,
notAfter : now . Add ( 365 * 24 * time . Hour ) . Truncate ( time . Second ) ,
2018-10-05 21:48:36 +00:00
}
2020-07-23 02:18:45 +00:00
} ,
"ok with custom template" : func ( t * testing . T ) * signTest {
csr := getCSR ( t , priv )
testAuthority := testAuthority ( t )
testAuthority . config . AuthorityConfig . Template = a . config . AuthorityConfig . Template
p , ok := testAuthority . provisioners . Load ( "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc" )
if ! ok {
t . Fatal ( "provisioner not found" )
}
p . ( * provisioner . JWK ) . Options = & provisioner . Options {
2020-07-31 00:44:22 +00:00
X509 : & provisioner . X509Options { Template : ` {
2020-07-23 02:18:45 +00:00
"subject" : { { toJson . Subject } } ,
"dnsNames" : { { toJson . Insecure . CR . DNSNames } } ,
"keyUsage" : [ "digitalSignature" ] ,
"extKeyUsage" : [ "serverAuth" , "clientAuth" ]
2020-07-31 00:44:22 +00:00
} ` } ,
2020-07-23 02:18:45 +00:00
}
testExtraOpts , err := testAuthority . Authorize ( ctx , token )
assert . FatalError ( t , err )
testAuthority . db = & db . MockAuthDB {
MStoreCertificate : func ( crt * x509 . Certificate ) error {
assert . Equals ( t , crt . Subject . CommonName , "smallstep test" )
return nil
} ,
}
return & signTest {
auth : testAuthority ,
csr : csr ,
extraOpts : testExtraOpts ,
signOpts : signOpts ,
notBefore : signOpts . NotBefore . Time ( ) . Truncate ( time . Second ) ,
notAfter : signOpts . NotAfter . Time ( ) . Truncate ( time . Second ) ,
}
2018-10-05 21:48:36 +00:00
} ,
}
for name , genTestCase := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := genTestCase ( t )
2019-10-09 19:57:12 +00:00
certChain , err := tc . auth . Sign ( tc . csr , tc . signOpts , tc . extraOpts ... )
2018-10-05 21:48:36 +00:00
if err != nil {
2019-12-20 21:30:05 +00:00
if assert . NotNil ( t , tc . err , fmt . Sprintf ( "unexpected error: %s" , err ) ) {
assert . Nil ( t , certChain )
sc , ok := err . ( errs . StatusCoder )
assert . Fatal ( t , ok , "error does not implement StatusCoder interface" )
assert . Equals ( t , sc . StatusCode ( ) , tc . code )
assert . HasPrefix ( t , err . Error ( ) , tc . err . Error ( ) )
ctxErr , ok := err . ( * errs . Error )
assert . Fatal ( t , ok , "error is not of type *errs.Error" )
assert . Equals ( t , ctxErr . Details [ "csr" ] , tc . csr )
assert . Equals ( t , ctxErr . Details [ "signOptions" ] , tc . signOpts )
2018-10-05 21:48:36 +00:00
}
} else {
2019-10-09 19:57:12 +00:00
leaf := certChain [ 0 ]
intermediate := certChain [ 1 ]
2018-10-05 21:48:36 +00:00
if assert . Nil ( t , tc . err ) {
2020-03-31 18:41:36 +00:00
assert . Equals ( t , leaf . NotBefore , tc . notBefore )
assert . Equals ( t , leaf . NotAfter , tc . notAfter )
2018-10-05 21:48:36 +00:00
tmplt := a . config . AuthorityConfig . Template
assert . Equals ( t , fmt . Sprintf ( "%v" , leaf . Subject ) ,
fmt . Sprintf ( "%v" , & pkix . Name {
Country : [ ] string { tmplt . Country } ,
Organization : [ ] string { tmplt . Organization } ,
Locality : [ ] string { tmplt . Locality } ,
StreetAddress : [ ] string { tmplt . StreetAddress } ,
Province : [ ] string { tmplt . Province } ,
2019-02-07 00:26:25 +00:00
CommonName : "smallstep test" ,
2018-10-05 21:48:36 +00:00
} ) )
assert . Equals ( t , leaf . Issuer , intermediate . Subject )
assert . Equals ( t , leaf . SignatureAlgorithm , x509 . ECDSAWithSHA256 )
assert . Equals ( t , leaf . PublicKeyAlgorithm , x509 . ECDSA )
assert . Equals ( t , leaf . ExtKeyUsage ,
[ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth , x509 . ExtKeyUsageClientAuth } )
2018-10-19 05:26:39 +00:00
assert . Equals ( t , leaf . DNSNames , [ ] string { "test.smallstep.com" } )
2018-10-05 21:48:36 +00:00
2020-07-21 00:51:33 +00:00
subjectKeyID , err := generateSubjectKeyID ( pub )
2018-10-05 21:48:36 +00:00
assert . FatalError ( t , err )
2020-07-21 00:51:33 +00:00
assert . Equals ( t , leaf . SubjectKeyId , subjectKeyID )
2018-10-05 21:48:36 +00:00
2020-02-11 22:05:37 +00:00
assert . Equals ( t , leaf . AuthorityKeyId , a . x509Issuer . SubjectKeyId )
2018-10-05 21:48:36 +00:00
2018-10-26 06:49:23 +00:00
// Verify Provisioner OID
found := 0
for _ , ext := range leaf . Extensions {
2020-06-25 06:25:15 +00:00
switch {
case ext . Id . Equal ( stepOIDProvisioner ) :
found ++
val := stepProvisionerASN1 { }
_ , err := asn1 . Unmarshal ( ext . Value , & val )
assert . FatalError ( t , err )
assert . Equals ( t , val . Type , provisionerTypeJWK )
assert . Equals ( t , val . Name , [ ] byte ( p . Name ) )
assert . Equals ( t , val . CredentialID , [ ] byte ( p . Key . KeyID ) )
// Basic Constraints
case ext . Id . Equal ( asn1 . ObjectIdentifier ( [ ] int { 2 , 5 , 29 , 19 } ) ) :
val := basicConstraints { }
_ , err := asn1 . Unmarshal ( ext . Value , & val )
assert . FatalError ( t , err )
assert . False ( t , val . IsCA , false )
assert . Equals ( t , val . MaxPathLen , 0 )
2018-10-26 06:49:23 +00:00
}
}
2018-10-26 21:24:16 +00:00
assert . Equals ( t , found , 1 )
2020-06-25 06:25:15 +00:00
assert . Len ( t , 6 , leaf . Extensions )
2018-10-26 06:49:23 +00:00
2020-02-11 22:05:37 +00:00
realIntermediate , err := x509 . ParseCertificate ( a . x509Issuer . Raw )
2018-10-05 21:48:36 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , intermediate , realIntermediate )
}
}
} )
}
}
2020-07-09 19:11:40 +00:00
func TestAuthority_Renew ( t * testing . T ) {
pub , _ , err := keys . GenerateDefaultKeyPair ( )
assert . FatalError ( t , err )
a := testAuthority ( t )
a . config . AuthorityConfig . Template = & x509util . ASN1DN {
Country : "Tazmania" ,
Organization : "Acme Co" ,
Locality : "Landscapes" ,
Province : "Sudden Cliffs" ,
StreetAddress : "TNT" ,
CommonName : "renew" ,
}
2020-07-21 00:51:33 +00:00
certModToWithOptions := func ( m provisioner . CertificateModifierFunc ) x509util . WithOption {
return func ( p x509util . Profile ) error {
crt := p . Subject ( )
2020-07-23 01:24:45 +00:00
return m . Modify ( crt , provisioner . SignOptions { } )
2020-07-21 00:51:33 +00:00
}
}
2020-07-09 19:11:40 +00:00
now := time . Now ( ) . UTC ( )
nb1 := now . Add ( - time . Minute * 7 )
na1 := now
2020-07-23 01:24:45 +00:00
so := & provisioner . SignOptions {
2020-07-09 19:11:40 +00:00
NotBefore : provisioner . NewTimeDuration ( nb1 ) ,
NotAfter : provisioner . NewTimeDuration ( na1 ) ,
}
leaf , err := x509util . NewLeafProfile ( "renew" , a . x509Issuer , a . x509Signer ,
x509util . WithNotBeforeAfterDuration ( so . NotBefore . Time ( ) , so . NotAfter . Time ( ) , 0 ) ,
2020-07-21 00:51:33 +00:00
certModToWithOptions ( withDefaultASN1DN ( a . config . AuthorityConfig . Template ) ) ,
2020-07-09 19:11:40 +00:00
x509util . WithPublicKey ( pub ) , x509util . WithHosts ( "test.smallstep.com,test" ) ,
withProvisionerOID ( "Max" , a . config . AuthorityConfig . Provisioners [ 0 ] . ( * provisioner . JWK ) . Key . KeyID ) )
assert . FatalError ( t , err )
certBytes , err := leaf . CreateCertificate ( )
assert . FatalError ( t , err )
cert , err := x509 . ParseCertificate ( certBytes )
assert . FatalError ( t , err )
leafNoRenew , err := x509util . NewLeafProfile ( "norenew" , a . x509Issuer , a . x509Signer ,
x509util . WithNotBeforeAfterDuration ( so . NotBefore . Time ( ) , so . NotAfter . Time ( ) , 0 ) ,
2020-07-21 00:51:33 +00:00
certModToWithOptions ( withDefaultASN1DN ( a . config . AuthorityConfig . Template ) ) ,
2020-07-09 19:11:40 +00:00
x509util . WithPublicKey ( pub ) , x509util . WithHosts ( "test.smallstep.com,test" ) ,
withProvisionerOID ( "dev" , a . config . AuthorityConfig . Provisioners [ 2 ] . ( * provisioner . JWK ) . Key . KeyID ) ,
)
assert . FatalError ( t , err )
certBytesNoRenew , err := leafNoRenew . CreateCertificate ( )
assert . FatalError ( t , err )
certNoRenew , err := x509 . ParseCertificate ( certBytesNoRenew )
assert . FatalError ( t , err )
type renewTest struct {
auth * Authority
cert * x509 . Certificate
err error
code int
}
tests := map [ string ] func ( ) ( * renewTest , error ) {
"fail/create-cert" : func ( ) ( * renewTest , error ) {
_a := testAuthority ( t )
_a . x509Signer = nil
return & renewTest {
auth : _a ,
cert : cert ,
err : errors . New ( "authority.Rekey; error renewing certificate from existing server certificate" ) ,
code : http . StatusInternalServerError ,
} , nil
} ,
"fail/unauthorized" : func ( ) ( * renewTest , error ) {
return & renewTest {
cert : certNoRenew ,
err : errors . New ( "authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk" ) ,
code : http . StatusUnauthorized ,
} , nil
} ,
"ok" : func ( ) ( * renewTest , error ) {
return & renewTest {
auth : a ,
cert : cert ,
} , nil
} ,
"ok/success-new-intermediate" : func ( ) ( * renewTest , error ) {
newRootProfile , err := x509util . NewRootProfile ( "new-root" )
assert . FatalError ( t , err )
newRootBytes , err := newRootProfile . CreateCertificate ( )
assert . FatalError ( t , err )
newRootCert , err := x509 . ParseCertificate ( newRootBytes )
assert . FatalError ( t , err )
newIntermediateProfile , err := x509util . NewIntermediateProfile ( "new-intermediate" ,
newRootCert , newRootProfile . SubjectPrivateKey ( ) )
assert . FatalError ( t , err )
newIntermediateBytes , err := newIntermediateProfile . CreateCertificate ( )
assert . FatalError ( t , err )
newIntermediateCert , err := x509 . ParseCertificate ( newIntermediateBytes )
assert . FatalError ( t , err )
_a := testAuthority ( t )
_a . x509Signer = newIntermediateProfile . SubjectPrivateKey ( ) . ( crypto . Signer )
_a . x509Issuer = newIntermediateCert
return & renewTest {
auth : _a ,
cert : cert ,
} , nil
} ,
}
for name , genTestCase := range tests {
t . Run ( name , func ( t * testing . T ) {
tc , err := genTestCase ( )
assert . FatalError ( t , err )
var certChain [ ] * x509 . Certificate
if tc . auth != nil {
certChain , err = tc . auth . Renew ( tc . cert )
} else {
certChain , err = a . Renew ( tc . cert )
}
if err != nil {
if assert . NotNil ( t , tc . err , fmt . Sprintf ( "unexpected error: %s" , err ) ) {
assert . Nil ( t , certChain )
sc , ok := err . ( errs . StatusCoder )
assert . Fatal ( t , ok , "error does not implement StatusCoder interface" )
assert . Equals ( t , sc . StatusCode ( ) , tc . code )
assert . HasPrefix ( t , err . Error ( ) , tc . err . Error ( ) )
ctxErr , ok := err . ( * errs . Error )
assert . Fatal ( t , ok , "error is not of type *errs.Error" )
assert . Equals ( t , ctxErr . Details [ "serialNumber" ] , tc . cert . SerialNumber . String ( ) )
}
} else {
leaf := certChain [ 0 ]
intermediate := certChain [ 1 ]
if assert . Nil ( t , tc . err ) {
assert . Equals ( t , leaf . NotAfter . Sub ( leaf . NotBefore ) , tc . cert . NotAfter . Sub ( cert . NotBefore ) )
assert . True ( t , leaf . NotBefore . After ( now . Add ( - 2 * time . Minute ) ) )
assert . True ( t , leaf . NotBefore . Before ( now . Add ( time . Minute ) ) )
expiry := now . Add ( time . Minute * 7 )
assert . True ( t , leaf . NotAfter . After ( expiry . Add ( - 2 * time . Minute ) ) )
assert . True ( t , leaf . NotAfter . Before ( expiry . Add ( time . Minute ) ) )
tmplt := a . config . AuthorityConfig . Template
assert . Equals ( t , fmt . Sprintf ( "%v" , leaf . Subject ) ,
fmt . Sprintf ( "%v" , & pkix . Name {
Country : [ ] string { tmplt . Country } ,
Organization : [ ] string { tmplt . Organization } ,
Locality : [ ] string { tmplt . Locality } ,
StreetAddress : [ ] string { tmplt . StreetAddress } ,
Province : [ ] string { tmplt . Province } ,
CommonName : tmplt . CommonName ,
} ) )
assert . Equals ( t , leaf . Issuer , intermediate . Subject )
assert . Equals ( t , leaf . SignatureAlgorithm , x509 . ECDSAWithSHA256 )
assert . Equals ( t , leaf . PublicKeyAlgorithm , x509 . ECDSA )
assert . Equals ( t , leaf . ExtKeyUsage ,
[ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth , x509 . ExtKeyUsageClientAuth } )
assert . Equals ( t , leaf . DNSNames , [ ] string { "test.smallstep.com" , "test" } )
2020-07-21 00:51:33 +00:00
subjectKeyID , err := generateSubjectKeyID ( pub )
2020-07-09 19:11:40 +00:00
assert . FatalError ( t , err )
2020-07-21 00:51:33 +00:00
assert . Equals ( t , leaf . SubjectKeyId , subjectKeyID )
2020-07-09 19:11:40 +00:00
// We did not change the intermediate before renewing.
if a . x509Issuer . SerialNumber == tc . auth . x509Issuer . SerialNumber {
assert . Equals ( t , leaf . AuthorityKeyId , a . x509Issuer . SubjectKeyId )
// Compare extensions: they can be in a different order
for _ , ext1 := range tc . cert . Extensions {
//skip SubjectKeyIdentifier
if ext1 . Id . Equal ( oidSubjectKeyIdentifier ) {
continue
}
found := false
for _ , ext2 := range leaf . Extensions {
if reflect . DeepEqual ( ext1 , ext2 ) {
found = true
break
}
}
if ! found {
t . Errorf ( "x509 extension %s not found in renewed certificate" , ext1 . Id . String ( ) )
}
}
} else {
// We did change the intermediate before renewing.
assert . Equals ( t , leaf . AuthorityKeyId , tc . auth . x509Issuer . SubjectKeyId )
// Compare extensions: they can be in a different order
for _ , ext1 := range tc . cert . Extensions {
//skip SubjectKeyIdentifier
if ext1 . Id . Equal ( oidSubjectKeyIdentifier ) {
continue
}
// The authority key id extension should be different b/c the intermediates are different.
if ext1 . Id . Equal ( oidAuthorityKeyIdentifier ) {
for _ , ext2 := range leaf . Extensions {
assert . False ( t , reflect . DeepEqual ( ext1 , ext2 ) )
}
continue
} else {
found := false
for _ , ext2 := range leaf . Extensions {
if reflect . DeepEqual ( ext1 , ext2 ) {
found = true
break
}
}
if ! found {
t . Errorf ( "x509 extension %s not found in renewed certificate" , ext1 . Id . String ( ) )
}
}
}
}
realIntermediate , err := x509 . ParseCertificate ( tc . auth . x509Issuer . Raw )
assert . FatalError ( t , err )
assert . Equals ( t , intermediate , realIntermediate )
}
}
} )
}
}
2020-07-08 06:17:59 +00:00
func TestAuthority_Rekey ( t * testing . T ) {
2018-10-05 21:48:36 +00:00
pub , _ , err := keys . GenerateDefaultKeyPair ( )
assert . FatalError ( t , err )
2020-07-05 16:47:57 +00:00
pub1 , _ , err := keys . GenerateDefaultKeyPair ( )
2018-10-05 21:48:36 +00:00
assert . FatalError ( t , err )
a := testAuthority ( t )
a . config . AuthorityConfig . Template = & x509util . ASN1DN {
Country : "Tazmania" ,
Organization : "Acme Co" ,
Locality : "Landscapes" ,
Province : "Sudden Cliffs" ,
StreetAddress : "TNT" ,
CommonName : "renew" ,
}
2020-07-21 21:34:55 +00:00
certModToWithOptions := func ( m provisioner . CertificateModifierFunc ) x509util . WithOption {
return func ( p x509util . Profile ) error {
crt := p . Subject ( )
2020-07-23 01:24:45 +00:00
return m . Modify ( crt , provisioner . SignOptions { } )
2020-07-21 21:34:55 +00:00
}
}
2018-10-05 21:48:36 +00:00
now := time . Now ( ) . UTC ( )
nb1 := now . Add ( - time . Minute * 7 )
na1 := now
2020-07-23 01:24:45 +00:00
so := & provisioner . SignOptions {
2019-03-25 19:35:21 +00:00
NotBefore : provisioner . NewTimeDuration ( nb1 ) ,
NotAfter : provisioner . NewTimeDuration ( na1 ) ,
2018-10-05 21:48:36 +00:00
}
2020-02-11 22:05:37 +00:00
leaf , err := x509util . NewLeafProfile ( "renew" , a . x509Issuer , a . x509Signer ,
2019-03-25 19:35:21 +00:00
x509util . WithNotBeforeAfterDuration ( so . NotBefore . Time ( ) , so . NotAfter . Time ( ) , 0 ) ,
2020-07-21 21:34:55 +00:00
certModToWithOptions ( withDefaultASN1DN ( a . config . AuthorityConfig . Template ) ) ,
2019-02-15 00:44:36 +00:00
x509util . WithPublicKey ( pub ) , x509util . WithHosts ( "test.smallstep.com,test" ) ,
2019-03-08 03:30:17 +00:00
withProvisionerOID ( "Max" , a . config . AuthorityConfig . Provisioners [ 0 ] . ( * provisioner . JWK ) . Key . KeyID ) )
2018-10-05 21:48:36 +00:00
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
certBytes , err := leaf . CreateCertificate ( )
2018-10-05 21:48:36 +00:00
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
cert , err := x509 . ParseCertificate ( certBytes )
2018-10-05 21:48:36 +00:00
assert . FatalError ( t , err )
2020-02-11 22:05:37 +00:00
leafNoRenew , err := x509util . NewLeafProfile ( "norenew" , a . x509Issuer , a . x509Signer ,
2019-03-25 19:35:21 +00:00
x509util . WithNotBeforeAfterDuration ( so . NotBefore . Time ( ) , so . NotAfter . Time ( ) , 0 ) ,
2020-07-21 21:34:55 +00:00
certModToWithOptions ( withDefaultASN1DN ( a . config . AuthorityConfig . Template ) ) ,
2018-11-01 22:43:24 +00:00
x509util . WithPublicKey ( pub ) , x509util . WithHosts ( "test.smallstep.com,test" ) ,
2019-03-08 03:30:17 +00:00
withProvisionerOID ( "dev" , a . config . AuthorityConfig . Provisioners [ 2 ] . ( * provisioner . JWK ) . Key . KeyID ) ,
2018-11-01 22:43:24 +00:00
)
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
certBytesNoRenew , err := leafNoRenew . CreateCertificate ( )
2018-11-01 22:43:24 +00:00
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
certNoRenew , err := x509 . ParseCertificate ( certBytesNoRenew )
2018-11-01 22:43:24 +00:00
assert . FatalError ( t , err )
2018-10-05 21:48:36 +00:00
type renewTest struct {
auth * Authority
2019-12-20 21:30:05 +00:00
cert * x509 . Certificate
2020-07-09 19:11:40 +00:00
pk crypto . PublicKey
2019-12-20 21:30:05 +00:00
err error
code int
2018-10-05 21:48:36 +00:00
}
tests := map [ string ] func ( ) ( * renewTest , error ) {
2020-07-09 19:11:40 +00:00
"fail/create-cert" : func ( ) ( * renewTest , error ) {
2018-10-05 21:48:36 +00:00
_a := testAuthority ( t )
2020-02-11 22:05:37 +00:00
_a . x509Signer = nil
2018-10-05 21:48:36 +00:00
return & renewTest {
auth : _a ,
2019-12-20 21:30:05 +00:00
cert : cert ,
2020-07-08 06:17:59 +00:00
err : errors . New ( "authority.Rekey; error renewing certificate from existing server certificate" ) ,
2019-12-20 21:30:05 +00:00
code : http . StatusInternalServerError ,
2018-10-05 21:48:36 +00:00
} , nil
} ,
2020-07-09 19:11:40 +00:00
"fail/unauthorized" : func ( ) ( * renewTest , error ) {
2018-11-01 22:43:24 +00:00
return & renewTest {
2019-12-20 21:30:05 +00:00
cert : certNoRenew ,
2020-07-08 06:17:59 +00:00
err : errors . New ( "authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk" ) ,
2019-12-20 21:30:05 +00:00
code : http . StatusUnauthorized ,
2018-11-01 22:43:24 +00:00
} , nil
} ,
2020-07-09 19:11:40 +00:00
"ok/renew" : func ( ) ( * renewTest , error ) {
return & renewTest {
auth : a ,
cert : cert ,
} , nil
} ,
"ok/rekey" : func ( ) ( * renewTest , error ) {
2018-10-05 21:48:36 +00:00
return & renewTest {
2019-02-15 03:17:42 +00:00
auth : a ,
2019-12-20 21:30:05 +00:00
cert : cert ,
2020-07-09 19:11:40 +00:00
pk : pub1 ,
2019-02-15 03:17:42 +00:00
} , nil
} ,
2020-07-09 19:11:40 +00:00
"ok/renew/success-new-intermediate" : func ( ) ( * renewTest , error ) {
2019-02-15 03:17:42 +00:00
newRootProfile , err := x509util . NewRootProfile ( "new-root" )
assert . FatalError ( t , err )
newRootBytes , err := newRootProfile . CreateCertificate ( )
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
newRootCert , err := x509 . ParseCertificate ( newRootBytes )
2019-02-15 03:17:42 +00:00
assert . FatalError ( t , err )
newIntermediateProfile , err := x509util . NewIntermediateProfile ( "new-intermediate" ,
2019-12-20 21:30:05 +00:00
newRootCert , newRootProfile . SubjectPrivateKey ( ) )
2019-02-15 03:17:42 +00:00
assert . FatalError ( t , err )
newIntermediateBytes , err := newIntermediateProfile . CreateCertificate ( )
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
newIntermediateCert , err := x509 . ParseCertificate ( newIntermediateBytes )
2019-02-15 03:17:42 +00:00
assert . FatalError ( t , err )
_a := testAuthority ( t )
2020-02-11 22:05:37 +00:00
_a . x509Signer = newIntermediateProfile . SubjectPrivateKey ( ) . ( crypto . Signer )
_a . x509Issuer = newIntermediateCert
2019-02-15 03:17:42 +00:00
return & renewTest {
auth : _a ,
2019-12-20 21:30:05 +00:00
cert : cert ,
2018-10-05 21:48:36 +00:00
} , nil
} ,
}
for name , genTestCase := range tests {
t . Run ( name , func ( t * testing . T ) {
tc , err := genTestCase ( )
assert . FatalError ( t , err )
2019-10-09 19:57:12 +00:00
var certChain [ ] * x509 . Certificate
2018-10-05 21:48:36 +00:00
if tc . auth != nil {
2020-07-09 19:11:40 +00:00
certChain , err = tc . auth . Rekey ( tc . cert , tc . pk )
2018-10-05 21:48:36 +00:00
} else {
2020-07-09 19:11:40 +00:00
certChain , err = a . Rekey ( tc . cert , tc . pk )
2018-10-05 21:48:36 +00:00
}
if err != nil {
2019-12-20 21:30:05 +00:00
if assert . NotNil ( t , tc . err , fmt . Sprintf ( "unexpected error: %s" , err ) ) {
assert . Nil ( t , certChain )
sc , ok := err . ( errs . StatusCoder )
assert . Fatal ( t , ok , "error does not implement StatusCoder interface" )
assert . Equals ( t , sc . StatusCode ( ) , tc . code )
assert . HasPrefix ( t , err . Error ( ) , tc . err . Error ( ) )
ctxErr , ok := err . ( * errs . Error )
assert . Fatal ( t , ok , "error is not of type *errs.Error" )
assert . Equals ( t , ctxErr . Details [ "serialNumber" ] , tc . cert . SerialNumber . String ( ) )
2018-10-05 21:48:36 +00:00
}
} else {
2019-10-09 19:57:12 +00:00
leaf := certChain [ 0 ]
intermediate := certChain [ 1 ]
2018-10-05 21:48:36 +00:00
if assert . Nil ( t , tc . err ) {
2019-12-20 21:30:05 +00:00
assert . Equals ( t , leaf . NotAfter . Sub ( leaf . NotBefore ) , tc . cert . NotAfter . Sub ( cert . NotBefore ) )
2018-10-05 21:48:36 +00:00
2019-12-20 21:30:05 +00:00
assert . True ( t , leaf . NotBefore . After ( now . Add ( - 2 * time . Minute ) ) )
2018-10-05 21:48:36 +00:00
assert . True ( t , leaf . NotBefore . Before ( now . Add ( time . Minute ) ) )
expiry := now . Add ( time . Minute * 7 )
2019-12-20 21:30:05 +00:00
assert . True ( t , leaf . NotAfter . After ( expiry . Add ( - 2 * time . Minute ) ) )
2018-10-05 21:48:36 +00:00
assert . True ( t , leaf . NotAfter . Before ( expiry . Add ( time . Minute ) ) )
tmplt := a . config . AuthorityConfig . Template
assert . Equals ( t , fmt . Sprintf ( "%v" , leaf . Subject ) ,
fmt . Sprintf ( "%v" , & pkix . Name {
Country : [ ] string { tmplt . Country } ,
Organization : [ ] string { tmplt . Organization } ,
Locality : [ ] string { tmplt . Locality } ,
StreetAddress : [ ] string { tmplt . StreetAddress } ,
Province : [ ] string { tmplt . Province } ,
CommonName : tmplt . CommonName ,
} ) )
assert . Equals ( t , leaf . Issuer , intermediate . Subject )
assert . Equals ( t , leaf . SignatureAlgorithm , x509 . ECDSAWithSHA256 )
assert . Equals ( t , leaf . PublicKeyAlgorithm , x509 . ECDSA )
assert . Equals ( t , leaf . ExtKeyUsage ,
[ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth , x509 . ExtKeyUsageClientAuth } )
assert . Equals ( t , leaf . DNSNames , [ ] string { "test.smallstep.com" , "test" } )
2020-07-09 19:11:40 +00:00
// Test Public Key and SubjectKeyId
expectedPK := tc . pk
if tc . pk == nil {
expectedPK = cert . PublicKey
}
assert . Equals ( t , leaf . PublicKey , expectedPK )
2020-07-21 21:34:55 +00:00
subjectKeyID , err := generateSubjectKeyID ( expectedPK )
2018-10-05 21:48:36 +00:00
assert . FatalError ( t , err )
2020-07-21 21:34:55 +00:00
assert . Equals ( t , leaf . SubjectKeyId , subjectKeyID )
2020-07-09 19:11:40 +00:00
if tc . pk == nil {
assert . Equals ( t , leaf . SubjectKeyId , cert . SubjectKeyId )
}
2018-10-05 21:48:36 +00:00
2019-02-15 03:17:42 +00:00
// We did not change the intermediate before renewing.
2020-02-11 22:05:37 +00:00
if a . x509Issuer . SerialNumber == tc . auth . x509Issuer . SerialNumber {
assert . Equals ( t , leaf . AuthorityKeyId , a . x509Issuer . SubjectKeyId )
2019-02-15 03:17:42 +00:00
// Compare extensions: they can be in a different order
2019-12-20 21:30:05 +00:00
for _ , ext1 := range tc . cert . Extensions {
2020-07-05 16:47:57 +00:00
//skip SubjectKeyIdentifier
if ext1 . Id . Equal ( oidSubjectKeyIdentifier ) {
continue
}
2019-02-15 03:17:42 +00:00
found := false
for _ , ext2 := range leaf . Extensions {
if reflect . DeepEqual ( ext1 , ext2 ) {
found = true
break
}
}
if ! found {
t . Errorf ( "x509 extension %s not found in renewed certificate" , ext1 . Id . String ( ) )
2019-02-15 00:44:36 +00:00
}
}
2019-02-15 03:17:42 +00:00
} else {
// We did change the intermediate before renewing.
2020-02-11 22:05:37 +00:00
assert . Equals ( t , leaf . AuthorityKeyId , tc . auth . x509Issuer . SubjectKeyId )
2019-02-15 03:17:42 +00:00
// Compare extensions: they can be in a different order
2019-12-20 21:30:05 +00:00
for _ , ext1 := range tc . cert . Extensions {
2020-07-05 16:47:57 +00:00
//skip SubjectKeyIdentifier
if ext1 . Id . Equal ( oidSubjectKeyIdentifier ) {
continue
}
2019-02-15 03:17:42 +00:00
// The authority key id extension should be different b/c the intermediates are different.
if ext1 . Id . Equal ( oidAuthorityKeyIdentifier ) {
for _ , ext2 := range leaf . Extensions {
assert . False ( t , reflect . DeepEqual ( ext1 , ext2 ) )
}
continue
} else {
found := false
for _ , ext2 := range leaf . Extensions {
if reflect . DeepEqual ( ext1 , ext2 ) {
found = true
break
}
}
if ! found {
t . Errorf ( "x509 extension %s not found in renewed certificate" , ext1 . Id . String ( ) )
}
}
2019-02-15 00:44:36 +00:00
}
}
2019-02-15 03:17:42 +00:00
2020-02-11 22:05:37 +00:00
realIntermediate , err := x509 . ParseCertificate ( tc . auth . x509Issuer . Raw )
2019-02-15 03:17:42 +00:00
assert . FatalError ( t , err )
assert . Equals ( t , intermediate , realIntermediate )
2018-10-05 21:48:36 +00:00
}
}
} )
}
}
2019-12-20 21:30:05 +00:00
func TestAuthority_GetTLSOptions ( t * testing . T ) {
2018-10-05 21:48:36 +00:00
type renewTest struct {
auth * Authority
opts * tlsutil . TLSOptions
}
tests := map [ string ] func ( ) ( * renewTest , error ) {
"default" : func ( ) ( * renewTest , error ) {
a := testAuthority ( t )
return & renewTest { auth : a , opts : & DefaultTLSOptions } , nil
} ,
"non-default" : func ( ) ( * renewTest , error ) {
a := testAuthority ( t )
a . config . TLS = & tlsutil . TLSOptions {
CipherSuites : x509util . CipherSuites {
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305" ,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" ,
} ,
MinVersion : 1.0 ,
MaxVersion : 1.1 ,
Renegotiation : true ,
}
return & renewTest { auth : a , opts : a . config . TLS } , nil
} ,
}
for name , genTestCase := range tests {
t . Run ( name , func ( t * testing . T ) {
tc , err := genTestCase ( )
assert . FatalError ( t , err )
opts := tc . auth . GetTLSOptions ( )
assert . Equals ( t , opts , tc . opts )
} )
}
}
2019-03-05 08:07:13 +00:00
2019-12-20 21:30:05 +00:00
func TestAuthority_Revoke ( t * testing . T ) {
2019-03-05 08:07:13 +00:00
reasonCode := 2
reason := "bob was let go"
validIssuer := "step-cli"
2019-12-20 21:30:05 +00:00
validAudience := testAudiences . Revoke
2019-03-05 08:07:13 +00:00
now := time . Now ( ) . UTC ( )
jwk , err := jose . ParseKey ( "testdata/secrets/step_cli_key_priv.jwk" , jose . WithPassword ( [ ] byte ( "pass" ) ) )
assert . FatalError ( t , err )
sig , err := jose . NewSigner ( jose . SigningKey { Algorithm : jose . ES256 , Key : jwk . Key } ,
( & jose . SignerOptions { } ) . WithType ( "JWT" ) . WithHeader ( "kid" , jwk . KeyID ) )
assert . FatalError ( t , err )
2019-12-20 21:30:05 +00:00
a := testAuthority ( t )
2019-03-05 08:07:13 +00:00
type test struct {
2019-12-20 21:30:05 +00:00
auth * Authority
opts * RevokeOptions
err error
code int
checkErrDetails func ( err * errs . Error )
2019-03-05 08:07:13 +00:00
}
tests := map [ string ] func ( ) test {
2019-12-20 21:30:05 +00:00
"fail/token/authorizeRevoke error" : func ( ) test {
2019-03-05 08:07:13 +00:00
return test {
2019-12-20 21:30:05 +00:00
auth : a ,
2019-03-05 08:07:13 +00:00
opts : & RevokeOptions {
OTT : "foo" ,
Serial : "sn" ,
ReasonCode : reasonCode ,
Reason : reason ,
} ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Revoke; error parsing token" ) ,
code : http . StatusUnauthorized ,
2019-03-05 08:07:13 +00:00
}
} ,
2019-12-20 21:30:05 +00:00
"fail/nil-db" : func ( ) test {
2019-03-05 08:07:13 +00:00
cl := jwt . Claims {
Subject : "sn" ,
Issuer : validIssuer ,
NotBefore : jwt . NewNumericDate ( now ) ,
Expiry : jwt . NewNumericDate ( now . Add ( time . Minute ) ) ,
Audience : validAudience ,
ID : "44" ,
}
raw , err := jwt . Signed ( sig ) . Claims ( cl ) . CompactSerialize ( )
assert . FatalError ( t , err )
return test {
2019-12-20 21:30:05 +00:00
auth : a ,
2019-03-05 08:07:13 +00:00
opts : & RevokeOptions {
Serial : "sn" ,
ReasonCode : reasonCode ,
Reason : reason ,
OTT : raw ,
} ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Revoke; no persistence layer configured" ) ,
code : http . StatusNotImplemented ,
checkErrDetails : func ( err * errs . Error ) {
assert . Equals ( t , err . Details [ "token" ] , raw )
assert . Equals ( t , err . Details [ "tokenID" ] , "44" )
assert . Equals ( t , err . Details [ "provisionerID" ] , "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc" )
} ,
2019-03-05 08:07:13 +00:00
}
} ,
2019-12-20 21:30:05 +00:00
"fail/db-revoke" : func ( ) test {
_a := testAuthority ( t , WithDatabase ( & db . MockAuthDB {
MUseToken : func ( id , tok string ) ( bool , error ) {
2019-05-02 22:26:18 +00:00
return true , nil
} ,
2019-12-20 21:30:05 +00:00
Err : errors . New ( "force" ) ,
} ) )
2019-03-05 08:07:13 +00:00
cl := jwt . Claims {
Subject : "sn" ,
Issuer : validIssuer ,
NotBefore : jwt . NewNumericDate ( now ) ,
Expiry : jwt . NewNumericDate ( now . Add ( time . Minute ) ) ,
Audience : validAudience ,
ID : "44" ,
}
raw , err := jwt . Signed ( sig ) . Claims ( cl ) . CompactSerialize ( )
assert . FatalError ( t , err )
return test {
2019-12-20 21:30:05 +00:00
auth : _a ,
2019-03-05 08:07:13 +00:00
opts : & RevokeOptions {
Serial : "sn" ,
ReasonCode : reasonCode ,
Reason : reason ,
OTT : raw ,
} ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Revoke: force" ) ,
code : http . StatusInternalServerError ,
checkErrDetails : func ( err * errs . Error ) {
assert . Equals ( t , err . Details [ "token" ] , raw )
assert . Equals ( t , err . Details [ "tokenID" ] , "44" )
assert . Equals ( t , err . Details [ "provisionerID" ] , "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc" )
} ,
2019-03-05 08:07:13 +00:00
}
} ,
2019-12-20 21:30:05 +00:00
"fail/already-revoked" : func ( ) test {
_a := testAuthority ( t , WithDatabase ( & db . MockAuthDB {
MUseToken : func ( id , tok string ) ( bool , error ) {
2019-05-02 22:26:18 +00:00
return true , nil
} ,
2019-12-20 21:30:05 +00:00
Err : db . ErrAlreadyExists ,
} ) )
2019-03-05 08:07:13 +00:00
cl := jwt . Claims {
Subject : "sn" ,
Issuer : validIssuer ,
NotBefore : jwt . NewNumericDate ( now ) ,
Expiry : jwt . NewNumericDate ( now . Add ( time . Minute ) ) ,
Audience : validAudience ,
ID : "44" ,
}
raw , err := jwt . Signed ( sig ) . Claims ( cl ) . CompactSerialize ( )
assert . FatalError ( t , err )
return test {
2019-12-20 21:30:05 +00:00
auth : _a ,
2019-03-05 08:07:13 +00:00
opts : & RevokeOptions {
Serial : "sn" ,
ReasonCode : reasonCode ,
Reason : reason ,
OTT : raw ,
} ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Revoke; certificate with serial number sn has already been revoked" ) ,
code : http . StatusBadRequest ,
checkErrDetails : func ( err * errs . Error ) {
assert . Equals ( t , err . Details [ "token" ] , raw )
assert . Equals ( t , err . Details [ "tokenID" ] , "44" )
assert . Equals ( t , err . Details [ "provisionerID" ] , "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc" )
} ,
2019-03-05 08:07:13 +00:00
}
} ,
"ok/token" : func ( ) test {
2019-12-20 21:30:05 +00:00
_a := testAuthority ( t , WithDatabase ( & db . MockAuthDB {
MUseToken : func ( id , tok string ) ( bool , error ) {
2019-05-02 22:26:18 +00:00
return true , nil
} ,
2019-12-20 21:30:05 +00:00
} ) )
2019-03-05 08:07:13 +00:00
cl := jwt . Claims {
Subject : "sn" ,
Issuer : validIssuer ,
NotBefore : jwt . NewNumericDate ( now ) ,
Expiry : jwt . NewNumericDate ( now . Add ( time . Minute ) ) ,
Audience : validAudience ,
ID : "44" ,
}
raw , err := jwt . Signed ( sig ) . Claims ( cl ) . CompactSerialize ( )
assert . FatalError ( t , err )
return test {
2019-12-20 21:30:05 +00:00
auth : _a ,
2019-03-05 08:07:13 +00:00
opts : & RevokeOptions {
Serial : "sn" ,
ReasonCode : reasonCode ,
Reason : reason ,
OTT : raw ,
} ,
}
} ,
"ok/mTLS" : func ( ) test {
2019-12-20 21:30:05 +00:00
_a := testAuthority ( t , WithDatabase ( & db . MockAuthDB { } ) )
2019-03-05 08:07:13 +00:00
crt , err := pemutil . ReadCertificate ( "./testdata/certs/foo.crt" )
assert . FatalError ( t , err )
return test {
2019-12-20 21:30:05 +00:00
auth : _a ,
2019-03-05 08:07:13 +00:00
opts : & RevokeOptions {
Crt : crt ,
Serial : "102012593071130646873265215610956555026" ,
ReasonCode : reasonCode ,
Reason : reason ,
MTLS : true ,
} ,
}
} ,
}
for name , f := range tests {
tc := f ( )
t . Run ( name , func ( t * testing . T ) {
2019-12-20 21:30:05 +00:00
ctx := provisioner . NewContextWithMethod ( context . Background ( ) , provisioner . RevokeMethod )
if err := tc . auth . Revoke ( ctx , tc . opts ) ; err != nil {
if assert . NotNil ( t , tc . err , fmt . Sprintf ( "unexpected error: %s" , err ) ) {
sc , ok := err . ( errs . StatusCoder )
assert . Fatal ( t , ok , "error does not implement StatusCoder interface" )
assert . Equals ( t , sc . StatusCode ( ) , tc . code )
assert . HasPrefix ( t , err . Error ( ) , tc . err . Error ( ) )
ctxErr , ok := err . ( * errs . Error )
assert . Fatal ( t , ok , "error is not of type *errs.Error" )
assert . Equals ( t , ctxErr . Details [ "serialNumber" ] , tc . opts . Serial )
assert . Equals ( t , ctxErr . Details [ "reasonCode" ] , tc . opts . ReasonCode )
assert . Equals ( t , ctxErr . Details [ "reason" ] , tc . opts . Reason )
assert . Equals ( t , ctxErr . Details [ "MTLS" ] , tc . opts . MTLS )
2020-08-17 20:46:30 +00:00
assert . Equals ( t , ctxErr . Details [ "context" ] , fmt . Sprint ( provisioner . RevokeMethod ) )
2019-12-20 21:30:05 +00:00
if tc . checkErrDetails != nil {
tc . checkErrDetails ( ctxErr )
2019-03-05 08:07:13 +00:00
}
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}