acme: add support for TLS-ALPN
This adds support for the new challenge type, as described in https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01 Updates golang/go#25013 Change-Id: I81b335ff4b4e89e705a70e7d38dd21c3d5f5c25f Reviewed-on: https://go-review.googlesource.com/116995 Reviewed-by: Alex Vaghin <ddos@google.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Родитель
b47b158736
Коммит
8ac0e0d97c
81
acme/acme.go
81
acme/acme.go
|
@ -22,6 +22,8 @@ import (
|
|||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
@ -40,6 +42,9 @@ import (
|
|||
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
|
||||
const LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
|
||||
// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
|
||||
var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
|
||||
|
||||
const (
|
||||
maxChainLen = 5 // max depth and breadth of a certificate chain
|
||||
maxCertSize = 1 << 20 // max size of a certificate, in bytes
|
||||
|
@ -526,7 +531,7 @@ func (c *Client) HTTP01ChallengePath(token string) string {
|
|||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name of the client hello matches exactly the returned name value.
|
||||
// the server name of the TLS ClientHello matches exactly the returned name value.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
|
@ -553,7 +558,7 @@ func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tl
|
|||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the client hello matches exactly the returned name value.
|
||||
// the server name in the TLS ClientHello matches exactly the returned name value.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
b := sha256.Sum256([]byte(token))
|
||||
h := hex.EncodeToString(b[:])
|
||||
|
@ -574,6 +579,48 @@ func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tl
|
|||
return cert, sanA, nil
|
||||
}
|
||||
|
||||
// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-ALPN-01 see
|
||||
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
// and the public part is used to specify the signee.
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
|
||||
// has been specified.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
shasum := sha256.Sum256([]byte(ka))
|
||||
acmeExtension := pkix.Extension{
|
||||
Id: idPeACMEIdentifierV1,
|
||||
Critical: true,
|
||||
Value: shasum[:],
|
||||
}
|
||||
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
|
||||
var newOpt []CertOption
|
||||
for _, o := range opt {
|
||||
switch o := o.(type) {
|
||||
case *certOptTemplate:
|
||||
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||
tmpl = &t
|
||||
default:
|
||||
newOpt = append(newOpt, o)
|
||||
}
|
||||
}
|
||||
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
|
||||
newOpt = append(newOpt, WithTemplate(tmpl))
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
}
|
||||
|
||||
// doReg sends all types of registration requests.
|
||||
// The type of request is identified by typ argument, which is a "resource"
|
||||
// in the ACME spec terms.
|
||||
|
@ -795,15 +842,25 @@ func keyAuth(pub crypto.PublicKey, token string) (string, error) {
|
|||
return fmt.Sprintf("%s.%s", token, th), nil
|
||||
}
|
||||
|
||||
// defaultTLSChallengeCertTemplate is a template used to create challenge certs for TLS challenges.
|
||||
func defaultTLSChallengeCertTemplate() *x509.Certificate {
|
||||
return &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(24 * time.Hour),
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
}
|
||||
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
|
||||
// with the given SANs and auto-generated public/private key pair.
|
||||
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||
// 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
|
||||
tmpl *x509.Certificate
|
||||
)
|
||||
var key crypto.Signer
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
for _, o := range opt {
|
||||
switch o := o.(type) {
|
||||
case *certOptKey:
|
||||
|
@ -812,7 +869,7 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
|||
}
|
||||
key = o.key
|
||||
case *certOptTemplate:
|
||||
var t = *(*x509.Certificate)(o) // shallow copy is ok
|
||||
t := *(*x509.Certificate)(o) // shallow copy is ok
|
||||
tmpl = &t
|
||||
default:
|
||||
// package's fault, if we let this happen:
|
||||
|
@ -825,16 +882,6 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
|||
return tls.Certificate{}, err
|
||||
}
|
||||
}
|
||||
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 | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
if len(san) > 0 {
|
||||
tmpl.Subject.CommonName = san[0]
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -1160,6 +1161,58 @@ func TestTLSSNI02ChallengeCert(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTLSALPN01ChallengeCert(t *testing.T) {
|
||||
const (
|
||||
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
|
||||
keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA." + testKeyECThumbprint
|
||||
// echo -n <token.testKeyECThumbprint> | shasum -a 256
|
||||
h = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cda27d320e4b30332f0b6cb441734ad7b0"
|
||||
domain = "example.com"
|
||||
)
|
||||
|
||||
extValue, err := hex.DecodeString(h)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client := &Client{Key: testKeyEC}
|
||||
tlscert, err := client.TLSALPN01ChallengeCert(token, domain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n := len(tlscert.Certificate); n != 1 {
|
||||
t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(tlscert.Certificate[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
names := []string{domain}
|
||||
if !reflect.DeepEqual(cert.DNSNames, names) {
|
||||
t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
|
||||
}
|
||||
if cn := cert.Subject.CommonName; cn != domain {
|
||||
t.Errorf("CommonName = %q; want %q", cn, domain)
|
||||
}
|
||||
acmeExts := []pkix.Extension{}
|
||||
for _, ext := range cert.Extensions {
|
||||
if idPeACMEIdentifierV1.Equal(ext.Id) {
|
||||
acmeExts = append(acmeExts, ext)
|
||||
}
|
||||
}
|
||||
if len(acmeExts) != 1 {
|
||||
t.Errorf("acmeExts = %v; want exactly one", acmeExts)
|
||||
}
|
||||
if !acmeExts[0].Critical {
|
||||
t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical)
|
||||
}
|
||||
if bytes.Compare(acmeExts[0].Value, extValue) != 0 {
|
||||
t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTLSChallengeCertOpt(t *testing.T) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 512)
|
||||
if err != nil {
|
||||
|
|
|
@ -296,8 +296,8 @@ func (e *wireError) error(h http.Header) *Error {
|
|||
}
|
||||
}
|
||||
|
||||
// CertOption is an optional argument type for the TLSSNIxChallengeCert methods for
|
||||
// customizing a temporary certificate for TLS-SNI challenges.
|
||||
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
||||
// customizing a temporary certificate for TLS-based challenges.
|
||||
type CertOption interface {
|
||||
privateCertOpt()
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ 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,
|
||||
// In TLS ChallengeCert 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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче