瀏覽代碼

acme: use EC keys where possible

This change improves tests speed further by switching to EC keys where
possible.

We are also switching to EC-based token certs for TLS-SNI-x challenges
in this change. I've confirmed it works with Let's Encrypt production
servers as well, by fulfilling TLS-SNI-01 challenge for a test domain
using autocert.Manager.

Change-Id: I07c532d535ba413af97463dd97cca3d650dfccee
Reviewed-on: https://go-review.googlesource.com/27439
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Alex Vaghin 9 年之前
父節點
當前提交
7e016f1856
共有 4 個文件被更改,包括 42 次插入45 次删除
  1. 12 8
      acme/autocert/autocert_test.go
  2. 3 2
      acme/internal/acme/acme.go
  3. 24 35
      acme/internal/acme/acme_test.go
  4. 3 0
      acme/internal/acme/jws_test.go

+ 12 - 8
acme/autocert/autocert_test.go

@@ -6,8 +6,9 @@ package autocert
 
 import (
 	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
 	"crypto/rand"
-	"crypto/rsa"
 	"crypto/tls"
 	"crypto/x509"
 	"crypto/x509/pkix"
@@ -47,8 +48,8 @@ var authzTmpl = template.Must(template.New("authz").Parse(`{
 }`))
 
 func dummyCert(san ...string) ([]byte, error) {
-	// use smaller key to run faster on 386
-	key, err := rsa.GenerateKey(rand.Reader, 512)
+	// use EC key to run faster on 386
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 		return nil, err
 	}
@@ -129,8 +130,8 @@ func TestGetCertificate(t *testing.T) {
 	}))
 	defer ca.Close()
 
-	// use smaller key to run faster on 386
-	key, kerr := rsa.GenerateKey(rand.Reader, 512)
+	// use EC key to run faster on 386
+	key, kerr := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if kerr != nil {
 		t.Fatal(kerr)
 	}
@@ -151,7 +152,7 @@ func TestGetCertificate(t *testing.T) {
 		close(done)
 	}()
 	select {
-	case <-time.After(15 * time.Second):
+	case <-time.After(time.Minute):
 		t.Fatal("man.GetCertificate took too long to return")
 	case <-done:
 	}
