From a42a5f66d7e34f5da078810cd397462423874aaa Mon Sep 17 00:00:00 2001 From: xenolf Date: Wed, 23 Mar 2016 21:43:15 +0100 Subject: [PATCH] Add initial TLS-SNI-02 challenge --- acme/challenges.go | 3 ++ acme/client.go | 5 +++ acme/client_test.go | 2 +- acme/crypto.go | 8 ++-- acme/tls_sni_02_challenge.go | 72 ++++++++++++++++++++++++++++++++++++ acme/tls_sni_challenge.go | 2 - 6 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 acme/tls_sni_02_challenge.go diff --git a/acme/challenges.go b/acme/challenges.go index 85790050..220b3d46 100644 --- a/acme/challenges.go +++ b/acme/challenges.go @@ -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 // Note: TLSSNI01ChallengeCert returns a certificate to fulfill this challenge 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 // Note: DNS01Record returns a DNS record which will fulfill this challenge DNS01 = Challenge("dns-01") diff --git a/acme/client.go b/acme/client.go index d4febfb2..c1d0b370 100644 --- a/acme/client.go +++ b/acme/client.go @@ -92,6 +92,7 @@ func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) { solvers := make(map[Challenge]solver) solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}} 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 } @@ -139,6 +140,10 @@ func (c *Client) SetTLSAddress(iface string) error { if chlng, ok := c.solvers[TLSSNI01]; ok { chlng.(*tlsSNIChallenge).provider = NewTLSProviderServer(host, port) } + + if chlng, ok := c.solvers[TLSSNI02]; ok { + chlng.(*tlsSNI02Challenge).provider = NewTLSProviderServer(host, port) + } return nil } diff --git a/acme/client_test.go b/acme/client_test.go index e309554f..15ca6085 100644 --- a/acme/client_test.go +++ b/acme/client_test.go @@ -46,7 +46,7 @@ func TestNewClient(t *testing.T) { 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) } } diff --git a/acme/crypto.go b/acme/crypto.go index 33240b61..9801728b 100644 --- a/acme/crypto.go +++ b/acme/crypto.go @@ -298,8 +298,8 @@ func getCertExpiration(cert []byte) (time.Time, error) { return pCert.NotAfter, nil } -func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) { - derBytes, err := generateDerCert(privKey, time.Time{}, domain) +func generatePemCert(privKey *rsa.PrivateKey, domains ...string) ([]byte, error) { + derBytes, err := generateDerCert(privKey, time.Time{}, domains...) if err != nil { 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 } -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) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { @@ -328,7 +328,7 @@ func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain strin KeyUsage: x509.KeyUsageKeyEncipherment, BasicConstraintsValid: true, - DNSNames: []string{domain}, + DNSNames: domains, } return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) diff --git a/acme/tls_sni_02_challenge.go b/acme/tls_sni_02_challenge.go new file mode 100644 index 00000000..adefd333 --- /dev/null +++ b/acme/tls_sni_02_challenge.go @@ -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 +} diff --git a/acme/tls_sni_challenge.go b/acme/tls_sni_challenge.go index 34383cbf..588a4ea9 100644 --- a/acme/tls_sni_challenge.go +++ b/acme/tls_sni_challenge.go @@ -16,8 +16,6 @@ type tlsSNIChallenge struct { } 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)