2015-06-13 15:00:18 +00:00
|
|
|
package acme
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
2015-09-26 20:59:16 +00:00
|
|
|
"encoding/json"
|
2016-01-08 07:04:50 +00:00
|
|
|
"net"
|
2015-09-26 20:59:16 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2015-12-05 14:53:53 +00:00
|
|
|
"strings"
|
2015-06-13 15:00:18 +00:00
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestNewClient(t *testing.T) {
|
|
|
|
keyBits := 32 // small value keeps test fast
|
|
|
|
key, err := rsa.GenerateKey(rand.Reader, keyBits)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("Could not generate test key:", err)
|
|
|
|
}
|
|
|
|
user := mockUser{
|
|
|
|
email: "test@test.com",
|
|
|
|
regres: new(RegistrationResource),
|
|
|
|
privatekey: key,
|
|
|
|
}
|
2015-09-26 20:59:16 +00:00
|
|
|
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
|
|
|
|
w.Write(data)
|
|
|
|
}))
|
|
|
|
|
2015-12-27 17:56:44 +00:00
|
|
|
client, err := NewClient(ts.URL, user, keyBits)
|
2015-10-27 23:00:42 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Could not create client: %v", err)
|
|
|
|
}
|
2015-06-13 15:00:18 +00:00
|
|
|
|
|
|
|
if client.jws == nil {
|
|
|
|
t.Fatalf("Expected client.jws to not be nil")
|
|
|
|
}
|
|
|
|
if expected, actual := key, client.jws.privKey; actual != expected {
|
|
|
|
t.Errorf("Expected jws.privKey to be %p but was %p", expected, actual)
|
|
|
|
}
|
|
|
|
|
|
|
|
if client.keyBits != keyBits {
|
|
|
|
t.Errorf("Expected keyBits to be %d but was %d", keyBits, client.keyBits)
|
|
|
|
}
|
|
|
|
|
2015-11-22 18:33:26 +00:00
|
|
|
if expected, actual := 2, len(client.solvers); actual != expected {
|
2015-11-06 06:43:42 +00:00
|
|
|
t.Fatalf("Expected %d solver(s), got %d", expected, actual)
|
2015-06-13 15:00:18 +00:00
|
|
|
}
|
2015-12-05 21:01:08 +00:00
|
|
|
}
|
|
|
|
|
2015-12-27 17:56:44 +00:00
|
|
|
func TestClientOptPort(t *testing.T) {
|
2015-12-05 21:01:08 +00:00
|
|
|
keyBits := 32 // small value keeps test fast
|
|
|
|
key, err := rsa.GenerateKey(rand.Reader, keyBits)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("Could not generate test key:", err)
|
|
|
|
}
|
|
|
|
user := mockUser{
|
|
|
|
email: "test@test.com",
|
|
|
|
regres: new(RegistrationResource),
|
|
|
|
privatekey: key,
|
|
|
|
}
|
|
|
|
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
|
|
|
|
w.Write(data)
|
|
|
|
}))
|
|
|
|
|
|
|
|
optPort := "1234"
|
2016-01-08 07:04:50 +00:00
|
|
|
optHost := ""
|
2015-12-27 17:56:44 +00:00
|
|
|
client, err := NewClient(ts.URL, user, keyBits)
|
2015-12-05 21:01:08 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Could not create client: %v", err)
|
|
|
|
}
|
2016-01-08 07:04:50 +00:00
|
|
|
client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
|
|
|
|
client.SetTLSAddress(net.JoinHostPort(optHost, optPort))
|
2015-06-13 15:00:18 +00:00
|
|
|
|
2015-11-18 21:15:37 +00:00
|
|
|
httpSolver, ok := client.solvers["http-01"].(*httpChallenge)
|
2015-06-13 15:00:18 +00:00
|
|
|
if !ok {
|
2015-11-20 19:01:06 +00:00
|
|
|
t.Fatal("Expected http-01 solver to be httpChallenge type")
|
2015-06-13 15:00:18 +00:00
|
|
|
}
|
2015-11-18 21:15:37 +00:00
|
|
|
if httpSolver.jws != client.jws {
|
2015-11-20 19:01:06 +00:00
|
|
|
t.Error("Expected http-01 to have same jws as client")
|
2015-06-13 15:00:18 +00:00
|
|
|
}
|
2016-01-08 07:04:50 +00:00
|
|
|
if httpSolver.port != optPort {
|
|
|
|
t.Errorf("Expected http-01 to have port %s but was %s", optPort, httpSolver.port)
|
|
|
|
}
|
|
|
|
if httpSolver.iface != optHost {
|
|
|
|
t.Errorf("Expected http-01 to have iface %s but was %s", optHost, httpSolver.iface)
|
2015-06-13 15:00:18 +00:00
|
|
|
}
|
2015-12-27 17:56:44 +00:00
|
|
|
|
|
|
|
httpsSolver, ok := client.solvers["tls-sni-01"].(*tlsSNIChallenge)
|
|
|
|
if !ok {
|
|
|
|
t.Fatal("Expected tls-sni-01 solver to be httpChallenge type")
|
|
|
|
}
|
|
|
|
if httpsSolver.jws != client.jws {
|
|
|
|
t.Error("Expected tls-sni-01 to have same jws as client")
|
|
|
|
}
|
2016-01-08 07:04:50 +00:00
|
|
|
if httpsSolver.port != optPort {
|
|
|
|
t.Errorf("Expected tls-sni-01 to have port %s but was %s", optPort, httpSolver.port)
|
|
|
|
}
|
|
|
|
if httpsSolver.port != optPort {
|
|
|
|
t.Errorf("Expected tls-sni-01 to have port %s but was %s", optHost, httpSolver.iface)
|
|
|
|
}
|
|
|
|
|
|
|
|
// test setting different host
|
|
|
|
optHost = "127.0.0.1"
|
|
|
|
client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
|
|
|
|
client.SetTLSAddress(net.JoinHostPort(optHost, optPort))
|
|
|
|
|
|
|
|
if httpSolver.iface != optHost {
|
|
|
|
t.Errorf("Expected http-01 to have iface %s but was %s", optHost, httpSolver.iface)
|
|
|
|
}
|
|
|
|
if httpsSolver.port != optPort {
|
|
|
|
t.Errorf("Expected tls-sni-01 to have port %s but was %s", optHost, httpSolver.iface)
|
2015-12-27 17:56:44 +00:00
|
|
|
}
|
2015-06-13 15:00:18 +00:00
|
|
|
}
|
|
|
|
|
2015-12-05 14:53:53 +00:00
|
|
|
func TestValidate(t *testing.T) {
|
|
|
|
var statuses []string
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// Minimal stub ACME server for validation.
|
|
|
|
w.Header().Add("Replay-Nonce", "12345")
|
2015-12-05 21:32:53 +00:00
|
|
|
w.Header().Add("Retry-After", "0")
|
2015-12-05 14:53:53 +00:00
|
|
|
switch r.Method {
|
|
|
|
case "HEAD":
|
|
|
|
case "POST":
|
|
|
|
st := statuses[0]
|
|
|
|
statuses = statuses[1:]
|
|
|
|
writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
|
|
|
|
|
|
|
|
case "GET":
|
|
|
|
st := statuses[0]
|
|
|
|
statuses = statuses[1:]
|
|
|
|
writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
|
|
|
|
|
|
|
|
default:
|
|
|
|
http.Error(w, r.Method, http.StatusMethodNotAllowed)
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
privKey, _ := generatePrivateKey(rsakey, 512)
|
|
|
|
j := &jws{privKey: privKey.(*rsa.PrivateKey), directoryURL: ts.URL}
|
|
|
|
|
|
|
|
tsts := []struct {
|
|
|
|
name string
|
|
|
|
statuses []string
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{"POST-unexpected", []string{"weird"}, "unexpected"},
|
|
|
|
{"POST-valid", []string{"valid"}, ""},
|
2015-12-27 18:26:47 +00:00
|
|
|
{"POST-invalid", []string{"invalid"}, "Error Detail"},
|
2015-12-05 14:53:53 +00:00
|
|
|
{"GET-unexpected", []string{"pending", "weird"}, "unexpected"},
|
|
|
|
{"GET-valid", []string{"pending", "valid"}, ""},
|
2015-12-27 18:26:47 +00:00
|
|
|
{"GET-invalid", []string{"pending", "invalid"}, "Error Detail"},
|
2015-12-05 14:53:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tst := range tsts {
|
|
|
|
statuses = tst.statuses
|
2015-12-27 18:26:47 +00:00
|
|
|
if err := validate(j, "example.com", ts.URL, challenge{Type: "http-01", Token: "token"}); err == nil && tst.want != "" {
|
2015-12-05 14:53:53 +00:00
|
|
|
t.Errorf("[%s] validate: got error %v, want something with %q", tst.name, err, tst.want)
|
|
|
|
} else if err != nil && !strings.Contains(err.Error(), tst.want) {
|
|
|
|
t.Errorf("[%s] validate: got error %v, want something with %q", tst.name, err, tst.want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeJSONResponse marshals the body as JSON and writes it to the response.
|
|
|
|
func writeJSONResponse(w http.ResponseWriter, body interface{}) {
|
|
|
|
bs, err := json.Marshal(body)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
if _, err := w.Write(bs); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// stubValidate is like validate, except it does nothing.
|
2015-12-27 18:26:47 +00:00
|
|
|
func stubValidate(j *jws, domain, uri string, chlng challenge) error {
|
2015-12-05 14:53:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-06-13 15:00:18 +00:00
|
|
|
type mockUser struct {
|
|
|
|
email string
|
|
|
|
regres *RegistrationResource
|
|
|
|
privatekey *rsa.PrivateKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u mockUser) GetEmail() string { return u.email }
|
|
|
|
func (u mockUser) GetRegistration() *RegistrationResource { return u.regres }
|
|
|
|
func (u mockUser) GetPrivateKey() *rsa.PrivateKey { return u.privatekey }
|