...

Source file src/golang.org/x/crypto/acme/rfc8555_test.go

Documentation: golang.org/x/crypto/acme

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package acme
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto/hmac"
    11  	"crypto/rand"
    12  	"crypto/sha256"
    13  	"crypto/x509"
    14  	"crypto/x509/pkix"
    15  	"encoding/base64"
    16  	"encoding/json"
    17  	"encoding/pem"
    18  	"errors"
    19  	"fmt"
    20  	"io"
    21  	"math/big"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"reflect"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  )
    30  
    31  // While contents of this file is pertinent only to RFC8555,
    32  // it is complementary to the tests in the other _test.go files
    33  // many of which are valid for both pre- and RFC8555.
    34  // This will make it easier to clean up the tests once non-RFC compliant
    35  // code is removed.
    36  
    37  func TestRFC_Discover(t *testing.T) {
    38  	const (
    39  		nonce       = "https://example.com/acme/new-nonce"
    40  		reg         = "https://example.com/acme/new-acct"
    41  		order       = "https://example.com/acme/new-order"
    42  		authz       = "https://example.com/acme/new-authz"
    43  		revoke      = "https://example.com/acme/revoke-cert"
    44  		keychange   = "https://example.com/acme/key-change"
    45  		metaTerms   = "https://example.com/acme/terms/2017-5-30"
    46  		metaWebsite = "https://www.example.com/"
    47  		metaCAA     = "example.com"
    48  	)
    49  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    50  		w.Header().Set("Content-Type", "application/json")
    51  		fmt.Fprintf(w, `{
    52  			"newNonce": %q,
    53  			"newAccount": %q,
    54  			"newOrder": %q,
    55  			"newAuthz": %q,
    56  			"revokeCert": %q,
    57  			"keyChange": %q,
    58  			"meta": {
    59  				"termsOfService": %q,
    60  				"website": %q,
    61  				"caaIdentities": [%q],
    62  				"externalAccountRequired": true
    63  			}
    64  		}`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA)
    65  	}))
    66  	defer ts.Close()
    67  	c := &Client{DirectoryURL: ts.URL}
    68  	dir, err := c.Discover(context.Background())
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	if dir.NonceURL != nonce {
    73  		t.Errorf("dir.NonceURL = %q; want %q", dir.NonceURL, nonce)
    74  	}
    75  	if dir.RegURL != reg {
    76  		t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
    77  	}
    78  	if dir.OrderURL != order {
    79  		t.Errorf("dir.OrderURL = %q; want %q", dir.OrderURL, order)
    80  	}
    81  	if dir.AuthzURL != authz {
    82  		t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
    83  	}
    84  	if dir.RevokeURL != revoke {
    85  		t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
    86  	}
    87  	if dir.KeyChangeURL != keychange {
    88  		t.Errorf("dir.KeyChangeURL = %q; want %q", dir.KeyChangeURL, keychange)
    89  	}
    90  	if dir.Terms != metaTerms {
    91  		t.Errorf("dir.Terms = %q; want %q", dir.Terms, metaTerms)
    92  	}
    93  	if dir.Website != metaWebsite {
    94  		t.Errorf("dir.Website = %q; want %q", dir.Website, metaWebsite)
    95  	}
    96  	if len(dir.CAA) == 0 || dir.CAA[0] != metaCAA {
    97  		t.Errorf("dir.CAA = %q; want [%q]", dir.CAA, metaCAA)
    98  	}
    99  	if !dir.ExternalAccountRequired {
   100  		t.Error("dir.Meta.ExternalAccountRequired is false")
   101  	}
   102  }
   103  
   104  func TestRFC_popNonce(t *testing.T) {
   105  	var count int
   106  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   107  		// The Client uses only Directory.NonceURL when specified.
   108  		// Expect no other URL paths.
   109  		if r.URL.Path != "/new-nonce" {
   110  			t.Errorf("r.URL.Path = %q; want /new-nonce", r.URL.Path)
   111  		}
   112  		if count > 0 {
   113  			w.WriteHeader(http.StatusTooManyRequests)
   114  			return
   115  		}
   116  		count++
   117  		w.Header().Set("Replay-Nonce", "second")
   118  	}))
   119  	cl := &Client{
   120  		DirectoryURL: ts.URL,
   121  		dir:          &Directory{NonceURL: ts.URL + "/new-nonce"},
   122  	}
   123  	cl.addNonce(http.Header{"Replay-Nonce": {"first"}})
   124  
   125  	for i, nonce := range []string{"first", "second"} {
   126  		v, err := cl.popNonce(context.Background(), "")
   127  		if err != nil {
   128  			t.Errorf("%d: cl.popNonce: %v", i, err)
   129  		}
   130  		if v != nonce {
   131  			t.Errorf("%d: cl.popNonce = %q; want %q", i, v, nonce)
   132  		}
   133  	}
   134  	// No more nonces and server replies with an error past first nonce fetch.
   135  	// Expected to fail.
   136  	if _, err := cl.popNonce(context.Background(), ""); err == nil {
   137  		t.Error("last cl.popNonce returned nil error")
   138  	}
   139  }
   140  
   141  func TestRFC_postKID(t *testing.T) {
   142  	var ts *httptest.Server
   143  	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   144  		switch r.URL.Path {
   145  		case "/new-nonce":
   146  			w.Header().Set("Replay-Nonce", "nonce")
   147  		case "/new-account":
   148  			w.Header().Set("Location", "/account-1")
   149  			w.Write([]byte(`{"status":"valid"}`))
   150  		case "/post":
   151  			b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
   152  			head, err := decodeJWSHead(bytes.NewReader(b))
   153  			if err != nil {
   154  				t.Errorf("decodeJWSHead: %v", err)
   155  				return
   156  			}
   157  			if head.KID != "/account-1" {
   158  				t.Errorf("head.KID = %q; want /account-1", head.KID)
   159  			}
   160  			if len(head.JWK) != 0 {
   161  				t.Errorf("head.JWK = %q; want zero map", head.JWK)
   162  			}
   163  			if v := ts.URL + "/post"; head.URL != v {
   164  				t.Errorf("head.URL = %q; want %q", head.URL, v)
   165  			}
   166  
   167  			var payload struct{ Msg string }
   168  			decodeJWSRequest(t, &payload, bytes.NewReader(b))
   169  			if payload.Msg != "ping" {
   170  				t.Errorf("payload.Msg = %q; want ping", payload.Msg)
   171  			}
   172  			w.Write([]byte("pong"))
   173  		default:
   174  			t.Errorf("unhandled %s %s", r.Method, r.URL)
   175  			w.WriteHeader(http.StatusBadRequest)
   176  		}
   177  	}))
   178  	defer ts.Close()
   179  
   180  	ctx := context.Background()
   181  	cl := &Client{
   182  		Key:          testKey,
   183  		DirectoryURL: ts.URL,
   184  		dir: &Directory{
   185  			NonceURL: ts.URL + "/new-nonce",
   186  			RegURL:   ts.URL + "/new-account",
   187  			OrderURL: "/force-rfc-mode",
   188  		},
   189  	}
   190  	req := json.RawMessage(`{"msg":"ping"}`)
   191  	res, err := cl.post(ctx, nil /* use kid */, ts.URL+"/post", req, wantStatus(http.StatusOK))
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  	defer res.Body.Close()
   196  	b, _ := io.ReadAll(res.Body) // don't care about err - just checking b
   197  	if string(b) != "pong" {
   198  		t.Errorf("res.Body = %q; want pong", b)
   199  	}
   200  }
   201  
   202  // acmeServer simulates a subset of RFC 8555 compliant CA.
   203  //
   204  // TODO: We also have x/crypto/acme/autocert/acmetest and startACMEServerStub in autocert_test.go.
   205  // It feels like this acmeServer is a sweet spot between usefulness and added complexity.
   206  // Also, acmetest and startACMEServerStub were both written for draft-02, no RFC support.
   207  // The goal is to consolidate all into one ACME test server.
   208  type acmeServer struct {
   209  	ts      *httptest.Server
   210  	handler map[string]http.HandlerFunc // keyed by r.URL.Path
   211  
   212  	mu     sync.Mutex
   213  	nnonce int
   214  }
   215  
   216  func newACMEServer() *acmeServer {
   217  	return &acmeServer{handler: make(map[string]http.HandlerFunc)}
   218  }
   219  
   220  func (s *acmeServer) handle(path string, f func(http.ResponseWriter, *http.Request)) {
   221  	s.handler[path] = http.HandlerFunc(f)
   222  }
   223  
   224  func (s *acmeServer) start() {
   225  	s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   226  		w.Header().Set("Content-Type", "application/json")
   227  
   228  		// Directory request.
   229  		if r.URL.Path == "/" {
   230  			fmt.Fprintf(w, `{
   231  				"newNonce": %q,
   232  				"newAccount": %q,
   233  				"newOrder": %q,
   234  				"newAuthz": %q,
   235  				"revokeCert": %q,
   236  				"keyChange": %q,
   237  				"meta": {"termsOfService": %q}
   238  				}`,
   239  				s.url("/acme/new-nonce"),
   240  				s.url("/acme/new-account"),
   241  				s.url("/acme/new-order"),
   242  				s.url("/acme/new-authz"),
   243  				s.url("/acme/revoke-cert"),
   244  				s.url("/acme/key-change"),
   245  				s.url("/terms"),
   246  			)
   247  			return
   248  		}
   249  
   250  		// All other responses contain a nonce value unconditionally.
   251  		w.Header().Set("Replay-Nonce", s.nonce())
   252  		if r.URL.Path == "/acme/new-nonce" {
   253  			return
   254  		}
   255  
   256  		h := s.handler[r.URL.Path]
   257  		if h == nil {
   258  			w.WriteHeader(http.StatusBadRequest)
   259  			fmt.Fprintf(w, "Unhandled %s", r.URL.Path)
   260  			return
   261  		}
   262  		h.ServeHTTP(w, r)
   263  	}))
   264  }
   265  
   266  func (s *acmeServer) close() {
   267  	s.ts.Close()
   268  }
   269  
   270  func (s *acmeServer) url(path string) string {
   271  	return s.ts.URL + path
   272  }
   273  
   274  func (s *acmeServer) nonce() string {
   275  	s.mu.Lock()
   276  	defer s.mu.Unlock()
   277  	s.nnonce++
   278  	return fmt.Sprintf("nonce%d", s.nnonce)
   279  }
   280  
   281  func (s *acmeServer) error(w http.ResponseWriter, e *wireError) {
   282  	w.WriteHeader(e.Status)
   283  	json.NewEncoder(w).Encode(e)
   284  }
   285  
   286  func TestRFC_Register(t *testing.T) {
   287  	const email = "mailto:user@example.org"
   288  
   289  	s := newACMEServer()
   290  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   291  		w.Header().Set("Location", s.url("/accounts/1"))
   292  		w.WriteHeader(http.StatusCreated) // 201 means new account created
   293  		fmt.Fprintf(w, `{
   294  			"status": "valid",
   295  			"contact": [%q],
   296  			"orders": %q
   297  		}`, email, s.url("/accounts/1/orders"))
   298  
   299  		b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
   300  		head, err := decodeJWSHead(bytes.NewReader(b))
   301  		if err != nil {
   302  			t.Errorf("decodeJWSHead: %v", err)
   303  			return
   304  		}
   305  		if len(head.JWK) == 0 {
   306  			t.Error("head.JWK is empty")
   307  		}
   308  
   309  		var req struct{ Contact []string }
   310  		decodeJWSRequest(t, &req, bytes.NewReader(b))
   311  		if len(req.Contact) != 1 || req.Contact[0] != email {
   312  			t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
   313  		}
   314  	})
   315  	s.start()
   316  	defer s.close()
   317  
   318  	ctx := context.Background()
   319  	cl := &Client{
   320  		Key:          testKeyEC,
   321  		DirectoryURL: s.url("/"),
   322  	}
   323  
   324  	var didPrompt bool
   325  	a := &Account{Contact: []string{email}}
   326  	acct, err := cl.Register(ctx, a, func(tos string) bool {
   327  		didPrompt = true
   328  		terms := s.url("/terms")
   329  		if tos != terms {
   330  			t.Errorf("tos = %q; want %q", tos, terms)
   331  		}
   332  		return true
   333  	})
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	okAccount := &Account{
   338  		URI:       s.url("/accounts/1"),
   339  		Status:    StatusValid,
   340  		Contact:   []string{email},
   341  		OrdersURL: s.url("/accounts/1/orders"),
   342  	}
   343  	if !reflect.DeepEqual(acct, okAccount) {
   344  		t.Errorf("acct = %+v; want %+v", acct, okAccount)
   345  	}
   346  	if !didPrompt {
   347  		t.Error("tos prompt wasn't called")
   348  	}
   349  	if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) {
   350  		t.Errorf("account kid = %q; want %q", v, okAccount.URI)
   351  	}
   352  }
   353  
   354  func TestRFC_RegisterExternalAccountBinding(t *testing.T) {
   355  	eab := &ExternalAccountBinding{
   356  		KID: "kid-1",
   357  		Key: []byte("secret"),
   358  	}
   359  
   360  	type protected struct {
   361  		Algorithm string `json:"alg"`
   362  		KID       string `json:"kid"`
   363  		URL       string `json:"url"`
   364  	}
   365  	const email = "mailto:user@example.org"
   366  
   367  	s := newACMEServer()
   368  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   369  		w.Header().Set("Location", s.url("/accounts/1"))
   370  		if r.Method != "POST" {
   371  			t.Errorf("r.Method = %q; want POST", r.Method)
   372  		}
   373  
   374  		var j struct {
   375  			Protected              string
   376  			Contact                []string
   377  			TermsOfServiceAgreed   bool
   378  			ExternalaccountBinding struct {
   379  				Protected string
   380  				Payload   string
   381  				Signature string
   382  			}
   383  		}
   384  		decodeJWSRequest(t, &j, r.Body)
   385  		protData, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Protected)
   386  		if err != nil {
   387  			t.Fatal(err)
   388  		}
   389  
   390  		var prot protected
   391  		err = json.Unmarshal(protData, &prot)
   392  		if err != nil {
   393  			t.Fatal(err)
   394  		}
   395  
   396  		if !reflect.DeepEqual(j.Contact, []string{email}) {
   397  			t.Errorf("j.Contact = %v; want %v", j.Contact, []string{email})
   398  		}
   399  		if !j.TermsOfServiceAgreed {
   400  			t.Error("j.TermsOfServiceAgreed = false; want true")
   401  		}
   402  
   403  		// Ensure same KID.
   404  		if prot.KID != eab.KID {
   405  			t.Errorf("j.ExternalAccountBinding.KID = %s; want %s", prot.KID, eab.KID)
   406  		}
   407  		// Ensure expected Algorithm.
   408  		if prot.Algorithm != "HS256" {
   409  			t.Errorf("j.ExternalAccountBinding.Alg = %s; want %s",
   410  				prot.Algorithm, "HS256")
   411  		}
   412  
   413  		// Ensure same URL as outer JWS.
   414  		url := fmt.Sprintf("http://%s/acme/new-account", r.Host)
   415  		if prot.URL != url {
   416  			t.Errorf("j.ExternalAccountBinding.URL = %s; want %s",
   417  				prot.URL, url)
   418  		}
   419  
   420  		// Ensure payload is base64URL encoded string of JWK in outer JWS
   421  		jwk, err := jwkEncode(testKeyEC.Public())
   422  		if err != nil {
   423  			t.Fatal(err)
   424  		}
   425  		decodedPayload, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Payload)
   426  		if err != nil {
   427  			t.Fatal(err)
   428  		}
   429  		if jwk != string(decodedPayload) {
   430  			t.Errorf("j.ExternalAccountBinding.Payload = %s; want %s", decodedPayload, jwk)
   431  		}
   432  
   433  		// Check signature on inner external account binding JWS
   434  		hmac := hmac.New(sha256.New, []byte("secret"))
   435  		_, err = hmac.Write([]byte(j.ExternalaccountBinding.Protected + "." + j.ExternalaccountBinding.Payload))
   436  		if err != nil {
   437  			t.Fatal(err)
   438  		}
   439  		mac := hmac.Sum(nil)
   440  		encodedMAC := base64.RawURLEncoding.EncodeToString(mac)
   441  
   442  		if !bytes.Equal([]byte(encodedMAC), []byte(j.ExternalaccountBinding.Signature)) {
   443  			t.Errorf("j.ExternalAccountBinding.Signature = %v; want %v",
   444  				[]byte(j.ExternalaccountBinding.Signature), encodedMAC)
   445  		}
   446  
   447  		w.Header().Set("Location", s.url("/accounts/1"))
   448  		w.WriteHeader(http.StatusCreated)
   449  		b, _ := json.Marshal([]string{email})
   450  		fmt.Fprintf(w, `{"status":"valid","orders":"%s","contact":%s}`, s.url("/accounts/1/orders"), b)
   451  	})
   452  	s.start()
   453  	defer s.close()
   454  
   455  	ctx := context.Background()
   456  	cl := &Client{
   457  		Key:          testKeyEC,
   458  		DirectoryURL: s.url("/"),
   459  	}
   460  
   461  	var didPrompt bool
   462  	a := &Account{Contact: []string{email}, ExternalAccountBinding: eab}
   463  	acct, err := cl.Register(ctx, a, func(tos string) bool {
   464  		didPrompt = true
   465  		terms := s.url("/terms")
   466  		if tos != terms {
   467  			t.Errorf("tos = %q; want %q", tos, terms)
   468  		}
   469  		return true
   470  	})
   471  	if err != nil {
   472  		t.Fatal(err)
   473  	}
   474  	okAccount := &Account{
   475  		URI:       s.url("/accounts/1"),
   476  		Status:    StatusValid,
   477  		Contact:   []string{email},
   478  		OrdersURL: s.url("/accounts/1/orders"),
   479  	}
   480  	if !reflect.DeepEqual(acct, okAccount) {
   481  		t.Errorf("acct = %+v; want %+v", acct, okAccount)
   482  	}
   483  	if !didPrompt {
   484  		t.Error("tos prompt wasn't called")
   485  	}
   486  	if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) {
   487  		t.Errorf("account kid = %q; want %q", v, okAccount.URI)
   488  	}
   489  }
   490  
   491  func TestRFC_RegisterExisting(t *testing.T) {
   492  	s := newACMEServer()
   493  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   494  		w.Header().Set("Location", s.url("/accounts/1"))
   495  		w.WriteHeader(http.StatusOK) // 200 means account already exists
   496  		w.Write([]byte(`{"status": "valid"}`))
   497  	})
   498  	s.start()
   499  	defer s.close()
   500  
   501  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   502  	_, err := cl.Register(context.Background(), &Account{}, AcceptTOS)
   503  	if err != ErrAccountAlreadyExists {
   504  		t.Errorf("err = %v; want %v", err, ErrAccountAlreadyExists)
   505  	}
   506  	kid := KeyID(s.url("/accounts/1"))
   507  	if v := cl.accountKID(context.Background()); v != kid {
   508  		t.Errorf("account kid = %q; want %q", v, kid)
   509  	}
   510  }
   511  
   512  func TestRFC_UpdateReg(t *testing.T) {
   513  	const email = "mailto:user@example.org"
   514  
   515  	s := newACMEServer()
   516  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   517  		w.Header().Set("Location", s.url("/accounts/1"))
   518  		w.WriteHeader(http.StatusOK)
   519  		w.Write([]byte(`{"status": "valid"}`))
   520  	})
   521  	var didUpdate bool
   522  	s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
   523  		didUpdate = true
   524  		w.Header().Set("Location", s.url("/accounts/1"))
   525  		w.WriteHeader(http.StatusOK)
   526  		w.Write([]byte(`{"status": "valid"}`))
   527  
   528  		b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
   529  		head, err := decodeJWSHead(bytes.NewReader(b))
   530  		if err != nil {
   531  			t.Errorf("decodeJWSHead: %v", err)
   532  			return
   533  		}
   534  		if len(head.JWK) != 0 {
   535  			t.Error("head.JWK is non-zero")
   536  		}
   537  		kid := s.url("/accounts/1")
   538  		if head.KID != kid {
   539  			t.Errorf("head.KID = %q; want %q", head.KID, kid)
   540  		}
   541  
   542  		var req struct{ Contact []string }
   543  		decodeJWSRequest(t, &req, bytes.NewReader(b))
   544  		if len(req.Contact) != 1 || req.Contact[0] != email {
   545  			t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
   546  		}
   547  	})
   548  	s.start()
   549  	defer s.close()
   550  
   551  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   552  	_, err := cl.UpdateReg(context.Background(), &Account{Contact: []string{email}})
   553  	if err != nil {
   554  		t.Error(err)
   555  	}
   556  	if !didUpdate {
   557  		t.Error("UpdateReg didn't update the account")
   558  	}
   559  }
   560  
   561  func TestRFC_GetReg(t *testing.T) {
   562  	s := newACMEServer()
   563  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   564  		w.Header().Set("Location", s.url("/accounts/1"))
   565  		w.WriteHeader(http.StatusOK)
   566  		w.Write([]byte(`{"status": "valid"}`))
   567  
   568  		head, err := decodeJWSHead(r.Body)
   569  		if err != nil {
   570  			t.Errorf("decodeJWSHead: %v", err)
   571  			return
   572  		}
   573  		if len(head.JWK) == 0 {
   574  			t.Error("head.JWK is empty")
   575  		}
   576  	})
   577  	s.start()
   578  	defer s.close()
   579  
   580  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   581  	acct, err := cl.GetReg(context.Background(), "")
   582  	if err != nil {
   583  		t.Fatal(err)
   584  	}
   585  	okAccount := &Account{
   586  		URI:    s.url("/accounts/1"),
   587  		Status: StatusValid,
   588  	}
   589  	if !reflect.DeepEqual(acct, okAccount) {
   590  		t.Errorf("acct = %+v; want %+v", acct, okAccount)
   591  	}
   592  }
   593  
   594  func TestRFC_GetRegNoAccount(t *testing.T) {
   595  	s := newACMEServer()
   596  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   597  		s.error(w, &wireError{
   598  			Status: http.StatusBadRequest,
   599  			Type:   "urn:ietf:params:acme:error:accountDoesNotExist",
   600  		})
   601  	})
   602  	s.start()
   603  	defer s.close()
   604  
   605  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   606  	if _, err := cl.GetReg(context.Background(), ""); err != ErrNoAccount {
   607  		t.Errorf("err = %v; want %v", err, ErrNoAccount)
   608  	}
   609  }
   610  
   611  func TestRFC_GetRegOtherError(t *testing.T) {
   612  	s := newACMEServer()
   613  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   614  		w.WriteHeader(http.StatusBadRequest)
   615  	})
   616  	s.start()
   617  	defer s.close()
   618  
   619  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   620  	if _, err := cl.GetReg(context.Background(), ""); err == nil || err == ErrNoAccount {
   621  		t.Errorf("GetReg: %v; want any other non-nil err", err)
   622  	}
   623  }
   624  
   625  func TestRFC_AccountKeyRollover(t *testing.T) {
   626  	s := newACMEServer()
   627  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   628  		w.Header().Set("Location", s.url("/accounts/1"))
   629  		w.WriteHeader(http.StatusOK)
   630  		w.Write([]byte(`{"status": "valid"}`))
   631  	})
   632  	s.handle("/acme/key-change", func(w http.ResponseWriter, r *http.Request) {
   633  		w.WriteHeader(http.StatusOK)
   634  	})
   635  	s.start()
   636  	defer s.close()
   637  
   638  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   639  	if err := cl.AccountKeyRollover(context.Background(), testKeyEC384); err != nil {
   640  		t.Errorf("AccountKeyRollover: %v, wanted no error", err)
   641  	} else if cl.Key != testKeyEC384 {
   642  		t.Error("AccountKeyRollover did not rotate the client key")
   643  	}
   644  }
   645  
   646  func TestRFC_DeactivateReg(t *testing.T) {
   647  	const email = "mailto:user@example.org"
   648  	curStatus := StatusValid
   649  
   650  	type account struct {
   651  		Status    string   `json:"status"`
   652  		Contact   []string `json:"contact"`
   653  		AcceptTOS bool     `json:"termsOfServiceAgreed"`
   654  		Orders    string   `json:"orders"`
   655  	}
   656  
   657  	s := newACMEServer()
   658  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   659  		w.Header().Set("Location", s.url("/accounts/1"))
   660  		w.WriteHeader(http.StatusOK) // 200 means existing account
   661  		json.NewEncoder(w).Encode(account{
   662  			Status:    curStatus,
   663  			Contact:   []string{email},
   664  			AcceptTOS: true,
   665  			Orders:    s.url("/accounts/1/orders"),
   666  		})
   667  
   668  		b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
   669  		head, err := decodeJWSHead(bytes.NewReader(b))
   670  		if err != nil {
   671  			t.Errorf("decodeJWSHead: %v", err)
   672  			return
   673  		}
   674  		if len(head.JWK) == 0 {
   675  			t.Error("head.JWK is empty")
   676  		}
   677  
   678  		var req struct {
   679  			Status       string   `json:"status"`
   680  			Contact      []string `json:"contact"`
   681  			AcceptTOS    bool     `json:"termsOfServiceAgreed"`
   682  			OnlyExisting bool     `json:"onlyReturnExisting"`
   683  		}
   684  		decodeJWSRequest(t, &req, bytes.NewReader(b))
   685  		if !req.OnlyExisting {
   686  			t.Errorf("req.OnlyReturnExisting = %t; want = %t", req.OnlyExisting, true)
   687  		}
   688  	})
   689  	s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
   690  		if curStatus == StatusValid {
   691  			curStatus = StatusDeactivated
   692  			w.WriteHeader(http.StatusOK)
   693  		} else {
   694  			s.error(w, &wireError{
   695  				Status: http.StatusUnauthorized,
   696  				Type:   "urn:ietf:params:acme:error:unauthorized",
   697  			})
   698  		}
   699  		var req account
   700  		b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
   701  		head, err := decodeJWSHead(bytes.NewReader(b))
   702  		if err != nil {
   703  			t.Errorf("decodeJWSHead: %v", err)
   704  			return
   705  		}
   706  		if len(head.JWK) != 0 {
   707  			t.Error("head.JWK is not empty")
   708  		}
   709  		if !strings.HasSuffix(head.KID, "/accounts/1") {
   710  			t.Errorf("head.KID = %q; want suffix /accounts/1", head.KID)
   711  		}
   712  
   713  		decodeJWSRequest(t, &req, bytes.NewReader(b))
   714  		if req.Status != StatusDeactivated {
   715  			t.Errorf("req.Status = %q; want = %q", req.Status, StatusDeactivated)
   716  		}
   717  	})
   718  	s.start()
   719  	defer s.close()
   720  
   721  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   722  	if err := cl.DeactivateReg(context.Background()); err != nil {
   723  		t.Errorf("DeactivateReg: %v, wanted no error", err)
   724  	}
   725  	if err := cl.DeactivateReg(context.Background()); err == nil {
   726  		t.Errorf("DeactivateReg: %v, wanted error for unauthorized", err)
   727  	}
   728  }
   729  
   730  func TestRF_DeactivateRegNoAccount(t *testing.T) {
   731  	s := newACMEServer()
   732  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   733  		s.error(w, &wireError{
   734  			Status: http.StatusBadRequest,
   735  			Type:   "urn:ietf:params:acme:error:accountDoesNotExist",
   736  		})
   737  	})
   738  	s.start()
   739  	defer s.close()
   740  
   741  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   742  	if err := cl.DeactivateReg(context.Background()); !errors.Is(err, ErrNoAccount) {
   743  		t.Errorf("DeactivateReg: %v, wanted ErrNoAccount", err)
   744  	}
   745  }
   746  
   747  func TestRFC_AuthorizeOrder(t *testing.T) {
   748  	s := newACMEServer()
   749  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   750  		w.Header().Set("Location", s.url("/accounts/1"))
   751  		w.WriteHeader(http.StatusOK)
   752  		w.Write([]byte(`{"status": "valid"}`))
   753  	})
   754  	s.handle("/acme/new-order", func(w http.ResponseWriter, r *http.Request) {
   755  		w.Header().Set("Location", s.url("/orders/1"))
   756  		w.WriteHeader(http.StatusCreated)
   757  		fmt.Fprintf(w, `{
   758  			"status": "pending",
   759  			"expires": "2019-09-01T00:00:00Z",
   760  			"notBefore": "2019-08-31T00:00:00Z",
   761  			"notAfter": "2019-09-02T00:00:00Z",
   762  			"identifiers": [{"type":"dns", "value":"example.org"}],
   763  			"authorizations": [%q]
   764  		}`, s.url("/authz/1"))
   765  	})
   766  	s.start()
   767  	defer s.close()
   768  
   769  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   770  	o, err := cl.AuthorizeOrder(context.Background(), DomainIDs("example.org"),
   771  		WithOrderNotBefore(time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC)),
   772  		WithOrderNotAfter(time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC)),
   773  	)
   774  	if err != nil {
   775  		t.Fatal(err)
   776  	}
   777  	okOrder := &Order{
   778  		URI:         s.url("/orders/1"),
   779  		Status:      StatusPending,
   780  		Expires:     time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
   781  		NotBefore:   time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC),
   782  		NotAfter:    time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC),
   783  		Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}},
   784  		AuthzURLs:   []string{s.url("/authz/1")},
   785  	}
   786  	if !reflect.DeepEqual(o, okOrder) {
   787  		t.Errorf("AuthorizeOrder = %+v; want %+v", o, okOrder)
   788  	}
   789  }
   790  
   791  func TestRFC_GetOrder(t *testing.T) {
   792  	s := newACMEServer()
   793  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   794  		w.Header().Set("Location", s.url("/accounts/1"))
   795  		w.WriteHeader(http.StatusOK)
   796  		w.Write([]byte(`{"status": "valid"}`))
   797  	})
   798  	s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
   799  		w.Header().Set("Location", s.url("/orders/1"))
   800  		w.WriteHeader(http.StatusOK)
   801  		w.Write([]byte(`{
   802  			"status": "invalid",
   803  			"expires": "2019-09-01T00:00:00Z",
   804  			"notBefore": "2019-08-31T00:00:00Z",
   805  			"notAfter": "2019-09-02T00:00:00Z",
   806  			"identifiers": [{"type":"dns", "value":"example.org"}],
   807  			"authorizations": ["/authz/1"],
   808  			"finalize": "/orders/1/fin",
   809  			"certificate": "/orders/1/cert",
   810  			"error": {"type": "badRequest"}
   811  		}`))
   812  	})
   813  	s.start()
   814  	defer s.close()
   815  
   816  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   817  	o, err := cl.GetOrder(context.Background(), s.url("/orders/1"))
   818  	if err != nil {
   819  		t.Fatal(err)
   820  	}
   821  	okOrder := &Order{
   822  		URI:         s.url("/orders/1"),
   823  		Status:      StatusInvalid,
   824  		Expires:     time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
   825  		NotBefore:   time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC),
   826  		NotAfter:    time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC),
   827  		Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}},
   828  		AuthzURLs:   []string{"/authz/1"},
   829  		FinalizeURL: "/orders/1/fin",
   830  		CertURL:     "/orders/1/cert",
   831  		Error:       &Error{ProblemType: "badRequest"},
   832  	}
   833  	if !reflect.DeepEqual(o, okOrder) {
   834  		t.Errorf("GetOrder = %+v\nwant %+v", o, okOrder)
   835  	}
   836  }
   837  
   838  func TestRFC_WaitOrder(t *testing.T) {
   839  	for _, st := range []string{StatusReady, StatusValid} {
   840  		t.Run(st, func(t *testing.T) {
   841  			testWaitOrderStatus(t, st)
   842  		})
   843  	}
   844  }
   845  
   846  func testWaitOrderStatus(t *testing.T, okStatus string) {
   847  	s := newACMEServer()
   848  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   849  		w.Header().Set("Location", s.url("/accounts/1"))
   850  		w.WriteHeader(http.StatusOK)
   851  		w.Write([]byte(`{"status": "valid"}`))
   852  	})
   853  	var count int
   854  	s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
   855  		w.Header().Set("Location", s.url("/orders/1"))
   856  		w.WriteHeader(http.StatusOK)
   857  		s := StatusPending
   858  		if count > 0 {
   859  			s = okStatus
   860  		}
   861  		fmt.Fprintf(w, `{"status": %q}`, s)
   862  		count++
   863  	})
   864  	s.start()
   865  	defer s.close()
   866  
   867  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   868  	order, err := cl.WaitOrder(context.Background(), s.url("/orders/1"))
   869  	if err != nil {
   870  		t.Fatalf("WaitOrder: %v", err)
   871  	}
   872  	if order.Status != okStatus {
   873  		t.Errorf("order.Status = %q; want %q", order.Status, okStatus)
   874  	}
   875  }
   876  
   877  func TestRFC_WaitOrderError(t *testing.T) {
   878  	s := newACMEServer()
   879  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   880  		w.Header().Set("Location", s.url("/accounts/1"))
   881  		w.WriteHeader(http.StatusOK)
   882  		w.Write([]byte(`{"status": "valid"}`))
   883  	})
   884  	var count int
   885  	s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
   886  		w.Header().Set("Location", s.url("/orders/1"))
   887  		w.WriteHeader(http.StatusOK)
   888  		s := StatusPending
   889  		if count > 0 {
   890  			s = StatusInvalid
   891  		}
   892  		fmt.Fprintf(w, `{"status": %q}`, s)
   893  		count++
   894  	})
   895  	s.start()
   896  	defer s.close()
   897  
   898  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   899  	_, err := cl.WaitOrder(context.Background(), s.url("/orders/1"))
   900  	if err == nil {
   901  		t.Fatal("WaitOrder returned nil error")
   902  	}
   903  	e, ok := err.(*OrderError)
   904  	if !ok {
   905  		t.Fatalf("err = %v (%T); want OrderError", err, err)
   906  	}
   907  	if e.OrderURL != s.url("/orders/1") {
   908  		t.Errorf("e.OrderURL = %q; want %q", e.OrderURL, s.url("/orders/1"))
   909  	}
   910  	if e.Status != StatusInvalid {
   911  		t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid)
   912  	}
   913  }
   914  
   915  func TestRFC_CreateOrderCert(t *testing.T) {
   916  	q := &x509.CertificateRequest{
   917  		Subject: pkix.Name{CommonName: "example.org"},
   918  	}
   919  	csr, err := x509.CreateCertificateRequest(rand.Reader, q, testKeyEC)
   920  	if err != nil {
   921  		t.Fatal(err)
   922  	}
   923  
   924  	tmpl := &x509.Certificate{SerialNumber: big.NewInt(1)}
   925  	leaf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testKeyEC.PublicKey, testKeyEC)
   926  	if err != nil {
   927  		t.Fatal(err)
   928  	}
   929  
   930  	s := newACMEServer()
   931  	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
   932  		w.Header().Set("Location", s.url("/accounts/1"))
   933  		w.Write([]byte(`{"status": "valid"}`))
   934  	})
   935  	var count int
   936  	s.handle("/pleaseissue", func(w http.ResponseWriter, r *http.Request) {
   937  		w.Header().Set("Location", s.url("/pleaseissue"))
   938  		st := StatusProcessing
   939  		if count > 0 {
   940  			st = StatusValid
   941  		}
   942  		fmt.Fprintf(w, `{"status":%q, "certificate":%q}`, st, s.url("/crt"))
   943  		count++
   944  	})
   945  	s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
   946  		w.Header().Set("Content-Type", "application/pem-certificate-chain")
   947  		pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: leaf})
   948  	})
   949  	s.start()
   950  	defer s.close()
   951  	ctx := context.Background()
   952  
   953  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   954  	cert, curl, err := cl.CreateOrderCert(ctx, s.url("/pleaseissue"), csr, true)
   955  	if err != nil {
   956  		t.Fatalf("CreateOrderCert: %v", err)
   957  	}
   958  	if _, err := x509.ParseCertificate(cert[0]); err != nil {
   959  		t.Errorf("ParseCertificate: %v", err)
   960  	}
   961  	if !reflect.DeepEqual(cert[0], leaf) {
   962  		t.Errorf("cert and leaf bytes don't match")
   963  	}
   964  	if u := s.url("/crt"); curl != u {
   965  		t.Errorf("curl = %q; want %q", curl, u)
   966  	}
   967  }
   968  
   969  func TestRFC_AlreadyRevokedCert(t *testing.T) {
   970  	s := newACMEServer()
   971  	s.handle("/acme/revoke-cert", func(w http.ResponseWriter, r *http.Request) {
   972  		s.error(w, &wireError{
   973  			Status: http.StatusBadRequest,
   974  			Type:   "urn:ietf:params:acme:error:alreadyRevoked",
   975  		})
   976  	})
   977  	s.start()
   978  	defer s.close()
   979  
   980  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
   981  	err := cl.RevokeCert(context.Background(), testKeyEC, []byte{0}, CRLReasonUnspecified)
   982  	if err != nil {
   983  		t.Fatalf("RevokeCert: %v", err)
   984  	}
   985  }
   986  
   987  func TestRFC_ListCertAlternates(t *testing.T) {
   988  	s := newACMEServer()
   989  	s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
   990  		w.Header().Set("Content-Type", "application/pem-certificate-chain")
   991  		w.Header().Add("Link", `<https://example.com/crt/2>;rel="alternate"`)
   992  		w.Header().Add("Link", `<https://example.com/crt/3>; rel="alternate"`)
   993  		w.Header().Add("Link", `<https://example.com/acme>; rel="index"`)
   994  	})
   995  	s.handle("/crt2", func(w http.ResponseWriter, r *http.Request) {
   996  		w.Header().Set("Content-Type", "application/pem-certificate-chain")
   997  	})
   998  	s.start()
   999  	defer s.close()
  1000  
  1001  	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
  1002  	crts, err := cl.ListCertAlternates(context.Background(), s.url("/crt"))
  1003  	if err != nil {
  1004  		t.Fatalf("ListCertAlternates: %v", err)
  1005  	}
  1006  	want := []string{"https://example.com/crt/2", "https://example.com/crt/3"}
  1007  	if !reflect.DeepEqual(crts, want) {
  1008  		t.Errorf("ListCertAlternates(/crt): %v; want %v", crts, want)
  1009  	}
  1010  	crts, err = cl.ListCertAlternates(context.Background(), s.url("/crt2"))
  1011  	if err != nil {
  1012  		t.Fatalf("ListCertAlternates: %v", err)
  1013  	}
  1014  	if crts != nil {
  1015  		t.Errorf("ListCertAlternates(/crt2): %v; want nil", crts)
  1016  	}
  1017  }
  1018  

View as plain text