lego/acme/client_test.go

379 lines
9.7 KiB
Go
Raw Normal View History

package acme
import (
2016-01-27 01:01:58 +00:00
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"fmt"
"io/ioutil"
2016-01-08 07:04:50 +00:00
"net"
"net/http"
"net/http/httptest"
"testing"
"time"
2018-09-15 17:16:35 +00:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
)
func TestNewClient(t *testing.T) {
keyBits := 32 // small value keeps test fast
2016-01-27 01:01:58 +00:00
keyType := RSA2048
key, err := rsa.GenerateKey(rand.Reader, keyBits)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not generate test key")
user := mockUser{
email: "test@test.com",
regres: new(RegistrationResource),
privatekey: key,
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2018-05-30 17:53:04 +00:00
data, _ := json.Marshal(directory{
NewNonceURL: "http://test",
NewAccountURL: "http://test",
NewOrderURL: "http://test",
RevokeCertURL: "http://test",
KeyChangeURL: "http://test",
})
_, err = w.Write(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}))
2016-01-27 01:01:58 +00:00
client, err := NewClient(ts.URL, user, keyType)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not create client")
2018-09-15 17:16:35 +00:00
require.NotNil(t, client.jws, "client.jws")
assert.Equal(t, key, client.jws.privKey, "client.jws.privKey")
assert.Equal(t, keyType, client.keyType, "client.keyType")
assert.Len(t, client.solvers, 2, "solvers")
}
2015-12-27 17:56:44 +00:00
func TestClientOptPort(t *testing.T) {
keyBits := 32 // small value keeps test fast
key, err := rsa.GenerateKey(rand.Reader, keyBits)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not generate test key")
user := mockUser{
email: "test@test.com",
regres: new(RegistrationResource),
privatekey: key,
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2018-05-30 17:53:04 +00:00
data, _ := json.Marshal(directory{
NewNonceURL: "http://test",
NewAccountURL: "http://test",
NewOrderURL: "http://test",
RevokeCertURL: "http://test",
KeyChangeURL: "http://test",
})
_, err = w.Write(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}))
optPort := "1234"
2016-01-08 07:04:50 +00:00
optHost := ""
2018-09-15 17:16:35 +00:00
2016-01-27 01:01:58 +00:00
client, err := NewClient(ts.URL, user, RSA2048)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not create client")
err = client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
require.NoError(t, err)
2018-09-15 17:16:35 +00:00
require.IsType(t, &httpChallenge{}, client.solvers[HTTP01])
httpSolver := client.solvers[HTTP01].(*httpChallenge)
assert.Equal(t, httpSolver.jws, client.jws, "Expected http-01 to have same jws as client")
httpProviderServer := httpSolver.provider.(*HTTPProviderServer)
assert.Equal(t, optPort, httpProviderServer.port, "port")
assert.Equal(t, optHost, httpProviderServer.iface, "iface")
2015-12-27 17:56:44 +00:00
2016-01-08 07:04:50 +00:00
// test setting different host
optHost = "127.0.0.1"
err = client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
require.NoError(t, err)
2016-01-08 07:04:50 +00:00
2018-09-15 17:16:35 +00:00
assert.Equal(t, optHost, httpSolver.provider.(*HTTPProviderServer).iface, "iface")
}
func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(250 * time.Millisecond)
w.Header().Add("Replay-Nonce", "12345")
w.Header().Add("Retry-After", "0")
2018-05-30 17:53:04 +00:00
writeJSONResponse(w, &challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"})
}))
defer ts.Close()
2018-09-15 17:16:35 +00:00
privKey, err := rsa.GenerateKey(rand.Reader, 512)
require.NoError(t, err)
2018-05-30 17:53:04 +00:00
j := &jws{privKey: privKey, getNonceURL: ts.URL}
ch := make(chan bool)
resultCh := make(chan bool)
go func() {
_, errN := j.Nonce()
if errN != nil {
t.Log(errN)
}
ch <- true
}()
go func() {
_, errN := j.Nonce()
if errN != nil {
t.Log(errN)
}
ch <- true
}()
go func() {
<-ch
<-ch
resultCh <- true
}()
select {
case <-resultCh:
case <-time.After(400 * time.Millisecond):
t.Fatal("JWS is probably holding a lock while making HTTP request")
}
}
func TestValidate(t *testing.T) {
var statuses []string
2018-09-15 17:16:35 +00:00
privKey, err := rsa.GenerateKey(rand.Reader, 512)
require.NoError(t, err)
// validateNoBody reads the http.Request POST body, parses the JWS and
// validates it to read the body. If there is an error doing this, or if the
// JWS body is not the empty JSON payload "{}" an error is returned. We use
// this to verify challenge POSTs to the ts below do not send a JWS body.
validateNoBody := func(r *http.Request) error {
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
jws, err := jose.ParseSigned(string(reqBody))
if err != nil {
return err
}
body, err := jws.Verify(&jose.JSONWebKey{
Key: privKey.Public(),
Algorithm: "RSA",
})
if err != nil {
return err
}
if bodyStr := string(body); bodyStr != "{}" {
return fmt.Errorf(`expected JWS POST body "{}", got %q`, bodyStr)
}
return nil
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Minimal stub ACME server for validation.
w.Header().Add("Replay-Nonce", "12345")
w.Header().Add("Retry-After", "0")
2018-09-15 17:16:35 +00:00
switch r.Method {
case http.MethodHead:
case http.MethodPost:
if err := validateNoBody(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
st := statuses[0]
statuses = statuses[1:]
2018-05-30 17:53:04 +00:00
writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"})
case http.MethodGet:
st := statuses[0]
statuses = statuses[1:]
2018-05-30 17:53:04 +00:00
writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"})
default:
http.Error(w, r.Method, http.StatusMethodNotAllowed)
}
}))
defer ts.Close()
2018-05-30 17:53:04 +00:00
j := &jws{privKey: privKey, getNonceURL: ts.URL}
2018-09-15 17:16:35 +00:00
testCases := []struct {
name string
statuses []string
want string
}{
2018-09-15 17:16:35 +00:00
{
name: "POST-unexpected",
statuses: []string{"weird"},
want: "unexpected",
},
{
name: "POST-valid",
statuses: []string{"valid"},
},
{
name: "POST-invalid",
statuses: []string{"invalid"},
want: "Error",
},
{
name: "GET-unexpected",
statuses: []string{"pending", "weird"},
want: "unexpected",
},
{
name: "GET-valid",
statuses: []string{"pending", "valid"},
},
{
name: "GET-invalid",
statuses: []string{"pending", "invalid"},
want: "Error",
},
}
2018-09-15 17:16:35 +00:00
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
statuses = test.statuses
err := validate(j, "example.com", ts.URL, challenge{Type: "http-01", Token: "token"})
if test.want == "" {
require.NoError(t, err)
2018-09-15 17:16:35 +00:00
} else {
assert.Error(t, err)
assert.Contains(t, err.Error(), test.want)
}
})
}
}
func TestGetChallenges(t *testing.T) {
var ts *httptest.Server
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet, http.MethodHead:
w.Header().Add("Replay-Nonce", "12345")
w.Header().Add("Retry-After", "0")
2018-05-30 17:53:04 +00:00
writeJSONResponse(w, directory{
NewNonceURL: ts.URL,
NewAccountURL: ts.URL,
NewOrderURL: ts.URL,
RevokeCertURL: ts.URL,
KeyChangeURL: ts.URL,
})
case http.MethodPost:
2018-05-30 17:53:04 +00:00
writeJSONResponse(w, orderMessage{})
}
}))
defer ts.Close()
keyBits := 512 // small value keeps test fast
keyType := RSA2048
2018-09-15 17:16:35 +00:00
key, err := rsa.GenerateKey(rand.Reader, keyBits)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not generate test key")
user := mockUser{
email: "test@test.com",
2018-05-30 17:53:04 +00:00
regres: &RegistrationResource{URI: ts.URL},
privatekey: key,
}
client, err := NewClient(ts.URL, user, keyType)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not create client")
2018-05-30 17:53:04 +00:00
_, err = client.createOrderForIdentifiers([]string{"example.com"})
require.NoError(t, err)
}
2018-05-30 17:53:04 +00:00
func TestResolveAccountByKey(t *testing.T) {
keyBits := 512
keyType := RSA2048
2018-09-15 17:16:35 +00:00
2018-05-30 17:53:04 +00:00
key, err := rsa.GenerateKey(rand.Reader, keyBits)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not generate test key")
2018-05-30 17:53:04 +00:00
user := mockUser{
email: "test@test.com",
regres: new(RegistrationResource),
privatekey: key,
}
var ts *httptest.Server
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.RequestURI {
case "/directory":
writeJSONResponse(w, directory{
NewNonceURL: ts.URL + "/nonce",
NewAccountURL: ts.URL + "/account",
NewOrderURL: ts.URL + "/newOrder",
RevokeCertURL: ts.URL + "/revokeCert",
KeyChangeURL: ts.URL + "/keyChange",
})
case "/nonce":
w.Header().Add("Replay-Nonce", "12345")
w.Header().Add("Retry-After", "0")
case "/account":
w.Header().Set("Location", ts.URL+"/account_recovery")
case "/account_recovery":
writeJSONResponse(w, accountMessage{
Status: "valid",
})
}
}))
client, err := NewClient(ts.URL+"/directory", user, keyType)
2018-09-15 17:16:35 +00:00
require.NoError(t, err, "Could not create client")
2018-05-30 17:53:04 +00:00
2018-09-15 17:16:35 +00:00
res, err := client.ResolveAccountByKey()
require.NoError(t, err, "Unexpected error resolving account by key")
assert.Equal(t, "valid", res.Body.Status, "Unexpected account status")
2018-05-30 17:53:04 +00:00
}
// 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.
2018-09-15 17:16:35 +00:00
func stubValidate(_ *jws, _, _ string, _ challenge) error {
return nil
}
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 }
2016-01-27 01:01:58 +00:00
func (u mockUser) GetPrivateKey() crypto.PrivateKey { return u.privatekey }