From 81372b2fc2f10bef2a7f338da115c315a56b2726 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Tue, 13 Sep 2016 15:28:34 -0400 Subject: [PATCH] acme/autocert: cache ACME account key If no key is provided in the *acme.Client, one is generated, but it is not cached. This means that every restart of the server process will use a new account. This change caches the account key, if one is generated, so that restarts of the server process use the same key. Change-Id: I80f127b2cc79745a854b220b8918724ca228e87a Reviewed-on: https://go-review.googlesource.com/28980 Run-TryBot: Alex Vaghin TryBot-Result: Gobot Gobot Reviewed-by: Alex Vaghin --- acme/autocert/autocert.go | 56 +++++++++++++++++++++++++++++----- acme/autocert/autocert_test.go | 18 +++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/acme/autocert/autocert.go b/acme/autocert/autocert.go index 2d95e27..12c9010 100644 --- a/acme/autocert/autocert.go +++ b/acme/autocert/autocert.go @@ -21,6 +21,7 @@ import ( "encoding/pem" "errors" "fmt" + "io" mathrand "math/rand" "net/http" "strconv" @@ -300,12 +301,7 @@ func (m *Manager) cachePut(domain string, tlscert *tls.Certificate) error { // private switch key := tlscert.PrivateKey.(type) { case *ecdsa.PrivateKey: - b, err := x509.MarshalECPrivateKey(key) - if err != nil { - return err - } - pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} - if err := pem.Encode(&buf, pb); err != nil { + if err := encodeECDSAKey(&buf, key); err != nil { return err } case *rsa.PrivateKey: @@ -331,6 +327,15 @@ func (m *Manager) cachePut(domain string, tlscert *tls.Certificate) error { return m.Cache.Put(ctx, domain, buf.Bytes()) } +func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error { + b, err := x509.MarshalECPrivateKey(key) + if err != nil { + return err + } + pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + return pem.Encode(w, pb) +} + // createCert starts the domain ownership verification and returns a certificate // for that domain upon success. // @@ -545,6 +550,43 @@ func (m *Manager) stopRenew() { } } +func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) { + const keyName = "acme_account.key" + + genKey := func() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + } + + if m.Cache == nil { + return genKey() + } + + data, err := m.Cache.Get(ctx, keyName) + if err == ErrCacheMiss { + key, err := genKey() + if err != nil { + return nil, err + } + var buf bytes.Buffer + if err := encodeECDSAKey(&buf, key); err != nil { + return nil, err + } + if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil { + return nil, err + } + return key, nil + } + if err != nil { + return nil, err + } + + priv, _ := pem.Decode(data) + if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { + return nil, errors.New("acme/autocert: invalid account key found in cache") + } + return parsePrivateKey(priv.Bytes) +} + func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) { m.clientMu.Lock() defer m.clientMu.Unlock() @@ -558,7 +600,7 @@ func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) { } if client.Key == nil { var err error - client.Key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + client.Key, err = m.accountKey(ctx) if err != nil { return nil, err } diff --git a/acme/autocert/autocert_test.go b/acme/autocert/autocert_test.go index f5bff8b..3a9daa1 100644 --- a/acme/autocert/autocert_test.go +++ b/acme/autocert/autocert_test.go @@ -21,6 +21,7 @@ import ( "math/big" "net/http" "net/http/httptest" + "reflect" "testing" "time" @@ -245,6 +246,23 @@ func TestGetCertificate(t *testing.T) { } } +func TestAccountKeyCache(t *testing.T) { + cache := make(memCache) + m := Manager{Cache: cache} + ctx := context.Background() + k1, err := m.accountKey(ctx) + if err != nil { + t.Fatal(err) + } + k2, err := m.accountKey(ctx) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1, k2) { + t.Errorf("account keys don't match: k1 = %#v; k2 = %#v", k1, k2) + } +} + func TestCache(t *testing.T) { privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil {