diff --git a/acme/http_challenge_test.go b/acme/http_challenge_test.go index cfd9fe2a..bf085ffd 100644 --- a/acme/http_challenge_test.go +++ b/acme/http_challenge_test.go @@ -21,7 +21,7 @@ func TestHTTPNonRootBind(t *testing.T) { solver := &httpChallenge{jws: jws} clientChallenge := challenge{Type: "http01", Status: "pending", URI: "localhost:4000", Token: "http1"} - // validate error on non-root bind to 443 + // validate error on non-root bind to 80 if err := solver.Solve(clientChallenge, "127.0.0.1"); err == nil { t.Error("BIND: Expected Solve to return an error but the error was nil.") } else { diff --git a/acme/tls_sni_challenge_test.go b/acme/tls_sni_challenge_test.go new file mode 100644 index 00000000..41c3f9db --- /dev/null +++ b/acme/tls_sni_challenge_test.go @@ -0,0 +1,100 @@ +package acme + +import ( + "crypto/rsa" + "crypto/sha256" + "crypto/tls" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/square/go-jose" +) + +func TestTLSSNINonRootBind(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 128) + jws := &jws{privKey: privKey.(*rsa.PrivateKey)} + + solver := &tlsSNIChallenge{jws: jws} + clientChallenge := challenge{Type: "tls-sni-01", Status: "pending", URI: "localhost:4000", Token: "tls1"} + + // validate error on non-root bind to 443 + if err := solver.Solve(clientChallenge, "127.0.0.1"); err == nil { + t.Error("BIND: Expected Solve to return an error but the error was nil.") + } else { + expectedError := "Could not start HTTPS server for challenge -> listen tcp :443: bind: permission denied" + if err.Error() != expectedError { + t.Errorf("Expected error \"%s\" but instead got \"%s\"", expectedError, err.Error()) + } + } +} + +func TestTLSSNI(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + optPort := "5001" + + ts := httptest.NewServer(nil) + + ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var request challenge + w.Header().Add("Replay-Nonce", "12345") + + if r.Method == "HEAD" { + return + } + + clientJws, _ := ioutil.ReadAll(r.Body) + j, err := jose.ParseSigned(string(clientJws)) + if err != nil { + t.Errorf("Client sent invalid JWS to the server.\n\t%v", err) + return + } + output, err := j.Verify(&privKey.(*rsa.PrivateKey).PublicKey) + if err != nil { + t.Errorf("Unable to verify client data -> %v", err) + } + json.Unmarshal(output, &request) + + conn, err := tls.Dial("tcp", "localhost:"+optPort, &tls.Config{ + InsecureSkipVerify: true, + }) + if err != nil { + t.Errorf("Expected to connect to challenge server without an error. %s", err.Error()) + } + + // Expect the server to only return one certificate + connState := conn.ConnectionState() + if count := len(connState.PeerCertificates); count != 1 { + t.Errorf("Expected the challenge server to return exactly one certificate but got %d", count) + } + + remoteCert := connState.PeerCertificates[0] + if count := len(remoteCert.DNSNames); count != 1 { + t.Errorf("Expected the challenge certificate to have exactly one DNSNames entry but had %d", count) + } + + zBytes := sha256.Sum256([]byte(request.KeyAuthorization)) + z := hex.EncodeToString(zBytes[:sha256.Size]) + domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:]) + + if remoteCert.DNSNames[0] != domain { + t.Errorf("Expected the challenge certificate DNSName to match %s but was %s", domain, remoteCert.DNSNames[0]) + } + + valid := challenge{Type: "tls-sni-01", Status: "valid", URI: ts.URL, Token: "tls1"} + jsonBytes, _ := json.Marshal(&valid) + w.Write(jsonBytes) + }) + + jws := &jws{privKey: privKey.(*rsa.PrivateKey), directoryURL: ts.URL} + solver := &tlsSNIChallenge{jws: jws, optPort: optPort} + clientChallenge := challenge{Type: "tls-sni-01", Status: "pending", URI: ts.URL, Token: "tls1"} + + if err := solver.Solve(clientChallenge, "127.0.0.1"); err != nil { + t.Error("UNEXPECTED: Expected Solve to return no error but the error was %s.", err.Error()) + } +}