瀏覽代碼

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 <ddos@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alex Vaghin <ddos@google.com>
Jonathan Rudenberg 9 年之前
父節點
當前提交
81372b2fc2
共有 2 個文件被更改,包括 67 次插入7 次删除
  1. 49 7
      acme/autocert/autocert.go
  2. 18 0
      acme/autocert/autocert_test.go

+ 49 - 7
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
 		}

+ 18 - 0
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 {