浏览代码

acme: add WithTemplate option for tls-sni certs

This change allows for more customizations when creating a tls-sni
challenge response.

Same reason as with https://go-review.googlesource.com/27750.

Change-Id: Ia702ede2f4dd867814cfdc1f8925557d3eb455e9
Reviewed-on: https://go-review.googlesource.com/29053
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Alex Vaghin 9 年之前
父节点
当前提交
e311231e83
共有 2 个文件被更改,包括 58 次插入13 次删除
  1. 33 10
      acme/acme.go
  2. 25 3
      acme/acme_test.go

+ 33 - 10
acme/acme.go

@@ -65,7 +65,21 @@ type certOptKey struct {
 	key crypto.Signer
 }
 
-func (co *certOptKey) privateCertOpt() {}
+func (*certOptKey) privateCertOpt() {}
+
+// WithTemplate creates an option for specifying a certificate template.
+// See x509.CreateCertificate for template usage details.
+//
+// In TLSSNIxChallengeCert methods, the template is also used as parent,
+// resulting in a self-signed certificate.
+// The DNSNames field of t is always overwritten for tls-sni challenge certs.
+func WithTemplate(t *x509.Certificate) CertOption {
+	return (*certOptTemplate)(t)
+}
+
+type certOptTemplate x509.Certificate
+
+func (*certOptTemplate) privateCertOpt() {}
 
 // Client is an ACME client.
 // The only required field is Key. An example of creating a client with a new key
@@ -874,7 +888,10 @@ func keyAuth(pub crypto.PublicKey, token string) (string, error) {
 // with the given SANs and auto-generated public/private key pair.
 // To create a cert with a custom key pair, specify WithKey option.
 func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
-	var key crypto.Signer
+	var (
+		key  crypto.Signer
+		tmpl *x509.Certificate
+	)
 	for _, o := range opt {
 		switch o := o.(type) {
 		case *certOptKey:
@@ -882,6 +899,9 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
 				return tls.Certificate{}, errors.New("acme: duplicate key option")
 			}
 			key = o.key
+		case *certOptTemplate:
+			var t = *(*x509.Certificate)(o) // shallow copy is ok
+			tmpl = &t
 		default:
 			// package's fault, if we let this happen:
 			panic(fmt.Sprintf("unsupported option type %T", o))
@@ -893,15 +913,18 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
 			return tls.Certificate{}, err
 		}
 	}
-	t := x509.Certificate{
-		SerialNumber:          big.NewInt(1),
-		NotBefore:             time.Now(),
-		NotAfter:              time.Now().Add(24 * time.Hour),
-		BasicConstraintsValid: true,
-		KeyUsage:              x509.KeyUsageKeyEncipherment,
-		DNSNames:              san,
+	if tmpl == nil {
+		tmpl = &x509.Certificate{
+			SerialNumber:          big.NewInt(1),
+			NotBefore:             time.Now(),
+			NotAfter:              time.Now().Add(24 * time.Hour),
+			BasicConstraintsValid: true,
+			KeyUsage:              x509.KeyUsageKeyEncipherment,
+		}
 	}
-	der, err := x509.CreateCertificate(rand.Reader, &t, &t, key.Public(), key)
+	tmpl.DNSNames = san
+
+	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
 	if err != nil {
 		return tls.Certificate{}, err
 	}

+ 25 - 3
acme/acme_test.go

@@ -1060,20 +1060,28 @@ func TestTLSSNI02ChallengeCert(t *testing.T) {
 	}
 }
 
-func TestTLSChallengeCertRSA(t *testing.T) {
+func TestTLSChallengeCertOpt(t *testing.T) {
 	key, err := rsa.GenerateKey(rand.Reader, 512)
 	if err != nil {
 		t.Fatal(err)
 	}
+	tmpl := &x509.Certificate{
+		SerialNumber: big.NewInt(2),
+		Subject:      pkix.Name{Organization: []string{"Test"}},
+		DNSNames:     []string{"should-be-overwritten"},
+	}
+	opts := []CertOption{WithKey(key), WithTemplate(tmpl)}
+
 	client := &Client{Key: testKeyEC}
-	cert1, _, err := client.TLSSNI01ChallengeCert("token", WithKey(key))
+	cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...)
 	if err != nil {
 		t.Fatal(err)
 	}
-	cert2, _, err := client.TLSSNI02ChallengeCert("token", WithKey(key))
+	cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...)
 	if err != nil {
 		t.Fatal(err)
 	}
+
 	for i, tlscert := range []tls.Certificate{cert1, cert2} {
 		// verify generated cert private key
 		tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey)
@@ -1098,6 +1106,20 @@ func TestTLSChallengeCertRSA(t *testing.T) {
 		if tlspub.N.Cmp(key.N) != 0 {
 			t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N)
 		}
+		// verify template option
+		sn := big.NewInt(2)
+		if x509Cert.SerialNumber.Cmp(sn) != 0 {
+			t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn)
+		}
+		org := []string{"Test"}
+		if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
+			t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org)
+		}
+		for _, v := range x509Cert.DNSNames {
+			if !strings.HasSuffix(v, ".acme.invalid") {
+				t.Errorf("%d: invalid DNSNames element: %q", i, v)
+			}
+		}
 	}
 }