@@ -211,7 +212,7 @@ func (m memCache) Delete(ctx context.Context, key string) error {
 }
 
 func TestCache(t *testing.T) {
-	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -242,7 +243,10 @@ func TestCache(t *testing.T) {
 		t.Fatal("res is nil")
 	}
 
-	priv := x509.MarshalPKCS1PrivateKey(privKey)
+	priv, err := x509.MarshalECPrivateKey(privKey)
+	if err != nil {
+		t.Fatalf("MarshalECPrivateKey: %v", err)
+	}
 	dummy, err := dummyCert("dummy")
 	if err != nil {
 		t.Fatalf("dummyCert: %v", err)

+ 3 - 2
acme/internal/acme/acme.go

@@ -12,8 +12,9 @@ package acme
 import (
 	"bytes"
 	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
 	"crypto/rand"
-	"crypto/rsa"
 	"crypto/sha256"
 	"crypto/tls"
 	"crypto/x509"
@@ -717,7 +718,7 @@ func keyAuth(pub crypto.PublicKey, token string) (string, error) {
 // tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
 // with the given SANs.
 func tlsChallengeCert(san ...string) (tls.Certificate, error) {
-	key, err := rsa.GenerateKey(rand.Reader, 2048)
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 		return tls.Certificate{}, err
 	}

+ 24 - 35
acme/internal/acme/acme_test.go

@@ -112,10 +112,7 @@ func TestRegister(t *testing.T) {
 		w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`)
 		w.WriteHeader(http.StatusCreated)
 		b, _ := json.Marshal(contacts)
-		fmt.Fprintf(w, `{
-			"key":%q,
-			"contact":%s
-		}`, testKeyThumbprint, b)
+		fmt.Fprintf(w, `{"contact": %s}`, b)
 	}))
 	defer ts.Close()
 
@@ -127,7 +124,7 @@ func TestRegister(t *testing.T) {
 		return false
 	}
 
-	c := Client{Key: testKey, dir: &Directory{RegURL: ts.URL}}
+	c := Client{Key: testKeyEC, dir: &Directory{RegURL: ts.URL}}
 	a := &Account{Contact: contacts}
 	var err error
 	if a, err = c.Register(context.Background(), a, prompt); err != nil {
@@ -183,15 +180,11 @@ func TestUpdateReg(t *testing.T) {
 		w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms))
 		w.WriteHeader(http.StatusOK)
 		b, _ := json.Marshal(contacts)
-		fmt.Fprintf(w, `{
-			"key":%q,
-			"contact":%s,
-			"agreement":%q
-		}`, testKeyThumbprint, b, terms)
+		fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
 	}))
 	defer ts.Close()
 
-	c := Client{Key: testKey}
+	c := Client{Key: testKeyEC}
 	a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms}
 	var err error
 	if a, err = c.UpdateReg(context.Background(), a); err != nil {
@@ -248,15 +241,11 @@ func TestGetReg(t *testing.T) {
 		w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms))
 		w.WriteHeader(http.StatusOK)
 		b, _ := json.Marshal(contacts)
-		fmt.Fprintf(w, `{
-			"key":%q,
-			"contact":%s,
-			"agreement":%q
-		}`, testKeyThumbprint, b, terms)
+		fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
 	}))
 	defer ts.Close()
 
-	c := Client{Key: testKey}
+	c := Client{Key: testKeyEC}
 	a, err := c.GetReg(context.Background(), ts.URL)
 	if err != nil {
 		t.Fatal(err)
@@ -328,7 +317,7 @@ func TestAuthorize(t *testing.T) {
 	}))
 	defer ts.Close()
 
-	cl := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
+	cl := Client{Key: testKeyEC, dir: &Directory{AuthzURL: ts.URL}}
 	auth, err := cl.Authorize(context.Background(), "example.com")
 	if err != nil {
 		t.Fatal(err)
@@ -407,7 +396,7 @@ func TestPollAuthz(t *testing.T) {
 	}))
 	defer ts.Close()
 
-	cl := Client{Key: testKey}
+	cl := Client{Key: testKeyEC}
 	auth, err := cl.GetAuthz(context.Background(), ts.URL)
 	if err != nil {
 		t.Fatal(err)
@@ -470,7 +459,7 @@ func TestPollChallenge(t *testing.T) {
 	}))
 	defer ts.Close()
 
-	cl := Client{Key: testKey}
+	cl := Client{Key: testKeyEC}
 	chall, err := cl.GetChallenge(context.Background(), ts.URL)
 	if err != nil {
 		t.Fatal(err)
@@ -514,7 +503,7 @@ func TestAcceptChallenge(t *testing.T) {
 		if j.Type != "http-01" {
 			t.Errorf(`type = %q; want "http-01"`, j.Type)
 		}
-		keyAuth := "token1." + testKeyThumbprint
+		keyAuth := "token1." + testKeyECThumbprint
 		if j.Auth != keyAuth {
 			t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth)
 		}
@@ -531,7 +520,7 @@ func TestAcceptChallenge(t *testing.T) {
 	}))
 	defer ts.Close()
 
-	cl := Client{Key: testKey}
+	cl := Client{Key: testKeyEC}
 	c, err := cl.Accept(context.Background(), &Challenge{
 		URI:   ts.URL,
 		Token: "token1",
@@ -599,7 +588,7 @@ func TestNewCert(t *testing.T) {
 			BasicConstraintsValid: true,
 		}
 
-		sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKey.PublicKey, testKey)
+		sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC)
 		if err != nil {
 			t.Fatalf("Error creating certificate: %v", err)
 		}
@@ -617,12 +606,12 @@ func TestNewCert(t *testing.T) {
 			Organization: []string{"goacme"},
 		},
 	}
-	csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKey)
+	csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	c := Client{Key: testKey, dir: &Directory{CertURL: ts.URL}}
+	c := Client{Key: testKeyEC, dir: &Directory{CertURL: ts.URL}}
 	cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false)
 	if err != nil {
 		t.Fatal(err)
@@ -772,7 +761,7 @@ func TestRevokeCert(t *testing.T) {
 	}))
 	defer ts.Close()
 	client := &Client{
-		Key: testKey,
+		Key: testKeyEC,
 		dir: &Directory{RevokeURL: ts.URL},
 	}
 	ctx := context.Background()
@@ -871,11 +860,11 @@ func TestErrorResponse(t *testing.T) {
 func TestTLSSNI01ChallengeCert(t *testing.T) {
 	const (
 		token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
-		// echo -n <token.testKeyThumbprint> | shasum -a 256
-		san = "b6ddc3df57802969e2e0b88eb548d4be.febc5bd6cf3690eb526081b5d10deda4.acme.invalid"
+		// echo -n <token.testKeyECThumbprint> | shasum -a 256
+		san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid"
 	)
 
-	client := &Client{Key: testKey}
+	client := &Client{Key: testKeyEC}
 	tlscert, name, err := client.TLSSNI01ChallengeCert(token)
 	if err != nil {
 		t.Fatal(err)
@@ -901,11 +890,11 @@ func TestTLSSNI02ChallengeCert(t *testing.T) {
 		token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
 		// echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256
 		sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid"
-		// echo -n <token.testKeyThumbprint> | shasum -a 256
-		sanB = "b6ddc3df57802969e2e0b88eb548d4be.febc5bd6cf3690eb526081b5d10deda4.ka.acme.invalid"
+		// echo -n <token.testKeyECThumbprint> | shasum -a 256
+		sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid"
 	)
 
-	client := &Client{Key: testKey}
+	client := &Client{Key: testKeyEC}
 	tlscert, name, err := client.TLSSNI02ChallengeCert(token)
 	if err != nil {
 		t.Fatal(err)
@@ -932,11 +921,11 @@ func TestTLSSNI02ChallengeCert(t *testing.T) {
 func TestHTTP01Challenge(t *testing.T) {
 	const (
 		token = "xxx"
-		// thumbprint is precomputed for testKey in jws_test.go
-		value   = token + "." + testKeyThumbprint
+		// thumbprint is precomputed for testKeyEC in jws_test.go
+		value   = token + "." + testKeyECThumbprint
 		urlpath = "/.well-known/acme-challenge/" + token
 	)
-	client := &Client{Key: testKey}
+	client := &Client{Key: testKeyEC}
 	val, err := client.HTTP01ChallengeResponse(token)
 	if err != nil {
 		t.Fatal(err)

+ 3 - 0
acme/internal/acme/jws_test.go

@@ -63,6 +63,9 @@ QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ==
 	// 3. covert each with: echo <val> | xxd -r -p | base64 | tr -d '=' | tr '/+' '_-'
 	testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
 	testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
+	// echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
+	// openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-'
+	testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
 )
 
 var (