acme: TLS-SNI challenges implementation
Current and N-1 versions of the spec adopted TLS-SNI-02, but Let's Encrypt seems to support TLS-SNI-01, devised in v1 of the spec. This change implements both, older 01 and current 02 versions of TLS-SNI challenges. Change-Id: I2bb76ad852e105098f703cb6113d3996c0e0b118 Reviewed-on: https://go-review.googlesource.com/25450 Reviewed-by: Alex Vaghin <ddos@google.com>
This commit is contained in:
@@ -11,12 +11,19 @@ package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -351,6 +358,45 @@ func (c *Client) HTTP01Handler(token string) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name.
|
||||
//
|
||||
// The implementation is incomplete in that the returned value is a single certificate,
|
||||
// computed only for Z0 of the key authorization. ACME CAs are expected to update
|
||||
// their implementations to use the newer version, TLS-SNI-02.
|
||||
// For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// The returned certificate is valid for the next 24 hours.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string) (tls.Certificate, error) {
|
||||
ka := keyAuth(&c.Key.PublicKey, token)
|
||||
b := sha256.Sum256([]byte(ka))
|
||||
h := hex.EncodeToString(b[:])
|
||||
name := fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:])
|
||||
return tlsChallengeCert(name)
|
||||
}
|
||||
|
||||
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-SNI-02 see
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3.
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// The returned certificate is valid for the next 24 hours.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string) (tls.Certificate, error) {
|
||||
b := sha256.Sum256([]byte(token))
|
||||
h := hex.EncodeToString(b[:])
|
||||
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:])
|
||||
|
||||
ka := keyAuth(&c.Key.PublicKey, token)
|
||||
b = sha256.Sum256([]byte(ka))
|
||||
h = hex.EncodeToString(b[:])
|
||||
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:])
|
||||
|
||||
return tlsChallengeCert(sanA, sanB)
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
@@ -531,5 +577,35 @@ func keyAuth(pub *rsa.PublicKey, token string) string {
|
||||
return fmt.Sprintf("%s.%s", token, JWKThumbprint(pub))
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
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,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, &t, &t, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
cert := encodePEM("CERTIFICATE", der)
|
||||
keyp := encodePEM("RSA PRIVATE KEY", x509.MarshalPKCS1PrivateKey(key))
|
||||
return tls.X509KeyPair(cert, keyp)
|
||||
}
|
||||
|
||||
// encodePEM returns b encoded as PEM with block of type typ.
|
||||
func encodePEM(typ string, b []byte) []byte {
|
||||
pb := &pem.Block{Type: typ, Bytes: b}
|
||||
return pem.EncodeToMemory(pb)
|
||||
}
|
||||
|
||||
// timeNow is useful for testing for fixed current time.
|
||||
var timeNow = time.Now
|
||||
|
||||
@@ -766,3 +766,55 @@ func TestErrorResponse(t *testing.T) {
|
||||
t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSSNI01ChallengeCert(t *testing.T) {
|
||||
const (
|
||||
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
|
||||
// echo -n <token.testKeyThumbprint> | shasum -a 256
|
||||
san = "b6ddc3df57802969e2e0b88eb548d4be.febc5bd6cf3690eb526081b5d10deda4.acme.invalid"
|
||||
)
|
||||
|
||||
client := &Client{Key: testKey}
|
||||
tlscert, err := client.TLSSNI01ChallengeCert(token)
|
||||
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)
|
||||
}
|
||||
if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san {
|
||||
t.Errorf("cert.DNSNames = %v; want %q", cert.DNSNames, san)
|
||||
}
|
||||
}
|
||||
func TestTLSSNI02ChallengeCert(t *testing.T) {
|
||||
const (
|
||||
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"
|
||||
)
|
||||
|
||||
client := &Client{Key: testKey}
|
||||
tlscert, err := client.TLSSNI02ChallengeCert(token)
|
||||
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{sanA, sanB}
|
||||
if !reflect.DeepEqual(cert.DNSNames, names) {
|
||||
t.Errorf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user