Add initial TLS-SNI-02 challenge

This commit is contained in:
xenolf 2016-03-23 21:43:15 +01:00
parent 0eba8326e9
commit a42a5f66d7
6 changed files with 85 additions and 7 deletions

View file

@ -10,6 +10,9 @@ const (
// TLSSNI01 is the "tls-sni-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#tls-with-server-name-indication-tls-sni // TLSSNI01 is the "tls-sni-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#tls-with-server-name-indication-tls-sni
// Note: TLSSNI01ChallengeCert returns a certificate to fulfill this challenge // Note: TLSSNI01ChallengeCert returns a certificate to fulfill this challenge
TLSSNI01 = Challenge("tls-sni-01") TLSSNI01 = Challenge("tls-sni-01")
// TLSSNI02 is the "tls-sni-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#tls-with-server-name-indication-tls-sni
// Note: TLSSNI02ChallengeCert returns a certificate to fulfill this challenge
TLSSNI02 = Challenge("tls-sni-02")
// DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns // DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns
// Note: DNS01Record returns a DNS record which will fulfill this challenge // Note: DNS01Record returns a DNS record which will fulfill this challenge
DNS01 = Challenge("dns-01") DNS01 = Challenge("dns-01")

View file

@ -92,6 +92,7 @@ func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
solvers := make(map[Challenge]solver) solvers := make(map[Challenge]solver)
solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}} solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}}
solvers[TLSSNI01] = &tlsSNIChallenge{jws: jws, validate: validate, provider: &TLSProviderServer{}} solvers[TLSSNI01] = &tlsSNIChallenge{jws: jws, validate: validate, provider: &TLSProviderServer{}}
solvers[TLSSNI02] = &tlsSNI02Challenge{jws: jws, validate: validate, provider: &TLSProviderServer{}}
return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
} }
@ -139,6 +140,10 @@ func (c *Client) SetTLSAddress(iface string) error {
if chlng, ok := c.solvers[TLSSNI01]; ok { if chlng, ok := c.solvers[TLSSNI01]; ok {
chlng.(*tlsSNIChallenge).provider = NewTLSProviderServer(host, port) chlng.(*tlsSNIChallenge).provider = NewTLSProviderServer(host, port)
} }
if chlng, ok := c.solvers[TLSSNI02]; ok {
chlng.(*tlsSNI02Challenge).provider = NewTLSProviderServer(host, port)
}
return nil return nil
} }

View file

@ -46,7 +46,7 @@ func TestNewClient(t *testing.T) {
t.Errorf("Expected keyType to be %s but was %s", keyType, client.keyType) t.Errorf("Expected keyType to be %s but was %s", keyType, client.keyType)
} }
if expected, actual := 2, len(client.solvers); actual != expected { if expected, actual := 3, len(client.solvers); actual != expected {
t.Fatalf("Expected %d solver(s), got %d", expected, actual) t.Fatalf("Expected %d solver(s), got %d", expected, actual)
} }
} }

View file

@ -298,8 +298,8 @@ func getCertExpiration(cert []byte) (time.Time, error) {
return pCert.NotAfter, nil return pCert.NotAfter, nil
} }
func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) { func generatePemCert(privKey *rsa.PrivateKey, domains ...string) ([]byte, error) {
derBytes, err := generateDerCert(privKey, time.Time{}, domain) derBytes, err := generateDerCert(privKey, time.Time{}, domains...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -307,7 +307,7 @@ func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) {
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
} }
func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) { func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domains ...string) ([]byte, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil { if err != nil {
@ -328,7 +328,7 @@ func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain strin
KeyUsage: x509.KeyUsageKeyEncipherment, KeyUsage: x509.KeyUsageKeyEncipherment,
BasicConstraintsValid: true, BasicConstraintsValid: true,
DNSNames: []string{domain}, DNSNames: domains,
} }
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)

View file

@ -0,0 +1,72 @@
package acme
import (
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"fmt"
"log"
)
type tlsSNI02Challenge struct {
jws *jws
validate validateFunc
provider ChallengeProvider
}
func (t *tlsSNI02Challenge) Solve(chlng challenge, domain string) error {
logf("[INFO][%s] acme: Trying to solve TLS-SNI-02", domain)
// Generate the Key Authorization for the challenge
keyAuth, err := getKeyAuthorization(chlng.Token, t.jws.privKey)
if err != nil {
return err
}
err = t.provider.Present(domain, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] error presenting token: %v", domain, err)
}
defer func() {
err := t.provider.CleanUp(domain, chlng.Token, keyAuth)
if err != nil {
log.Printf("[%s] error cleaning up: %v", domain, err)
}
}()
return t.validate(t.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
}
// TLSSNI02ChallengeCert returns a certificate for the `tls-sni-02` challenge
func TLSSNI02ChallengeCert(token, keyAuth string) (tls.Certificate, error) {
// Construct SanA value from token sha256
tokenShaBytes := sha256.Sum256([]byte(token))
tokenSha := hex.EncodeToString(tokenShaBytes[:sha256.Size])
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", tokenSha[:32], tokenSha[32:])
// Construct SanB value from keyAuth sha256
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
keyAuthSha := hex.EncodeToString(keyAuthShaBytes[:sha256.Size])
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", keyAuthSha[:32], keyAuthSha[32:])
// generate a new RSA key for the certificate
tempPrivKey, err := generatePrivateKey(RSA2048)
if err != nil {
return tls.Certificate{}, err
}
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
rsaPrivPEM := pemEncode(rsaPrivKey)
tempCertPEM, err := generatePemCert(rsaPrivKey, sanA, sanB)
if err != nil {
return tls.Certificate{}, err
}
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
if err != nil {
return tls.Certificate{}, err
}
return certificate, nil
}

View file

@ -16,8 +16,6 @@ type tlsSNIChallenge struct {
} }
func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error { func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
// FIXME: https://github.com/ietf-wg-acme/acme/pull/22
// Currently we implement this challenge to track boulder, not the current spec!
logf("[INFO][%s] acme: Trying to solve TLS-SNI-01", domain) logf("[INFO][%s] acme: Trying to solve TLS-SNI-01", domain)