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) { challenge := &simpleHTTPChallenge{} // determine public ip resp, err := http.Get("https://icanhazip.com/") if err != nil { t.Errorf("Could not get public IP -> %v", err) } ip, err := ioutil.ReadAll(resp.Body) if err != nil { t.Errorf("Could not get public IP -> %v", err) } ipStr := string(ip) if expected, actual := false, challenge.CanSolve("google.com"); expected != actual { t.Errorf("Expected CanSolve to return %t for domain 'google.com' but was %t", expected, actual) } localResolv := strings.Replace(ipStr, "\n", "", -1) + ".xip.io" if expected, actual := true, challenge.CanSolve(localResolv); expected != actual { 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) } }