Skip to content

Commit fd05f32

Browse files
committed
A few last fixes and tests added for rekey/renew ...
- remove all `renewOrRekey` - explicitly test difference between renew and rekey (diff pub keys) - add back tests for renew
1 parent ea9bc49 commit fd05f32

File tree

3 files changed

+264
-16
lines changed

3 files changed

+264
-16
lines changed

api/api_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ type mockAuthority struct {
552552
root func(shasum string) (*x509.Certificate, error)
553553
sign func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
554554
renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
555-
renewOrRekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
555+
rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
556556
loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error)
557557
loadProvisionerByID func(provID string) (provisioner.Interface, error)
558558
getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error)
@@ -614,8 +614,8 @@ func (m *mockAuthority) Renew(cert *x509.Certificate) ([]*x509.Certificate, erro
614614
}
615615

616616
func (m *mockAuthority) Rekey(oldcert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) {
617-
if m.renewOrRekey != nil {
618-
return m.renewOrRekey(oldcert, pk)
617+
if m.rekey != nil {
618+
return m.rekey(oldcert, pk)
619619
}
620620
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
621621
}

authority/tls.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,17 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
140140
return a.Rekey(oldCert, nil)
141141
}
142142

143-
// Func is used for renewing or rekeying based on the public key passed.
143+
// Rekey is used for rekeying and renewing based on the public key.
144+
// If the public key is 'nil' then it's assumed that the cert should be renewed
145+
// using the existing public key. If the public key is not 'nil' then it's
146+
// assumed that the cert should be rekeyed.
147+
// For both Rekey and Renew all other attributes of the new certificate should
148+
// match the old certificate. The exceptions are 'AuthorityKeyId' (which may
149+
// have changed), 'SubjectKeyId' (different in case of rekey), and
150+
// 'NotBefore/NotAfter' (the validity duration of the new certificate should be
151+
// equal to the old one, but starting 'now').
144152
func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) {
153+
isRekey := (pk != nil)
145154
opts := []interface{}{errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String())}
146155

147156
// Check step provisioner extensions
@@ -186,10 +195,10 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
186195
PolicyIdentifiers: oldCert.PolicyIdentifiers,
187196
}
188197

189-
if pk == nil {
190-
newCert.PublicKey = oldCert.PublicKey
191-
} else {
198+
if isRekey {
192199
newCert.PublicKey = pk
200+
} else {
201+
newCert.PublicKey = oldCert.PublicKey
193202
}
194203

195204
// Copy all extensions except:
@@ -201,7 +210,7 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
201210
if ext.Id.Equal(oidAuthorityKeyIdentifier) {
202211
continue
203212
}
204-
if ext.Id.Equal(oidSubjectKeyIdentifier) && (pk != nil) {
213+
if ext.Id.Equal(oidSubjectKeyIdentifier) && isRekey {
205214
newCert.SubjectKeyId = nil
206215
continue
207216
}

authority/tls_test.go

Lines changed: 247 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,228 @@ ZYtQ9Ot36qc=
397397
}
398398
}
399399

