diff --git a/.gitignore b/.gitignore index 7cba0d088..ed2ab99d0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,3 @@ coverage.txt vendor output -.idea diff --git a/.travis.yml b/.travis.yml index 1be30e694..991bee278 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ after_success: not collect coverage reports" notifications: email: false +stage: Github Release deploy: provider: releases skip_cleanup: true diff --git a/LICENSE b/LICENSE index 38d7895a4..9ea39e4f5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2020 Smallstep Labs, Inc. +Copyright (c) 2019 Smallstep Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index f9169da6f..806d1775d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ It's super easy to get started and to operate `step-ca` thanks to [streamlined i ### [Your own private ACME Server](https://smallstep.com/blog/private-acme-server/) - Issue certificates using ACMEv2 ([RFC8555](https://tools.ietf.org/html/rfc8555)), **the protocol used by Let's Encrypt** - Great for [using ACME in development & pre-production](https://smallstep.com/blog/private-acme-server/#local-development-pre-production) -- Supports the `http-01`, `tls-alpn-01`, and `dns-01` ACME challenge types +- Supports the `http-01` and `dns-01` ACME challenge types - Works with any compliant ACME client including [certbot](https://smallstep.com/blog/private-acme-server/#certbot-uploads-acme-certbot-png-certbot-example), [acme.sh](https://smallstep.com/blog/private-acme-server/#acme-sh-uploads-acme-acme-sh-png-acme-sh-example), [Caddy](https://smallstep.com/blog/private-acme-server/#caddy-uploads-acme-caddy-png-caddy-example), and [traefik](https://smallstep.com/blog/private-acme-server/#traefik-uploads-acme-traefik-png-traefik-example) - Get certificates programmatically (e.g., in [Go](https://smallstep.com/blog/private-acme-server/#golang-uploads-acme-golang-png-go-example), [Python](https://smallstep.com/blog/private-acme-server/#python-uploads-acme-python-png-python-example), [Node.js](https://smallstep.com/blog/private-acme-server/#node-js-uploads-acme-node-js-png-node-js-example)) @@ -342,8 +342,8 @@ Documentation can be found in a handful of different places: 1. The [docs](./docs/README.md) sub-repo has an index of documentation and tutorials. -2. On the command line with `step help ca xxx` where `xxx` is the subcommand -you are interested in. Ex: `step help ca provisioner list`. +2. On the command line with `step ca help xxx` where `xxx` is the subcommand +you are interested in. Ex: `step help ca provisioners list`. 3. On the web at https://smallstep.com/docs/certificates. diff --git a/acme/api/account.go b/acme/api/account.go index bb6c92d6d..fb43d4f94 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -96,7 +96,7 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(r) if err != nil { acmeErr, ok := err.(*acme.Error) - if !ok || acmeErr.Status != http.StatusBadRequest { + if !ok || acmeErr.Status != http.StatusNotFound { // Something went wrong ... api.WriteError(w, err) return diff --git a/acme/api/account_test.go b/acme/api/account_test.go index a3ebf55c4..193088f29 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -202,7 +202,7 @@ func TestHandlerGetOrdersByAccount(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -212,7 +212,7 @@ func TestHandlerGetOrdersByAccount(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -378,7 +378,7 @@ func TestHandlerNewAccount(t *testing.T) { ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b}) return test{ ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -569,7 +569,7 @@ func TestHandlerGetUpdateAccount(t *testing.T) { "fail/no-account": func(t *testing.T) test { return test{ ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -578,7 +578,7 @@ func TestHandlerGetUpdateAccount(t *testing.T) { ctx = context.WithValue(ctx, accContextKey, nil) return test{ ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, diff --git a/acme/api/handler.go b/acme/api/handler.go index 11cd74f22..31e19b7be 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -2,14 +2,13 @@ package api import ( "fmt" - "net/http" - "github.com/go-chi/chi" "github.com/pkg/errors" "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/cli/jose" + "net/http" ) func link(url, typ string) string { @@ -181,13 +180,20 @@ func (h *Handler) GetChallenge(w http.ResponseWriter, r *http.Request) { ch, err = h.Auth.ValidateChallenge(prov, acc.GetID(), chID, acc.GetKey()) if err != nil { api.WriteError(w, err) - return + } else if ch.Retry.Active { + retryAfter, err := h.Auth.BackoffChallenge(prov, acc.GetID(), chID, acc.GetKey()) + if err != nil { + api.WriteError(w, err) + } else { + w.Header().Add("Retry-After", retryAfter.String()) + api.WriteProcessing(w, ch) + } + } else { + getLink := h.Auth.GetLink + w.Header().Add("Link", link(getLink(acme.AuthzLink, acme.URLSafeProvisionerName(prov), true, ch.GetAuthzID()), "up")) + w.Header().Set("Location", getLink(acme.ChallengeLink, acme.URLSafeProvisionerName(prov), true, ch.GetID())) + api.JSON(w, ch) } - - getLink := h.Auth.GetLink - w.Header().Add("Link", link(getLink(acme.AuthzLink, acme.URLSafeProvisionerName(prov), true, ch.GetAuthzID()), "up")) - w.Header().Set("Location", getLink(acme.ChallengeLink, acme.URLSafeProvisionerName(prov), true, ch.GetID())) - api.JSON(w, ch) } // GetCertificate ACME api for retrieving a Certificate. diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index ebafbbb86..33e13d159 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -372,7 +372,7 @@ func TestHandlerGetAuthz(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -382,7 +382,7 @@ func TestHandlerGetAuthz(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -504,7 +504,7 @@ func TestHandlerGetCertificate(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -513,7 +513,7 @@ func TestHandlerGetCertificate(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -589,6 +589,7 @@ func ch() acme.Challenge { URL: "https://ca.smallstep.com/acme/challenge/chID", ID: "chID", AuthzID: "authzID", + Retry: &acme.Retry{Called:0, Backoffs:1, Active:false}, } } @@ -623,7 +624,7 @@ func TestHandlerGetChallenge(t *testing.T) { "fail/no-account": func(t *testing.T) test { return test{ ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -632,7 +633,7 @@ func TestHandlerGetChallenge(t *testing.T) { ctx = context.WithValue(ctx, accContextKey, nil) return test{ ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -733,6 +734,37 @@ func TestHandlerGetChallenge(t *testing.T) { ch: ch, } }, + "ok/retry-after": func(t *testing.T) test { + key, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + assert.FatalError(t, err) + acc := &acme.Account{ID: "accID", Key: key} + ctx := context.WithValue(context.Background(), provisionerContextKey, prov) + ctx = context.WithValue(ctx, accContextKey, acc) + // TODO: Add correct key such that challenge object is already "active" + chiCtxInactive := chi.NewRouteContext() + chiCtxInactive.URLParams.Add("chID", "chID") + //chiCtxInactive.URLParams.Add("Active", "true") + ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtxInactive) + ch := ch() + ch.Retry.Active = true + chJSON, err := json.Marshal(ch) + assert.FatalError(t, err) + ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: chJSON}) + return test{ + auth: &mockAcmeAuthority{ + validateChallenge: func(p provisioner.Interface, accID, id string, jwk *jose.JSONWebKey) (*acme.Challenge, error) { + assert.Equals(t, p, prov) + assert.Equals(t, accID, acc.ID) + assert.Equals(t, id, ch.ID) + assert.Equals(t, jwk.KeyID, key.KeyID) + return &ch, nil + }, + }, + ctx: ctx, + statusCode: 200, + ch: ch, + } + }, } for name, run := range tests { tc := run(t) @@ -760,13 +792,19 @@ func TestHandlerGetChallenge(t *testing.T) { assert.Equals(t, ae.Identifier, prob.Identifier) assert.Equals(t, ae.Subproblems, prob.Subproblems) assert.Equals(t, res.Header["Content-Type"], []string{"application/problem+json"}) - } else { + } else if res.StatusCode >= 200 { expB, err := json.Marshal(tc.ch) assert.FatalError(t, err) assert.Equals(t, bytes.TrimSpace(body), expB) assert.Equals(t, res.Header["Link"], []string{fmt.Sprintf(";rel=\"up\"", tc.ch.AuthzID)}) assert.Equals(t, res.Header["Location"], []string{url}) assert.Equals(t, res.Header["Content-Type"], []string{"application/json"}) + } else if res.StatusCode >= 100 { + expB, err := json.Marshal(tc.ch) + assert.FatalError(t, err) + assert.Equals(t, bytes.TrimSpace(body), expB) + assert.True(t, res.Header["Retry-After"] != nil) + assert.Equals(t, res.Header["Content-Type"], []string{"application/json"}) } }) } diff --git a/acme/api/middleware_test.go b/acme/api/middleware_test.go index e617e5bd7..f8aa322c4 100644 --- a/acme/api/middleware_test.go +++ b/acme/api/middleware_test.go @@ -842,7 +842,7 @@ func TestHandlerLookupJWK(t *testing.T) { }, }, ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 0931d832e..68bd4f46e 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -205,7 +205,7 @@ func TestHandlerGetOrder(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -215,7 +215,7 @@ func TestHandlerGetOrder(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -343,7 +343,7 @@ func TestHandlerNewOrder(t *testing.T) { "fail/no-account": func(t *testing.T) test { return test{ ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -352,7 +352,7 @@ func TestHandlerNewOrder(t *testing.T) { ctx = context.WithValue(ctx, accContextKey, nil) return test{ ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -597,7 +597,7 @@ func TestHandlerFinalizeOrder(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: context.WithValue(context.Background(), provisionerContextKey, prov), - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, @@ -607,7 +607,7 @@ func TestHandlerFinalizeOrder(t *testing.T) { return test{ auth: &mockAcmeAuthority{}, ctx: ctx, - statusCode: 400, + statusCode: 404, problem: acme.AccountDoesNotExistErr(nil), } }, diff --git a/acme/authority.go b/acme/authority.go index fe51ea9bb..8ddf8e0a0 100644 --- a/acme/authority.go +++ b/acme/authority.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" + "math" "net" "net/http" "net/url" @@ -19,6 +20,7 @@ import ( // Interface is the acme authority interface. type Interface interface { + BackoffChallenge(provisioner.Interface, string, string, *jose.JSONWebKey) (time.Duration, error) DeactivateAccount(provisioner.Interface, string) (*Account, error) FinalizeOrder(provisioner.Interface, string, string, *x509.CertificateRequest) (*Order, error) GetAccount(provisioner.Interface, string) (*Account, error) @@ -263,21 +265,39 @@ func (a *Authority) ValidateChallenge(p provisioner.Interface, accID, chID strin if accID != ch.getAccountID() { return nil, UnauthorizedErr(errors.New("account does not own challenge")) } + retry := ch.getRetry() + if retry.Active { + return ch.toACME(a.db, a.dir, p) + } + retry.Mux.Lock() + defer retry.Mux.Unlock() + client := http.Client{ Timeout: time.Duration(30 * time.Second), } + dialer := &net.Dialer{ Timeout: 30 * time.Second, } - ch, err = ch.validate(a.db, jwk, validateOptions{ - httpGet: client.Get, - lookupTxt: net.LookupTXT, - tlsDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) { - return tls.DialWithDialer(dialer, network, addr, config) - }, - }) - if err != nil { - return nil, Wrap(err, "error attempting challenge validation") + + for ch.getRetry().Active { + ch, err = ch.validate(a.db, jwk, validateOptions{ + httpGet: client.Get, + lookupTxt: net.LookupTXT, + tlsDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) { + return tls.DialWithDialer(dialer, network, addr, config) + }, + }) + if err != nil { + return nil, Wrap(err, "error attempting challenge validation") + } + if ch.getStatus() == StatusValid { + break + } + if ch.getStatus() == StatusInvalid { + return ch.toACME(a.db, a.dir, p) + } + time.Sleep(ch.getBackoff()) } return ch.toACME(a.db, a.dir, p) } @@ -293,3 +313,24 @@ func (a *Authority) GetCertificate(accID, certID string) ([]byte, error) { } return cert.toACME(a.db, a.dir) } + +// BackoffChallenge returns the total time to wait until the next sequence of validation attempts completes +func (a *Authority) BackoffChallenge(p provisioner.Interface, accID, chID string, jwk *jose.JSONWebKey) (time.Duration, error) { + ch, err := getChallenge(a.db, chID) + if err != nil { + return -1, err + } + if accID != ch.getAccountID() { + return -1, UnauthorizedErr(errors.New("account does not own challenge")) + } + + remCalls := ch.getRetry().MaxAttempts - math.Mod(ch.getRetry().Called, ch.getRetry().MaxAttempts) + totBackoff := 0*time.Second + for i:=0; i < int(remCalls); i++ { + clone := ch.clone() + clone.Retry.Called += float64(i) + totBackoff += clone.getBackoff() + } + + return totBackoff, nil +} \ No newline at end of file diff --git a/acme/authority_test.go b/acme/authority_test.go index 525a61b9f..574dc3e38 100644 --- a/acme/authority_test.go +++ b/acme/authority_test.go @@ -730,7 +730,7 @@ func TestAuthorityGetAuthz(t *testing.T) { } }, "ok": func(t *testing.T) test { - var ch1B, ch2B, ch3B = &[]byte{}, &[]byte{}, &[]byte{} + var ch1B, ch2B = &[]byte{}, &[]byte{} count := 0 mockdb := &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { @@ -739,8 +739,6 @@ func TestAuthorityGetAuthz(t *testing.T) { *ch1B = newval case 1: *ch2B = newval - case 2: - *ch3B = newval } count++ return nil, true, nil @@ -760,8 +758,6 @@ func TestAuthorityGetAuthz(t *testing.T) { assert.FatalError(t, err) ch2, err := unmarshalChallenge(*ch2B) assert.FatalError(t, err) - ch3, err := unmarshalChallenge(*ch3B) - assert.FatalError(t, err) count = 0 mockdb = &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { @@ -775,10 +771,6 @@ func TestAuthorityGetAuthz(t *testing.T) { assert.Equals(t, bucket, challengeTable) assert.Equals(t, key, []byte(ch2.getID())) ret = *ch2B - case 2: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch3.getID())) - ret = *ch3B } count++ return ret, nil @@ -804,10 +796,6 @@ func TestAuthorityGetAuthz(t *testing.T) { assert.Equals(t, bucket, challengeTable) assert.Equals(t, key, []byte(ch2.getID())) ret = *ch2B - case 3: - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch3.getID())) - ret = *ch3B } count++ return ret, nil @@ -888,25 +876,21 @@ func TestAuthorityNewOrder(t *testing.T) { case 1: assert.Equals(t, bucket, challengeTable) case 2: - assert.Equals(t, bucket, challengeTable) - case 3: assert.Equals(t, bucket, authzTable) + case 3: + assert.Equals(t, bucket, challengeTable) case 4: assert.Equals(t, bucket, challengeTable) case 5: - assert.Equals(t, bucket, challengeTable) - case 6: - assert.Equals(t, bucket, challengeTable) - case 7: assert.Equals(t, bucket, authzTable) - case 8: + case 6: assert.Equals(t, bucket, orderTable) var o order assert.FatalError(t, json.Unmarshal(newval, &o)) *acmeO, err = o.toACME(nil, dir, prov) assert.FatalError(t, err) *accID = o.AccountID - case 9: + case 7: assert.Equals(t, bucket, ordersByAccountIDTable) assert.Equals(t, string(key), *accID) } @@ -1276,6 +1260,7 @@ func TestAuthorityValidateChallenge(t *testing.T) { assert.Fatal(t, ok) _ch.baseChallenge.Status = StatusValid _ch.baseChallenge.Validated = clock.Now() + _ch.baseChallenge.Retry.Called = 0 b, err := json.Marshal(ch) assert.FatalError(t, err) auth, err := NewAuthority(&db.MockNoSQLDB{ @@ -1309,12 +1294,10 @@ func TestAuthorityValidateChallenge(t *testing.T) { if assert.Nil(t, tc.err) { gotb, err := json.Marshal(acmeCh) assert.FatalError(t, err) - acmeExp, err := tc.ch.toACME(nil, tc.auth.dir, prov) assert.FatalError(t, err) expb, err := json.Marshal(acmeExp) assert.FatalError(t, err) - assert.Equals(t, expb, gotb) } } diff --git a/acme/authz.go b/acme/authz.go index cdcb15e5b..27e98051c 100644 --- a/acme/authz.go +++ b/acme/authz.go @@ -294,7 +294,7 @@ func newDNSAuthz(db nosql.DB, accID string, identifier Identifier) (authz, error ba.Challenges = []string{} if !ba.Wildcard { - // http and alpn challenges are only permitted if the DNS is not a wildcard dns. + // http challenges are only permitted if the DNS is not a wildcard dns. ch1, err := newHTTP01Challenge(db, ChallengeOptions{ AccountID: accID, AuthzID: ba.ID, @@ -303,25 +303,15 @@ func newDNSAuthz(db nosql.DB, accID string, identifier Identifier) (authz, error return nil, Wrap(err, "error creating http challenge") } ba.Challenges = append(ba.Challenges, ch1.getID()) - - ch2, err := newTLSALPN01Challenge(db, ChallengeOptions{ - AccountID: accID, - AuthzID: ba.ID, - Identifier: ba.Identifier, - }) - if err != nil { - return nil, Wrap(err, "error creating alpn challenge") - } - ba.Challenges = append(ba.Challenges, ch2.getID()) } - ch3, err := newDNS01Challenge(db, ChallengeOptions{ + ch2, err := newDNS01Challenge(db, ChallengeOptions{ AccountID: accID, AuthzID: ba.ID, Identifier: identifier}) if err != nil { return nil, Wrap(err, "error creating dns challenge") } - ba.Challenges = append(ba.Challenges, ch3.getID()) + ba.Challenges = append(ba.Challenges, ch2.getID()) da := &dnsAuthz{ba} if err := da.save(db, nil); err != nil { diff --git a/acme/authz_test.go b/acme/authz_test.go index 05e3c40b5..96213e4fb 100644 --- a/acme/authz_test.go +++ b/acme/authz_test.go @@ -173,29 +173,13 @@ func TestNewAuthz(t *testing.T) { err: ServerInternalErr(errors.New("error creating http challenge: error saving acme challenge: force")), } }, - "fail/new-tls-alpn-chall-error": func(t *testing.T) test { - count := 0 - return test{ - iden: iden, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 1 { - return nil, false, errors.New("force") - } - count++ - return nil, true, nil - }, - }, - err: ServerInternalErr(errors.New("error creating alpn challenge: error saving acme challenge: force")), - } - }, "fail/new-dns-chall-error": func(t *testing.T) test { count := 0 return test{ iden: iden, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 2 { + if count == 1 { return nil, false, errors.New("force") } count++ @@ -211,7 +195,7 @@ func TestNewAuthz(t *testing.T) { iden: iden, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 3 { + if count == 2 { return nil, false, errors.New("force") } count++ @@ -228,7 +212,7 @@ func TestNewAuthz(t *testing.T) { iden: iden, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count == 3 { + if count == 2 { assert.Equals(t, bucket, authzTable) assert.Equals(t, old, nil) @@ -706,8 +690,7 @@ func TestAuthzUpdateStatus(t *testing.T) { }, "ok/valid": func(t *testing.T) test { var ( - ch3 challenge - ch2Bytes = &([]byte{}) + ch2 challenge ch1Bytes = &([]byte{}) err error ) @@ -718,9 +701,7 @@ func TestAuthzUpdateStatus(t *testing.T) { if count == 0 { *ch1Bytes = newval } else if count == 1 { - *ch2Bytes = newval - } else if count == 2 { - ch3, err = unmarshalChallenge(newval) + ch2, err = unmarshalChallenge(newval) assert.FatalError(t, err) } count++ @@ -736,10 +717,10 @@ func TestAuthzUpdateStatus(t *testing.T) { assert.Fatal(t, ok) _az.baseAuthz.Error = MalformedErr(nil) - _ch, ok := ch3.(*dns01Challenge) + _ch, ok := ch2.(*dns01Challenge) assert.Fatal(t, ok) _ch.baseChallenge.Status = StatusValid - chb, err := json.Marshal(ch3) + chb, err := json.Marshal(ch2) clone := az.clone() clone.Status = StatusValid @@ -755,10 +736,6 @@ func TestAuthzUpdateStatus(t *testing.T) { count++ return *ch1Bytes, nil } - if count == 1 { - count++ - return *ch2Bytes, nil - } count++ return chb, nil }, diff --git a/acme/challenge.go b/acme/challenge.go index 1d1cf50bd..b0f6c4694 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -11,9 +11,12 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math" + "math/rand" "net" "net/http" "strings" + "sync" "time" "github.com/pkg/errors" @@ -31,10 +34,22 @@ type Challenge struct { Validated string `json:"validated,omitempty"` URL string `json:"url"` Error *AError `json:"error,omitempty"` + Retry *Retry `json:"retry"` ID string `json:"-"` AuthzID string `json:"-"` } +type Retry struct { + Called float64 `json:"id"` + BaseDelay time.Duration `json:"basedelay"` + MaxDelay time.Duration `json:"maxdelay"` + Multiplier float64 `json:"multiplier"` + Jitter float64 `json:"jitter"` + MaxAttempts float64 `json:"maxattempts"` + Active bool `json:"active"` + Mux sync.Mutex `json:"mux"` +} + // ToLog enables response logging. func (c *Challenge) ToLog() (interface{}, error) { b, err := json.Marshal(c) @@ -75,6 +90,8 @@ type challenge interface { getID() string getAuthzID() string getToken() string + getRetry() *Retry + getBackoff() time.Duration clone() *baseChallenge getAccountID() string getValidated() time.Time @@ -101,6 +118,7 @@ type baseChallenge struct { Validated time.Time `json:"validated"` Created time.Time `json:"created"` Error *AError `json:"error"` + Retry *Retry `json:"retry"` } func newBaseChallenge(accountID, authzID string) (*baseChallenge, error) { @@ -120,6 +138,7 @@ func newBaseChallenge(accountID, authzID string) (*baseChallenge, error) { Status: StatusPending, Token: token, Created: clock.Now(), + Retry: &Retry{Called:0, BaseDelay:1, MaxDelay: 30, Jitter: 1, MaxAttempts:10, Active:false}, }, nil } @@ -158,6 +177,11 @@ func (bc *baseChallenge) getToken() string { return bc.Token } +// getRetry returns the retry state of the baseChallenge +func (bc *baseChallenge) getRetry() *Retry { + return bc.Retry +} + // getValidated returns the validated time of the baseChallenge. func (bc *baseChallenge) getValidated() time.Time { return bc.Validated @@ -173,6 +197,25 @@ func (bc *baseChallenge) getError() *AError { return bc.Error } +// getBackoff returns the backoff time of the baseChallenge. +func (bc *baseChallenge) getBackoff() time.Duration { + if bc.Retry.Called == 0 { + return bc.Retry.BaseDelay + } + + backoff, max := float64(bc.Retry.BaseDelay), float64(bc.Retry.MaxDelay) + backoff *= math.Pow(bc.Retry.Multiplier, bc.Retry.Called) + if backoff > max { + backoff = max + } + // Introduce Jitter to ensure that clustered requests wont operate in unison + backoff *= 1 + bc.Retry.Jitter*(rand.Float64()*2-1) + if backoff < 0 { + return 0 + } + return time.Duration(backoff) +} + // toACME converts the internal Challenge type into the public acmeChallenge // type for presentation in the ACME protocol. func (bc *baseChallenge) toACME(db nosql.DB, dir *directory, p provisioner.Interface) (*Challenge, error) { @@ -190,6 +233,9 @@ func (bc *baseChallenge) toACME(db nosql.DB, dir *directory, p provisioner.Inter if bc.Error != nil { ac.Error = bc.Error } + if bc.Retry != nil { + ac.Retry = bc.Retry + } return ac, nil } @@ -303,23 +349,30 @@ func newHTTP01Challenge(db nosql.DB, ops ChallengeOptions) (challenge, error) { // updated. func (hc *http01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo validateOptions) (challenge, error) { // If already valid or invalid then return without performing validation. - if hc.getStatus() == StatusValid || hc.getStatus() == StatusInvalid { + if hc.getStatus() == StatusValid { + return hc, nil + } + if hc.getStatus() == StatusInvalid { + // TODO: Resolve segfault on upd.save + upd := hc.clone() + upd.Status = StatusPending + if err := upd.save(db, hc); err != nil { + return nil, err + } return hc, nil } url := fmt.Sprintf("http://%s/.well-known/acme-challenge/%s", hc.Value, hc.Token) resp, err := vo.httpGet(url) if err != nil { - if err = hc.storeError(db, ConnectionErr(errors.Wrapf(err, - "error doing http GET for url %s", url))); err != nil { + if err = hc.iterateRetry(db, ConnectionErr(errors.Wrapf(err, "error doing http GET for url %s", url))); err != nil { return nil, err } return hc, nil } if resp.StatusCode >= 400 { - if err = hc.storeError(db, - ConnectionErr(errors.Errorf("error doing http GET for url %s with status code %d", - url, resp.StatusCode))); err != nil { + if err = hc.iterateRetry(db, ConnectionErr(errors.Errorf("error doing http GET for url %s with status code %d", + url, resp.StatusCode))); err != nil { return nil, err } return hc, nil @@ -338,12 +391,11 @@ func (hc *http01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo valida return nil, err } if keyAuth != expected { - if err = hc.storeError(db, - RejectedIdentifierErr(errors.Errorf("keyAuthorization does not match; "+ - "expected %s, but got %s", expected, keyAuth))); err != nil { + if err = hc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("keyAuthorization does not match; "+ + "expected %s, but got %s", expected, keyAuth))); err != nil { return nil, err } - return hc, nil + return hc, err } // Update and store the challenge. @@ -351,6 +403,8 @@ func (hc *http01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo valida upd.Status = StatusValid upd.Error = nil upd.Validated = clock.Now() + upd.Retry.Active = false + upd.Retry.Called = 0 if err := upd.save(db, hc); err != nil { return nil, err @@ -380,7 +434,16 @@ func newTLSALPN01Challenge(db nosql.DB, ops ChallengeOptions) (challenge, error) func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo validateOptions) (challenge, error) { // If already valid or invalid then return without performing validation. - if tc.getStatus() == StatusValid || tc.getStatus() == StatusInvalid { + if tc.getStatus() == StatusValid { + return tc, nil + } + if tc.getStatus() == StatusInvalid { + // TODO: Resolve segfault on upd.save + upd := tc.clone() + upd.Status = StatusPending + if err := upd.save(db, tc); err != nil { + return nil, err + } return tc, nil } @@ -394,8 +457,7 @@ func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo val conn, err := vo.tlsDial("tcp", hostPort, config) if err != nil { - if err = tc.storeError(db, - ConnectionErr(errors.Wrapf(err, "error doing TLS dial for %s", hostPort))); err != nil { + if err = tc.iterateRetry(db, ConnectionErr(errors.Wrapf(err, "error doing TLS dial for %s", hostPort))); err != nil { return nil, err } return tc, nil @@ -406,18 +468,16 @@ func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo val certs := cs.PeerCertificates if len(certs) == 0 { - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("%s challenge for %s resulted in no certificates", - tc.Type, tc.Value))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("%s challenge for %s resulted in no certificates", + tc.Type, tc.Value))); err != nil { return nil, err } return tc, nil } if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != "acme-tls/1" { - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("cannot negotiate ALPN acme-tls/1 protocol for "+ - "tls-alpn-01 challenge"))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("cannot negotiate ALPN acme-tls/1 protocol for "+ + "tls-alpn-01 challenge"))); err != nil { return nil, err } return tc, nil @@ -426,9 +486,8 @@ func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo val leafCert := certs[0] if len(leafCert.DNSNames) != 1 || !strings.EqualFold(leafCert.DNSNames[0], tc.Value) { - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ - "leaf certificate must contain a single DNS name, %v", tc.Value))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ + "leaf certificate must contain a single DNS name, %v", tc.Value))); err != nil { return nil, err } return tc, nil @@ -447,9 +506,8 @@ func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo val for _, ext := range leafCert.Extensions { if idPeAcmeIdentifier.Equal(ext.Id) { if !ext.Critical { - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ - "acmeValidationV1 extension not critical"))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: " + + "acmeValidationV1 extension not critical"))); err != nil { return nil, err } return tc, nil @@ -459,19 +517,17 @@ func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo val rest, err := asn1.Unmarshal(ext.Value, &extValue) if err != nil || len(rest) > 0 || len(hashedKeyAuth) != len(extValue) { - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ - "malformed acmeValidationV1 extension value"))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ + "malformed acmeValidationV1 extension value"))); err != nil { return nil, err } return tc, nil } if subtle.ConstantTimeCompare(hashedKeyAuth[:], extValue) != 1 { - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ - "expected acmeValidationV1 extension value %s for this challenge but got %s", - hex.EncodeToString(hashedKeyAuth[:]), hex.EncodeToString(extValue)))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ + "expected acmeValidationV1 extension value %s for this challenge but got %s", + hex.EncodeToString(hashedKeyAuth[:]), hex.EncodeToString(extValue)))); err != nil { return nil, err } return tc, nil @@ -481,6 +537,7 @@ func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo val upd.Status = StatusValid upd.Error = nil upd.Validated = clock.Now() + upd.Retry.Active = false if err := upd.save(db, tc); err != nil { return nil, err @@ -494,17 +551,15 @@ func (tc *tlsALPN01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo val } if foundIDPeAcmeIdentifierV1Obsolete { - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ - "obsolete id-pe-acmeIdentifier in acmeValidationV1 extension"))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ + "obsolete id-pe-acmeIdentifier in acmeValidationV1 extension"))); err != nil { return nil, err } return tc, nil } - if err = tc.storeError(db, - RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ - "missing acmeValidationV1 extension"))); err != nil { + if err = tc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ + "missing acmeValidationV1 extension"))); err != nil { return nil, err } return tc, nil @@ -547,7 +602,16 @@ func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) { // updated. func (dc *dns01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo validateOptions) (challenge, error) { // If already valid or invalid then return without performing validation. - if dc.getStatus() == StatusValid || dc.getStatus() == StatusInvalid { + if dc.getStatus() == StatusValid { + return dc, nil + } + if dc.getStatus() == StatusInvalid { + // TODO: Resolve segfault on upd.save + upd := dc.clone() + upd.Status = StatusPending + if err := upd.save(db, dc); err != nil { + return nil, err + } return dc, nil } @@ -559,9 +623,8 @@ func (dc *dns01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo validat txtRecords, err := vo.lookupTxt("_acme-challenge." + domain) if err != nil { - if err = dc.storeError(db, - DNSErr(errors.Wrapf(err, "error looking up TXT "+ - "records for domain %s", domain))); err != nil { + if err = dc.iterateRetry(db, DNSErr(errors.Wrapf(err, "error looking up TXT "+ + "records for domain %s", domain))); err != nil { return nil, err } return dc, nil @@ -581,19 +644,19 @@ func (dc *dns01Challenge) validate(db nosql.DB, jwk *jose.JSONWebKey, vo validat } } if !found { - if err = dc.storeError(db, - RejectedIdentifierErr(errors.Errorf("keyAuthorization "+ - "does not match; expected %s, but got %s", expectedKeyAuth, txtRecords))); err != nil { + if err = dc.iterateRetry(db, RejectedIdentifierErr(errors.Errorf("keyAuthorization "+ + "does not match; expected %s, but got %s", expectedKeyAuth, txtRecords))); err != nil { return nil, err } return dc, nil } - // Update and store the challenge. upd := &dns01Challenge{dc.baseChallenge.clone()} upd.Status = StatusValid upd.Error = nil upd.Validated = time.Now().UTC() + upd.Retry.Active = false + upd.Retry.Called = 0 if err := upd.save(db, dc); err != nil { return nil, err @@ -615,3 +678,20 @@ func getChallenge(db nosql.DB, id string) (challenge, error) { } return ch, nil } + +// iterateRetry iterates a challenge's retry and error objects upon a failed validation attempt +func (bc *baseChallenge) iterateRetry(db nosql.DB, error *Error) error { + upd := bc.clone() + upd.Error = error.ToACME() + upd.Error.Subproblems = append(upd.Error.Subproblems, error) + upd.Retry.Called ++ + upd.Retry.Active = true + if math.Mod(upd.Retry.Called , upd.Retry.MaxAttempts) == 0 { + upd.Status = StatusInvalid + upd.Retry.Active = false + } + if err := upd.save(db, bc); err != nil { + return err + } + return nil +} diff --git a/acme/challenge_test.go b/acme/challenge_test.go index b2b249cbe..5672dc1ed 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -3,23 +3,12 @@ package acme import ( "bytes" "crypto" - "crypto/rand" - "crypto/rsa" "crypto/sha256" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "encoding/base64" - "encoding/hex" "encoding/json" "fmt" - "io" "io/ioutil" - "math/big" - "net" "net/http" - "net/http/httptest" "testing" "time" @@ -49,15 +38,6 @@ func newDNSCh() (challenge, error) { return newDNS01Challenge(mockdb, testOps) } -func newTLSALPNCh() (challenge, error) { - mockdb := &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return []byte("foo"), true, nil - }, - } - return newTLSALPN01Challenge(mockdb, testOps) -} - func newHTTPCh() (challenge, error) { mockdb := &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { @@ -131,70 +111,6 @@ func TestNewHTTP01Challenge(t *testing.T) { } } -func TestNewTLSALPN01Challenge(t *testing.T) { - ops := ChallengeOptions{ - AccountID: "accID", - AuthzID: "authzID", - Identifier: Identifier{ - Type: "http", - Value: "zap.internal", - }, - } - type test struct { - ops ChallengeOptions - db nosql.DB - err *Error - } - tests := map[string]test{ - "fail/store-error": { - ops: ops, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return nil, false, errors.New("force") - }, - }, - err: ServerInternalErr(errors.New("error saving acme challenge: force")), - }, - "ok": { - ops: ops, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - return []byte("foo"), true, nil - }, - }, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - ch, err := newTLSALPN01Challenge(tc.db, tc.ops) - if err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - if assert.Nil(t, tc.err) { - assert.Equals(t, ch.getAccountID(), ops.AccountID) - assert.Equals(t, ch.getAuthzID(), ops.AuthzID) - assert.Equals(t, ch.getType(), "tls-alpn-01") - assert.Equals(t, ch.getValue(), "zap.internal") - assert.Equals(t, ch.getStatus(), StatusPending) - - assert.True(t, ch.getValidated().IsZero()) - assert.True(t, ch.getCreated().Before(time.Now().UTC().Add(time.Minute))) - assert.True(t, ch.getCreated().After(time.Now().UTC().Add(-1*time.Minute))) - - assert.True(t, ch.getID() != "") - assert.True(t, ch.getToken() != "") - } - } - }) - } -} - func TestNewDNS01Challenge(t *testing.T) { ops := ChallengeOptions{ AccountID: "accID", @@ -267,16 +183,13 @@ func TestChallengeToACME(t *testing.T) { _httpCh, ok := httpCh.(*http01Challenge) assert.Fatal(t, ok) _httpCh.baseChallenge.Validated = clock.Now() + dnsCh, err := newDNSCh() assert.FatalError(t, err) - tlsALPNCh, err := newTLSALPNCh() - assert.FatalError(t, err) - prov := newProv() tests := map[string]challenge{ - "dns": dnsCh, - "http": httpCh, - "tls-alpn": tlsALPNCh, + "dns": dnsCh, + "http": httpCh, } for name, ch := range tests { t.Run(name, func(t *testing.T) { @@ -433,7 +346,7 @@ func TestChallengeUnmarshal(t *testing.T) { err: ServerInternalErr(errors.New("error unmarshaling challenge type: unexpected end of JSON input")), } }, - "fail/unexpected-type-http": func(t *testing.T) test { + "fail/unexpected-type": func(t *testing.T) test { httpCh, err := newHTTPCh() assert.FatalError(t, err) _httpCh, ok := httpCh.(*http01Challenge) @@ -446,32 +359,6 @@ func TestChallengeUnmarshal(t *testing.T) { err: ServerInternalErr(errors.New("unexpected challenge type foo")), } }, - "fail/unexpected-type-alpn": func(t *testing.T) test { - tlsALPNCh, err := newTLSALPNCh() - assert.FatalError(t, err) - _tlsALPNCh, ok := tlsALPNCh.(*tlsALPN01Challenge) - assert.Fatal(t, ok) - _tlsALPNCh.baseChallenge.Type = "foo" - b, err := json.Marshal(tlsALPNCh) - assert.FatalError(t, err) - return test{ - chb: b, - err: ServerInternalErr(errors.New("unexpected challenge type foo")), - } - }, - "fail/unexpected-type-dns": func(t *testing.T) test { - dnsCh, err := newDNSCh() - assert.FatalError(t, err) - _dnsCh, ok := dnsCh.(*dns01Challenge) - assert.Fatal(t, ok) - _dnsCh.baseChallenge.Type = "foo" - b, err := json.Marshal(dnsCh) - assert.FatalError(t, err) - return test{ - chb: b, - err: ServerInternalErr(errors.New("unexpected challenge type foo")), - } - }, "ok/dns": func(t *testing.T) test { dnsCh, err := newDNSCh() assert.FatalError(t, err) @@ -492,16 +379,6 @@ func TestChallengeUnmarshal(t *testing.T) { chb: b, } }, - "ok/alpn": func(t *testing.T) test { - tlsALPNCh, err := newTLSALPNCh() - assert.FatalError(t, err) - b, err := json.Marshal(tlsALPNCh) - assert.FatalError(t, err) - return test{ - ch: tlsALPNCh, - chb: b, - } - }, "ok/err": func(t *testing.T) test { httpCh, err := newHTTPCh() assert.FatalError(t, err) @@ -772,7 +649,6 @@ func TestHTTP01Validate(t *testing.T) { assert.FatalError(t, err) oldb, err := json.Marshal(ch) assert.FatalError(t, err) - expErr := ConnectionErr(errors.Errorf("error doing http GET for url "+ "http://zap.internal/.well-known/acme-challenge/%s with status code 400", ch.getToken())) baseClone := ch.clone() @@ -858,6 +734,7 @@ func TestHTTP01Validate(t *testing.T) { "expected %s, but got foo", expKeyAuth)) baseClone := ch.clone() baseClone.Error = expErr.ToACME() + baseClone.Error.Subproblems = append(baseClone.Error.Subproblems, expErr) newCh := &http01Challenge{baseClone} newb, err := json.Marshal(newCh) assert.FatalError(t, err) @@ -983,727 +860,13 @@ func TestHTTP01Validate(t *testing.T) { assert.Equals(t, tc.res.getCreated(), ch.getCreated()) assert.Equals(t, tc.res.getValidated(), ch.getValidated()) assert.Equals(t, tc.res.getError(), ch.getError()) + assert.Equals(t, tc.res.getRetry(), ch.getRetry()) } } }) } } -func TestTLSALPN01Validate(t *testing.T) { - type test struct { - srv *httptest.Server - vo validateOptions - ch challenge - res challenge - jwk *jose.JSONWebKey - db nosql.DB - err *Error - } - tests := map[string]func(t *testing.T) test{ - "ok/status-already-valid": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - _ch, ok := ch.(*tlsALPN01Challenge) - assert.Fatal(t, ok) - _ch.baseChallenge.Status = StatusValid - - return test{ - ch: ch, - res: ch, - } - }, - "ok/status-already-invalid": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - _ch, ok := ch.(*tlsALPN01Challenge) - assert.Fatal(t, ok) - _ch.baseChallenge.Status = StatusInvalid - - return test{ - ch: ch, - res: ch, - } - }, - "ok/tls-dial-error": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := ConnectionErr(errors.Errorf("error doing TLS dial for %v:443: force", ch.getValue())) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - return test{ - ch: ch, - vo: validateOptions{ - tlsDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) { - return nil, errors.New("force") - }, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, newval, newb) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/timeout": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := ConnectionErr(errors.Errorf("error doing TLS dial for %v:443: tls: DialWithDialer timed out", ch.getValue())) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(nil) - // srv.Start() - do not start server to cause timeout - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/no-certificates": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.Errorf("tls-alpn-01 challenge for %v resulted in no certificates", ch.getValue())) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - return test{ - ch: ch, - vo: validateOptions{ - tlsDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) { - return tls.Client(&noopConn{}, config), nil - }, - }, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/no-names": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.getValue())) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) - assert.FatalError(t, err) - expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) - - cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/too-many-names": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.getValue())) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) - assert.FatalError(t, err) - expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) - - cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true, ch.getValue(), "other.internal") - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/wrong-name": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.getValue())) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) - assert.FatalError(t, err) - expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) - - cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true, "other.internal") - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/no-extension": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.New("incorrect certificate for tls-alpn-01 challenge: missing acmeValidationV1 extension")) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - cert, err := newTLSALPNValidationCert(nil, false, true, ch.getValue()) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/extension-not-critical": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.New("incorrect certificate for tls-alpn-01 challenge: acmeValidationV1 extension not critical")) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) - assert.FatalError(t, err) - expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) - - cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, false, ch.getValue()) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/extension-malformed": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.New("incorrect certificate for tls-alpn-01 challenge: malformed acmeValidationV1 extension value")) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - cert, err := newTLSALPNValidationCert([]byte{1, 2, 3}, false, true, ch.getValue()) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/no-protocol": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.New("cannot negotiate ALPN acme-tls/1 protocol for tls-alpn-01 challenge")) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - srv := httptest.NewTLSServer(nil) - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) { - return tls.DialWithDialer(&net.Dialer{Timeout: time.Second}, "tcp", srv.Listener.Addr().String(), config) - }, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/mismatched-token": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) - assert.FatalError(t, err) - expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) - incorrectTokenHash := sha256.Sum256([]byte("mismatched")) - - expErr := RejectedIdentifierErr(errors.Errorf("incorrect certificate for tls-alpn-01 challenge: "+ - "expected acmeValidationV1 extension value %s for this challenge but got %s", - hex.EncodeToString(expKeyAuthHash[:]), hex.EncodeToString(incorrectTokenHash[:]))) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - cert, err := newTLSALPNValidationCert(incorrectTokenHash[:], false, true, ch.getValue()) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok/obsolete-oid": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expErr := RejectedIdentifierErr(errors.New("incorrect certificate for tls-alpn-01 challenge: " + - "obsolete id-pe-acmeIdentifier in acmeValidationV1 extension")) - baseClone := ch.clone() - baseClone.Error = expErr.ToACME() - newCh := &tlsALPN01Challenge{baseClone} - newb, err := json.Marshal(newCh) - assert.FatalError(t, err) - - expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) - assert.FatalError(t, err) - expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) - - cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], true, true, ch.getValue()) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: tlsDial, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - assert.Equals(t, string(newval), string(newb)) - return nil, true, nil - }, - }, - res: ch, - } - }, - "ok": func(t *testing.T) test { - ch, err := newTLSALPNCh() - assert.FatalError(t, err) - _ch, ok := ch.(*tlsALPN01Challenge) - assert.Fatal(t, ok) - _ch.baseChallenge.Error = MalformedErr(nil).ToACME() - oldb, err := json.Marshal(ch) - assert.FatalError(t, err) - - baseClone := ch.clone() - baseClone.Status = StatusValid - baseClone.Error = nil - newCh := &tlsALPN01Challenge{baseClone} - - jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) - - expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) - assert.FatalError(t, err) - expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) - - cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true, ch.getValue()) - assert.FatalError(t, err) - - srv, tlsDial := newTestTLSALPNServer(cert) - srv.Start() - - return test{ - srv: srv, - ch: ch, - vo: validateOptions{ - tlsDial: func(network, addr string, config *tls.Config) (conn *tls.Conn, err error) { - assert.Equals(t, network, "tcp") - assert.Equals(t, addr, net.JoinHostPort(newCh.getValue(), "443")) - assert.Equals(t, config.NextProtos, []string{"acme-tls/1"}) - assert.Equals(t, config.ServerName, newCh.getValue()) - assert.True(t, config.InsecureSkipVerify) - - return tlsDial(network, addr, config) - }, - }, - jwk: jwk, - db: &db.MockNoSQLDB{ - MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - assert.Equals(t, bucket, challengeTable) - assert.Equals(t, key, []byte(ch.getID())) - assert.Equals(t, old, oldb) - - alpnCh, err := unmarshalChallenge(newval) - assert.FatalError(t, err) - assert.Equals(t, alpnCh.getStatus(), StatusValid) - assert.True(t, alpnCh.getValidated().Before(time.Now().UTC().Add(time.Minute))) - assert.True(t, alpnCh.getValidated().After(time.Now().UTC().Add(-1*time.Second))) - - baseClone.Validated = alpnCh.getValidated() - - return nil, true, nil - }, - }, - res: newCh, - } - }, - } - for name, run := range tests { - t.Run(name, func(t *testing.T) { - tc := run(t) - - if tc.srv != nil { - defer tc.srv.Close() - } - - if ch, err := tc.ch.validate(tc.db, tc.jwk, tc.vo); err != nil { - if assert.NotNil(t, tc.err) { - ae, ok := err.(*Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) - } - } else { - if assert.Nil(t, tc.err) { - assert.Equals(t, tc.res.getID(), ch.getID()) - assert.Equals(t, tc.res.getAccountID(), ch.getAccountID()) - assert.Equals(t, tc.res.getAuthzID(), ch.getAuthzID()) - assert.Equals(t, tc.res.getStatus(), ch.getStatus()) - assert.Equals(t, tc.res.getToken(), ch.getToken()) - assert.Equals(t, tc.res.getCreated(), ch.getCreated()) - assert.Equals(t, tc.res.getValidated(), ch.getValidated()) - assert.Equals(t, tc.res.getError(), ch.getError()) - } - } - }) - } -} - -func newTestTLSALPNServer(validationCert *tls.Certificate) (*httptest.Server, tlsDialer) { - srv := httptest.NewUnstartedServer(http.NewServeMux()) - - srv.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){ - "acme-tls/1": func(_ *http.Server, conn *tls.Conn, _ http.Handler) { - // no-op - }, - "http/1.1": func(_ *http.Server, conn *tls.Conn, _ http.Handler) { - panic("unexpected http/1.1 next proto") - }, - } - - srv.TLS = &tls.Config{ - GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { - if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == "acme-tls/1" { - return validationCert, nil - } - return nil, nil - }, - NextProtos: []string{ - "acme-tls/1", - "http/1.1", - }, - } - - srv.Listener = tls.NewListener(srv.Listener, srv.TLS) - //srv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // hush - - return srv, func(network, addr string, config *tls.Config) (conn *tls.Conn, err error) { - return tls.DialWithDialer(&net.Dialer{Timeout: time.Second}, "tcp", srv.Listener.Addr().String(), config) - } -} - -// noopConn is a mock net.Conn that does nothing. -type noopConn struct{} - -func (c *noopConn) Read(_ []byte) (n int, err error) { return 0, io.EOF } -func (c *noopConn) Write(_ []byte) (n int, err error) { return 0, io.EOF } -func (c *noopConn) Close() error { return nil } -func (c *noopConn) LocalAddr() net.Addr { return &net.IPAddr{IP: net.IPv4zero, Zone: ""} } -func (c *noopConn) RemoteAddr() net.Addr { return &net.IPAddr{IP: net.IPv4zero, Zone: ""} } -func (c *noopConn) SetDeadline(t time.Time) error { return nil } -func (c *noopConn) SetReadDeadline(t time.Time) error { return nil } -func (c *noopConn) SetWriteDeadline(t time.Time) error { return nil } - -func newTLSALPNValidationCert(keyAuthHash []byte, obsoleteOID, critical bool, names ...string) (*tls.Certificate, error) { - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - - certTemplate := &x509.Certificate{ - SerialNumber: big.NewInt(1337), - Subject: pkix.Name{ - Organization: []string{"Test"}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(0, 0, 1), - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - DNSNames: names, - } - - if keyAuthHash != nil { - oid := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} - if obsoleteOID { - oid = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1} - } - - keyAuthHashEnc, _ := asn1.Marshal(keyAuthHash[:]) - - certTemplate.ExtraExtensions = []pkix.Extension{ - { - Id: oid, - Critical: critical, - Value: keyAuthHashEnc, - }, - } - } - - cert, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, privateKey.Public(), privateKey) - if err != nil { - return nil, err - } - - return &tls.Certificate{ - PrivateKey: privateKey, - Certificate: [][]byte{cert}, - }, nil -} - func TestDNS01Validate(t *testing.T) { type test struct { vo validateOptions @@ -1746,6 +909,7 @@ func TestDNS01Validate(t *testing.T) { "domain %s: force", ch.getValue())) baseClone := ch.clone() baseClone.Error = expErr.ToACME() + baseClone.Error.Subproblems = append(baseClone.Error.Subproblems, expErr) newCh := &dns01Challenge{baseClone} newb, err := json.Marshal(newCh) assert.FatalError(t, err) @@ -1842,6 +1006,7 @@ func TestDNS01Validate(t *testing.T) { "expected %s, but got %s", expKeyAuth, []string{"foo", "bar"})) baseClone := ch.clone() baseClone.Error = expErr.ToACME() + baseClone.Error.Subproblems = append(baseClone.Error.Subproblems, expErr) newCh := &http01Challenge{baseClone} newb, err := json.Marshal(newCh) assert.FatalError(t, err) diff --git a/acme/errors.go b/acme/errors.go index dd6f4afff..9facac5f0 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -9,7 +9,7 @@ func AccountDoesNotExistErr(err error) *Error { return &Error{ Type: accountDoesNotExistErr, Detail: "Account does not exist", - Status: 400, + Status: 404, Err: err, } } diff --git a/acme/order_test.go b/acme/order_test.go index b04537549..77c21e245 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -325,7 +325,7 @@ func TestNewOrder(t *testing.T) { ops: defaultOrderOps(), db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 8 { + if count >= 6 { return nil, false, errors.New("force") } count++ @@ -342,7 +342,7 @@ func TestNewOrder(t *testing.T) { ops: ops, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 9 { + if count >= 7 { return nil, false, errors.New("force") } count++ @@ -357,7 +357,7 @@ func TestNewOrder(t *testing.T) { }, "fail/save-orderIDs-error": func(t *testing.T) test { count := 0 - oids := []string{"1", "2", "3"} + oids := []string{"1", "2"} oidsB, err := json.Marshal(oids) assert.FatalError(t, err) var ( @@ -369,11 +369,11 @@ func TestNewOrder(t *testing.T) { ops: ops, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 9 { + if count >= 7 { assert.Equals(t, bucket, ordersByAccountIDTable) assert.Equals(t, key, []byte(ops.AccountID)) return nil, false, errors.New("force") - } else if count == 8 { + } else if count == 6 { *oid = string(key) } count++ @@ -393,7 +393,7 @@ func TestNewOrder(t *testing.T) { }, "ok": func(t *testing.T) test { count := 0 - oids := []string{"1", "2", "3"} + oids := []string{"1", "2"} oidsB, err := json.Marshal(oids) assert.FatalError(t, err) authzs := &([]string{}) @@ -406,18 +406,18 @@ func TestNewOrder(t *testing.T) { ops: ops, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { - if count >= 9 { + if count >= 7 { assert.Equals(t, bucket, ordersByAccountIDTable) assert.Equals(t, key, []byte(ops.AccountID)) assert.Equals(t, old, oidsB) newB, err := json.Marshal(append(oids, *oid)) assert.FatalError(t, err) assert.Equals(t, newval, newB) - } else if count == 8 { + } else if count == 6 { *oid = string(key) - } else if count == 7 { + } else if count == 5 { *authzs = append(*authzs, string(key)) - } else if count == 3 { + } else if count == 2 { *authzs = []string{string(key)} } count++ @@ -649,37 +649,29 @@ func TestOrderUpdateStatus(t *testing.T) { assert.FatalError(t, err) az2, err := newAz() assert.FatalError(t, err) - az3, err := newAz() - assert.FatalError(t, err) ch1, err := newHTTPCh() assert.FatalError(t, err) - ch2, err := newTLSALPNCh() - assert.FatalError(t, err) - ch3, err := newDNSCh() + ch2, err := newDNSCh() assert.FatalError(t, err) ch1b, err := json.Marshal(ch1) assert.FatalError(t, err) ch2b, err := json.Marshal(ch2) assert.FatalError(t, err) - ch3b, err := json.Marshal(ch3) - assert.FatalError(t, err) o, err := newO() assert.FatalError(t, err) - o.Authorizations = []string{az1.getID(), az2.getID(), az3.getID()} + o.Authorizations = []string{az1.getID(), az2.getID()} - _az3, ok := az3.(*dnsAuthz) + _az2, ok := az2.(*dnsAuthz) assert.Fatal(t, ok) - _az3.baseAuthz.Status = StatusValid + _az2.baseAuthz.Status = StatusValid b1, err := json.Marshal(az1) assert.FatalError(t, err) b2, err := json.Marshal(az2) assert.FatalError(t, err) - b3, err := json.Marshal(az3) - assert.FatalError(t, err) count := 0 return test{ @@ -696,17 +688,7 @@ func TestOrderUpdateStatus(t *testing.T) { case 2: ret = ch2b case 3: - ret = ch3b - case 4: ret = b2 - case 5: - ret = ch1b - case 6: - ret = ch2b - case 7: - ret = ch3b - case 8: - ret = b3 default: return nil, errors.New("unexpected count") } @@ -724,37 +706,29 @@ func TestOrderUpdateStatus(t *testing.T) { assert.FatalError(t, err) az2, err := newAz() assert.FatalError(t, err) - az3, err := newAz() - assert.FatalError(t, err) ch1, err := newHTTPCh() assert.FatalError(t, err) - ch2, err := newTLSALPNCh() - assert.FatalError(t, err) - ch3, err := newDNSCh() + ch2, err := newDNSCh() assert.FatalError(t, err) ch1b, err := json.Marshal(ch1) assert.FatalError(t, err) ch2b, err := json.Marshal(ch2) assert.FatalError(t, err) - ch3b, err := json.Marshal(ch3) - assert.FatalError(t, err) o, err := newO() assert.FatalError(t, err) - o.Authorizations = []string{az1.getID(), az2.getID(), az3.getID()} + o.Authorizations = []string{az1.getID(), az2.getID()} - _az3, ok := az3.(*dnsAuthz) + _az2, ok := az2.(*dnsAuthz) assert.Fatal(t, ok) - _az3.baseAuthz.Status = StatusInvalid + _az2.baseAuthz.Status = StatusInvalid b1, err := json.Marshal(az1) assert.FatalError(t, err) b2, err := json.Marshal(az2) assert.FatalError(t, err) - b3, err := json.Marshal(az3) - assert.FatalError(t, err) _o := *o clone := &_o @@ -775,17 +749,7 @@ func TestOrderUpdateStatus(t *testing.T) { case 2: ret = ch2b case 3: - ret = ch3b - case 4: ret = b2 - case 5: - ret = ch1b - case 6: - ret = ch2b - case 7: - ret = ch3b - case 8: - ret = b3 default: return nil, errors.New("unexpected count") } @@ -882,37 +846,29 @@ func TestOrderFinalize(t *testing.T) { assert.FatalError(t, err) az2, err := newAz() assert.FatalError(t, err) - az3, err := newAz() - assert.FatalError(t, err) ch1, err := newHTTPCh() assert.FatalError(t, err) - ch2, err := newTLSALPNCh() - assert.FatalError(t, err) - ch3, err := newDNSCh() + ch2, err := newDNSCh() assert.FatalError(t, err) ch1b, err := json.Marshal(ch1) assert.FatalError(t, err) ch2b, err := json.Marshal(ch2) assert.FatalError(t, err) - ch3b, err := json.Marshal(ch3) - assert.FatalError(t, err) o, err := newO() assert.FatalError(t, err) - o.Authorizations = []string{az1.getID(), az2.getID(), az3.getID()} + o.Authorizations = []string{az1.getID(), az2.getID()} - _az3, ok := az3.(*dnsAuthz) + _az2, ok := az2.(*dnsAuthz) assert.Fatal(t, ok) - _az3.baseAuthz.Status = StatusValid + _az2.baseAuthz.Status = StatusValid b1, err := json.Marshal(az1) assert.FatalError(t, err) b2, err := json.Marshal(az2) assert.FatalError(t, err) - b3, err := json.Marshal(az3) - assert.FatalError(t, err) count := 0 return test{ @@ -929,17 +885,7 @@ func TestOrderFinalize(t *testing.T) { case 2: ret = ch2b case 3: - ret = ch3b - case 4: ret = b2 - case 5: - ret = ch1b - case 6: - ret = ch2b - case 7: - ret = ch3b - case 8: - ret = b3 default: return nil, errors.New("unexpected count") } diff --git a/api/utils.go b/api/utils.go index 0d87a0651..154023d06 100644 --- a/api/utils.go +++ b/api/utils.go @@ -52,6 +52,11 @@ func JSON(w http.ResponseWriter, v interface{}) { JSONStatus(w, v, http.StatusOK) } +// JSON writes the passed value into the http.ResponseWriter. +func WriteProcessing(w http.ResponseWriter, v interface{}) { + JSONStatus(w, v, http.StatusProcessing) +} + // JSONStatus writes the given value into the http.ResponseWriter and the // given status is written as the status code of the response. func JSONStatus(w http.ResponseWriter, v interface{}, status int) { diff --git a/authority/authority.go b/authority/authority.go index 0730fe5e5..25b403508 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -12,11 +12,10 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" - "github.com/smallstep/certificates/kms" - kmsapi "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/templates" "github.com/smallstep/cli/crypto/pemutil" + "github.com/smallstep/cli/crypto/x509util" "golang.org/x/crypto/ssh" ) @@ -26,30 +25,21 @@ const ( // Authority implements the Certificate Authority internal interface. type Authority struct { - config *Config - keyManager kms.KeyManager - provisioners *provisioner.Collection - db db.AuthDB - - // X509 CA - rootX509Certs []*x509.Certificate - federatedX509Certs []*x509.Certificate - x509Signer crypto.Signer - x509Issuer *x509.Certificate - certificates *sync.Map - - // SSH CA + config *Config + rootX509Certs []*x509.Certificate + intermediateIdentity *x509util.Identity sshCAUserCertSignKey ssh.Signer sshCAHostCertSignKey ssh.Signer sshCAUserCerts []ssh.PublicKey sshCAHostCerts []ssh.PublicKey sshCAUserFederatedCerts []ssh.PublicKey sshCAHostFederatedCerts []ssh.PublicKey - + certificates *sync.Map + startTime time.Time + provisioners *provisioner.Collection + db db.AuthDB // Do not re-initialize - initOnce bool - startTime time.Time - + initOnce bool // Custom functions sshBastionFunc func(user, hostname string) (*Bastion, error) sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error) @@ -69,19 +59,12 @@ func New(config *Config, opts ...Option) (*Authority, error) { certificates: new(sync.Map), provisioners: provisioner.NewCollection(config.getAudiences()), } - - // Apply options. - for _, fn := range opts { - if err := fn(a); err != nil { - return nil, err - } + for _, opt := range opts { + opt(a) } - - // Initialize authority from options or configuration. if err := a.init(); err != nil { return nil, err } - return a, nil } @@ -93,19 +76,6 @@ func (a *Authority) init() error { } var err error - - // Initialize key manager if it has not been set in the options. - if a.keyManager == nil { - var options kmsapi.Options - if a.config.KMS != nil { - options = *a.config.KMS - } - a.keyManager, err = kms.New(context.Background(), options) - if err != nil { - return err - } - } - // Initialize step-ca Database if it's not already initialized with WithDB. // If a.config.DB is nil then a simple, barebones in memory DB will be used. if a.db == nil { @@ -114,62 +84,50 @@ func (a *Authority) init() error { } } - // Read root certificates and store them in the certificates map. - if len(a.rootX509Certs) == 0 { - a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) - for i, path := range a.config.Root { - crt, err := pemutil.ReadCertificate(path) - if err != nil { - return err - } - a.rootX509Certs[i] = crt + // Load the root certificates and add them to the certificate store + a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) + for i, path := range a.config.Root { + crt, err := pemutil.ReadCertificate(path) + if err != nil { + return err } - } - for _, crt := range a.rootX509Certs { + // Add root certificate to the certificate map sum := sha256.Sum256(crt.Raw) a.certificates.Store(hex.EncodeToString(sum[:]), crt) + a.rootX509Certs[i] = crt } - // Read federated certificates and store them in the certificates map. - if len(a.federatedX509Certs) == 0 { - a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots)) - for i, path := range a.config.FederatedRoots { - crt, err := pemutil.ReadCertificate(path) - if err != nil { - return err - } - a.federatedX509Certs[i] = crt + // Add federated roots + for _, path := range a.config.FederatedRoots { + crt, err := pemutil.ReadCertificate(path) + if err != nil { + return err } - } - for _, crt := range a.federatedX509Certs { sum := sha256.Sum256(crt.Raw) a.certificates.Store(hex.EncodeToString(sum[:]), crt) } - // Read intermediate and create X509 signer. - if a.x509Signer == nil { - crt, err := pemutil.ReadCertificate(a.config.IntermediateCert) + // Decrypt and load intermediate public / private key pair. + if len(a.config.Password) > 0 { + a.intermediateIdentity, err = x509util.LoadIdentityFromDisk( + a.config.IntermediateCert, + a.config.IntermediateKey, + pemutil.WithPassword([]byte(a.config.Password)), + ) if err != nil { return err } - signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) + } else { + a.intermediateIdentity, err = x509util.LoadIdentityFromDisk(a.config.IntermediateCert, a.config.IntermediateKey) if err != nil { return err } - a.x509Signer = signer - a.x509Issuer = crt } // Decrypt and load SSH keys if a.config.SSH != nil { if a.config.SSH.HostKey != "" { - signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.SSH.HostKey, - Password: []byte(a.config.Password), - }) + signer, err := parseCryptoSigner(a.config.SSH.HostKey, a.config.Password) if err != nil { return err } @@ -182,10 +140,7 @@ func (a *Authority) init() error { a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey()) } if a.config.SSH.UserKey != "" { - signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.SSH.UserKey, - Password: []byte(a.config.Password), - }) + signer, err := parseCryptoSigner(a.config.SSH.UserKey, a.config.Password) if err != nil { return err } @@ -290,3 +245,19 @@ func (a *Authority) GetDatabase() db.AuthDB { func (a *Authority) Shutdown() error { return a.db.Shutdown() } + +func parseCryptoSigner(filename, password string) (crypto.Signer, error) { + var opts []pemutil.Options + if password != "" { + opts = append(opts, pemutil.WithPassword([]byte(password))) + } + key, err := pemutil.Read(filename, opts...) + if err != nil { + return nil, err + } + signer, ok := key.(crypto.Signer) + if !ok { + return nil, errors.Errorf("key %s of type %T cannot be used for signing operations", filename, key) + } + return signer, nil +} diff --git a/authority/authority_test.go b/authority/authority_test.go index 058a4c252..e6a65453b 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -137,8 +137,7 @@ func TestAuthorityNew(t *testing.T) { assert.Equals(t, auth.rootX509Certs[0], root) assert.True(t, auth.initOnce) - assert.NotNil(t, auth.x509Signer) - assert.NotNil(t, auth.x509Issuer) + assert.NotNil(t, auth.intermediateIdentity) for _, p := range tc.config.AuthorityConfig.Provisioners { var _p provisioner.Interface _p, ok = auth.provisioners.Load(p.GetID()) diff --git a/authority/config.go b/authority/config.go index ceb2ea890..75f55a12d 100644 --- a/authority/config.go +++ b/authority/config.go @@ -10,7 +10,6 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" - kms "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/templates" "github.com/smallstep/cli/crypto/tlsutil" "github.com/smallstep/cli/crypto/x509util" @@ -55,7 +54,6 @@ type Config struct { IntermediateKey string `json:"key"` Address string `json:"address"` DNSNames []string `json:"dnsNames"` - KMS *kms.Options `json:"kms,omitempty"` SSH *SSHConfig `json:"ssh,omitempty"` Logger json.RawMessage `json:"logger,omitempty"` DB *db.Config `json:"db,omitempty"` @@ -181,11 +179,6 @@ func (c *Config) Validate() error { c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation } - // Validate KMS options, nil is ok. - if err := c.KMS.Validate(); err != nil { - return err - } - // Validate ssh: nil is ok if err := c.SSH.Validate(); err != nil { return err diff --git a/authority/options.go b/authority/options.go index 2d655a2b1..10f0ec1ae 100644 --- a/authority/options.go +++ b/authority/options.go @@ -2,54 +2,45 @@ package authority import ( "context" - "crypto" "crypto/x509" - "encoding/pem" - "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" - "github.com/smallstep/certificates/kms" "github.com/smallstep/certificates/sshutil" - "golang.org/x/crypto/ssh" ) // Option sets options to the Authority. -type Option func(*Authority) error +type Option func(*Authority) // WithDatabase sets an already initialized authority database to a new // authority. This option is intended to be use on graceful reloads. func WithDatabase(db db.AuthDB) Option { - return func(a *Authority) error { + return func(a *Authority) { a.db = db - return nil } } // WithGetIdentityFunc sets a custom function to retrieve the identity from // an external resource. func WithGetIdentityFunc(fn func(p provisioner.Interface, email string) (*provisioner.Identity, error)) Option { - return func(a *Authority) error { + return func(a *Authority) { a.getIdentityFunc = fn - return nil } } // WithSSHBastionFunc sets a custom function to get the bastion for a // given user-host pair. func WithSSHBastionFunc(fn func(user, host string) (*Bastion, error)) Option { - return func(a *Authority) error { + return func(a *Authority) { a.sshBastionFunc = fn - return nil } } // WithSSHGetHosts sets a custom function to get the bastion for a // given user-host pair. func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Option { - return func(a *Authority) error { + return func(a *Authority) { a.sshGetHostsFunc = fn - return nil } } @@ -57,127 +48,7 @@ func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Op // step ssh enabled. The token is used to validate the request, while the roots // are used to validate the token. func WithSSHCheckHost(fn func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)) Option { - return func(a *Authority) error { + return func(a *Authority) { a.sshCheckHostFunc = fn - return nil } } - -// WithKeyManager defines the key manager used to get and create keys, and sign -// certificates. -func WithKeyManager(k kms.KeyManager) Option { - return func(a *Authority) error { - a.keyManager = k - return nil - } -} - -// WithX509Signer defines the signer used to sign X509 certificates. -func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option { - return func(a *Authority) error { - a.x509Issuer = crt - a.x509Signer = s - return nil - } -} - -// WithSSHUserSigner defines the signer used to sign SSH user certificates. -func WithSSHUserSigner(s crypto.Signer) Option { - return func(a *Authority) error { - signer, err := ssh.NewSignerFromSigner(s) - if err != nil { - return errors.Wrap(err, "error creating ssh user signer") - } - a.sshCAUserCertSignKey = signer - // Append public key to list of user certs - pub := signer.PublicKey() - a.sshCAUserCerts = append(a.sshCAUserCerts, pub) - a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, pub) - return nil - } -} - -// WithSSHHostSigner defines the signer used to sign SSH host certificates. -func WithSSHHostSigner(s crypto.Signer) Option { - return func(a *Authority) error { - signer, err := ssh.NewSignerFromSigner(s) - if err != nil { - return errors.Wrap(err, "error creating ssh host signer") - } - a.sshCAHostCertSignKey = signer - // Append public key to list of host certs - pub := signer.PublicKey() - a.sshCAHostCerts = append(a.sshCAHostCerts, pub) - a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, pub) - return nil - } -} - -// WithX509RootCerts is an option that allows to define the list of root -// certificates to use. This option will replace any root certificate defined -// before. -func WithX509RootCerts(rootCerts ...*x509.Certificate) Option { - return func(a *Authority) error { - a.rootX509Certs = rootCerts - return nil - } -} - -// WithX509FederatedCerts is an option that allows to define the list of -// federated certificates. This option will replace any federated certificate -// defined before. -func WithX509FederatedCerts(certs ...*x509.Certificate) Option { - return func(a *Authority) error { - a.federatedX509Certs = certs - return nil - } -} - -// WithX509RootBundle is an option that allows to define the list of root -// certificates. This option will replace any root certificate defined before. -func WithX509RootBundle(pemCerts []byte) Option { - return func(a *Authority) error { - certs, err := readCertificateBundle(pemCerts) - if err != nil { - return err - } - a.rootX509Certs = certs - return nil - } -} - -// WithX509FederatedBundle is an option that allows to define the list of -// federated certificates. This option will replace any federated certificate -// defined before. -func WithX509FederatedBundle(pemCerts []byte) Option { - return func(a *Authority) error { - certs, err := readCertificateBundle(pemCerts) - if err != nil { - return err - } - a.federatedX509Certs = certs - return nil - } -} - -func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) { - var block *pem.Block - var certs []*x509.Certificate - for len(pemCerts) > 0 { - block, pemCerts = pem.Decode(pemCerts) - if block == nil { - break - } - if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { - continue - } - - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, err - } - - certs = append(certs, cert) - } - return certs, nil -} diff --git a/authority/tls.go b/authority/tls.go index 4480314cb..03a9ec331 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -64,6 +64,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti opts = []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)} certValidators = []provisioner.CertificateValidator{} + issIdentity = a.intermediateIdentity ) // Set backdate with the configured value @@ -88,7 +89,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti return nil, errs.Wrap(http.StatusBadRequest, err, "authority.Sign; invalid certificate request", opts...) } - leaf, err := x509util.NewLeafProfileWithCSR(csr, a.x509Issuer, a.x509Signer, mods...) + leaf, err := x509util.NewLeafProfileWithCSR(csr, issIdentity.Crt, issIdentity.Key, mods...) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } @@ -111,6 +112,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti "authority.Sign; error parsing new leaf certificate", opts...) } + caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, + "authority.Sign; error parsing intermediate certificate", opts...) + } + if err = a.db.StoreCertificate(serverCert); err != nil { if err != db.ErrNotImplemented { return nil, errs.Wrap(http.StatusInternalServerError, err, @@ -118,7 +125,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti } } - return []*x509.Certificate{serverCert, a.x509Issuer}, nil + return []*x509.Certificate{serverCert, caCert}, nil } // Renew creates a new Certificate identical to the old certificate, except @@ -131,6 +138,9 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...) } + // Issuer + issIdentity := a.intermediateIdentity + // Durations backdate := a.config.AuthorityConfig.Backdate.Duration duration := oldCert.NotAfter.Sub(oldCert.NotBefore) @@ -138,7 +148,7 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error newCert := &x509.Certificate{ PublicKey: oldCert.PublicKey, - Issuer: a.x509Issuer.Subject, + Issuer: issIdentity.Crt.Subject, Subject: oldCert.Subject, NotBefore: now.Add(-1 * backdate), NotAfter: now.Add(duration - backdate), @@ -178,7 +188,8 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error } } - leaf, err := x509util.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer) + leaf, err := x509util.NewLeafProfileWithTemplate(newCert, + issIdentity.Crt, issIdentity.Key) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...) } @@ -193,6 +204,11 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew; error parsing new server certificate", opts...) } + caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, + "authority.Renew; error parsing intermediate certificate", opts...) + } if err = a.db.StoreCertificate(serverCert); err != nil { if err != db.ErrNotImplemented { @@ -200,7 +216,7 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error } } - return []*x509.Certificate{serverCert, a.x509Issuer}, nil + return []*x509.Certificate{serverCert, caCert}, nil } // RevokeOptions are the options for the Revoke API. @@ -304,7 +320,8 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server. func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { - profile, err := x509util.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer, + profile, err := x509util.NewLeafProfile("Step Online CA", + a.intermediateIdentity.Crt, a.intermediateIdentity.Key, x509util.WithHosts(strings.Join(a.config.DNSNames, ","))) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") @@ -327,7 +344,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { // Load the x509 key pair (combining server and intermediate blocks) // to a tls.Certificate. - intermediatePEM, err := pemutil.Serialize(a.x509Issuer) + intermediatePEM, err := pemutil.Serialize(a.intermediateIdentity.Crt) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") } diff --git a/authority/tls_test.go b/authority/tls_test.go index 722338d37..f946022f1 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -2,7 +2,6 @@ package authority import ( "context" - "crypto" "crypto/rand" "crypto/sha1" "crypto/x509" @@ -157,7 +156,7 @@ func TestAuthority_Sign(t *testing.T) { }, "fail create cert": func(t *testing.T) *signTest { _a := testAuthority(t) - _a.x509Signer = nil + _a.intermediateIdentity.Key = nil csr := getCSR(t, priv) return &signTest{ auth: _a, @@ -304,7 +303,7 @@ ZYtQ9Ot36qc= hash := sha1.Sum(pubBytes) assert.Equals(t, leaf.SubjectKeyId, hash[:]) - assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.SubjectKeyId) + assert.Equals(t, leaf.AuthorityKeyId, a.intermediateIdentity.Crt.SubjectKeyId) // Verify Provisioner OID found := 0 @@ -323,7 +322,7 @@ ZYtQ9Ot36qc= } assert.Equals(t, found, 1) - realIntermediate, err := x509.ParseCertificate(a.x509Issuer.Raw) + realIntermediate, err := x509.ParseCertificate(a.intermediateIdentity.Crt.Raw) assert.FatalError(t, err) assert.Equals(t, intermediate, realIntermediate) } @@ -354,7 +353,8 @@ func TestAuthority_Renew(t *testing.T) { NotAfter: provisioner.NewTimeDuration(na1), } - leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer, + leaf, err := x509util.NewLeafProfile("renew", a.intermediateIdentity.Crt, + a.intermediateIdentity.Key, x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), withDefaultASN1DN(a.config.AuthorityConfig.Template), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"), @@ -365,7 +365,8 @@ func TestAuthority_Renew(t *testing.T) { cert, err := x509.ParseCertificate(certBytes) assert.FatalError(t, err) - leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer, + leafNoRenew, err := x509util.NewLeafProfile("norenew", a.intermediateIdentity.Crt, + a.intermediateIdentity.Key, x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), withDefaultASN1DN(a.config.AuthorityConfig.Template), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"), @@ -386,7 +387,7 @@ func TestAuthority_Renew(t *testing.T) { tests := map[string]func() (*renewTest, error){ "fail-create-cert": func() (*renewTest, error) { _a := testAuthority(t) - _a.x509Signer = nil + _a.intermediateIdentity.Key = nil return &renewTest{ auth: _a, cert: cert, @@ -424,8 +425,8 @@ func TestAuthority_Renew(t *testing.T) { assert.FatalError(t, err) _a := testAuthority(t) - _a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer) - _a.x509Issuer = newIntermediateCert + _a.intermediateIdentity.Key = newIntermediateProfile.SubjectPrivateKey() + _a.intermediateIdentity.Crt = newIntermediateCert return &renewTest{ auth: _a, cert: cert, @@ -493,8 +494,8 @@ func TestAuthority_Renew(t *testing.T) { assert.Equals(t, leaf.SubjectKeyId, hash[:]) // We did not change the intermediate before renewing. - if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber { - assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.SubjectKeyId) + if a.intermediateIdentity.Crt.SerialNumber == tc.auth.intermediateIdentity.Crt.SerialNumber { + assert.Equals(t, leaf.AuthorityKeyId, a.intermediateIdentity.Crt.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { found := false @@ -510,7 +511,7 @@ func TestAuthority_Renew(t *testing.T) { } } else { // We did change the intermediate before renewing. - assert.Equals(t, leaf.AuthorityKeyId, tc.auth.x509Issuer.SubjectKeyId) + assert.Equals(t, leaf.AuthorityKeyId, tc.auth.intermediateIdentity.Crt.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { // The authority key id extension should be different b/c the intermediates are different. @@ -534,7 +535,7 @@ func TestAuthority_Renew(t *testing.T) { } } - realIntermediate, err := x509.ParseCertificate(tc.auth.x509Issuer.Raw) + realIntermediate, err := x509.ParseCertificate(tc.auth.intermediateIdentity.Crt.Raw) assert.FatalError(t, err) assert.Equals(t, intermediate, realIntermediate) } diff --git a/ca/identity/client.go b/ca/identity/client.go index 7daafacdc..d615a019b 100644 --- a/ca/identity/client.go +++ b/ca/identity/client.go @@ -23,7 +23,7 @@ func (c *Client) ResolveReference(ref *url.URL) *url.URL { return c.CaURL.ResolveReference(ref) } -// LoadClient configures an http.Client with the root in +// LoadStepClient configures an http.Client with the root in // $STEPPATH/config/defaults.json and the identity defined in // $STEPPATH/config/identity.json func LoadClient() (*Client, error) { diff --git a/ca/provisioner.go b/ca/provisioner.go index 1b7067e08..3f86c068d 100644 --- a/ca/provisioner.go +++ b/ca/provisioner.go @@ -118,7 +118,6 @@ func (p *Provisioner) Token(subject string, sans ...string) (string, error) { return tok.SignedString(p.jwk.Algorithm, p.jwk.Key) } -// SSHToken generates a SSH token. func (p *Provisioner) SSHToken(certType, keyID string, principals []string) (string, error) { jwtID, err := randutil.Hex(64) if err != nil { diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 0486cd5fc..468f70849 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -91,7 +91,7 @@ func main() { app.HelpName = "step-ca" app.Version = config.Version() app.Usage = "an online certificate authority for secure automated certificate management" - app.UsageText = `**step-ca** [**--password-file**=] [**--resolver**=] [**--help**] [**--version**]` + app.UsageText = `**step-ca** [**--password-file**=] [**--help**] [**--version**]` app.Description = `**step-ca** runs the Step Online Certificate Authority (Step CA) using the given configuration. See the README.md for more detailed configuration documentation. @@ -116,7 +116,7 @@ $ step-ca $STEPPATH/config/ca.json --password-file ./password.txt '''` app.Flags = append(app.Flags, commands.AppCommand.Flags...) app.Flags = append(app.Flags, cli.HelpFlag) - app.Copyright = "(c) 2018-2020 Smallstep Labs, Inc." + app.Copyright = "(c) 2019 Smallstep Labs, Inc." // All non-successful output should be written to stderr app.Writer = os.Stdout diff --git a/commands/app.go b/commands/app.go index 9b22ac2d3..36155bd93 100644 --- a/commands/app.go +++ b/commands/app.go @@ -2,10 +2,8 @@ package commands import ( "bytes" - "context" "fmt" "io/ioutil" - "net" "net/http" "os" "unicode" @@ -22,25 +20,19 @@ var AppCommand = cli.Command{ Name: "start", Action: appAction, UsageText: `**step-ca** - [**--password-file**=] - [**--resolver**=]`, + [**--password-file**=]`, Flags: []cli.Flag{ cli.StringFlag{ Name: "password-file", Usage: `path to the containing the password to decrypt the intermediate private key.`, }, - cli.StringFlag{ - Name: "resolver", - Usage: "address of a DNS resolver to be used instead of the default.", - }, }, } // AppAction is the action used when the top command runs. func appAction(ctx *cli.Context) error { passFile := ctx.String("password-file") - resolver := ctx.String("resolver") // If zero cmd line args show help, if >1 cmd line args show error. if ctx.NArg() == 0 { @@ -64,14 +56,6 @@ func appAction(ctx *cli.Context) error { password = bytes.TrimRightFunc(password, unicode.IsSpace) } - // replace resolver if requested - if resolver != "" { - net.DefaultResolver.PreferGo = true - net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) { - return net.Dial(network, resolver) - } - } - srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password)) if err != nil { fatal(err) diff --git a/go.mod b/go.mod index 69a64a1e3..004c50d7e 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,8 @@ module github.com/smallstep/certificates go 1.13 require ( - cloud.google.com/go v0.51.0 github.com/Masterminds/sprig/v3 v3.0.0 github.com/go-chi/chi v4.0.2+incompatible - github.com/google/go-cmp v0.4.0 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 github.com/newrelic/go-agent v2.15.0+incompatible github.com/pkg/errors v0.8.1 github.com/rs/xid v1.2.1 @@ -18,9 +15,6 @@ require ( github.com/urfave/cli v1.22.2 golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 - google.golang.org/api v0.15.0 - google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb - google.golang.org/grpc v1.26.0 gopkg.in/square/go-jose.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 5f1248ac6..b37f253cf 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -109,7 +97,6 @@ github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= github.com/go-critic/go-critic v0.4.0 h1:sXD3pix0wDemuPuSlrXpJNNYXlUiKiysLrtPVQmxkzI= github.com/go-critic/go-critic v0.4.0/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= @@ -148,11 +135,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -208,15 +192,11 @@ github.com/google/certificate-transparency-go v1.1.0/go.mod h1:i+Q7XY+ArBveOUT36 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/monologue v0.0.0-20190606152607-4b11a32b5934/go.mod h1:6NTfaQoUpg5QmPsCUWLR3ig33FHrKXhTtWzF0DVdmuk= github.com/google/monologue v0.0.0-20191220140058-35abc9683a6c/go.mod h1:6NTfaQoUpg5QmPsCUWLR3ig33FHrKXhTtWzF0DVdmuk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/trillian v1.2.2-0.20190612132142-05461f4df60a/go.mod h1:YPmUVn5NGwgnDUgqlVyFGMTgaWlnSvH7W5p+NdOG8UA= github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c/go.mod h1:WgL3XZ3pA8/9cm7yxqWrZE6iZkESB2ItGxy5Fo6k2lk= @@ -224,8 +204,6 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -245,7 +223,6 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -260,7 +237,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= @@ -290,7 +266,6 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible h1:GfzE+uq7odDW7nOmp1QWuilLEK7kJf8i84XcIfk3mKA= github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -324,7 +299,6 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/pkcs11 v1.0.2 h1:CIBkOawOtzJNE0B+EpRiUBzuVW7JEQAwdwhSS6YhIeg= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -564,8 +538,6 @@ go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSF go.etcd.io/etcd v3.3.18+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -580,7 +552,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= @@ -589,24 +560,14 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -623,7 +584,6 @@ golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -635,8 +595,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -662,16 +620,12 @@ golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0 h1:V+O002es++Mnym06Rj/S6Fl7V golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -696,7 +650,6 @@ golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -704,55 +657,36 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc h1:MR2F33ipDGog0C4eMhU6u9o3q6c3dvYis2aG6Jl12Wg= golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb h1:ADPHZzpzM4tk4V4S5cnCrr5SwzvlrPRmqqCuJDB8UTs= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -760,7 +694,6 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -804,7 +737,6 @@ mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskX mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2 h1:K7wru2CfJGumS5hkiguQ0Rb9ebKM2Jo8s5d4Jm9lFaM= mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2/go.mod h1:rCqoQrfAmpTX/h2APczwM7UymU/uvaOluiVPIYCSY/k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o= diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go deleted file mode 100644 index a46db0371..000000000 --- a/kms/apiv1/options.go +++ /dev/null @@ -1,68 +0,0 @@ -package apiv1 - -import ( - "strings" - - "github.com/pkg/errors" -) - -// ErrNotImplemented -type ErrNotImplemented struct { - msg string -} - -func (e ErrNotImplemented) Error() string { - if e.msg != "" { - return e.msg - } - return "not implemented" -} - -// Type represents the KMS type used. -type Type string - -const ( - // DefaultKMS is a KMS implementation using software. - DefaultKMS Type = "" - // SoftKMS is a KMS implementation using software. - SoftKMS Type = "softkms" - // CloudKMS is a KMS implementation using Google's Cloud KMS. - CloudKMS Type = "cloudkms" - // AmazonKMS is a KMS implementation using Amazon AWS KMS. - AmazonKMS Type = "awskms" - // PKCS11 is a KMS implementation using the PKCS11 standard. - PKCS11 Type = "pkcs11" -) - -type Options struct { - // The type of the KMS to use. - Type string `json:"type"` - - // Path to the credentials file used in CloudKMS. - CredentialsFile string `json:"credentialsFile"` - - // Path to the module used with PKCS11 KMS. - Module string `json:"module"` - - // Pin used to access the PKCS11 module. - Pin string `json:"pin"` -} - -// Validate checks the fields in Options. -func (o *Options) Validate() error { - if o == nil { - return nil - } - - switch Type(strings.ToLower(o.Type)) { - case DefaultKMS, SoftKMS, CloudKMS: - case AmazonKMS: - return ErrNotImplemented{"support for AmazonKMS is not yet implemented"} - case PKCS11: - return ErrNotImplemented{"support for PKCS11 is not yet implemented"} - default: - return errors.Errorf("unsupported kms type %s", o.Type) - } - - return nil -} diff --git a/kms/apiv1/options_test.go b/kms/apiv1/options_test.go deleted file mode 100644 index 645b63b1b..000000000 --- a/kms/apiv1/options_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package apiv1 - -import ( - "testing" -) - -func TestOptions_Validate(t *testing.T) { - tests := []struct { - name string - options *Options - wantErr bool - }{ - {"nil", nil, false}, - {"softkms", &Options{Type: "softkms"}, false}, - {"cloudkms", &Options{Type: "cloudkms"}, false}, - {"awskms", &Options{Type: "awskms"}, true}, - {"pkcs11", &Options{Type: "pkcs11"}, true}, - {"unsupported", &Options{Type: "unsupported"}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.options.Validate(); (err != nil) != tt.wantErr { - t.Errorf("Options.Validate() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestErrNotImplemented_Error(t *testing.T) { - type fields struct { - msg string - } - tests := []struct { - name string - fields fields - want string - }{ - {"default", fields{}, "not implemented"}, - {"custom", fields{"custom message: not implemented"}, "custom message: not implemented"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrNotImplemented{ - msg: tt.fields.msg, - } - if got := e.Error(); got != tt.want { - t.Errorf("ErrNotImplemented.Error() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/kms/apiv1/requests.go b/kms/apiv1/requests.go deleted file mode 100644 index 35c2fcaec..000000000 --- a/kms/apiv1/requests.go +++ /dev/null @@ -1,126 +0,0 @@ -package apiv1 - -import ( - "crypto" - "fmt" -) - -// ProtectionLevel specifies on some KMS how cryptographic operations are -// performed. -type ProtectionLevel int - -const ( - // Protection level not specified. - UnspecifiedProtectionLevel ProtectionLevel = iota - // Crypto operations are performed in software. - Software - // Crypto operations are performed in a Hardware Security Module. - HSM -) - -// String returns a string representation of p. -func (p ProtectionLevel) String() string { - switch p { - case UnspecifiedProtectionLevel: - return "unspecified" - case Software: - return "software" - case HSM: - return "hsm" - default: - return fmt.Sprintf("unknown(%d)", p) - } -} - -// SignatureAlgorithm used for cryptographic signing. -type SignatureAlgorithm int - -const ( - // Not specified. - UnspecifiedSignAlgorithm SignatureAlgorithm = iota - // RSASSA-PKCS1-v1_5 key and a SHA256 digest. - SHA256WithRSA - // RSASSA-PKCS1-v1_5 key and a SHA384 digest. - SHA384WithRSA - // RSASSA-PKCS1-v1_5 key and a SHA512 digest. - SHA512WithRSA - // RSASSA-PSS key with a SHA256 digest. - SHA256WithRSAPSS - // RSASSA-PSS key with a SHA384 digest. - SHA384WithRSAPSS - // RSASSA-PSS key with a SHA512 digest. - SHA512WithRSAPSS - // ECDSA on the NIST P-256 curve with a SHA256 digest. - ECDSAWithSHA256 - // ECDSA on the NIST P-384 curve with a SHA384 digest. - ECDSAWithSHA384 - // ECDSA on the NIST P-521 curve with a SHA512 digest. - ECDSAWithSHA512 - // EdDSA on Curve25519 with a SHA512 digest. - PureEd25519 -) - -// String returns a string representation of s. -func (s SignatureAlgorithm) String() string { - switch s { - case UnspecifiedSignAlgorithm: - return "unspecified" - case SHA256WithRSA: - return "SHA256-RSA" - case SHA384WithRSA: - return "SHA384-RSA" - case SHA512WithRSA: - return "SHA512-RSA" - case SHA256WithRSAPSS: - return "SHA256-RSAPSS" - case SHA384WithRSAPSS: - return "SHA384-RSAPSS" - case SHA512WithRSAPSS: - return "SHA512-RSAPSS" - case ECDSAWithSHA256: - return "ECDSA-SHA256" - case ECDSAWithSHA384: - return "ECDSA-SHA384" - case ECDSAWithSHA512: - return "ECDSA-SHA512" - case PureEd25519: - return "Ed25519" - default: - return fmt.Sprintf("unknown(%d)", s) - } -} - -// GetPublicKeyRequest is the parameter used in the kms.GetPublicKey method. -type GetPublicKeyRequest struct { - Name string -} - -// CreateKeyRequest is the parameter used in the kms.CreateKey method. -type CreateKeyRequest struct { - Name string - SignatureAlgorithm SignatureAlgorithm - Bits int - - // ProtectionLevel specifies how cryptographic operations are performed. - // Used by: cloudkms - ProtectionLevel ProtectionLevel -} - -// CreateKeyResponse is the response value of the kms.CreateKey method. -type CreateKeyResponse struct { - Name string - PublicKey crypto.PublicKey - PrivateKey crypto.PrivateKey - CreateSignerRequest CreateSignerRequest -} - -// CreateSignerRequest is the parameter used in the kms.CreateSigner method. -type CreateSignerRequest struct { - Signer crypto.Signer - SigningKey string - SigningKeyPEM []byte - TokenLabel string - PublicKey string - PublicKeyPEM []byte - Password []byte -} diff --git a/kms/apiv1/requests_test.go b/kms/apiv1/requests_test.go deleted file mode 100644 index b378e631a..000000000 --- a/kms/apiv1/requests_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package apiv1 - -import "testing" - -func TestProtectionLevel_String(t *testing.T) { - tests := []struct { - name string - p ProtectionLevel - want string - }{ - {"unspecified", UnspecifiedProtectionLevel, "unspecified"}, - {"software", Software, "software"}, - {"hsm", HSM, "hsm"}, - {"unknown", ProtectionLevel(100), "unknown(100)"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.p.String(); got != tt.want { - t.Errorf("ProtectionLevel.String() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSignatureAlgorithm_String(t *testing.T) { - tests := []struct { - name string - s SignatureAlgorithm - want string - }{ - {"UnspecifiedSignAlgorithm", UnspecifiedSignAlgorithm, "unspecified"}, - {"SHA256WithRSA", SHA256WithRSA, "SHA256-RSA"}, - {"SHA384WithRSA", SHA384WithRSA, "SHA384-RSA"}, - {"SHA512WithRSA", SHA512WithRSA, "SHA512-RSA"}, - {"SHA256WithRSAPSS", SHA256WithRSAPSS, "SHA256-RSAPSS"}, - {"SHA384WithRSAPSS", SHA384WithRSAPSS, "SHA384-RSAPSS"}, - {"SHA512WithRSAPSS", SHA512WithRSAPSS, "SHA512-RSAPSS"}, - {"ECDSAWithSHA256", ECDSAWithSHA256, "ECDSA-SHA256"}, - {"ECDSAWithSHA384", ECDSAWithSHA384, "ECDSA-SHA384"}, - {"ECDSAWithSHA512", ECDSAWithSHA512, "ECDSA-SHA512"}, - {"PureEd25519", PureEd25519, "Ed25519"}, - {"unknown", SignatureAlgorithm(100), "unknown(100)"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.s.String(); got != tt.want { - t.Errorf("SignatureAlgorithm.String() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/kms/cloudkms/cloudkms.go b/kms/cloudkms/cloudkms.go deleted file mode 100644 index a2332d5f3..000000000 --- a/kms/cloudkms/cloudkms.go +++ /dev/null @@ -1,280 +0,0 @@ -package cloudkms - -import ( - "context" - "crypto" - "strings" - "time" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - cloudkms "cloud.google.com/go/kms/apiv1" - gax "github.com/googleapis/gax-go/v2" - "github.com/pkg/errors" - "github.com/smallstep/certificates/kms/apiv1" - "github.com/smallstep/cli/crypto/pemutil" - "google.golang.org/api/option" - kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" -) - -// protectionLevelMapping maps step protection levels with cloud kms ones. -var protectionLevelMapping = map[apiv1.ProtectionLevel]kmspb.ProtectionLevel{ - apiv1.UnspecifiedProtectionLevel: kmspb.ProtectionLevel_PROTECTION_LEVEL_UNSPECIFIED, - apiv1.Software: kmspb.ProtectionLevel_SOFTWARE, - apiv1.HSM: kmspb.ProtectionLevel_HSM, -} - -// signatureAlgorithmMapping is a mapping between the step signature algorithm, -// and bits for RSA keys, with cloud kms one. -// -// Cloud KMS does not support SHA384WithRSA, SHA384WithRSAPSS, SHA384WithRSAPSS, -// ECDSAWithSHA512, and PureEd25519. -var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{ - apiv1.UnspecifiedSignAlgorithm: kmspb.CryptoKeyVersion_CRYPTO_KEY_VERSION_ALGORITHM_UNSPECIFIED, - apiv1.SHA256WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{ - 0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256, - 2048: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, - 3072: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256, - 4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256, - }, - apiv1.SHA512WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{ - 0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256, - 4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256, - }, - apiv1.SHA256WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{ - 0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256, - 2048: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256, - 3072: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256, - 4096: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256, - }, - apiv1.SHA512WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{ - 0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512, - 4096: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512, - }, - apiv1.ECDSAWithSHA256: kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256, - apiv1.ECDSAWithSHA384: kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384, -} - -// KeyManagementClient defines the methods on KeyManagementClient that this -// package will use. This interface will be used for unit testing. -type KeyManagementClient interface { - Close() error - GetPublicKey(context.Context, *kmspb.GetPublicKeyRequest, ...gax.CallOption) (*kmspb.PublicKey, error) - AsymmetricSign(context.Context, *kmspb.AsymmetricSignRequest, ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) - CreateCryptoKey(context.Context, *kmspb.CreateCryptoKeyRequest, ...gax.CallOption) (*kmspb.CryptoKey, error) - GetKeyRing(context.Context, *kmspb.GetKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error) - CreateKeyRing(context.Context, *kmspb.CreateKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error) - CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) -} - -// CloudKMS implements a KMS using Google's Cloud apiv1. -type CloudKMS struct { - client KeyManagementClient -} - -// New creates a new CloudKMS configured with a new client. -func New(ctx context.Context, opts apiv1.Options) (*CloudKMS, error) { - var cloudOpts []option.ClientOption - if opts.CredentialsFile != "" { - cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile)) - } - - client, err := cloudkms.NewKeyManagementClient(ctx, cloudOpts...) - if err != nil { - return nil, err - } - - return &CloudKMS{ - client: client, - }, nil -} - -// NewCloudKMS creates a CloudKMS with a given client. -func NewCloudKMS(client KeyManagementClient) *CloudKMS { - return &CloudKMS{ - client: client, - } -} - -// Close closes the connection of the Cloud KMS client. -func (k *CloudKMS) Close() error { - if err := k.client.Close(); err != nil { - return errors.Wrap(err, "cloudKMS Close failed") - } - return nil -} - -// CreateSigner returns a new cloudkms signer configured with the given signing -// key name. -func (k *CloudKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { - if req.SigningKey == "" { - return nil, errors.New("signing key cannot be empty") - } - - return NewSigner(k.client, req.SigningKey), nil -} - -// CreateKey creates in Google's Cloud KMS a new asymmetric key for signing. -func (k *CloudKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { - if req.Name == "" { - return nil, errors.New("createKeyRequest 'name' cannot be empty") - } - - protectionLevel, ok := protectionLevelMapping[req.ProtectionLevel] - if !ok { - return nil, errors.Errorf("cloudKMS does not support protection level '%s'", req.ProtectionLevel) - } - - var signatureAlgorithm kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm - v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] - if !ok { - return nil, errors.Errorf("cloudKMS does not support signature algorithm '%s'", req.SignatureAlgorithm) - } - switch v := v.(type) { - case kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm: - signatureAlgorithm = v - case map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm: - if signatureAlgorithm, ok = v[req.Bits]; !ok { - return nil, errors.Errorf("cloudKMS does not support signature algorithm '%s' with '%d' bits", req.SignatureAlgorithm, req.Bits) - } - default: - return nil, errors.Errorf("unexpected error: this should not happen") - } - - var crytoKeyName string - - // Split `projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID` - // to `projects/PROJECT_ID/locations/global/keyRings/RING_ID` and `KEY_ID`. - keyRing, keyID := Parent(req.Name) - if err := k.createKeyRingIfNeeded(keyRing); err != nil { - return nil, err - } - - ctx, cancel := defaultContext() - defer cancel() - - // Create private key in CloudKMS. - response, err := k.client.CreateCryptoKey(ctx, &kmspb.CreateCryptoKeyRequest{ - Parent: keyRing, - CryptoKeyId: keyID, - CryptoKey: &kmspb.CryptoKey{ - Purpose: kmspb.CryptoKey_ASYMMETRIC_SIGN, - VersionTemplate: &kmspb.CryptoKeyVersionTemplate{ - ProtectionLevel: protectionLevel, - Algorithm: signatureAlgorithm, - }, - }, - }) - if err != nil { - if status.Code(err) != codes.AlreadyExists { - return nil, errors.Wrap(err, "cloudKMS CreateCryptoKey failed") - } - // Create a new version if the key already exists. - // - // Note that it will have the same purpose, protection level and - // algorithm than as previous one. - req := &kmspb.CreateCryptoKeyVersionRequest{ - Parent: req.Name, - CryptoKeyVersion: &kmspb.CryptoKeyVersion{ - State: kmspb.CryptoKeyVersion_ENABLED, - }, - } - response, err := k.client.CreateCryptoKeyVersion(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "cloudKMS CreateCryptoKeyVersion failed") - } - crytoKeyName = response.Name - } else { - crytoKeyName = response.Name + "/cryptoKeyVersions/1" - } - - // Retrieve public key to add it to the response. - pk, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ - Name: crytoKeyName, - }) - if err != nil { - return nil, errors.Wrap(err, "cloudKMS GetPublicKey failed") - } - - return &apiv1.CreateKeyResponse{ - Name: crytoKeyName, - PublicKey: pk, - CreateSignerRequest: apiv1.CreateSignerRequest{ - SigningKey: crytoKeyName, - }, - }, nil -} - -func (k *CloudKMS) createKeyRingIfNeeded(name string) error { - ctx, cancel := defaultContext() - defer cancel() - - _, err := k.client.GetKeyRing(ctx, &kmspb.GetKeyRingRequest{ - Name: name, - }) - if err == nil { - return nil - } - - parent, child := Parent(name) - _, err = k.client.CreateKeyRing(ctx, &kmspb.CreateKeyRingRequest{ - Parent: parent, - KeyRingId: child, - }) - if err != nil && status.Code(err) != codes.AlreadyExists { - return errors.Wrap(err, "cloudKMS CreateKeyRing failed") - } - - return nil -} - -// GetPublicKey gets from Google's Cloud KMS a public key by name. Key names -// follow the pattern: -// projects/([^/]+)/locations/([a-zA-Z0-9_-]{1,63})/keyRings/([a-zA-Z0-9_-]{1,63})/cryptoKeys/([a-zA-Z0-9_-]{1,63})/cryptoKeyVersions/([a-zA-Z0-9_-]{1,63}) -func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { - if req.Name == "" { - return nil, errors.New("createKeyRequest 'name' cannot be empty") - } - - ctx, cancel := defaultContext() - defer cancel() - - response, err := k.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{ - Name: req.Name, - }) - if err != nil { - return nil, errors.Wrap(err, "cloudKMS GetPublicKey failed") - } - - pk, err := pemutil.ParseKey([]byte(response.Pem)) - if err != nil { - return nil, err - } - - return pk, nil -} - -func defaultContext() (context.Context, context.CancelFunc) { - return context.WithTimeout(context.Background(), 15*time.Second) -} - -// Parent splits a string in the format `key/value/key2/value2` in a parent and -// child, for the previous string it will return `key/value` and `value2`. -func Parent(name string) (string, string) { - a, b := parent(name) - a, _ = parent(a) - return a, b -} - -func parent(name string) (string, string) { - i := strings.LastIndex(name, "/") - switch i { - case -1: - return "", name - case 0: - return "", name[i+1:] - default: - return name[:i], name[i+1:] - } -} diff --git a/kms/cloudkms/cloudkms_test.go b/kms/cloudkms/cloudkms_test.go deleted file mode 100644 index b4f92fa65..000000000 --- a/kms/cloudkms/cloudkms_test.go +++ /dev/null @@ -1,376 +0,0 @@ -package cloudkms - -import ( - "context" - "crypto" - "fmt" - "io/ioutil" - "os" - "reflect" - "testing" - - gax "github.com/googleapis/gax-go/v2" - "github.com/smallstep/certificates/kms/apiv1" - "github.com/smallstep/cli/crypto/pemutil" - kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func TestParent(t *testing.T) { - type args struct { - name string - } - tests := []struct { - name string - args args - want string - want1 string - }{ - {"zero", args{"child"}, "", "child"}, - {"one", args{"parent/child"}, "", "child"}, - {"two", args{"grandparent/parent/child"}, "grandparent", "child"}, - {"three", args{"great-grandparent/grandparent/parent/child"}, "great-grandparent/grandparent", "child"}, - {"empty", args{""}, "", ""}, - {"root", args{"/"}, "", ""}, - {"child", args{"/child"}, "", "child"}, - {"parent", args{"parent/"}, "", ""}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1 := Parent(tt.args.name) - if got != tt.want { - t.Errorf("Parent() got = %v, want %v", got, tt.want) - } - if got1 != tt.want1 { - t.Errorf("Parent() got1 = %v, want %v", got1, tt.want1) - } - }) - } -} - -func TestNew(t *testing.T) { - type args struct { - ctx context.Context - opts apiv1.Options - } - tests := []struct { - name string - skipOnCI bool - args args - want *CloudKMS - wantErr bool - }{ - {"fail authentication", true, args{context.Background(), apiv1.Options{}}, nil, true}, - {"fail credentials", false, args{context.Background(), apiv1.Options{CredentialsFile: "testdata/missing"}}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.skipOnCI && os.Getenv("CI") == "true" { - t.SkipNow() - } - - got, err := New(tt.args.ctx, tt.args.opts) - if (err != nil) != tt.wantErr { - t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("New() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestNewCloudKMS(t *testing.T) { - type args struct { - client KeyManagementClient - } - tests := []struct { - name string - args args - want *CloudKMS - }{ - {"ok", args{&MockClient{}}, &CloudKMS{&MockClient{}}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewCloudKMS(tt.args.client); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewCloudKMS() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCloudKMS_Close(t *testing.T) { - type fields struct { - client KeyManagementClient - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - {"ok", fields{&MockClient{close: func() error { return nil }}}, false}, - {"fail", fields{&MockClient{close: func() error { return fmt.Errorf("an error") }}}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &CloudKMS{ - client: tt.fields.client, - } - if err := k.Close(); (err != nil) != tt.wantErr { - t.Errorf("CloudKMS.Close() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestCloudKMS_CreateSigner(t *testing.T) { - keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" - type fields struct { - client KeyManagementClient - } - type args struct { - req *apiv1.CreateSignerRequest - } - tests := []struct { - name string - fields fields - args args - want crypto.Signer - wantErr bool - }{ - {"ok", fields{&MockClient{}}, args{&apiv1.CreateSignerRequest{SigningKey: keyName}}, &Signer{client: &MockClient{}, signingKey: keyName}, false}, - {"fail", fields{&MockClient{}}, args{&apiv1.CreateSignerRequest{SigningKey: ""}}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &CloudKMS{ - client: tt.fields.client, - } - got, err := k.CreateSigner(tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("CloudKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CloudKMS.CreateSigner() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCloudKMS_CreateKey(t *testing.T) { - keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c" - testError := fmt.Errorf("an error") - alreadyExists := status.Error(codes.AlreadyExists, "already exists") - - pemBytes, err := ioutil.ReadFile("testdata/pub.pem") - if err != nil { - t.Fatal(err) - } - pk, err := pemutil.ParseKey(pemBytes) - if err != nil { - t.Fatal(err) - } - - type fields struct { - client KeyManagementClient - } - type args struct { - req *apiv1.CreateKeyRequest - } - tests := []struct { - name string - fields fields - args args - want *apiv1.CreateKeyResponse - wantErr bool - }{ - {"ok", fields{ - &MockClient{ - getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return &kmspb.KeyRing{}, nil - }, - createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { - return &kmspb.CryptoKey{Name: keyName}, nil - }, - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return &kmspb.PublicKey{Pem: string(pemBytes)}, nil - }, - }}, - args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, - &apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/1"}}, false}, - {"ok new key ring", fields{ - &MockClient{ - getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return nil, testError - }, - createKeyRing: func(_ context.Context, _ *kmspb.CreateKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return nil, alreadyExists - }, - createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { - return &kmspb.CryptoKey{Name: keyName}, nil - }, - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return &kmspb.PublicKey{Pem: string(pemBytes)}, nil - }, - }}, - args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 3072}}, - &apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/1"}}, false}, - {"ok new key version", fields{ - &MockClient{ - getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return &kmspb.KeyRing{}, nil - }, - createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { - return nil, alreadyExists - }, - createCryptoKeyVersion: func(_ context.Context, _ *kmspb.CreateCryptoKeyVersionRequest, _ ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) { - return &kmspb.CryptoKeyVersion{Name: keyName + "/cryptoKeyVersions/2"}, nil - }, - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return &kmspb.PublicKey{Pem: string(pemBytes)}, nil - }, - }}, - args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, - &apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/2", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/2"}}, false}, - {"fail name", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{}}, nil, true}, - {"fail protection level", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.ProtectionLevel(100)}}, nil, true}, - {"fail signature algorithm", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SignatureAlgorithm(100)}}, nil, true}, - {"fail number of bits", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024}}, - nil, true}, - {"fail create key ring", fields{ - &MockClient{ - getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return nil, testError - }, - createKeyRing: func(_ context.Context, _ *kmspb.CreateKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return nil, testError - }, - }}, - args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, - nil, true}, - {"fail create key", fields{ - &MockClient{ - getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return &kmspb.KeyRing{}, nil - }, - createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { - return nil, testError - }, - }}, - args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, - nil, true}, - {"fail create key version", fields{ - &MockClient{ - getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return &kmspb.KeyRing{}, nil - }, - createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { - return nil, alreadyExists - }, - createCryptoKeyVersion: func(_ context.Context, _ *kmspb.CreateCryptoKeyVersionRequest, _ ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) { - return nil, testError - }, - }}, - args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, - nil, true}, - {"fail get public key", fields{ - &MockClient{ - getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { - return &kmspb.KeyRing{}, nil - }, - createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { - return &kmspb.CryptoKey{Name: keyName}, nil - }, - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return nil, testError - }, - }}, - args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, - nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &CloudKMS{ - client: tt.fields.client, - } - got, err := k.CreateKey(tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("CloudKMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CloudKMS.CreateKey() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCloudKMS_GetPublicKey(t *testing.T) { - keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" - testError := fmt.Errorf("an error") - - pemBytes, err := ioutil.ReadFile("testdata/pub.pem") - if err != nil { - t.Fatal(err) - } - pk, err := pemutil.ParseKey(pemBytes) - if err != nil { - t.Fatal(err) - } - - type fields struct { - client KeyManagementClient - } - type args struct { - req *apiv1.GetPublicKeyRequest - } - tests := []struct { - name string - fields fields - args args - want crypto.PublicKey - wantErr bool - }{ - {"ok", fields{ - &MockClient{ - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return &kmspb.PublicKey{Pem: string(pemBytes)}, nil - }, - }}, - args{&apiv1.GetPublicKeyRequest{Name: keyName}}, pk, false}, - {"fail name", fields{&MockClient{}}, args{&apiv1.GetPublicKeyRequest{}}, nil, true}, - {"fail get public key", fields{ - &MockClient{ - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return nil, testError - }, - }}, - args{&apiv1.GetPublicKeyRequest{Name: keyName}}, nil, true}, - {"fail parse pem", fields{ - &MockClient{ - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return &kmspb.PublicKey{Pem: string("bad pem")}, nil - }, - }}, - args{&apiv1.GetPublicKeyRequest{Name: keyName}}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &CloudKMS{ - client: tt.fields.client, - } - got, err := k.GetPublicKey(tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("CloudKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CloudKMS.GetPublicKey() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/kms/cloudkms/mock_test.go b/kms/cloudkms/mock_test.go deleted file mode 100644 index 7617bd85f..000000000 --- a/kms/cloudkms/mock_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package cloudkms - -import ( - "context" - - gax "github.com/googleapis/gax-go/v2" - kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" -) - -type MockClient struct { - close func() error - getPublicKey func(context.Context, *kmspb.GetPublicKeyRequest, ...gax.CallOption) (*kmspb.PublicKey, error) - asymmetricSign func(context.Context, *kmspb.AsymmetricSignRequest, ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) - createCryptoKey func(context.Context, *kmspb.CreateCryptoKeyRequest, ...gax.CallOption) (*kmspb.CryptoKey, error) - getKeyRing func(context.Context, *kmspb.GetKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error) - createKeyRing func(context.Context, *kmspb.CreateKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error) - createCryptoKeyVersion func(context.Context, *kmspb.CreateCryptoKeyVersionRequest, ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) -} - -func (m *MockClient) Close() error { - return m.close() -} - -func (m *MockClient) GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest, opts ...gax.CallOption) (*kmspb.PublicKey, error) { - return m.getPublicKey(ctx, req, opts...) -} - -func (m *MockClient) AsymmetricSign(ctx context.Context, req *kmspb.AsymmetricSignRequest, opts ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { - return m.asymmetricSign(ctx, req, opts...) -} - -func (m *MockClient) CreateCryptoKey(ctx context.Context, req *kmspb.CreateCryptoKeyRequest, opts ...gax.CallOption) (*kmspb.CryptoKey, error) { - return m.createCryptoKey(ctx, req, opts...) -} - -func (m *MockClient) GetKeyRing(ctx context.Context, req *kmspb.GetKeyRingRequest, opts ...gax.CallOption) (*kmspb.KeyRing, error) { - return m.getKeyRing(ctx, req, opts...) -} - -func (m *MockClient) CreateKeyRing(ctx context.Context, req *kmspb.CreateKeyRingRequest, opts ...gax.CallOption) (*kmspb.KeyRing, error) { - return m.createKeyRing(ctx, req, opts...) -} - -func (m *MockClient) CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) { - return m.createCryptoKeyVersion(ctx, req, opts...) -} diff --git a/kms/cloudkms/signer.go b/kms/cloudkms/signer.go deleted file mode 100644 index b9232ca4c..000000000 --- a/kms/cloudkms/signer.go +++ /dev/null @@ -1,78 +0,0 @@ -package cloudkms - -import ( - "crypto" - "io" - - "github.com/pkg/errors" - "github.com/smallstep/cli/crypto/pemutil" - kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" -) - -// Signer implements a crypto.Signer using Google's Cloud KMS. -type Signer struct { - client KeyManagementClient - signingKey string -} - -func NewSigner(c KeyManagementClient, signingKey string) *Signer { - return &Signer{ - client: c, - signingKey: signingKey, - } -} - -// Public returns the public key of this signer or an error. -func (s *Signer) Public() crypto.PublicKey { - ctx, cancel := defaultContext() - defer cancel() - - response, err := s.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{ - Name: s.signingKey, - }) - if err != nil { - return errors.Wrap(err, "cloudKMS GetPublicKey failed") - } - - pk, err := pemutil.ParseKey([]byte(response.Pem)) - if err != nil { - return err - } - - return pk -} - -// Sign signs digest with the private key stored in Google's Cloud KMS. -func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - req := &kmspb.AsymmetricSignRequest{ - Name: s.signingKey, - Digest: &kmspb.Digest{}, - } - - switch h := opts.HashFunc(); h { - case crypto.SHA256: - req.Digest.Digest = &kmspb.Digest_Sha256{ - Sha256: digest, - } - case crypto.SHA384: - req.Digest.Digest = &kmspb.Digest_Sha384{ - Sha384: digest, - } - case crypto.SHA512: - req.Digest.Digest = &kmspb.Digest_Sha512{ - Sha512: digest, - } - default: - return nil, errors.Errorf("unsupported hash function %v", h) - } - - ctx, cancel := defaultContext() - defer cancel() - - response, err := s.client.AsymmetricSign(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "cloudKMS AsymmetricSign failed") - } - - return response.Signature, nil -} diff --git a/kms/cloudkms/signer_test.go b/kms/cloudkms/signer_test.go deleted file mode 100644 index 9a05e131d..000000000 --- a/kms/cloudkms/signer_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package cloudkms - -import ( - "context" - "crypto" - "crypto/rand" - "fmt" - "io" - "io/ioutil" - "reflect" - "testing" - - gax "github.com/googleapis/gax-go/v2" - "github.com/smallstep/cli/crypto/pemutil" - kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" -) - -func Test_newSigner(t *testing.T) { - type args struct { - c KeyManagementClient - signingKey string - } - tests := []struct { - name string - args args - want *Signer - }{ - {"ok", args{&MockClient{}, "signingKey"}, &Signer{client: &MockClient{}, signingKey: "signingKey"}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewSigner(tt.args.c, tt.args.signingKey); !reflect.DeepEqual(got, tt.want) { - t.Errorf("newSigner() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_signer_Public(t *testing.T) { - keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" - testError := fmt.Errorf("an error") - - pemBytes, err := ioutil.ReadFile("testdata/pub.pem") - if err != nil { - t.Fatal(err) - } - pk, err := pemutil.ParseKey(pemBytes) - if err != nil { - t.Fatal(err) - } - - type fields struct { - client KeyManagementClient - signingKey string - } - tests := []struct { - name string - fields fields - want crypto.PublicKey - wantErr bool - }{ - {"ok", fields{&MockClient{ - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return &kmspb.PublicKey{Pem: string(pemBytes)}, nil - }, - }, keyName}, pk, false}, - {"fail get public key", fields{&MockClient{ - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return nil, testError - }, - }, keyName}, nil, true}, - {"fail parse pem", fields{ - &MockClient{ - getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { - return &kmspb.PublicKey{Pem: string("bad pem")}, nil - }, - }, keyName}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Signer{ - client: tt.fields.client, - signingKey: tt.fields.signingKey, - } - got := s.Public() - if _, ok := got.(error); ok != tt.wantErr { - t.Errorf("signer.Public() error = %v, wantErr %v", got, tt.wantErr) - return - } - if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { - t.Errorf("signer.Public() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_signer_Sign(t *testing.T) { - keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" - okClient := &MockClient{ - asymmetricSign: func(_ context.Context, _ *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { - return &kmspb.AsymmetricSignResponse{Signature: []byte("ok signature")}, nil - }, - } - failClient := &MockClient{ - asymmetricSign: func(_ context.Context, _ *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { - return nil, fmt.Errorf("an error") - }, - } - - type fields struct { - client KeyManagementClient - signingKey string - } - type args struct { - rand io.Reader - digest []byte - opts crypto.SignerOpts - } - tests := []struct { - name string - fields fields - args args - want []byte - wantErr bool - }{ - {"ok sha256", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA256}, []byte("ok signature"), false}, - {"ok sha384", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA384}, []byte("ok signature"), false}, - {"ok sha512", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA512}, []byte("ok signature"), false}, - {"fail MD5", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.MD5}, nil, true}, - {"fail asymmetric sign", fields{failClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA256}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Signer{ - client: tt.fields.client, - signingKey: tt.fields.signingKey, - } - got, err := s.Sign(tt.args.rand, tt.args.digest, tt.args.opts) - if (err != nil) != tt.wantErr { - t.Errorf("signer.Sign() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("signer.Sign() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/kms/cloudkms/testdata/pub.pem b/kms/cloudkms/testdata/pub.pem deleted file mode 100644 index e31e583e0..000000000 --- a/kms/cloudkms/testdata/pub.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 -veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== ------END PUBLIC KEY----- diff --git a/kms/kms.go b/kms/kms.go deleted file mode 100644 index 209783e5b..000000000 --- a/kms/kms.go +++ /dev/null @@ -1,36 +0,0 @@ -package kms - -import ( - "context" - "crypto" - "strings" - - "github.com/pkg/errors" - "github.com/smallstep/certificates/kms/apiv1" - "github.com/smallstep/certificates/kms/cloudkms" - "github.com/smallstep/certificates/kms/softkms" -) - -// KeyManager is the interface implemented by all the KMS. -type KeyManager interface { - GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) - CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) - CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) - Close() error -} - -// New initializes a new KMS from the given type. -func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) { - if err := opts.Validate(); err != nil { - return nil, err - } - - switch apiv1.Type(strings.ToLower(opts.Type)) { - case apiv1.DefaultKMS, apiv1.SoftKMS: - return softkms.New(ctx, opts) - case apiv1.CloudKMS: - return cloudkms.New(ctx, opts) - default: - return nil, errors.Errorf("unsupported kms type '%s'", opts.Type) - } -} diff --git a/kms/kms_test.go b/kms/kms_test.go deleted file mode 100644 index f377072f0..000000000 --- a/kms/kms_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package kms - -import ( - "context" - "os" - "reflect" - "testing" - - "github.com/smallstep/certificates/kms/apiv1" - "github.com/smallstep/certificates/kms/cloudkms" - "github.com/smallstep/certificates/kms/softkms" -) - -func TestNew(t *testing.T) { - ctx := context.Background() - - type args struct { - ctx context.Context - opts apiv1.Options - } - tests := []struct { - name string - skipOnCI bool - args args - want KeyManager - wantErr bool - }{ - {"softkms", false, args{ctx, apiv1.Options{Type: "softkms"}}, &softkms.SoftKMS{}, false}, - {"default", false, args{ctx, apiv1.Options{}}, &softkms.SoftKMS{}, false}, - {"cloudkms", true, args{ctx, apiv1.Options{Type: "cloudkms"}}, &cloudkms.CloudKMS{}, true}, // fails because not credentials - {"awskms", false, args{ctx, apiv1.Options{Type: "awskms"}}, nil, true}, // not yet supported - {"pkcs11", false, args{ctx, apiv1.Options{Type: "pkcs11"}}, nil, true}, // not yet supported - {"fail validation", false, args{ctx, apiv1.Options{Type: "foobar"}}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.skipOnCI && os.Getenv("CI") == "true" { - t.SkipNow() - } - - got, err := New(tt.args.ctx, tt.args.opts) - if (err != nil) != tt.wantErr { - t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) - return - } - if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { - t.Errorf("New() = %T, want %T", got, tt.want) - } - }) - } -} diff --git a/kms/softkms/softkms.go b/kms/softkms/softkms.go deleted file mode 100644 index fb38a1c5e..000000000 --- a/kms/softkms/softkms.go +++ /dev/null @@ -1,134 +0,0 @@ -package softkms - -import ( - "context" - "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" - "crypto/x509" - - "github.com/pkg/errors" - "github.com/smallstep/certificates/kms/apiv1" - "github.com/smallstep/cli/crypto/keys" - "github.com/smallstep/cli/crypto/pemutil" -) - -type algorithmAttributes struct { - Type string - Curve string -} - -// DefaultRSAKeySize is the default size for RSA keys. -const DefaultRSAKeySize = 3072 - -var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes{ - apiv1.UnspecifiedSignAlgorithm: {"EC", "P-256"}, - apiv1.SHA256WithRSA: {"RSA", ""}, - apiv1.SHA384WithRSA: {"RSA", ""}, - apiv1.SHA512WithRSA: {"RSA", ""}, - apiv1.SHA256WithRSAPSS: {"RSA", ""}, - apiv1.SHA384WithRSAPSS: {"RSA", ""}, - apiv1.SHA512WithRSAPSS: {"RSA", ""}, - apiv1.ECDSAWithSHA256: {"EC", "P-256"}, - apiv1.ECDSAWithSHA384: {"EC", "P-384"}, - apiv1.ECDSAWithSHA512: {"EC", "P-521"}, - apiv1.PureEd25519: {"OKP", "Ed25519"}, -} - -// generateKey is used for testing purposes. -var generateKey = func(kty, crv string, size int) (interface{}, interface{}, error) { - if kty == "RSA" && size == 0 { - size = DefaultRSAKeySize - } - return keys.GenerateKeyPair(kty, crv, size) -} - -// SoftKMS is a key manager that uses keys stored in disk. -type SoftKMS struct{} - -// New returns a new SoftKMS. -func New(ctx context.Context, opts apiv1.Options) (*SoftKMS, error) { - return &SoftKMS{}, nil -} - -// Close is a noop that just returns nil. -func (k *SoftKMS) Close() error { - return nil -} - -// CreateSigner returns a new signer configured with the given signing key. -func (k *SoftKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { - var opts []pemutil.Options - if req.Password != nil { - opts = append(opts, pemutil.WithPassword(req.Password)) - } - - switch { - case req.Signer != nil: - return req.Signer, nil - case len(req.SigningKeyPEM) != 0: - v, err := pemutil.ParseKey(req.SigningKeyPEM, opts...) - if err != nil { - return nil, err - } - sig, ok := v.(crypto.Signer) - if !ok { - return nil, errors.New("signingKeyPEM is not a crypto.Signer") - } - return sig, nil - case req.SigningKey != "": - v, err := pemutil.Read(req.SigningKey, opts...) - if err != nil { - return nil, err - } - sig, ok := v.(crypto.Signer) - if !ok { - return nil, errors.New("signingKey is not a crypto.Signer") - } - return sig, nil - default: - return nil, errors.New("failed to load softKMS: please define signingKeyPEM or signingKey") - } -} - -func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { - v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] - if !ok { - return nil, errors.Errorf("softKMS does not support signature algorithm '%s'", req.SignatureAlgorithm) - } - - pub, priv, err := generateKey(v.Type, v.Curve, req.Bits) - if err != nil { - return nil, err - } - signer, ok := priv.(crypto.Signer) - if !ok { - return nil, errors.Errorf("softKMS createKey result is not a crypto.Signer: type %T", priv) - } - - return &apiv1.CreateKeyResponse{ - Name: req.Name, - PublicKey: pub, - PrivateKey: priv, - CreateSignerRequest: apiv1.CreateSignerRequest{ - Signer: signer, - }, - }, nil -} - -func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { - v, err := pemutil.Read(req.Name) - if err != nil { - return nil, err - } - - switch vv := v.(type) { - case *x509.Certificate: - return vv.PublicKey, nil - case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: - return vv, nil - default: - return nil, errors.Errorf("unsupported public key type %T", v) - } -} diff --git a/kms/softkms/softkms_test.go b/kms/softkms/softkms_test.go deleted file mode 100644 index 44dccaa94..000000000 --- a/kms/softkms/softkms_test.go +++ /dev/null @@ -1,312 +0,0 @@ -package softkms - -import ( - "context" - "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "io/ioutil" - "reflect" - "testing" - - "github.com/smallstep/certificates/kms/apiv1" - "github.com/smallstep/cli/crypto/pemutil" -) - -func TestNew(t *testing.T) { - type args struct { - ctx context.Context - opts apiv1.Options - } - tests := []struct { - name string - args args - want *SoftKMS - wantErr bool - }{ - {"ok", args{context.Background(), apiv1.Options{}}, &SoftKMS{}, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := New(tt.args.ctx, tt.args.opts) - if (err != nil) != tt.wantErr { - t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("New() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSoftKMS_Close(t *testing.T) { - tests := []struct { - name string - wantErr bool - }{ - {"ok", false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &SoftKMS{} - if err := k.Close(); (err != nil) != tt.wantErr { - t.Errorf("SoftKMS.Close() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestSoftKMS_CreateSigner(t *testing.T) { - pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Fatal(err) - } - pemBlock, err := pemutil.Serialize(pk) - if err != nil { - t.Fatal(err) - } - pemBlockPassword, err := pemutil.Serialize(pk, pemutil.WithPassword([]byte("pass"))) - if err != nil { - t.Fatal(err) - } - - // Read and decode file using standard packages - b, err := ioutil.ReadFile("testdata/priv.pem") - if err != nil { - t.Fatal(err) - } - block, _ := pem.Decode(b) - block.Bytes, err = x509.DecryptPEMBlock(block, []byte("pass")) - if err != nil { - t.Fatal(err) - } - pk2, err := x509.ParseECPrivateKey(block.Bytes) - if err != nil { - t.Fatal(err) - } - - // Create a public PEM - b, err = x509.MarshalPKIXPublicKey(pk.Public()) - if err != nil { - t.Fatal(err) - } - pub := pem.EncodeToMemory(&pem.Block{ - Type: "PUBLIC KEY", - Bytes: b, - }) - - type args struct { - req *apiv1.CreateSignerRequest - } - tests := []struct { - name string - args args - want crypto.Signer - wantErr bool - }{ - {"signer", args{&apiv1.CreateSignerRequest{Signer: pk}}, pk, false}, - {"pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlock)}}, pk, false}, - {"pem password", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("pass")}}, pk, false}, - {"file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("pass")}}, pk2, false}, - {"fail", args{&apiv1.CreateSignerRequest{}}, nil, true}, - {"fail bad pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: []byte("bad pem")}}, nil, true}, - {"fail bad password", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("bad-pass")}}, nil, true}, - {"fail not a signer", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pub}}, nil, true}, - {"fail not a signer from file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/pub.pem"}}, nil, true}, - {"fail missing", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/missing"}}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &SoftKMS{} - got, err := k.CreateSigner(tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("SoftKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("SoftKMS.CreateSigner() = %v, want %v", got, tt.want) - } - }) - } -} - -func restoreGenerateKey() func() { - oldGenerateKey := generateKey - return func() { - generateKey = oldGenerateKey - } -} - -func TestSoftKMS_CreateKey(t *testing.T) { - fn := restoreGenerateKey() - defer fn() - - p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Fatal(err) - } - rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatal(err) - } - edpub, edpriv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - - type args struct { - req *apiv1.CreateKeyRequest - } - type params struct { - kty string - crv string - size int - } - tests := []struct { - name string - args args - generateKey func() (interface{}, interface{}, error) - want *apiv1.CreateKeyResponse - wantParams params - wantErr bool - }{ - {"p256", args{&apiv1.CreateKeyRequest{Name: "p256", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) { - return p256.Public(), p256, nil - }, &apiv1.CreateKeyResponse{Name: "p256", PublicKey: p256.Public(), PrivateKey: p256, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: p256}}, params{"EC", "P-256", 0}, false}, - {"rsa", args{&apiv1.CreateKeyRequest{Name: "rsa3072", SignatureAlgorithm: apiv1.SHA256WithRSA}}, func() (interface{}, interface{}, error) { - return rsa2048.Public(), rsa2048, nil - }, &apiv1.CreateKeyResponse{Name: "rsa3072", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 0}, false}, - {"rsa2048", args{&apiv1.CreateKeyRequest{Name: "rsa2048", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048}}, func() (interface{}, interface{}, error) { - return rsa2048.Public(), rsa2048, nil - }, &apiv1.CreateKeyResponse{Name: "rsa2048", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 2048}, false}, - {"rsaPSS2048", args{&apiv1.CreateKeyRequest{Name: "rsa2048", SignatureAlgorithm: apiv1.SHA256WithRSAPSS, Bits: 2048}}, func() (interface{}, interface{}, error) { - return rsa2048.Public(), rsa2048, nil - }, &apiv1.CreateKeyResponse{Name: "rsa2048", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 2048}, false}, - {"ed25519", args{&apiv1.CreateKeyRequest{Name: "ed25519", SignatureAlgorithm: apiv1.PureEd25519}}, func() (interface{}, interface{}, error) { - return edpub, edpriv, nil - }, &apiv1.CreateKeyResponse{Name: "ed25519", PublicKey: edpub, PrivateKey: edpriv, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: edpriv}}, params{"OKP", "Ed25519", 0}, false}, - {"default", args{&apiv1.CreateKeyRequest{Name: "default"}}, func() (interface{}, interface{}, error) { - return p256.Public(), p256, nil - }, &apiv1.CreateKeyResponse{Name: "default", PublicKey: p256.Public(), PrivateKey: p256, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: p256}}, params{"EC", "P-256", 0}, false}, - {"fail algorithm", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.SignatureAlgorithm(100)}}, func() (interface{}, interface{}, error) { - return p256.Public(), p256, nil - }, nil, params{}, true}, - {"fail generate key", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) { - return nil, nil, fmt.Errorf("an error") - }, nil, params{"EC", "P-256", 0}, true}, - {"fail no signer", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) { - return 1, 2, nil - }, nil, params{"EC", "P-256", 0}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &SoftKMS{} - generateKey = func(kty, crv string, size int) (interface{}, interface{}, error) { - if tt.wantParams.kty != kty { - t.Errorf("GenerateKey() kty = %s, want %s", kty, tt.wantParams.kty) - } - if tt.wantParams.crv != crv { - t.Errorf("GenerateKey() crv = %s, want %s", crv, tt.wantParams.crv) - } - if tt.wantParams.size != size { - t.Errorf("GenerateKey() size = %d, want %d", size, tt.wantParams.size) - } - return tt.generateKey() - } - - got, err := k.CreateKey(tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("SoftKMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("SoftKMS.CreateKey() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSoftKMS_GetPublicKey(t *testing.T) { - b, err := ioutil.ReadFile("testdata/pub.pem") - if err != nil { - t.Fatal(err) - } - block, _ := pem.Decode(b) - pub, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - t.Fatal(err) - } - - type args struct { - req *apiv1.GetPublicKeyRequest - } - tests := []struct { - name string - args args - want crypto.PublicKey - wantErr bool - }{ - {"key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/pub.pem"}}, pub, false}, - {"cert", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.crt"}}, pub, false}, - {"fail not exists", args{&apiv1.GetPublicKeyRequest{Name: "testdata/missing"}}, nil, true}, - {"fail type", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.key"}}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - k := &SoftKMS{} - got, err := k.GetPublicKey(tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("SoftKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("SoftKMS.GetPublicKey() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_generateKey(t *testing.T) { - type args struct { - kty string - crv string - size int - } - tests := []struct { - name string - args args - wantType interface{} - wantType1 interface{} - wantErr bool - }{ - {"rsa2048", args{"RSA", "", 0}, &rsa.PublicKey{}, &rsa.PrivateKey{}, false}, - {"rsa2048", args{"RSA", "", 2048}, &rsa.PublicKey{}, &rsa.PrivateKey{}, false}, - {"p256", args{"EC", "P-256", 0}, &ecdsa.PublicKey{}, &ecdsa.PrivateKey{}, false}, - {"ed25519", args{"OKP", "Ed25519", 0}, ed25519.PublicKey{}, ed25519.PrivateKey{}, false}, - {"fail kty", args{"FOO", "", 0}, nil, nil, true}, - {"fail crv", args{"EC", "P-123", 0}, nil, nil, true}, - {"fail size", args{"RSA", "", 1}, nil, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1, err := generateKey(tt.args.kty, tt.args.crv, tt.args.size) - if (err != nil) != tt.wantErr { - t.Errorf("generateKey() error = %v, wantErr %v", err, tt.wantErr) - return - } - if reflect.TypeOf(got) != reflect.TypeOf(tt.wantType) { - t.Errorf("generateKey() got = %T, want %T", got, tt.wantType) - } - if reflect.TypeOf(got1) != reflect.TypeOf(tt.wantType1) { - t.Errorf("generateKey() got1 = %T, want %T", got1, tt.wantType1) - } - }) - } -} diff --git a/kms/softkms/testdata/cert.crt b/kms/softkms/testdata/cert.crt deleted file mode 100644 index d6f02b219..000000000 --- a/kms/softkms/testdata/cert.crt +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBpzCCAU2gAwIBAgIQWaY8KIDAfak8aYljelf8eTAKBggqhkjOPQQDAjAdMRsw -GQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wHhcNMjAwMTE2MDAwNDU4WhcNMjAw -MTE3MDAwNDU4WjAdMRswGQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wWTATBgcq -hkjOPQIBBggqhkjOPQMBBwNCAATlU8P9blFefSWuzYx2g215NJn6yHW95PXeFqQ9 -kX1jNo1VmC6Oord3We37iM8QJT4QP9ZDUaAVmJUZSjd+W8H/o28wbTAOBgNVHQ8B -Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW -BBTn0wonKkm2lLRNYZrKhUukiynvqzAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl -cC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAJ5XqryBIY1X4fl/9l0isV69eQfA0Qo5 -1mjervUcEnOWAiBsmN4frz5YVw7i4UXChVBeZLZfJOKvn5eyh2gEzoq1+w== ------END CERTIFICATE----- diff --git a/kms/softkms/testdata/cert.key b/kms/softkms/testdata/cert.key deleted file mode 100644 index 187713cd5..000000000 --- a/kms/softkms/testdata/cert.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEICB6lIrMa9fVQJtdAYS4qmdYQ1BHJsEQDx8zxL38gA8toAoGCCqGSM49 -AwEHoUQDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1veT13hakPZF9YzaNVZgujqK3 -d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== ------END EC PRIVATE KEY----- diff --git a/kms/softkms/testdata/priv.pem b/kms/softkms/testdata/priv.pem deleted file mode 100644 index 81116ce7b..000000000 --- a/kms/softkms/testdata/priv.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,1fcec5dfbf3327f61bfe5ab6ae8a0626 - -V39b/pNHMbP80TXSHLsUY6UOTCzf3KwIxvj1e7S9brNMJJc9b3UiloMBJIYBkl00 -NKI8JU4jSlcerR58DqsTHIELiX6a+RJLe3/iR2/5Gru+CmmWJ68jQu872WCgh6Ms -o8TzhyGx74ETmdKn5CdtylsnKMa9heW3tBLFAbNCgKc= ------END EC PRIVATE KEY----- diff --git a/kms/softkms/testdata/pub.pem b/kms/softkms/testdata/pub.pem deleted file mode 100644 index e31e583e0..000000000 --- a/kms/softkms/testdata/pub.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 -veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== ------END PUBLIC KEY----- diff --git a/templates/values.go b/templates/values.go index ba03267b8..505b3b87d 100644 --- a/templates/values.go +++ b/templates/values.go @@ -9,7 +9,6 @@ type Step struct { SSH StepSSH } -// StepSSH holds SSH-related values for the CA. type StepSSH struct { HostKey ssh.PublicKey UserKey ssh.PublicKey