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>
This commit is contained in:
Alex Vaghin
2016-09-12 15:03:27 +00:00
parent 0e31b188fd
commit e311231e83
2 changed files with 58 additions and 13 deletions
+33 -10
View File
@@ -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
View File
@@ -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)
}
}
}
}