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
}
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
}
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 ( )
2019-03-08 03:30:17 +00:00
signOpts := provisioner . Options {
2019-03-25 19:35:21 +00:00
NotBefore : provisioner . NewTimeDuration ( nb ) ,
NotAfter : provisioner . NewTimeDuration ( nb . Add ( time . Minute * 5 ) ) ,
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
2019-03-08 03:30:17 +00:00
signOpts provisioner . Options
extraOpts [ ] provisioner . SignOption
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" ) ,
code : http . StatusInternalServerError ,
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 ,
2019-12-20 21:30:05 +00:00
err : errors . New ( "authority.Sign; error creating new leaf certificate" ) ,
code : http . StatusInternalServerError ,
2018-10-19 05:26:39 +00:00
}
} ,
"fail provisioner duration claim" : func ( t * testing . T ) * signTest {
csr := getCSR ( t , priv )
2019-03-08 03:30:17 +00:00
_signOpts := provisioner . Options {
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
}
} ,
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 ,
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 ) {
2019-03-25 19:35:21 +00:00
assert . Equals ( t , leaf . NotBefore , signOpts . NotBefore . Time ( ) . Truncate ( time . Second ) )
assert . Equals ( t , leaf . NotAfter , signOpts . NotAfter . Time ( ) . Truncate ( time . Second ) )
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
pubBytes , err := x509 . MarshalPKIXPublicKey ( pub )
assert . FatalError ( t , err )
hash := sha1 . Sum ( pubBytes )
assert . Equals ( t , leaf . SubjectKeyId , hash [ : ] )
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 {
id := ext . Id . String ( )
2018-10-26 21:24:16 +00:00
if id != stepOIDProvisioner . String ( ) {
2018-10-26 06:49:23 +00:00
continue
}
found ++
2018-10-26 21:24:16 +00:00
val := stepProvisionerASN1 { }
_ , err := asn1 . Unmarshal ( ext . Value , & val )
2018-10-26 06:49:23 +00:00
assert . FatalError ( t , err )
2018-10-26 21:24:16 +00:00
assert . Equals ( t , val . Type , provisionerTypeJWK )
2018-10-30 01:00:30 +00:00
assert . Equals ( t , val . Name , [ ] byte ( p . Name ) )
2018-10-26 21:24:16 +00:00
assert . Equals ( t , val . CredentialID , [ ] byte ( p . Key . KeyID ) )
2018-10-26 06:49:23 +00:00
}
2018-10-26 21:24:16 +00:00
assert . Equals ( t , found , 1 )
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 )
}
}
} )
}
}
2019-12-20 21:30:05 +00:00
func TestAuthority_Renew ( t * testing . T ) {
2018-10-05 21:48:36 +00:00
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" ,
}
now := time . Now ( ) . UTC ( )
nb1 := now . Add ( - time . Minute * 7 )
na1 := now
2019-03-08 03:30:17 +00:00
so := & provisioner . Options {
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 ) ,
2018-10-05 21:48:36 +00:00
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 ) ,
2018-11-01 22:43:24 +00:00
withDefaultASN1DN ( a . config . AuthorityConfig . Template ) ,
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
err error
code int
2018-10-05 21:48:36 +00:00
}
tests := map [ string ] func ( ) ( * renewTest , error ) {
"fail-create-cert" : func ( ) ( * renewTest , error ) {
_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 ,
err : errors . New ( "authority.Renew; error renewing certificate from existing server certificate" ) ,
code : http . StatusInternalServerError ,
2018-10-05 21:48:36 +00:00
} , nil
} ,
2018-11-01 22:43:24 +00:00
"fail-unauthorized" : func ( ) ( * renewTest , error ) {
return & renewTest {
2019-12-20 21:30:05 +00:00
cert : certNoRenew ,
err : errors . New ( "authority.Renew: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk" ) ,
code : http . StatusUnauthorized ,
2018-11-01 22:43:24 +00:00
} , nil
} ,
2018-10-05 21:48:36 +00:00
"success" : func ( ) ( * renewTest , error ) {
return & renewTest {
2019-02-15 03:17:42 +00:00
auth : a ,
2019-12-20 21:30:05 +00:00
cert : cert ,
2019-02-15 03:17:42 +00:00
} , nil
} ,
"success-new-intermediate" : func ( ) ( * renewTest , error ) {
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 {
2019-12-20 21:30:05 +00:00
certChain , err = tc . auth . Renew ( tc . cert )
2018-10-05 21:48:36 +00:00
} else {
2019-12-20 21:30:05 +00:00
certChain , err = a . Renew ( tc . cert )
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" } )
pubBytes , err := x509 . MarshalPKIXPublicKey ( pub )
assert . FatalError ( t , err )
hash := sha1 . Sum ( pubBytes )
assert . Equals ( t , leaf . SubjectKeyId , hash [ : ] )
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 {
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 {
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 )
assert . Equals ( t , ctxErr . Details [ "context" ] , string ( provisioner . RevokeMethod ) )
if tc . checkErrDetails != nil {
tc . checkErrDetails ( ctxErr )
2019-03-05 08:07:13 +00:00
}
}
} else {
assert . Nil ( t , tc . err )
}
} )
}
}