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>
This commit is contained in:
Jonathan Rudenberg
2016-09-15 07:14:17 +00:00
committed by Alex Vaghin
parent 119f50887f
commit 81372b2fc2
2 changed files with 67 additions and 7 deletions
+49 -7
View File
@@ -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
View File
@@ -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 {