diff --git a/acme/simple_http_challenge.go b/acme/simple_http_challenge.go index a74bf744..16ad35eb 100644 --- a/acme/simple_http_challenge.go +++ b/acme/simple_http_challenge.go @@ -97,8 +97,10 @@ loop: case "pending": break case "invalid": + listener.Close() return errors.New("The server could not validate our request.") default: + listener.Close() return errors.New("The server returned an unexpected state.") } diff --git a/acme/simple_http_challenge_test.go b/acme/simple_http_challenge_test.go index 5d2e8332..36a76536 100644 --- a/acme/simple_http_challenge_test.go +++ b/acme/simple_http_challenge_test.go @@ -1,10 +1,15 @@ package acme import ( + "crypto/tls" + "encoding/json" "io/ioutil" "net/http" + "net/http/httptest" "strings" "testing" + + "github.com/square/go-jose" ) func TestSimpleHTTPCanSolve(t *testing.T) { @@ -31,3 +36,89 @@ func TestSimpleHTTPCanSolve(t *testing.T) { t.Errorf("Expected CanSolve to return %t for domain 'localhost' but was %t", expected, actual) } } + +func TestSimpleHTTP(t *testing.T) { + privKey, err := generatePrivateKey(512) + if err != nil { + t.Errorf("Could not generate public key -> %v", err) + } + jws := &jws{privKey: privKey} + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + + solver := &simpleHTTPChallenge{jws: jws} + clientChallenge := challenge{Type: "simpleHttps", Status: "pending", URI: ts.URL, Token: "123456789"} + + // 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.") + } + + // Validate error on unexpected state + solver.optPort = "8080" + 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) { + failed := challenge{Type: "simpleHttps", Status: "invalid", URI: ts.URL, Token: "123456789"} + jsonBytes, _ := json.Marshal(&failed) + w.Write(jsonBytes) + }) + if err = solver.Solve(clientChallenge, "test.domain"); err == nil { + t.Error("FAILED: Expected Solve to return an error but the error was nil.") + } + + // Validate no error on valid response + ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + valid := challenge{Type: "simpleHttps", Status: "valid", URI: ts.URL, Token: "123456789"} + jsonBytes, _ := json.Marshal(&valid) + w.Write(jsonBytes) + }) + if err = solver.Solve(clientChallenge, "test.domain"); err != nil { + t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err) + } + + // Validate server on port 8080 which responds appropriately + ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var request challenge + + clientJws, _ := ioutil.ReadAll(r.Body) + j, err := jose.ParseSigned(string(clientJws)) + if err != nil { + t.Errorf("Client sent invalid JWS to the server. -> %v", err) + } + output, err := j.Verify(&privKey.PublicKey) + if err != nil { + t.Errorf("Unable to verify client data -> %v", err) + } + json.Unmarshal(output, &request) + + transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + client := &http.Client{Transport: transport} + + reqURL := "https://localhost:8080/.well-known/acme-challenge/" + request.Path + t.Logf("Request URL is: %s", reqURL) + req, _ := http.NewRequest("GET", reqURL, nil) + req.Host = "test.domain" + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected the solver to listen on port 8080 -> %v", err) + } + + body, _ := ioutil.ReadAll(resp.Body) + bodyStr := string(body) + if bodyStr != "123456789" { + t.Errorf("Expected the solver to return the token %s but instead returned '%s'", "123456789", bodyStr) + } + + valid := challenge{Type: "simpleHttps", Status: "valid", URI: ts.URL, Token: "123456789"} + jsonBytes, _ := json.Marshal(&valid) + w.Write(jsonBytes) + }) + if err = solver.Solve(clientChallenge, "test.domain"); err != nil { + t.Errorf("VALID: Expected Solve to return no error but the error was -> %v", err) + } +}