Merge pull request #307 from dharanikumar-s/master

Add support for rekeying Fixes #292
This commit is contained in:
Max 2020-07-09 11:39:00 -07:00 committed by GitHub
commit ea9bc493b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 184 additions and 17 deletions

View file

@ -2,6 +2,7 @@ package api
import (
"context"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/rsa"
@ -35,6 +36,7 @@ type Authority interface {
Root(shasum string) (*x509.Certificate, error)
Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error)
LoadProvisionerByID(string) (provisioner.Interface, error)
GetProvisioners(cursor string, limit int) (provisioner.List, string, error)
@ -249,6 +251,7 @@ func (h *caHandler) Route(r Router) {
r.MethodFunc("GET", "/root/{sha}", h.Root)
r.MethodFunc("POST", "/sign", h.Sign)
r.MethodFunc("POST", "/renew", h.Renew)
r.MethodFunc("POST", "/rekey", h.Rekey)
r.MethodFunc("POST", "/revoke", h.Revoke)
r.MethodFunc("GET", "/provisioners", h.Provisioners)
r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey)

View file

@ -3,6 +3,7 @@ package api
import (
"bytes"
"context"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
@ -551,6 +552,7 @@ type mockAuthority struct {
root func(shasum string) (*x509.Certificate, error)
sign func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
renewOrRekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error)
loadProvisionerByID func(provID string) (provisioner.Interface, error)
getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error)
@ -611,6 +613,13 @@ func (m *mockAuthority) Renew(cert *x509.Certificate) ([]*x509.Certificate, erro
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
}
func (m *mockAuthority) Rekey(oldcert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) {
if m.renewOrRekey != nil {
return m.renewOrRekey(oldcert, pk)
}
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
}
func (m *mockAuthority) GetProvisioners(nextCursor string, limit int) (provisioner.List, string, error) {
if m.getProvisioners != nil {
return m.getProvisioners(nextCursor, limit)
@ -952,6 +961,67 @@ func Test_caHandler_Renew(t *testing.T) {
}
}
func Test_caHandler_Rekey(t *testing.T) {
cs := &tls.ConnectionState{
PeerCertificates: []*x509.Certificate{parseCertificate(certPEM)},
}
csr := parseCertificateRequest(csrPEM)
valid, err := json.Marshal(RekeyRequest{
CsrPEM: CertificateRequest{csr},
})
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
input string
tls *tls.ConnectionState
cert *x509.Certificate
root *x509.Certificate
err error
statusCode int
}{
{"ok", string(valid), cs, parseCertificate(certPEM), parseCertificate(rootPEM), nil, http.StatusCreated},
{"no tls", string(valid), nil, nil, nil, nil, http.StatusBadRequest},
{"no peer certificates", string(valid), &tls.ConnectionState{}, nil, nil, nil, http.StatusBadRequest},
{"rekey error", string(valid), cs, nil, nil, errs.Forbidden("an error"), http.StatusForbidden},
{"json read error", "{", cs, nil, nil, nil, http.StatusBadRequest},
}
expected := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n","certChain":["` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := New(&mockAuthority{
ret1: tt.cert, ret2: tt.root, err: tt.err,
getTLSOptions: func() *tlsutil.TLSOptions {
return nil
},
}).(*caHandler)
req := httptest.NewRequest("POST", "http://example.com/rekey", strings.NewReader(tt.input))
req.TLS = tt.tls
w := httptest.NewRecorder()
h.Rekey(logging.NewResponseLogger(w), req)
res := w.Result()
if res.StatusCode != tt.statusCode {
t.Errorf("caHandler.Rekey StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
}
body, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("caHandler.Rekey unexpected error = %v", err)
}
if tt.statusCode < http.StatusBadRequest {
if !bytes.Equal(bytes.TrimSpace(body), expected) {
t.Errorf("caHandler.Rekey Body = %s, wants %s", body, expected)
}
}
})
}
}
func Test_caHandler_Provisioners(t *testing.T) {
type fields struct {
Authority Authority

64
api/rekey.go Normal file
View file

@ -0,0 +1,64 @@
package api
import (
"net/http"
"github.com/smallstep/certificates/errs"
)
// RekeyRequest is the request body for a certificate rekey request.
type RekeyRequest struct {
CsrPEM CertificateRequest `json:"csr"`
}
// Validate checks the fields of the RekeyRequest and returns nil if they are ok
// or an error if something is wrong.
func (s *RekeyRequest) Validate() error {
if s.CsrPEM.CertificateRequest == nil {
return errs.BadRequest("missing csr")
}
if err := s.CsrPEM.CertificateRequest.CheckSignature(); err != nil {
return errs.Wrap(http.StatusBadRequest, err, "invalid csr")
}
return nil
}
// Rekey is similar to renew except that the certificate will be renewed with new key from csr.
func (h *caHandler) Rekey(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
WriteError(w, errs.BadRequest("missing peer certificate"))
return
}
var body RekeyRequest
if err := ReadJSON(r.Body, &body); err != nil {
WriteError(w, errs.Wrap(http.StatusBadRequest, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
WriteError(w, err)
return
}
certChain, err := h.Authority.Rekey(r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey)
if err != nil {
WriteError(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey"))
return
}
certChainPEM := certChainToPEM(certChain)
var caPEM Certificate
if len(certChainPEM) > 1 {
caPEM = certChainPEM[1]
}
logCertificate(w, certChain[0])
JSONStatus(w, &SignResponse{
ServerPEM: certChainPEM[0],
CaPEM: caPEM,
CertChainPEM: certChainPEM,
TLSOptions: h.Authority.GetTLSOptions(),
}, http.StatusCreated)
}

View file

@ -2,6 +2,7 @@ package authority
import (
"context"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/asn1"
@ -27,6 +28,7 @@ func (a *Authority) GetTLSOptions() *tlsutil.TLSOptions {
}
var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14}
func withDefaultASN1DN(def *x509util.ASN1DN) x509util.WithOption {
return func(p x509util.Profile) error {
@ -135,11 +137,16 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti
// Renew creates a new Certificate identical to the old certificate, except
// with a validity window that begins 'now'.
func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error) {
return a.Rekey(oldCert, nil)
}
// Func is used for renewing or rekeying based on the public key passed.
func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) {
opts := []interface{}{errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String())}
// Check step provisioner extensions
if err := a.authorizeRenew(oldCert); err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...)
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...)
}
// Durations
@ -148,7 +155,6 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
now := time.Now().UTC()
newCert := &x509.Certificate{
PublicKey: oldCert.PublicKey,
Issuer: a.x509Issuer.Subject,
Subject: oldCert.Subject,
NotBefore: now.Add(-1 * backdate),
@ -180,34 +186,47 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error
PolicyIdentifiers: oldCert.PolicyIdentifiers,
}
// Copy all extensions except for Authority Key Identifier. This one might
// be different if we rotate the intermediate certificate and it will cause
// a TLS bad certificate error.
if pk == nil {
newCert.PublicKey = oldCert.PublicKey
} else {
newCert.PublicKey = pk
}
// Copy all extensions except:
// 1. Authority Key Identifier - This one might be different if we rotate the intermediate certificate
// and it will cause a TLS bad certificate error.
// 2. Subject Key Identifier, if rekey - For rekey, SubjectKeyIdentifier extension will be calculated
// for the new public key by NewLeafProfilewithTemplate()
for _, ext := range oldCert.Extensions {
if !ext.Id.Equal(oidAuthorityKeyIdentifier) {
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext)
if ext.Id.Equal(oidAuthorityKeyIdentifier) {
continue
}
if ext.Id.Equal(oidSubjectKeyIdentifier) && (pk != nil) {
newCert.SubjectKeyId = nil
continue
}
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext)
}
leaf, err := x509util.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...)
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...)
}
crtBytes, err := leaf.CreateCertificate()
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Renew; error renewing certificate from existing server certificate", opts...)
"authority.Rekey; error renewing certificate from existing server certificate", opts...)
}
serverCert, err := x509.ParseCertificate(crtBytes)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Renew; error parsing new server certificate", opts...)
"authority.Rekey; error parsing new server certificate", opts...)
}
if err = a.db.StoreCertificate(serverCert); err != nil {
if err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew; error storing certificate in db", opts...)
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey; error storing certificate in db", opts...)
}
}

View file

@ -397,9 +397,11 @@ ZYtQ9Ot36qc=
}
}
func TestAuthority_Renew(t *testing.T) {
func TestAuthority_Rekey(t *testing.T) {
pub, _, err := keys.GenerateDefaultKeyPair()
assert.FatalError(t, err)
pub1, _, err := keys.GenerateDefaultKeyPair()
assert.FatalError(t, err)
a := testAuthority(t)
a.config.AuthorityConfig.Template = &x509util.ASN1DN{
@ -455,14 +457,14 @@ func TestAuthority_Renew(t *testing.T) {
return &renewTest{
auth: _a,
cert: cert,
err: errors.New("authority.Renew; error renewing certificate from existing server certificate"),
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.Renew: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"),
err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"),
code: http.StatusUnauthorized,
}, nil
},
@ -505,9 +507,9 @@ func TestAuthority_Renew(t *testing.T) {
var certChain []*x509.Certificate
if tc.auth != nil {
certChain, err = tc.auth.Renew(tc.cert)
certChain, err = tc.auth.Rekey(tc.cert, pub1)
} else {
certChain, err = a.Renew(tc.cert)
certChain, err = a.Rekey(tc.cert, pub1)
}
if err != nil {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
@ -551,8 +553,9 @@ func TestAuthority_Renew(t *testing.T) {
assert.Equals(t, leaf.ExtKeyUsage,
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
assert.Equals(t, leaf.PublicKey, pub1)
pubBytes, err := x509.MarshalPKIXPublicKey(pub)
pubBytes, err := x509.MarshalPKIXPublicKey(pub1)
assert.FatalError(t, err)
hash := sha1.Sum(pubBytes)
assert.Equals(t, leaf.SubjectKeyId, hash[:])
@ -562,6 +565,10 @@ func TestAuthority_Renew(t *testing.T) {
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) {
@ -578,6 +585,10 @@ func TestAuthority_Renew(t *testing.T) {
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 {