diff --git a/acme/dvsni_challenge.go b/acme/dvsni_challenge.go index ae5bf77a..8d2a213b 100644 --- a/acme/dvsni_challenge.go +++ b/acme/dvsni_challenge.go @@ -1,11 +1 @@ package acme - -type dvsniChallenge struct{} - -func (s *dvsniChallenge) CanSolve(domain string) bool { - return false -} - -func (s *dvsniChallenge) Solve(challenge challenge, domain string) error { - return nil -} diff --git a/acme/simple_http_challenge.go b/acme/simple_http_challenge.go index fbcd9db0..4d367085 100644 --- a/acme/simple_http_challenge.go +++ b/acme/simple_http_challenge.go @@ -1,7 +1,6 @@ package acme import ( - "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" @@ -112,7 +111,7 @@ func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net tlsListener, err := tls.Listen("tcp", port, tlsConf) if err != nil { - return nil, fmt.Errorf("Could not start HTTP listener! -> %v", err) + return nil, err } jsonBytes, err := json.Marshal(challenge{Type: "simpleHttp", Token: token, TLS: true}) @@ -121,7 +120,7 @@ func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net } signed, err := s.jws.signContent(jsonBytes) if err != nil { - return nil, errors.New("startHTTPSServer: Failed to sign message") + return nil, fmt.Errorf("startHTTPSServer: Failed to sign message. %s", err) } signedCompact := signed.FullSerialize() if err != nil { @@ -145,13 +144,3 @@ func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net return tlsListener, nil } - -func getRandomString(length int) string { - const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - var bytes = make([]byte, length) - rand.Read(bytes) - for i, b := range bytes { - bytes[i] = alphanum[b%byte(len(alphanum))] - } - return string(bytes) -} diff --git a/acme/simple_http_challenge_test.go b/acme/simple_http_challenge_test.go index 7e3c75e2..5ac40865 100644 --- a/acme/simple_http_challenge_test.go +++ b/acme/simple_http_challenge_test.go @@ -12,54 +12,183 @@ import ( "github.com/square/go-jose" ) -func TestSimpleHTTP(t *testing.T) { - privKey, err := generatePrivateKey(rsakey, 512) - if err != nil { - t.Errorf("Could not generate public key -> %v", err) +func TestSimpleHTTPNonRootBind(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 128) + jws := &jws{privKey: privKey.(*rsa.PrivateKey)} + + solver := &simpleHTTPChallenge{jws: jws} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: "localhost:4000", Token: "1"} + + // validate error on non-root bind to 443 + if err := solver.Solve(clientChallenge, "test.domain"); 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 TestSimpleHTTPShortRSA(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 128) + jws := &jws{privKey: privKey.(*rsa.PrivateKey), nonces: []string{"test1", "test2"}} + + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: "http://localhost:4000", Token: "2"} + + if err := solver.Solve(clientChallenge, "test.domain"); err == nil { + t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.") + } else { + expectedError := "Could not start HTTPS server for challenge -> startHTTPSServer: Failed to sign message. crypto/rsa: message too long for RSA public key size" + if err.Error() != expectedError { + t.Errorf("Expected error %s but instead got %s", expectedError, err.Error()) + } + } +} + +func TestSimpleHTTPConnectionRefusal(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + jws := &jws{privKey: privKey.(*rsa.PrivateKey), nonces: []string{"test1", "test2"}} + + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: "http://localhost:4000", Token: "3"} + + if err := solver.Solve(clientChallenge, "test.domain"); err == nil { + t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.") + } else { + expectedError := "Failed to post JWS message. -> Post http://localhost:4000: dial tcp 127.0.0.1:4000: getsockopt: connection refused" + if err.Error() != expectedError { + t.Errorf("Expected error %s but instead got %s", expectedError, err.Error()) + } + } +} + +func TestSimpleHTTPUnexpectedServerState(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) jws := &jws{privKey: privKey.(*rsa.PrivateKey)} ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Replay-Nonce", "12345") + w.Write([]byte("{\"type\":\"simpleHttp\",\"status\":\"what\",\"uri\":\"http://some.url\",\"token\":\"4\"}")) })) - solver := &simpleHTTPChallenge{jws: jws} - clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "123456789"} + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "4"} - // validate error on non-root bind to 443 - if err = solver.Solve(clientChallenge, "test.domain"); err == nil { - t.Error("BIND: Expected Solve to return an error but the error was nil.") + if err := solver.Solve(clientChallenge, "test.domain"); err == nil { + t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.") + } else { + expectedError := "The server returned an unexpected state." + if err.Error() != expectedError { + t.Errorf("Expected error %s but instead got %s", expectedError, err.Error()) + } } +} - // Validate error on unexpected state - solver.optPort = "23456" - if err = solver.Solve(clientChallenge, "test.domain"); err == nil { +func TestSimpleHTTPChallengeServerUnexpectedDomain(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + jws := &jws{privKey: privKey.(*rsa.PrivateKey)} + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + req, _ := client.Get("https://localhost:23456/.well-known/acme-challenge/" + "5") + reqBytes, _ := ioutil.ReadAll(req.Body) + if string(reqBytes) != "TEST" { + t.Error("Expected simpleHTTP server to return string TEST on unexpected domain.") + } + } + + w.Header().Add("Replay-Nonce", "12345") + w.Write([]byte("{\"type\":\"simpleHttp\",\"status\":\"invalid\",\"uri\":\"http://some.url\",\"token\":\"5\"}")) + })) + + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "5"} + + if err := solver.Solve(clientChallenge, "test.domain"); err == nil { t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.") } +} - // Validate error on invalid status - ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Replay-Nonce", "12345") - failed := challenge{Type: "simpleHttp", Status: "invalid", URI: ts.URL, Token: "1234567810"} - jsonBytes, _ := json.Marshal(&failed) - w.Write(jsonBytes) - }) - clientChallenge.Token = "1234567810" - if err = solver.Solve(clientChallenge, "test.domain"); err == nil { - t.Error("FAILED: Expected Solve to return an error but the error was nil.") +func TestSimpleHTTPServerError(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + jws := &jws{privKey: privKey.(*rsa.PrivateKey)} + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "HEAD" { + w.Header().Add("Replay-Nonce", "12345") + } else { + w.WriteHeader(http.StatusInternalServerError) + w.Header().Add("Replay-Nonce", "12345") + w.Write([]byte("{\"type\":\"urn:acme:error:unauthorized\",\"detail\":\"Error creating new authz :: Syntax error\"}")) + } + })) + + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "6"} + + if err := solver.Solve(clientChallenge, "test.domain"); err == nil { + t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.") + } else { + expectedError := "[500] Type: urn:acme:error:unauthorized Detail: Error creating new authz :: Syntax error" + if err.Error() != expectedError { + t.Errorf("Expected error |%s| but instead got |%s|", expectedError, err.Error()) + } } +} - // Validate no error on valid response - ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +func TestSimpleHTTPInvalidServerState(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + jws := &jws{privKey: privKey.(*rsa.PrivateKey)} + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Replay-Nonce", "12345") - valid := challenge{Type: "simpleHttp", Status: "valid", URI: ts.URL, Token: "1234567811"} - jsonBytes, _ := json.Marshal(&valid) - w.Write(jsonBytes) - }) - clientChallenge.Token = "1234567811" - if err = solver.Solve(clientChallenge, "test.domain"); err != nil { + w.Write([]byte("{\"type\":\"simpleHttp\",\"status\":\"invalid\",\"uri\":\"http://some.url\",\"token\":\"7\"}")) + })) + + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "7"} + + if err := solver.Solve(clientChallenge, "test.domain"); err == nil { + t.Error("UNEXPECTED: Expected Solve to return an error but the error was nil.") + } else { + expectedError := "The server could not validate our request." + if err.Error() != expectedError { + t.Errorf("Expected error |%s| but instead got |%s|", expectedError, err.Error()) + } + } +} + +func TestSimpleHTTPValidServerResponse(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + jws := &jws{privKey: privKey.(*rsa.PrivateKey)} + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Replay-Nonce", "12345") + w.Write([]byte("{\"type\":\"simpleHttp\",\"status\":\"valid\",\"uri\":\"http://some.url\",\"token\":\"8\"}")) + })) + + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "8"} + + if err := solver.Solve(clientChallenge, "test.domain"); err != nil { t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err) } +} + +func TestSimpleHTTPValidFull(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + jws := &jws{privKey: privKey.(*rsa.PrivateKey)} + + ts := httptest.NewServer(nil) + + solver := &simpleHTTPChallenge{jws: jws, optPort: "23456"} + clientChallenge := challenge{Type: "simpleHttp", Status: "pending", URI: ts.URL, Token: "9"} // Validate server on port 23456 which responds appropriately clientChallenge.Token = "1234567812" @@ -112,7 +241,8 @@ func TestSimpleHTTP(t *testing.T) { jsonBytes, _ := json.Marshal(&valid) w.Write(jsonBytes) }) - if err = solver.Solve(clientChallenge, "test.domain"); err != nil { + + if err := solver.Solve(clientChallenge, "test.domain"); err != nil { t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err) } }