400+
func TestAuthority_Renew(t *testing.T) {
401+
pub, _, err := keys.GenerateDefaultKeyPair()
402+
assert.FatalError(t, err)
403+
404+
a := testAuthority(t)
405+
a.config.AuthorityConfig.Template = &x509util.ASN1DN{
406+
Country: "Tazmania",
407+
Organization: "Acme Co",
408+
Locality: "Landscapes",
409+
Province: "Sudden Cliffs",
410+
StreetAddress: "TNT",
411+
CommonName: "renew",
412+
}
413+
414+
now := time.Now().UTC()
415+
nb1 := now.Add(-time.Minute * 7)
416+
na1 := now
417+
so := &provisioner.Options{
418+
NotBefore: provisioner.NewTimeDuration(nb1),
419+
NotAfter: provisioner.NewTimeDuration(na1),
420+
}
421+
422+
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer,
423+
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
424+
withDefaultASN1DN(a.config.AuthorityConfig.Template),
425+
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
426+
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID))
427+
assert.FatalError(t, err)
428+
certBytes, err := leaf.CreateCertificate()
429+
assert.FatalError(t, err)
430+
cert, err := x509.ParseCertificate(certBytes)
431+
assert.FatalError(t, err)
432+
433+
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer,
434+
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
435+
withDefaultASN1DN(a.config.AuthorityConfig.Template),
436+
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
437+
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID),
438+
)
439+
assert.FatalError(t, err)
440+
certBytesNoRenew, err := leafNoRenew.CreateCertificate()
441+
assert.FatalError(t, err)
442+
certNoRenew, err := x509.ParseCertificate(certBytesNoRenew)
443+
assert.FatalError(t, err)
444+
445+
type renewTest struct {
446+
auth *Authority
447+
cert *x509.Certificate
448+
err error
449+
code int
450+
}
451+
tests := map[string]func() (*renewTest, error){
452+
"fail/create-cert": func() (*renewTest, error) {
453+
_a := testAuthority(t)
454+
_a.x509Signer = nil
455+
return &renewTest{
456+
auth: _a,
457+
cert: cert,
458+
err: errors.New("authority.Rekey; error renewing certificate from existing server certificate"),
459+
code: http.StatusInternalServerError,
460+
}, nil
461+
},
462+
"fail/unauthorized": func() (*renewTest, error) {
463+
return &renewTest{
464+
cert: certNoRenew,
465+
err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"),
466+
code: http.StatusUnauthorized,
467+
}, nil
468+
},
469+
"ok": func() (*renewTest, error) {
470+
return &renewTest{
471+
auth: a,
472+
cert: cert,
473+
}, nil
474+
},
475+
"ok/success-new-intermediate": func() (*renewTest, error) {
476+
newRootProfile, err := x509util.NewRootProfile("new-root")
477+
assert.FatalError(t, err)
478+
newRootBytes, err := newRootProfile.CreateCertificate()
479+
assert.FatalError(t, err)
480+
newRootCert, err := x509.ParseCertificate(newRootBytes)
481+
assert.FatalError(t, err)
482+
483+
newIntermediateProfile, err := x509util.NewIntermediateProfile("new-intermediate",
484+
newRootCert, newRootProfile.SubjectPrivateKey())
485+
assert.FatalError(t, err)
486+
newIntermediateBytes, err := newIntermediateProfile.CreateCertificate()
487+
assert.FatalError(t, err)
488+
newIntermediateCert, err := x509.ParseCertificate(newIntermediateBytes)
489+
assert.FatalError(t, err)
490+
491+
_a := testAuthority(t)
492+
_a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer)
493+
_a.x509Issuer = newIntermediateCert
494+
return &renewTest{
495+
auth: _a,
496+
cert: cert,
497+
}, nil
498+
},
499+
}
500+
501+
for name, genTestCase := range tests {
502+
t.Run(name, func(t *testing.T) {
503+
tc, err := genTestCase()
504+
assert.FatalError(t, err)
505+
506+
var certChain []*x509.Certificate
507+
if tc.auth != nil {
508+
certChain, err = tc.auth.Renew(tc.cert)
509+
} else {
510+
certChain, err = a.Renew(tc.cert)
511+
}
512+
if err != nil {
513+
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
514+
assert.Nil(t, certChain)
515+
sc, ok := err.(errs.StatusCoder)
516+
assert.Fatal(t, ok, "error does not implement StatusCoder interface")
517+
assert.Equals(t, sc.StatusCode(), tc.code)
518+
assert.HasPrefix(t, err.Error(), tc.err.Error())
519+
520+
ctxErr, ok := err.(*errs.Error)
521+
assert.Fatal(t, ok, "error is not of type *errs.Error")
522+
assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String())
523+
}
524+
} else {
525+
leaf := certChain[0]
526+
intermediate := certChain[1]
527+
if assert.Nil(t, tc.err) {
528+
assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore))
529+
530+
assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute)))
531+
assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute)))
532+
533+
expiry := now.Add(time.Minute * 7)
534+
assert.True(t, leaf.NotAfter.After(expiry.Add(-2*time.Minute)))
535+
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Minute)))
536+
537+
tmplt := a.config.AuthorityConfig.Template
538+
assert.Equals(t, fmt.Sprintf("%v", leaf.Subject),
539+
fmt.Sprintf("%v", &pkix.Name{
540+
Country: []string{tmplt.Country},
541+
Organization: []string{tmplt.Organization},
542+
Locality: []string{tmplt.Locality},
543+
StreetAddress: []string{tmplt.StreetAddress},
544+
Province: []string{tmplt.Province},
545+
CommonName: tmplt.CommonName,
546+
}))
547+
assert.Equals(t, leaf.Issuer, intermediate.Subject)
548+
549+
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
550+
assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA)
551+
assert.Equals(t, leaf.ExtKeyUsage,
552+
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
553+
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
554+
555+
// Test Public Key and SubjectKeyId
556+
assert.Equals(t, leaf.PublicKey, cert.PublicKey)
557+
pubBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
558+
assert.FatalError(t, err)
559+
hash := sha1.Sum(pubBytes)
560+
assert.Equals(t, leaf.SubjectKeyId, hash[:])
561+
assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId)
562+
563+
// We did not change the intermediate before renewing.
564+
if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber {
565+
assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.SubjectKeyId)
566+
// Compare extensions: they can be in a different order
567+
for _, ext1 := range tc.cert.Extensions {
568+
//skip SubjectKeyIdentifier
569+
if ext1.Id.Equal(oidSubjectKeyIdentifier) {
570+
continue
571+
}
572+
found := false
573+
for _, ext2 := range leaf.Extensions {
574+
if reflect.DeepEqual(ext1, ext2) {
575+
found = true
576+
break
577+
}
578+
}
579+
if !found {
580+
t.Errorf("x509 extension %s not found in renewed certificate", ext1.Id.String())
581+
}
582+
}
583+
} else {
584+
// We did change the intermediate before renewing.
585+
assert.Equals(t, leaf.AuthorityKeyId, tc.auth.x509Issuer.SubjectKeyId)
586+
// Compare extensions: they can be in a different order
587+
for _, ext1 := range tc.cert.Extensions {
588+
//skip SubjectKeyIdentifier
589+
if ext1.Id.Equal(oidSubjectKeyIdentifier) {
590+
continue
591+
}
592+
// The authority key id extension should be different b/c the intermediates are different.
593+
if ext1.Id.Equal(oidAuthorityKeyIdentifier) {
594+
for _, ext2 := range leaf.Extensions {
595+
assert.False(t, reflect.DeepEqual(ext1, ext2))
596+
}
597+
continue
598+
} else {
599+
found := false
600+
for _, ext2 := range leaf.Extensions {
601+
if reflect.DeepEqual(ext1, ext2) {
602+
found = true
603+
break
604+
}
605+
}
606+
if !found {
607+
t.Errorf("x509 extension %s not found in renewed certificate", ext1.Id.String())
608+
}
609+
}
610+
}
611+
}
612+
613+
realIntermediate, err := x509.ParseCertificate(tc.auth.x509Issuer.Raw)
614+
assert.FatalError(t, err)
615+
assert.Equals(t, intermediate, realIntermediate)
616+
}
617+
}
618+
})
619+
}
620+
}
621+
400622
func TestAuthority_Rekey(t *testing.T) {
401623
pub, _, err := keys.GenerateDefaultKeyPair()
402624
assert.FatalError(t, err)
@@ -447,11 +669,12 @@ func TestAuthority_Rekey(t *testing.T) {
447669
type renewTest struct {
448670
auth *Authority
449671
cert *x509.Certificate
672+
pk crypto.PublicKey
450673
err error
451674
code int
452675
}
453676
tests := map[string]func() (*renewTest, error){
454-
"fail-create-cert": func() (*renewTest, error) {
677+
"fail/create-cert": func() (*renewTest, error) {
455678
_a := testAuthority(t)
456679
_a.x509Signer = nil
457680
return &renewTest{
@@ -461,20 +684,27 @@ func TestAuthority_Rekey(t *testing.T) {
461684
code: http.StatusInternalServerError,
462685
}, nil
463686
},
464-
"fail-unauthorized": func() (*renewTest, error) {
687+
"fail/unauthorized": func() (*renewTest, error) {
465688
return &renewTest{
466689
cert: certNoRenew,
467690
err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"),
468691
code: http.StatusUnauthorized,
469692
}, nil
470693
},
471-
"success": func() (*renewTest, error) {
694+
"ok/renew": func() (*renewTest, error) {
472695
return &renewTest{
473696
auth: a,
474697
cert: cert,
475698
}, nil
476699
},
477-
"success-new-intermediate": func() (*renewTest, error) {
700+
"ok/rekey": func() (*renewTest, error) {
701+
return &renewTest{
702+
auth: a,
703+
cert: cert,
704+
pk: pub1,
705+
}, nil
706+
},
707+
"ok/renew/success-new-intermediate": func() (*renewTest, error) {
478708
newRootProfile, err := x509util.NewRootProfile("new-root")
479709
assert.FatalError(t, err)
480710
newRootBytes, err := newRootProfile.CreateCertificate()
@@ -507,9 +737,9 @@ func TestAuthority_Rekey(t *testing.T) {
507737

508738
var certChain []*x509.Certificate
509739
if tc.auth != nil {
510-
certChain, err = tc.auth.Rekey(tc.cert, pub1)
740+
certChain, err = tc.auth.Rekey(tc.cert, tc.pk)
511741
} else {
512-
certChain, err = a.Rekey(tc.cert, pub1)
742+
certChain, err = a.Rekey(tc.cert, tc.pk)
513743
}
514744
if err != nil {
515745
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
@@ -553,12 +783,21 @@ func TestAuthority_Rekey(t *testing.T) {
553783
assert.Equals(t, leaf.ExtKeyUsage,
554784
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
555785
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
556-
assert.Equals(t, leaf.PublicKey, pub1)
557786

558-
pubBytes, err := x509.MarshalPKIXPublicKey(pub1)
787+
// Test Public Key and SubjectKeyId
788+
expectedPK := tc.pk
789+
if tc.pk == nil {
790+
expectedPK = cert.PublicKey
791+
}
792+
assert.Equals(t, leaf.PublicKey, expectedPK)
793+
794+
pubBytes, err := x509.MarshalPKIXPublicKey(expectedPK)
559795
assert.FatalError(t, err)
560796
hash := sha1.Sum(pubBytes)
561797
assert.Equals(t, leaf.SubjectKeyId, hash[:])
798+
if tc.pk == nil {
799+
assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId)
800+
}
562801

563802
// We did not change the intermediate before renewing.
564803
if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber {

0 commit comments

Comments
 (0)