github.com/letsencrypt/boulder@v0.20251208.0/wfe2/verify_test.go (about)

     1  package wfe2
     2  
     3  import (
     4  	"context"
     5  	"crypto"
     6  	"crypto/dsa"
     7  	"crypto/ecdsa"
     8  	"crypto/elliptic"
     9  	"crypto/rsa"
    10  	"errors"
    11  	"fmt"
    12  	"net/http"
    13  	"slices"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/go-jose/go-jose/v4"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	"google.golang.org/grpc"
    20  
    21  	"github.com/letsencrypt/boulder/core"
    22  	corepb "github.com/letsencrypt/boulder/core/proto"
    23  	berrors "github.com/letsencrypt/boulder/errors"
    24  	"github.com/letsencrypt/boulder/goodkey"
    25  	bgrpc "github.com/letsencrypt/boulder/grpc"
    26  	"github.com/letsencrypt/boulder/grpc/noncebalancer"
    27  	noncepb "github.com/letsencrypt/boulder/nonce/proto"
    28  	sapb "github.com/letsencrypt/boulder/sa/proto"
    29  	"github.com/letsencrypt/boulder/test"
    30  	"github.com/letsencrypt/boulder/web"
    31  )
    32  
    33  // sigAlgForKey uses `signatureAlgorithmForKey` but fails immediately using the
    34  // testing object if the sig alg is unknown.
    35  func sigAlgForKey(t *testing.T, key any) jose.SignatureAlgorithm {
    36  	var sigAlg jose.SignatureAlgorithm
    37  	var err error
    38  	// Gracefully handle the case where a non-pointer public key is given where
    39  	// sigAlgorithmForKey always wants a pointer. It may be tempting to try and do
    40  	// `sigAlgorithmForKey(&jose.JSONWebKey{Key: &key})` without a type switch but this produces
    41  	// `*interface {}` and not the desired `*rsa.PublicKey` or `*ecdsa.PublicKey`.
    42  	switch k := key.(type) {
    43  	case rsa.PublicKey:
    44  		sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: &k})
    45  	case ecdsa.PublicKey:
    46  		sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: &k})
    47  	default:
    48  		sigAlg, err = sigAlgorithmForKey(&jose.JSONWebKey{Key: k})
    49  	}
    50  	test.Assert(t, err == nil, fmt.Sprintf("Error getting signature algorithm for key %#v", key))
    51  	return sigAlg
    52  }
    53  
    54  // keyAlgForKey returns a JWK key algorithm based on the provided private key.
    55  // Only ECDSA and RSA private keys are supported.
    56  func keyAlgForKey(t *testing.T, key any) string {
    57  	switch key.(type) {
    58  	case *rsa.PrivateKey, rsa.PrivateKey:
    59  		return "RSA"
    60  	case *ecdsa.PrivateKey, ecdsa.PrivateKey:
    61  		return "ECDSA"
    62  	}
    63  	t.Fatalf("Can't figure out keyAlgForKey: %#v", key)
    64  	return ""
    65  }
    66  
    67  // pubKeyForKey returns the public key of an RSA/ECDSA private key provided as
    68  // argument.
    69  func pubKeyForKey(t *testing.T, privKey any) any {
    70  	switch k := privKey.(type) {
    71  	case *rsa.PrivateKey:
    72  		return k.PublicKey
    73  	case *ecdsa.PrivateKey:
    74  		return k.PublicKey
    75  	}
    76  	t.Fatalf("Unable to get public key for private key %#v", privKey)
    77  	return nil
    78  }
    79  
    80  // requestSigner offers methods to sign requests that will be accepted by a
    81  // specific WFE in unittests. It is only valid for the lifetime of a single
    82  // unittest.
    83  type requestSigner struct {
    84  	t            *testing.T
    85  	nonceService jose.NonceSource
    86  }
    87  
    88  // embeddedJWK creates a JWS for a given request body with an embedded JWK
    89  // corresponding to the private key provided. The URL and nonce extra headers
    90  // are set based on the additional arguments. A computed JWS, the corresponding
    91  // embedded JWK and the JWS in serialized string form are returned.
    92  func (rs requestSigner) embeddedJWK(
    93  	privateKey any,
    94  	url string,
    95  	req string) (*jose.JSONWebSignature, *jose.JSONWebKey, string) {
    96  	// if no key is provided default to test1KeyPrivatePEM
    97  	var publicKey any
    98  	if privateKey == nil {
    99  		signer := loadKey(rs.t, []byte(test1KeyPrivatePEM))
   100  		privateKey = signer
   101  		publicKey = signer.Public()
   102  	} else {
   103  		publicKey = pubKeyForKey(rs.t, privateKey)
   104  	}
   105  
   106  	signerKey := jose.SigningKey{
   107  		Key:       privateKey,
   108  		Algorithm: sigAlgForKey(rs.t, publicKey),
   109  	}
   110  
   111  	opts := &jose.SignerOptions{
   112  		NonceSource: rs.nonceService,
   113  		EmbedJWK:    true,
   114  	}
   115  	if url != "" {
   116  		opts.ExtraHeaders = map[jose.HeaderKey]any{
   117  			"url": url,
   118  		}
   119  	}
   120  
   121  	signer, err := jose.NewSigner(signerKey, opts)
   122  	test.AssertNotError(rs.t, err, "Failed to make signer")
   123  
   124  	jws, err := signer.Sign([]byte(req))
   125  	test.AssertNotError(rs.t, err, "Failed to sign req")
   126  
   127  	body := jws.FullSerialize()
   128  	parsedJWS, err := jose.ParseSigned(body, getSupportedAlgs())
   129  	test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
   130  
   131  	return parsedJWS, parsedJWS.Signatures[0].Header.JSONWebKey, body
   132  }
   133  
   134  // signRequestKeyID creates a JWS for a given request body with key ID specified
   135  // based on the ID number provided. The URL and nonce extra headers
   136  // are set based on the additional arguments. A computed JWS, the corresponding
   137  // embedded JWK and the JWS in serialized string form are returned.
   138  func (rs requestSigner) byKeyID(
   139  	keyID int64,
   140  	privateKey any,
   141  	url string,
   142  	req string) (*jose.JSONWebSignature, *jose.JSONWebKey, string) {
   143  	// if no key is provided default to test1KeyPrivatePEM
   144  	if privateKey == nil {
   145  		privateKey = loadKey(rs.t, []byte(test1KeyPrivatePEM))
   146  	}
   147  
   148  	jwk := &jose.JSONWebKey{
   149  		Key:       privateKey,
   150  		Algorithm: keyAlgForKey(rs.t, privateKey),
   151  		KeyID:     fmt.Sprintf("http://localhost/acme/acct/%d", keyID),
   152  	}
   153  
   154  	signerKey := jose.SigningKey{
   155  		Key:       jwk,
   156  		Algorithm: jose.RS256,
   157  	}
   158  
   159  	opts := &jose.SignerOptions{
   160  		NonceSource: rs.nonceService,
   161  		ExtraHeaders: map[jose.HeaderKey]any{
   162  			"url": url,
   163  		},
   164  	}
   165  
   166  	signer, err := jose.NewSigner(signerKey, opts)
   167  	test.AssertNotError(rs.t, err, "Failed to make signer")
   168  	jws, err := signer.Sign([]byte(req))
   169  	test.AssertNotError(rs.t, err, "Failed to sign req")
   170  
   171  	body := jws.FullSerialize()
   172  	parsedJWS, err := jose.ParseSigned(body, getSupportedAlgs())
   173  	test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
   174  
   175  	return parsedJWS, jwk, body
   176  }
   177  
   178  func (rs requestSigner) makeJWS(ns jose.NonceSource) *jose.JSONWebSignature {
   179  	privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
   180  	jwk := &jose.JSONWebKey{
   181  		Key:       privateKey,
   182  		Algorithm: keyAlgForKey(rs.t, privateKey),
   183  		KeyID:     "http://localhost/acme/acct/1",
   184  	}
   185  	signerKey := jose.SigningKey{
   186  		Key:       jwk,
   187  		Algorithm: jose.RS256,
   188  	}
   189  
   190  	opts := &jose.SignerOptions{
   191  		NonceSource: ns,
   192  		ExtraHeaders: map[jose.HeaderKey]any{
   193  			"url": "https://example.com/acme/foo",
   194  		},
   195  	}
   196  
   197  	signer, err := jose.NewSigner(signerKey, opts)
   198  	test.AssertNotError(rs.t, err, "Failed to make signer")
   199  	jws, err := signer.Sign([]byte(""))
   200  	test.AssertNotError(rs.t, err, "Failed to sign req")
   201  
   202  	body := jws.FullSerialize()
   203  	parsedJWS, err := jose.ParseSigned(body, getSupportedAlgs())
   204  	test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
   205  
   206  	return parsedJWS
   207  }
   208  
   209  func TestRejectsNone(t *testing.T) {
   210  	noneJWSBody := `
   211  		{
   212  			"header": {
   213  				"alg": "none",
   214  				"jwk": {
   215  					"kty": "RSA",
   216  					"n": "vrjT",
   217  					"e": "AQAB"
   218  				}
   219  			},
   220  			"payload": "aGkK",
   221    		"signature": "ghTIjrhiRl2pQ09vAkUUBbF5KziJdhzOTB-okM9SPRzU8Hyj0W1H5JA1Zoc-A-LuJGNAtYYHWqMw1SeZbT0l9FHcbMPeWDaJNkHS9jz5_g_Oyol8vcrWur2GDtB2Jgw6APtZKrbuGATbrF7g41Wijk6Kk9GXDoCnlfOQOhHhsrFFcWlCPLG-03TtKD6EBBoVBhmlp8DRLs7YguWRZ6jWNaEX-1WiRntBmhLqoqQFtvZxCBw_PRuaRw_RZBd1x2_BNYqEdOmVNC43UHMSJg3y_3yrPo905ur09aUTscf-C_m4Sa4M0FuDKn3bQ_pFrtz-aCCq6rcTIyxYpDqNvHMT2Q"
   222  		}
   223  	`
   224  	_, err := jose.ParseSigned(noneJWSBody, getSupportedAlgs())
   225  	test.AssertError(t, err, "Should not have been able to parse 'none' algorithm")
   226  }
   227  
   228  func TestRejectsHS256(t *testing.T) {
   229  	hs256JWSBody := `
   230  		{
   231  			"header": {
   232  				"alg": "HS256",
   233  				"jwk": {
   234  					"kty": "RSA",
   235  					"n": "vrjT",
   236  					"e": "AQAB"
   237  				}
   238  			},
   239  			"payload": "aGkK",
   240    		"signature": "ghTIjrhiRl2pQ09vAkUUBbF5KziJdhzOTB-okM9SPRzU8Hyj0W1H5JA1Zoc-A-LuJGNAtYYHWqMw1SeZbT0l9FHcbMPeWDaJNkHS9jz5_g_Oyol8vcrWur2GDtB2Jgw6APtZKrbuGATbrF7g41Wijk6Kk9GXDoCnlfOQOhHhsrFFcWlCPLG-03TtKD6EBBoVBhmlp8DRLs7YguWRZ6jWNaEX-1WiRntBmhLqoqQFtvZxCBw_PRuaRw_RZBd1x2_BNYqEdOmVNC43UHMSJg3y_3yrPo905ur09aUTscf-C_m4Sa4M0FuDKn3bQ_pFrtz-aCCq6rcTIyxYpDqNvHMT2Q"
   241  		}
   242  	`
   243  
   244  	_, err := jose.ParseSigned(hs256JWSBody, getSupportedAlgs())
   245  	fmt.Println(err)
   246  	test.AssertError(t, err, "Parsed hs256JWSBody, but should not have")
   247  }
   248  
   249  func TestCheckAlgorithm(t *testing.T) {
   250  	testCases := []struct {
   251  		key         jose.JSONWebKey
   252  		jws         jose.JSONWebSignature
   253  		expectedErr string
   254  	}{
   255  		{
   256  			jose.JSONWebKey{},
   257  			jose.JSONWebSignature{
   258  				Signatures: []jose.Signature{
   259  					{
   260  						Header: jose.Header{
   261  							Algorithm: "RS256",
   262  						},
   263  					},
   264  				},
   265  			},
   266  			"JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521)",
   267  		},
   268  		{
   269  			jose.JSONWebKey{
   270  				Algorithm: "HS256",
   271  				Key:       &rsa.PublicKey{},
   272  			},
   273  			jose.JSONWebSignature{
   274  				Signatures: []jose.Signature{
   275  					{
   276  						Header: jose.Header{
   277  							Algorithm: "HS256",
   278  						},
   279  					},
   280  				},
   281  			},
   282  			"JWS signature header contains unsupported algorithm \"HS256\", expected one of [RS256 ES256 ES384 ES512]",
   283  		},
   284  		{
   285  			jose.JSONWebKey{
   286  				Algorithm: "ES256",
   287  				Key:       &dsa.PublicKey{},
   288  			},
   289  			jose.JSONWebSignature{
   290  				Signatures: []jose.Signature{
   291  					{
   292  						Header: jose.Header{
   293  							Algorithm: "ES512",
   294  						},
   295  					},
   296  				},
   297  			},
   298  			"JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521)",
   299  		},
   300  		{
   301  			jose.JSONWebKey{
   302  				Algorithm: "RS256",
   303  				Key:       &rsa.PublicKey{},
   304  			},
   305  			jose.JSONWebSignature{
   306  				Signatures: []jose.Signature{
   307  					{
   308  						Header: jose.Header{
   309  							Algorithm: "ES512",
   310  						},
   311  					},
   312  				},
   313  			},
   314  			"JWS signature header algorithm \"ES512\" does not match expected algorithm \"RS256\" for JWK",
   315  		},
   316  		{
   317  			jose.JSONWebKey{
   318  				Algorithm: "HS256",
   319  				Key:       &rsa.PublicKey{},
   320  			},
   321  			jose.JSONWebSignature{
   322  				Signatures: []jose.Signature{
   323  					{
   324  						Header: jose.Header{
   325  							Algorithm: "RS256",
   326  						},
   327  					},
   328  				},
   329  			},
   330  			"JWK key header algorithm \"HS256\" does not match expected algorithm \"RS256\" for JWK",
   331  		},
   332  	}
   333  	for i, tc := range testCases {
   334  		err := checkAlgorithm(&tc.key, tc.jws.Signatures[0].Header)
   335  		if tc.expectedErr != "" && err.Error() != tc.expectedErr {
   336  			t.Errorf("TestCheckAlgorithm %d: Expected %q, got %q", i, tc.expectedErr, err)
   337  		}
   338  	}
   339  }
   340  
   341  func TestCheckAlgorithmSuccess(t *testing.T) {
   342  	jwsRS256 := &jose.JSONWebSignature{
   343  		Signatures: []jose.Signature{
   344  			{
   345  				Header: jose.Header{
   346  					Algorithm: "RS256",
   347  				},
   348  			},
   349  		},
   350  	}
   351  	goodJSONWebKeyRS256 := &jose.JSONWebKey{
   352  		Algorithm: "RS256",
   353  		Key:       &rsa.PublicKey{},
   354  	}
   355  	err := checkAlgorithm(goodJSONWebKeyRS256, jwsRS256.Signatures[0].Header)
   356  	test.AssertNotError(t, err, "RS256 key: Expected nil error")
   357  
   358  	badJSONWebKeyRS256 := &jose.JSONWebKey{
   359  		Algorithm: "ObviouslyWrongButNotZeroValue",
   360  		Key:       &rsa.PublicKey{},
   361  	}
   362  	err = checkAlgorithm(badJSONWebKeyRS256, jwsRS256.Signatures[0].Header)
   363  	test.AssertError(t, err, "RS256 key: Expected nil error")
   364  	test.AssertContains(t, err.Error(), "JWK key header algorithm \"ObviouslyWrongButNotZeroValue\" does not match expected algorithm \"RS256\" for JWK")
   365  
   366  	jwsES256 := &jose.JSONWebSignature{
   367  		Signatures: []jose.Signature{
   368  			{
   369  				Header: jose.Header{
   370  					Algorithm: "ES256",
   371  				},
   372  			},
   373  		},
   374  	}
   375  	goodJSONWebKeyES256 := &jose.JSONWebKey{
   376  		Algorithm: "ES256",
   377  		Key: &ecdsa.PublicKey{
   378  			Curve: elliptic.P256(),
   379  		},
   380  	}
   381  	err = checkAlgorithm(goodJSONWebKeyES256, jwsES256.Signatures[0].Header)
   382  	test.AssertNotError(t, err, "ES256 key: Expected nil error")
   383  
   384  	badJSONWebKeyES256 := &jose.JSONWebKey{
   385  		Algorithm: "ObviouslyWrongButNotZeroValue",
   386  		Key: &ecdsa.PublicKey{
   387  			Curve: elliptic.P256(),
   388  		},
   389  	}
   390  	err = checkAlgorithm(badJSONWebKeyES256, jwsES256.Signatures[0].Header)
   391  	test.AssertError(t, err, "ES256 key: Expected nil error")
   392  	test.AssertContains(t, err.Error(), "JWK key header algorithm \"ObviouslyWrongButNotZeroValue\" does not match expected algorithm \"ES256\" for JWK")
   393  }
   394  
   395  func TestValidPOSTRequest(t *testing.T) {
   396  	wfe, _, _ := setupWFE(t)
   397  
   398  	dummyContentLength := []string{"pretty long, idk, maybe a nibble or two?"}
   399  
   400  	testCases := []struct {
   401  		Name               string
   402  		Headers            map[string][]string
   403  		Body               *string
   404  		HTTPStatus         int
   405  		ErrorDetail        string
   406  		ErrorStatType      string
   407  		EnforceContentType bool
   408  	}{
   409  		// POST requests with a Replay-Nonce header should produce a problem
   410  		{
   411  			Name: "POST with a Replay-Nonce HTTP header",
   412  			Headers: map[string][]string{
   413  				"Content-Length": dummyContentLength,
   414  				"Replay-Nonce":   {"ima-misplaced-nonce"},
   415  				"Content-Type":   {expectedJWSContentType},
   416  			},
   417  			HTTPStatus:    http.StatusBadRequest,
   418  			ErrorDetail:   "HTTP requests should NOT contain Replay-Nonce header. Use JWS nonce field",
   419  			ErrorStatType: "ReplayNonceOutsideJWS",
   420  		},
   421  		// POST requests without a body should produce a problem
   422  		{
   423  			Name: "POST with an empty POST body",
   424  			Headers: map[string][]string{
   425  				"Content-Length": dummyContentLength,
   426  				"Content-Type":   {expectedJWSContentType},
   427  			},
   428  			HTTPStatus:    http.StatusBadRequest,
   429  			ErrorDetail:   "No body on POST",
   430  			ErrorStatType: "NoPOSTBody",
   431  		},
   432  		{
   433  			Name: "POST without a Content-Type header",
   434  			Headers: map[string][]string{
   435  				"Content-Length": dummyContentLength,
   436  			},
   437  			HTTPStatus: http.StatusUnsupportedMediaType,
   438  			ErrorDetail: fmt.Sprintf(
   439  				"No Content-Type header on POST. Content-Type must be %q",
   440  				expectedJWSContentType),
   441  			ErrorStatType:      "NoContentType",
   442  			EnforceContentType: true,
   443  		},
   444  		{
   445  			Name: "POST with an invalid Content-Type header",
   446  			Headers: map[string][]string{
   447  				"Content-Length": dummyContentLength,
   448  				"Content-Type":   {"fresh.and.rare"},
   449  			},
   450  			HTTPStatus: http.StatusUnsupportedMediaType,
   451  			ErrorDetail: fmt.Sprintf(
   452  				"Invalid Content-Type header on POST. Content-Type must be %q",
   453  				expectedJWSContentType),
   454  			ErrorStatType:      "WrongContentType",
   455  			EnforceContentType: true,
   456  		},
   457  	}
   458  
   459  	for _, tc := range testCases {
   460  		input := &http.Request{
   461  			Method: "POST",
   462  			URL:    mustParseURL("/"),
   463  			Header: tc.Headers,
   464  		}
   465  		t.Run(tc.Name, func(t *testing.T) {
   466  			err := wfe.validPOSTRequest(input)
   467  			test.AssertError(t, err, "No error returned for invalid POST")
   468  			test.AssertErrorIs(t, err, berrors.Malformed)
   469  			test.AssertContains(t, err.Error(), tc.ErrorDetail)
   470  			test.AssertMetricWithLabelsEquals(
   471  				t, wfe.stats.httpErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
   472  		})
   473  	}
   474  }
   475  
   476  func TestEnforceJWSAuthType(t *testing.T) {
   477  	wfe, _, signer := setupWFE(t)
   478  
   479  	testKeyIDJWS, _, _ := signer.byKeyID(1, nil, "", "")
   480  	testEmbeddedJWS, _, _ := signer.embeddedJWK(nil, "", "")
   481  
   482  	// A hand crafted JWS that has both a Key ID and an embedded JWK
   483  	conflictJWSBody := `
   484  {
   485    "header": {
   486      "alg": "RS256", 
   487      "jwk": {
   488        "e": "AQAB", 
   489        "kty": "RSA", 
   490        "n": "ppbqGaMFnnq9TeMUryR6WW4Lr5WMgp46KlBXZkNaGDNQoifWt6LheeR5j9MgYkIFU7Z8Jw5-bpJzuBeEVwb-yHGh4Umwo_qKtvAJd44iLjBmhBSxq-OSe6P5hX1LGCByEZlYCyoy98zOtio8VK_XyS5VoOXqchCzBXYf32ksVUTrtH1jSlamKHGz0Q0pRKIsA2fLqkE_MD3jP6wUDD6ExMw_tKYLx21lGcK41WSrRpDH-kcZo1QdgCy2ceNzaliBX1eHmKG0-H8tY4tPQudk-oHQmWTdvUIiHO6gSKMGDZNWv6bq74VTCsRfUEAkuWhqUhgRSGzlvlZ24wjHv5Qdlw"
   491      }
   492    }, 
   493    "protected": "eyJub25jZSI6ICJibTl1WTJVIiwgInVybCI6ICJodHRwOi8vbG9jYWxob3N0L3Rlc3QiLCAia2lkIjogInRlc3RrZXkifQ", 
   494    "payload": "Zm9v", 
   495    "signature": "ghTIjrhiRl2pQ09vAkUUBbF5KziJdhzOTB-okM9SPRzU8Hyj0W1H5JA1Zoc-A-LuJGNAtYYHWqMw1SeZbT0l9FHcbMPeWDaJNkHS9jz5_g_Oyol8vcrWur2GDtB2Jgw6APtZKrbuGATbrF7g41Wijk6Kk9GXDoCnlfOQOhHhsrFFcWlCPLG-03TtKD6EBBoVBhmlp8DRLs7YguWRZ6jWNaEX-1WiRntBmhLqoqQFtvZxCBw_PRuaRw_RZBd1x2_BNYqEdOmVNC43UHMSJg3y_3yrPo905ur09aUTscf-C_m4Sa4M0FuDKn3bQ_pFrtz-aCCq6rcTIyxYpDqNvHMT2Q"
   496  }
   497  `
   498  
   499  	conflictJWS, err := jose.ParseSigned(conflictJWSBody, getSupportedAlgs())
   500  	if err != nil {
   501  		t.Fatal("Unable to parse conflict JWS")
   502  	}
   503  
   504  	testCases := []struct {
   505  		Name          string
   506  		JWS           *jose.JSONWebSignature
   507  		AuthType      jwsAuthType
   508  		WantErrType   berrors.ErrorType
   509  		WantErrDetail string
   510  		WantStatType  string
   511  	}{
   512  		{
   513  			Name:          "Key ID and embedded JWS",
   514  			JWS:           conflictJWS,
   515  			AuthType:      invalidAuthType,
   516  			WantErrType:   berrors.Malformed,
   517  			WantErrDetail: "jwk and kid header fields are mutually exclusive",
   518  			WantStatType:  "JWSAuthTypeInvalid",
   519  		},
   520  		{
   521  			Name:          "Key ID when expected is embedded JWK",
   522  			JWS:           testKeyIDJWS,
   523  			AuthType:      embeddedJWK,
   524  			WantErrType:   berrors.Malformed,
   525  			WantErrDetail: "No embedded JWK in JWS header",
   526  			WantStatType:  "JWSAuthTypeWrong",
   527  		},
   528  		{
   529  			Name:          "Embedded JWK when expected is Key ID",
   530  			JWS:           testEmbeddedJWS,
   531  			AuthType:      embeddedKeyID,
   532  			WantErrType:   berrors.Malformed,
   533  			WantErrDetail: "No Key ID in JWS header",
   534  			WantStatType:  "JWSAuthTypeWrong",
   535  		},
   536  		{
   537  			Name:     "Key ID when expected is KeyID",
   538  			JWS:      testKeyIDJWS,
   539  			AuthType: embeddedKeyID,
   540  		},
   541  		{
   542  			Name:     "Embedded JWK when expected is embedded JWK",
   543  			JWS:      testEmbeddedJWS,
   544  			AuthType: embeddedJWK,
   545  		},
   546  	}
   547  
   548  	for _, tc := range testCases {
   549  		t.Run(tc.Name, func(t *testing.T) {
   550  			wfe.stats.joseErrorCount.Reset()
   551  			in := tc.JWS.Signatures[0].Header
   552  
   553  			gotErr := wfe.enforceJWSAuthType(in, tc.AuthType)
   554  			if tc.WantErrDetail == "" {
   555  				if gotErr != nil {
   556  					t.Fatalf("enforceJWSAuthType(%#v, %#v) = %#v, want nil", in, tc.AuthType, gotErr)
   557  				}
   558  			} else {
   559  				berr, ok := gotErr.(*berrors.BoulderError)
   560  				if !ok {
   561  					t.Fatalf("enforceJWSAuthType(%#v, %#v) returned %T, want BoulderError", in, tc.AuthType, gotErr)
   562  				}
   563  				if berr.Type != tc.WantErrType {
   564  					t.Errorf("enforceJWSAuthType(%#v, %#v) = %#v, want %#v", in, tc.AuthType, berr.Type, tc.WantErrType)
   565  				}
   566  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
   567  					t.Errorf("enforceJWSAuthType(%#v, %#v) = %q, want %q", in, tc.AuthType, berr.Detail, tc.WantErrDetail)
   568  				}
   569  				test.AssertMetricWithLabelsEquals(
   570  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
   571  			}
   572  		})
   573  	}
   574  }
   575  
   576  type badNonceProvider struct {
   577  	malformed  bool
   578  	shortNonce bool
   579  }
   580  
   581  func (b badNonceProvider) Nonce() (string, error) {
   582  	if b.malformed {
   583  		return "im-a-nonce", nil
   584  	}
   585  	if b.shortNonce {
   586  		// A nonce length of 4 is considered "short" because there is no nonce
   587  		// material to be redeemed after the prefix. Derived prefixes are 8
   588  		// characters and static prefixes are 4 characters.
   589  		return "woww", nil
   590  	}
   591  	return "mlolmlol3ov77I5Ui-cdaY_k8IcjK58FvbG0y_BCRrx5rGQ8rjA", nil
   592  }
   593  
   594  func TestValidNonce(t *testing.T) {
   595  	wfe, _, signer := setupWFE(t)
   596  
   597  	goodJWS, _, _ := signer.embeddedJWK(nil, "", "")
   598  
   599  	testCases := []struct {
   600  		Name          string
   601  		JWS           *jose.JSONWebSignature
   602  		WantErrType   berrors.ErrorType
   603  		WantErrDetail string
   604  		WantStatType  string
   605  	}{
   606  		{
   607  			Name:          "No nonce in JWS",
   608  			JWS:           signer.makeJWS(nil),
   609  			WantErrType:   berrors.BadNonce,
   610  			WantErrDetail: "JWS has no anti-replay nonce",
   611  			WantStatType:  "JWSMissingNonce",
   612  		},
   613  		{
   614  			Name:          "Malformed nonce in JWS",
   615  			JWS:           signer.makeJWS(badNonceProvider{malformed: true}),
   616  			WantErrType:   berrors.BadNonce,
   617  			WantErrDetail: "JWS has a malformed anti-replay nonce: \"im-a-nonce\"",
   618  			WantStatType:  "JWSMalformedNonce",
   619  		},
   620  		{
   621  			Name:          "Canned nonce shorter than prefixLength in JWS",
   622  			JWS:           signer.makeJWS(badNonceProvider{shortNonce: true}),
   623  			WantErrType:   berrors.BadNonce,
   624  			WantErrDetail: "JWS has a malformed anti-replay nonce: \"woww\"",
   625  			WantStatType:  "JWSMalformedNonce",
   626  		},
   627  		{
   628  			Name:          "Unrecognized nonce in JWS",
   629  			JWS:           signer.makeJWS(badNonceProvider{}),
   630  			WantErrType:   berrors.BadNonce,
   631  			WantErrDetail: "unable to decrypt nonce",
   632  			WantStatType:  "JWSUnredeemableNonce",
   633  		},
   634  		// We don't have a test case for "invalid" (i.e. no backend matching the
   635  		// prefix) because the unit tests don't use the noncebalancer that does
   636  		// that routing.
   637  		{
   638  			Name: "Valid nonce in JWS",
   639  			JWS:  goodJWS,
   640  		},
   641  	}
   642  
   643  	for _, tc := range testCases {
   644  		t.Run(tc.Name, func(t *testing.T) {
   645  			in := tc.JWS.Signatures[0].Header
   646  			wfe.stats.joseErrorCount.Reset()
   647  
   648  			gotErr := wfe.validNonce(context.Background(), in)
   649  			if tc.WantErrDetail == "" {
   650  				if gotErr != nil {
   651  					t.Fatalf("validNonce(%#v) = %#v, want nil", in, gotErr)
   652  				}
   653  			} else {
   654  				berr, ok := gotErr.(*berrors.BoulderError)
   655  				if !ok {
   656  					t.Fatalf("validNonce(%#v) returned %T, want BoulderError", in, gotErr)
   657  				}
   658  				if berr.Type != tc.WantErrType {
   659  					t.Errorf("validNonce(%#v) = %#v, want %#v", in, berr.Type, tc.WantErrType)
   660  				}
   661  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
   662  					t.Errorf("validNonce(%#v) = %q, want %q", in, berr.Detail, tc.WantErrDetail)
   663  				}
   664  				test.AssertMetricWithLabelsEquals(
   665  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
   666  			}
   667  		})
   668  	}
   669  }
   670  
   671  // noBackendsNonceRedeemer is a nonce redeemer that always returns an error
   672  // indicating that the prefix matches no known nonce provider.
   673  type noBackendsNonceRedeemer struct{}
   674  
   675  func (n noBackendsNonceRedeemer) Redeem(ctx context.Context, _ *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
   676  	return nil, noncebalancer.ErrNoBackendsMatchPrefix.Err()
   677  }
   678  
   679  func TestValidNonce_NoMatchingBackendFound(t *testing.T) {
   680  	wfe, _, signer := setupWFE(t)
   681  	goodJWS, _, _ := signer.embeddedJWK(nil, "", "")
   682  	wfe.rnc = noBackendsNonceRedeemer{}
   683  
   684  	// A valid JWS with a nonce whose prefix matches no known nonce provider should
   685  	// result in a BadNonceProblem.
   686  	err := wfe.validNonce(context.Background(), goodJWS.Signatures[0].Header)
   687  	test.AssertError(t, err, "Expected error for valid nonce with no backend")
   688  	test.AssertErrorIs(t, err, berrors.BadNonce)
   689  	test.AssertContains(t, err.Error(), "JWS has a nonce whose prefix matches no nonce service")
   690  	test.AssertMetricWithLabelsEquals(t, wfe.stats.nonceNoMatchingBackendCount, prometheus.Labels{}, 1)
   691  }
   692  
   693  func (rs requestSigner) signExtraHeaders(
   694  	headers map[jose.HeaderKey]any) (*jose.JSONWebSignature, string) {
   695  	privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
   696  
   697  	signerKey := jose.SigningKey{
   698  		Key:       privateKey,
   699  		Algorithm: sigAlgForKey(rs.t, privateKey.Public()),
   700  	}
   701  
   702  	opts := &jose.SignerOptions{
   703  		NonceSource:  rs.nonceService,
   704  		EmbedJWK:     true,
   705  		ExtraHeaders: headers,
   706  	}
   707  
   708  	signer, err := jose.NewSigner(signerKey, opts)
   709  	test.AssertNotError(rs.t, err, "Failed to make signer")
   710  
   711  	jws, err := signer.Sign([]byte(""))
   712  	test.AssertNotError(rs.t, err, "Failed to sign req")
   713  
   714  	body := jws.FullSerialize()
   715  	parsedJWS, err := jose.ParseSigned(body, getSupportedAlgs())
   716  	test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
   717  
   718  	return parsedJWS, body
   719  }
   720  
   721  func TestValidPOSTURL(t *testing.T) {
   722  	wfe, _, signer := setupWFE(t)
   723  
   724  	// A JWS and HTTP request with no extra headers
   725  	noHeadersJWS, noHeadersJWSBody := signer.signExtraHeaders(nil)
   726  	noHeadersRequest := makePostRequestWithPath("test-path", noHeadersJWSBody)
   727  
   728  	// A JWS and HTTP request with extra headers, but no "url" extra header
   729  	noURLHeaders := map[jose.HeaderKey]any{
   730  		"nifty": "swell",
   731  	}
   732  	noURLHeaderJWS, noURLHeaderJWSBody := signer.signExtraHeaders(noURLHeaders)
   733  	noURLHeaderRequest := makePostRequestWithPath("test-path", noURLHeaderJWSBody)
   734  
   735  	// A JWS and HTTP request with a mismatched HTTP URL to JWS "url" header
   736  	wrongURLHeaders := map[jose.HeaderKey]any{
   737  		"url": "foobar",
   738  	}
   739  	wrongURLHeaderJWS, wrongURLHeaderJWSBody := signer.signExtraHeaders(wrongURLHeaders)
   740  	wrongURLHeaderRequest := makePostRequestWithPath("test-path", wrongURLHeaderJWSBody)
   741  
   742  	correctURLHeaderJWS, _, correctURLHeaderJWSBody := signer.embeddedJWK(nil, "http://localhost/test-path", "")
   743  	correctURLHeaderRequest := makePostRequestWithPath("test-path", correctURLHeaderJWSBody)
   744  
   745  	testCases := []struct {
   746  		Name          string
   747  		JWS           *jose.JSONWebSignature
   748  		Request       *http.Request
   749  		WantErrType   berrors.ErrorType
   750  		WantErrDetail string
   751  		WantStatType  string
   752  	}{
   753  		{
   754  			Name:          "No extra headers in JWS",
   755  			JWS:           noHeadersJWS,
   756  			Request:       noHeadersRequest,
   757  			WantErrType:   berrors.Malformed,
   758  			WantErrDetail: "JWS header parameter 'url' required",
   759  			WantStatType:  "JWSNoExtraHeaders",
   760  		},
   761  		{
   762  			Name:          "No URL header in JWS",
   763  			JWS:           noURLHeaderJWS,
   764  			Request:       noURLHeaderRequest,
   765  			WantErrType:   berrors.Malformed,
   766  			WantErrDetail: "JWS header parameter 'url' required",
   767  			WantStatType:  "JWSMissingURL",
   768  		},
   769  		{
   770  			Name:          "Wrong URL header in JWS",
   771  			JWS:           wrongURLHeaderJWS,
   772  			Request:       wrongURLHeaderRequest,
   773  			WantErrType:   berrors.Malformed,
   774  			WantErrDetail: "JWS header parameter 'url' incorrect. Expected \"http://localhost/test-path\" got \"foobar\"",
   775  			WantStatType:  "JWSMismatchedURL",
   776  		},
   777  		{
   778  			Name:    "Correct URL header in JWS",
   779  			JWS:     correctURLHeaderJWS,
   780  			Request: correctURLHeaderRequest,
   781  		},
   782  	}
   783  
   784  	for _, tc := range testCases {
   785  		t.Run(tc.Name, func(t *testing.T) {
   786  			in := tc.JWS.Signatures[0].Header
   787  			tc.Request.Header.Add("Content-Type", expectedJWSContentType)
   788  			wfe.stats.joseErrorCount.Reset()
   789  
   790  			got := wfe.validPOSTURL(tc.Request, in)
   791  			if tc.WantErrDetail == "" {
   792  				if got != nil {
   793  					t.Fatalf("validPOSTURL(%#v) = %#v, want nil", in, got)
   794  				}
   795  			} else {
   796  				berr, ok := got.(*berrors.BoulderError)
   797  				if !ok {
   798  					t.Fatalf("validPOSTURL(%#v) returned %T, want BoulderError", in, got)
   799  				}
   800  				if berr.Type != tc.WantErrType {
   801  					t.Errorf("validPOSTURL(%#v) = %#v, want %#v", in, berr.Type, tc.WantErrType)
   802  				}
   803  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
   804  					t.Errorf("validPOSTURL(%#v) = %q, want %q", in, berr.Detail, tc.WantErrDetail)
   805  				}
   806  				test.AssertMetricWithLabelsEquals(
   807  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
   808  			}
   809  		})
   810  	}
   811  }
   812  
   813  func (rs requestSigner) multiSigJWS() (*jose.JSONWebSignature, string) {
   814  	privateKeyA := loadKey(rs.t, []byte(test1KeyPrivatePEM))
   815  	privateKeyB := loadKey(rs.t, []byte(test2KeyPrivatePEM))
   816  
   817  	signerKeyA := jose.SigningKey{
   818  		Key:       privateKeyA,
   819  		Algorithm: sigAlgForKey(rs.t, privateKeyA.Public()),
   820  	}
   821  
   822  	signerKeyB := jose.SigningKey{
   823  		Key:       privateKeyB,
   824  		Algorithm: sigAlgForKey(rs.t, privateKeyB.Public()),
   825  	}
   826  
   827  	opts := &jose.SignerOptions{
   828  		NonceSource: rs.nonceService,
   829  		EmbedJWK:    true,
   830  	}
   831  
   832  	signer, err := jose.NewMultiSigner([]jose.SigningKey{signerKeyA, signerKeyB}, opts)
   833  	test.AssertNotError(rs.t, err, "Failed to make multi signer")
   834  
   835  	jws, err := signer.Sign([]byte(""))
   836  	test.AssertNotError(rs.t, err, "Failed to sign req")
   837  
   838  	body := jws.FullSerialize()
   839  	parsedJWS, err := jose.ParseSigned(body, getSupportedAlgs())
   840  	test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
   841  
   842  	return parsedJWS, body
   843  }
   844  
   845  func TestParseJWSRequest(t *testing.T) {
   846  	wfe, _, signer := setupWFE(t)
   847  
   848  	_, tooManySigsJWSBody := signer.multiSigJWS()
   849  
   850  	_, _, validJWSBody := signer.embeddedJWK(nil, "http://localhost/test-path", "")
   851  	validJWSRequest := makePostRequestWithPath("test-path", validJWSBody)
   852  
   853  	_, _, validJWSBody2 := signer.embeddedJWK(nil, "http://localhost/test-path", "")
   854  	validJWSChunkedRequest := makeChunkedPostRequestWithPath("test-path", validJWSBody2)
   855  
   856  	missingSigsJWSBody := `{"payload":"Zm9x","protected":"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoicW5BUkxyVDdYejRnUmNLeUxkeWRtQ3ItZXk5T3VQSW1YNFg0MHRoazNvbjI2RmtNem5SM2ZSanM2NmVMSzdtbVBjQlo2dU9Kc2VVUlU2d0FhWk5tZW1vWXgxZE12cXZXV0l5aVFsZUhTRDdROHZCcmhSNnVJb080akF6SlpSLUNoelp1U0R0N2lITi0zeFVWc3B1NVhHd1hVX01WSlpzaFR3cDRUYUZ4NWVsSElUX09iblR2VE9VM1hoaXNoMDdBYmdaS21Xc1ZiWGg1cy1DcklpY1U0T2V4SlBndW5XWl9ZSkp1ZU9LbVR2bkxsVFY0TXpLUjJvWmxCS1oyN1MwLVNmZFZfUUR4X3lkbGU1b01BeUtWdGxBVjM1Y3lQTUlzWU53Z1VHQkNkWV8yVXppNWVYMGxUYzdNUFJ3ejZxUjFraXAtaTU5VmNHY1VRZ3FIVjZGeXF3IiwiZSI6IkFRQUIifSwia2lkIjoiIiwibm9uY2UiOiJyNHpuenZQQUVwMDlDN1JwZUtYVHhvNkx3SGwxZVBVdmpGeXhOSE1hQnVvIiwidXJsIjoiaHR0cDovL2xvY2FsaG9zdC9hY21lL25ldy1yZWcifQ"}`
   857  	missingSigsJWSRequest := makePostRequestWithPath("test-path", missingSigsJWSBody)
   858  
   859  	unprotectedHeadersJWSBody := `
   860  {
   861    "header": {
   862      "alg": "RS256",
   863      "kid": "unprotected key id"
   864    },
   865    "protected": "eyJub25jZSI6ICJibTl1WTJVIiwgInVybCI6ICJodHRwOi8vbG9jYWxob3N0L3Rlc3QiLCAia2lkIjogInRlc3RrZXkifQ", 
   866    "payload": "Zm9v",
   867    "signature": "PKWWclRsiHF4bm-nmpxDez6Y_3Mdtu263YeYklbGYt1EiMOLiKY_dr_EqhUUKAKEWysFLO-hQLXVU7kVkHeYWQFFOA18oFgcZgkSF2Pr3DNZrVj9e2gl0eZ2i2jk6X5GYPt1lIfok_DrL92wrxEKGcrmxqXXGm0JgP6Al2VGapKZK2HaYbCHoGvtzNmzUX9rC21sKewq5CquJRvTmvQp5bmU7Q9KeafGibFr0jl6IA3W5LBGgf6xftuUtEVEbKmKaKtaG7tXsQH1mIVOPUZZoLWz9sWJSFLmV0QSXm3ZHV0DrOhLfcADbOCoQBMeGdseBQZuUO541A3BEKGv2Aikjw"
   868  }
   869  `
   870  
   871  	wrongSignaturesFieldJWSBody := `
   872  {
   873    "protected": "eyJub25jZSI6ICJibTl1WTJVIiwgInVybCI6ICJodHRwOi8vbG9jYWxob3N0L3Rlc3QiLCAia2lkIjogInRlc3RrZXkifQ", 
   874    "payload": "Zm9v",
   875    "signatures": ["PKWWclRsiHF4bm-nmpxDez6Y_3Mdtu263YeYklbGYt1EiMOLiKY_dr_EqhUUKAKEWysFLO-hQLXVU7kVkHeYWQFFOA18oFgcZgkSF2Pr3DNZrVj9e2gl0eZ2i2jk6X5GYPt1lIfok_DrL92wrxEKGcrmxqXXGm0JgP6Al2VGapKZK2HaYbCHoGvtzNmzUX9rC21sKewq5CquJRvTmvQp5bmU7Q9KeafGibFr0jl6IA3W5LBGgf6xftuUtEVEbKmKaKtaG7tXsQH1mIVOPUZZoLWz9sWJSFLmV0QSXm3ZHV0DrOhLfcADbOCoQBMeGdseBQZuUO541A3BEKGv2Aikjw"]
   876  }
   877  `
   878  	wrongSignatureTypeJWSBody := `
   879  {
   880    "protected": "eyJhbGciOiJIUzI1NiJ9",
   881    "payload" : "IiI",
   882    "signature" : "5WiUupHzCWfpJza6EMteSxMDY8_6xIV7HnKaUqmykIQ"
   883  }
   884  `
   885  
   886  	testCases := []struct {
   887  		Name          string
   888  		Request       *http.Request
   889  		WantErrType   berrors.ErrorType
   890  		WantErrDetail string
   891  		WantStatType  string
   892  	}{
   893  		{
   894  			Name:          "Invalid JWS in POST body",
   895  			Request:       makePostRequestWithPath("test-path", `{`),
   896  			WantErrType:   berrors.Malformed,
   897  			WantErrDetail: "Parse error reading JWS",
   898  			WantStatType:  "JWSUnmarshalFailed",
   899  		},
   900  		{
   901  			Name:          "Too few signatures in JWS",
   902  			Request:       missingSigsJWSRequest,
   903  			WantErrType:   berrors.Malformed,
   904  			WantErrDetail: "POST JWS not signed",
   905  			WantStatType:  "JWSEmptySignature",
   906  		},
   907  		{
   908  			Name:          "Too many signatures in JWS",
   909  			Request:       makePostRequestWithPath("test-path", tooManySigsJWSBody),
   910  			WantErrType:   berrors.Malformed,
   911  			WantErrDetail: "JWS \"signatures\" field not allowed. Only the \"signature\" field should contain a signature",
   912  			WantStatType:  "JWSMultiSig",
   913  		},
   914  		{
   915  			Name:          "Unprotected JWS headers",
   916  			Request:       makePostRequestWithPath("test-path", unprotectedHeadersJWSBody),
   917  			WantErrType:   berrors.Malformed,
   918  			WantErrDetail: "JWS \"header\" field not allowed. All headers must be in \"protected\" field",
   919  			WantStatType:  "JWSUnprotectedHeaders",
   920  		},
   921  		{
   922  			Name:          "Unsupported signatures field in JWS",
   923  			Request:       makePostRequestWithPath("test-path", wrongSignaturesFieldJWSBody),
   924  			WantErrType:   berrors.Malformed,
   925  			WantErrDetail: "JWS \"signatures\" field not allowed. Only the \"signature\" field should contain a signature",
   926  			WantStatType:  "JWSMultiSig",
   927  		},
   928  		{
   929  			Name:          "JWS with an invalid algorithm",
   930  			Request:       makePostRequestWithPath("test-path", wrongSignatureTypeJWSBody),
   931  			WantErrType:   berrors.BadSignatureAlgorithm,
   932  			WantErrDetail: "JWS signature header contains unsupported algorithm \"HS256\", expected one of [RS256 ES256 ES384 ES512]",
   933  			WantStatType:  "JWSAlgorithmCheckFailed",
   934  		},
   935  		{
   936  			Name:    "Valid JWS in POST request",
   937  			Request: validJWSRequest,
   938  		},
   939  		{
   940  			Name:    "Valid JWS in chunked encoded POST request",
   941  			Request: validJWSChunkedRequest,
   942  		},
   943  		{
   944  			Name:          "POST body too large",
   945  			Request:       makePostRequestWithPath("test-path", fmt.Sprintf(`{"a":"%s"}`, strings.Repeat("a", 50000))),
   946  			WantErrType:   berrors.Unauthorized,
   947  			WantErrDetail: "request body too large",
   948  		},
   949  		{
   950  			Name:          "chunked encoded POST body too large",
   951  			Request:       makeChunkedPostRequestWithPath("test-path", fmt.Sprintf(`{"a":"%s"}`, strings.Repeat("a", 50000))),
   952  			WantErrType:   berrors.Unauthorized,
   953  			WantErrDetail: "request body too large",
   954  		},
   955  	}
   956  
   957  	for _, tc := range testCases {
   958  		t.Run(tc.Name, func(t *testing.T) {
   959  			wfe.stats.joseErrorCount.Reset()
   960  
   961  			_, gotErr := wfe.parseJWSRequest(tc.Request)
   962  			if tc.WantErrDetail == "" {
   963  				if gotErr != nil {
   964  					t.Fatalf("parseJWSRequest(%#v) = %#v, want nil", tc.Request, gotErr)
   965  				}
   966  			} else {
   967  				berr, ok := gotErr.(*berrors.BoulderError)
   968  				if !ok {
   969  					t.Fatalf("parseJWSRequest(%#v) returned %T, want BoulderError", tc.Request, gotErr)
   970  				}
   971  				if berr.Type != tc.WantErrType {
   972  					t.Errorf("parseJWSRequest(%#v) = %#v, want %#v", tc.Request, berr.Type, tc.WantErrType)
   973  				}
   974  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
   975  					t.Errorf("parseJWSRequest(%#v) = %q, want %q", tc.Request, berr.Detail, tc.WantErrDetail)
   976  				}
   977  				if tc.WantStatType != "" {
   978  					test.AssertMetricWithLabelsEquals(
   979  						t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
   980  				}
   981  			}
   982  		})
   983  	}
   984  }
   985  
   986  func TestExtractJWK(t *testing.T) {
   987  	wfe, _, signer := setupWFE(t)
   988  
   989  	keyIDJWS, _, _ := signer.byKeyID(1, nil, "", "")
   990  	goodJWS, goodJWK, _ := signer.embeddedJWK(nil, "", "")
   991  
   992  	testCases := []struct {
   993  		Name          string
   994  		JWS           *jose.JSONWebSignature
   995  		WantKey       *jose.JSONWebKey
   996  		WantErrType   berrors.ErrorType
   997  		WantErrDetail string
   998  	}{
   999  		{
  1000  			Name:          "JWS with wrong auth type (Key ID vs embedded JWK)",
  1001  			JWS:           keyIDJWS,
  1002  			WantErrType:   berrors.Malformed,
  1003  			WantErrDetail: "No embedded JWK in JWS header",
  1004  		},
  1005  		{
  1006  			Name:    "Valid JWS with embedded JWK",
  1007  			JWS:     goodJWS,
  1008  			WantKey: goodJWK,
  1009  		},
  1010  	}
  1011  
  1012  	for _, tc := range testCases {
  1013  		t.Run(tc.Name, func(t *testing.T) {
  1014  			in := tc.JWS.Signatures[0].Header
  1015  
  1016  			gotKey, gotErr := wfe.extractJWK(in)
  1017  			if tc.WantErrDetail == "" {
  1018  				if gotErr != nil {
  1019  					t.Fatalf("extractJWK(%#v) = %#v, want nil", in, gotKey)
  1020  				}
  1021  				test.AssertMarshaledEquals(t, gotKey, tc.WantKey)
  1022  			} else {
  1023  				berr, ok := gotErr.(*berrors.BoulderError)
  1024  				if !ok {
  1025  					t.Fatalf("extractJWK(%#v) returned %T, want BoulderError", in, gotErr)
  1026  				}
  1027  				if berr.Type != tc.WantErrType {
  1028  					t.Errorf("extractJWK(%#v) = %#v, want %#v", in, berr.Type, tc.WantErrType)
  1029  				}
  1030  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
  1031  					t.Errorf("extractJWK(%#v) = %q, want %q", in, berr.Detail, tc.WantErrDetail)
  1032  				}
  1033  			}
  1034  		})
  1035  	}
  1036  }
  1037  
  1038  func (rs requestSigner) specifyKeyID(keyID string) (*jose.JSONWebSignature, string) {
  1039  	privateKey := loadKey(rs.t, []byte(test1KeyPrivatePEM))
  1040  
  1041  	if keyID == "" {
  1042  		keyID = "this is an invalid non-numeric key ID"
  1043  	}
  1044  
  1045  	jwk := &jose.JSONWebKey{
  1046  		Key:       privateKey,
  1047  		Algorithm: "RSA",
  1048  		KeyID:     keyID,
  1049  	}
  1050  
  1051  	signerKey := jose.SigningKey{
  1052  		Key:       jwk,
  1053  		Algorithm: jose.RS256,
  1054  	}
  1055  
  1056  	opts := &jose.SignerOptions{
  1057  		NonceSource: rs.nonceService,
  1058  		ExtraHeaders: map[jose.HeaderKey]any{
  1059  			"url": "http://localhost",
  1060  		},
  1061  	}
  1062  
  1063  	signer, err := jose.NewSigner(signerKey, opts)
  1064  	test.AssertNotError(rs.t, err, "Failed to make signer")
  1065  
  1066  	jws, err := signer.Sign([]byte(""))
  1067  	test.AssertNotError(rs.t, err, "Failed to sign req")
  1068  
  1069  	body := jws.FullSerialize()
  1070  	parsedJWS, err := jose.ParseSigned(body, getSupportedAlgs())
  1071  	test.AssertNotError(rs.t, err, "Failed to parse generated JWS")
  1072  
  1073  	return parsedJWS, body
  1074  }
  1075  
  1076  func TestLookupJWK(t *testing.T) {
  1077  	wfe, _, signer := setupWFE(t)
  1078  
  1079  	embeddedJWS, _, embeddedJWSBody := signer.embeddedJWK(nil, "", "")
  1080  	invalidKeyIDJWS, invalidKeyIDJWSBody := signer.specifyKeyID("https://acme-99.lettuceencrypt.org/acme/reg/1")
  1081  	// ID 100 is mocked to return a non-missing error from sa.GetRegistration
  1082  	errorIDJWS, _, errorIDJWSBody := signer.byKeyID(100, nil, "", "")
  1083  	// ID 102 is mocked to return an account does not exist error from sa.GetRegistration
  1084  	missingIDJWS, _, missingIDJWSBody := signer.byKeyID(102, nil, "", "")
  1085  	// ID 3 is mocked to return a deactivated account from sa.GetRegistration
  1086  	deactivatedIDJWS, _, deactivatedIDJWSBody := signer.byKeyID(3, nil, "", "")
  1087  
  1088  	wfe.LegacyKeyIDPrefix = "https://acme-v00.lettuceencrypt.org/acme/reg/"
  1089  	legacyKeyIDJWS, legacyKeyIDJWSBody := signer.specifyKeyID(wfe.LegacyKeyIDPrefix + "1")
  1090  
  1091  	nonNumericKeyIDJWS, nonNumericKeyIDJWSBody := signer.specifyKeyID(wfe.LegacyKeyIDPrefix + "abcd")
  1092  
  1093  	validJWS, validKey, validJWSBody := signer.byKeyID(1, nil, "", "")
  1094  	validAccountPB, _ := wfe.sa.GetRegistration(context.Background(), &sapb.RegistrationID{Id: 1})
  1095  	validAccount, _ := bgrpc.PbToRegistration(validAccountPB)
  1096  
  1097  	// good key, log event requester is set
  1098  
  1099  	testCases := []struct {
  1100  		Name          string
  1101  		JWS           *jose.JSONWebSignature
  1102  		Request       *http.Request
  1103  		WantJWK       *jose.JSONWebKey
  1104  		WantAccount   *core.Registration
  1105  		WantErrType   berrors.ErrorType
  1106  		WantErrDetail string
  1107  		WantStatType  string
  1108  	}{
  1109  		{
  1110  			Name:          "JWS with wrong auth type (embedded JWK vs Key ID)",
  1111  			JWS:           embeddedJWS,
  1112  			Request:       makePostRequestWithPath("test-path", embeddedJWSBody),
  1113  			WantErrType:   berrors.Malformed,
  1114  			WantErrDetail: "No Key ID in JWS header",
  1115  			WantStatType:  "JWSAuthTypeWrong",
  1116  		},
  1117  		{
  1118  			Name:          "JWS with invalid key ID URL",
  1119  			JWS:           invalidKeyIDJWS,
  1120  			Request:       makePostRequestWithPath("test-path", invalidKeyIDJWSBody),
  1121  			WantErrType:   berrors.Malformed,
  1122  			WantErrDetail: "KeyID header contained an invalid account URL: \"https://acme-99.lettuceencrypt.org/acme/reg/1\"",
  1123  			WantStatType:  "JWSInvalidKeyID",
  1124  		},
  1125  		{
  1126  			Name:          "JWS with non-numeric account ID in key ID URL",
  1127  			JWS:           nonNumericKeyIDJWS,
  1128  			Request:       makePostRequestWithPath("test-path", nonNumericKeyIDJWSBody),
  1129  			WantErrType:   berrors.Malformed,
  1130  			WantErrDetail: "Malformed account ID in KeyID header URL: \"https://acme-v00.lettuceencrypt.org/acme/reg/abcd\"",
  1131  			WantStatType:  "JWSInvalidKeyID",
  1132  		},
  1133  		{
  1134  			Name:          "JWS with account ID that causes GetRegistration error",
  1135  			JWS:           errorIDJWS,
  1136  			Request:       makePostRequestWithPath("test-path", errorIDJWSBody),
  1137  			WantErrType:   berrors.InternalServer,
  1138  			WantErrDetail: "Error retrieving account \"http://localhost/acme/acct/100\"",
  1139  			WantStatType:  "JWSKeyIDLookupFailed",
  1140  		},
  1141  		{
  1142  			Name:          "JWS with account ID that doesn't exist",
  1143  			JWS:           missingIDJWS,
  1144  			Request:       makePostRequestWithPath("test-path", missingIDJWSBody),
  1145  			WantErrType:   berrors.AccountDoesNotExist,
  1146  			WantErrDetail: "Account \"http://localhost/acme/acct/102\" not found",
  1147  			WantStatType:  "JWSKeyIDNotFound",
  1148  		},
  1149  		{
  1150  			Name:          "JWS with account ID that is deactivated",
  1151  			JWS:           deactivatedIDJWS,
  1152  			Request:       makePostRequestWithPath("test-path", deactivatedIDJWSBody),
  1153  			WantErrType:   berrors.Unauthorized,
  1154  			WantErrDetail: "Account is not valid, has status \"deactivated\"",
  1155  			WantStatType:  "JWSKeyIDAccountInvalid",
  1156  		},
  1157  		{
  1158  			Name:        "Valid JWS with legacy account ID",
  1159  			JWS:         legacyKeyIDJWS,
  1160  			Request:     makePostRequestWithPath("test-path", legacyKeyIDJWSBody),
  1161  			WantJWK:     validKey,
  1162  			WantAccount: &validAccount,
  1163  		},
  1164  		{
  1165  			Name:        "Valid JWS with valid account ID",
  1166  			JWS:         validJWS,
  1167  			Request:     makePostRequestWithPath("test-path", validJWSBody),
  1168  			WantJWK:     validKey,
  1169  			WantAccount: &validAccount,
  1170  		},
  1171  	}
  1172  	for _, tc := range testCases {
  1173  		t.Run(tc.Name, func(t *testing.T) {
  1174  			wfe.stats.joseErrorCount.Reset()
  1175  			in := tc.JWS.Signatures[0].Header
  1176  			inputLogEvent := newRequestEvent()
  1177  
  1178  			gotJWK, gotAcct, gotErr := wfe.lookupJWK(in, context.Background(), tc.Request, inputLogEvent)
  1179  			if tc.WantErrDetail == "" {
  1180  				if gotErr != nil {
  1181  					t.Fatalf("lookupJWK(%#v) = %#v, want nil", in, gotErr)
  1182  				}
  1183  				gotThumb, _ := gotJWK.Thumbprint(crypto.SHA256)
  1184  				wantThumb, _ := tc.WantJWK.Thumbprint(crypto.SHA256)
  1185  				if !slices.Equal(gotThumb, wantThumb) {
  1186  					t.Fatalf("lookupJWK(%#v) = %#v, want %#v", tc.Request, gotThumb, wantThumb)
  1187  				}
  1188  				test.AssertMarshaledEquals(t, gotAcct, tc.WantAccount)
  1189  				test.AssertEquals(t, inputLogEvent.Requester, gotAcct.ID)
  1190  			} else {
  1191  				var berr *berrors.BoulderError
  1192  				ok := errors.As(gotErr, &berr)
  1193  				if !ok {
  1194  					t.Fatalf("lookupJWK(%#v) returned %T, want BoulderError", in, gotErr)
  1195  				}
  1196  				if berr.Type != tc.WantErrType {
  1197  					t.Errorf("lookupJWK(%#v) = %#v, want %#v", in, berr.Type, tc.WantErrType)
  1198  				}
  1199  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
  1200  					t.Errorf("lookupJWK(%#v) = %q, want %q", in, berr.Detail, tc.WantErrDetail)
  1201  				}
  1202  				test.AssertMetricWithLabelsEquals(
  1203  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
  1204  			}
  1205  		})
  1206  	}
  1207  }
  1208  
  1209  func TestValidJWSForKey(t *testing.T) {
  1210  	wfe, _, signer := setupWFE(t)
  1211  
  1212  	payload := `{ "test": "payload" }`
  1213  	testURL := "http://localhost/test"
  1214  	goodJWS, goodJWK, _ := signer.embeddedJWK(nil, testURL, payload)
  1215  
  1216  	// badSigJWSBody is a JWS that has had the payload changed by 1 byte to break the signature
  1217  	badSigJWSBody := `{"payload":"Zm9x","protected":"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoicW5BUkxyVDdYejRnUmNLeUxkeWRtQ3ItZXk5T3VQSW1YNFg0MHRoazNvbjI2RmtNem5SM2ZSanM2NmVMSzdtbVBjQlo2dU9Kc2VVUlU2d0FhWk5tZW1vWXgxZE12cXZXV0l5aVFsZUhTRDdROHZCcmhSNnVJb080akF6SlpSLUNoelp1U0R0N2lITi0zeFVWc3B1NVhHd1hVX01WSlpzaFR3cDRUYUZ4NWVsSElUX09iblR2VE9VM1hoaXNoMDdBYmdaS21Xc1ZiWGg1cy1DcklpY1U0T2V4SlBndW5XWl9ZSkp1ZU9LbVR2bkxsVFY0TXpLUjJvWmxCS1oyN1MwLVNmZFZfUUR4X3lkbGU1b01BeUtWdGxBVjM1Y3lQTUlzWU53Z1VHQkNkWV8yVXppNWVYMGxUYzdNUFJ3ejZxUjFraXAtaTU5VmNHY1VRZ3FIVjZGeXF3IiwiZSI6IkFRQUIifSwia2lkIjoiIiwibm9uY2UiOiJyNHpuenZQQUVwMDlDN1JwZUtYVHhvNkx3SGwxZVBVdmpGeXhOSE1hQnVvIiwidXJsIjoiaHR0cDovL2xvY2FsaG9zdC9hY21lL25ldy1yZWcifQ","signature":"jcTdxSygm_cvD7KbXqsxgnoPApCTSkV4jolToSOd2ciRkg5W7Yl0ZKEEKwOc-dYIbQiwGiDzisyPCicwWsOUA1WSqHylKvZ3nxSMc6KtwJCW2DaOqcf0EEjy5VjiZJUrOt2c-r6b07tbn8sfOJKwlF2lsOeGi4s-rtvvkeQpAU-AWauzl9G4bv2nDUeCviAZjHx_PoUC-f9GmZhYrbDzAvXZ859ktM6RmMeD0OqPN7bhAeju2j9Gl0lnryZMtq2m0J2m1ucenQBL1g4ZkP1JiJvzd2cAz5G7Ftl2YeJJyWhqNd3qq0GVOt1P11s8PTGNaSoM0iR9QfUxT9A6jxARtg"}`
  1218  	badJWS, err := jose.ParseSigned(badSigJWSBody, getSupportedAlgs())
  1219  	test.AssertNotError(t, err, "error loading badSigJWS body")
  1220  
  1221  	// wrongAlgJWS is a JWS that has an invalid "HS256" algorithm in its header
  1222  	wrongAlgJWS := &jose.JSONWebSignature{
  1223  		Signatures: []jose.Signature{
  1224  			{
  1225  				Header: jose.Header{
  1226  					Algorithm: "HS256",
  1227  				},
  1228  			},
  1229  		},
  1230  	}
  1231  
  1232  	// A JWS and HTTP request with a mismatched HTTP URL to JWS "url" header
  1233  	wrongURLHeaders := map[jose.HeaderKey]any{
  1234  		"url": "foobar",
  1235  	}
  1236  	wrongURLHeaderJWS, _ := signer.signExtraHeaders(wrongURLHeaders)
  1237  
  1238  	// badJSONJWS has a valid signature over a body that is not valid JSON
  1239  	badJSONJWS, _, _ := signer.embeddedJWK(nil, testURL, `{`)
  1240  
  1241  	testCases := []struct {
  1242  		Name          string
  1243  		JWS           bJSONWebSignature
  1244  		JWK           *jose.JSONWebKey
  1245  		Body          string
  1246  		WantErrType   berrors.ErrorType
  1247  		WantErrDetail string
  1248  		WantStatType  string
  1249  	}{
  1250  		{
  1251  			Name:          "JWS with an invalid algorithm",
  1252  			JWS:           bJSONWebSignature{wrongAlgJWS},
  1253  			JWK:           goodJWK,
  1254  			WantErrType:   berrors.BadSignatureAlgorithm,
  1255  			WantErrDetail: "JWS signature header contains unsupported algorithm \"HS256\", expected one of [RS256 ES256 ES384 ES512]",
  1256  			WantStatType:  "JWSAlgorithmCheckFailed",
  1257  		},
  1258  		{
  1259  			Name:          "JWS with an unredeemable nonce",
  1260  			JWS:           bJSONWebSignature{signer.makeJWS(badNonceProvider{})},
  1261  			JWK:           goodJWK,
  1262  			WantErrType:   berrors.BadNonce,
  1263  			WantErrDetail: "unable to decrypt nonce",
  1264  			WantStatType:  "JWSUnredeemableNonce",
  1265  		},
  1266  		{
  1267  			Name:          "JWS with broken signature",
  1268  			JWS:           bJSONWebSignature{badJWS},
  1269  			JWK:           badJWS.Signatures[0].Header.JSONWebKey,
  1270  			WantErrType:   berrors.Malformed,
  1271  			WantErrDetail: "JWS verification error",
  1272  			WantStatType:  "JWSVerifyFailed",
  1273  		},
  1274  		{
  1275  			Name:          "JWS with incorrect URL",
  1276  			JWS:           bJSONWebSignature{wrongURLHeaderJWS},
  1277  			JWK:           wrongURLHeaderJWS.Signatures[0].Header.JSONWebKey,
  1278  			WantErrType:   berrors.Malformed,
  1279  			WantErrDetail: "JWS header parameter 'url' incorrect. Expected \"http://localhost/test\" got \"foobar\"",
  1280  			WantStatType:  "JWSMismatchedURL",
  1281  		},
  1282  		{
  1283  			Name:          "Valid JWS with invalid JSON in the protected body",
  1284  			JWS:           bJSONWebSignature{badJSONJWS},
  1285  			JWK:           goodJWK,
  1286  			WantErrType:   berrors.Malformed,
  1287  			WantErrDetail: "Request payload did not parse as JSON",
  1288  			WantStatType:  "JWSBodyUnmarshalFailed",
  1289  		},
  1290  		{
  1291  			Name: "Good JWS and JWK",
  1292  			JWS:  bJSONWebSignature{goodJWS},
  1293  			JWK:  goodJWK,
  1294  		},
  1295  	}
  1296  
  1297  	for _, tc := range testCases {
  1298  		t.Run(tc.Name, func(t *testing.T) {
  1299  			wfe.stats.joseErrorCount.Reset()
  1300  			request := makePostRequestWithPath("test", tc.Body)
  1301  
  1302  			gotPayload, gotErr := wfe.validJWSForKey(context.Background(), &tc.JWS, tc.JWK, request)
  1303  			if tc.WantErrDetail == "" {
  1304  				if gotErr != nil {
  1305  					t.Fatalf("validJWSForKey(%#v, %#v, %#v) = %#v, want nil", tc.JWS, tc.JWK, request, gotErr)
  1306  				}
  1307  				if string(gotPayload) != payload {
  1308  					t.Fatalf("validJWSForKey(%#v, %#v, %#v) = %q, want %q", tc.JWS, tc.JWK, request, string(gotPayload), payload)
  1309  				}
  1310  			} else {
  1311  				berr, ok := gotErr.(*berrors.BoulderError)
  1312  				if !ok {
  1313  					t.Fatalf("validJWSForKey(%#v, %#v, %#v) returned %T, want BoulderError", tc.JWS, tc.JWK, request, gotErr)
  1314  				}
  1315  				if berr.Type != tc.WantErrType {
  1316  					t.Errorf("validJWSForKey(%#v, %#v, %#v) = %#v, want %#v", tc.JWS, tc.JWK, request, berr.Type, tc.WantErrType)
  1317  				}
  1318  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
  1319  					t.Errorf("validJWSForKey(%#v, %#v, %#v) = %q, want %q", tc.JWS, tc.JWK, request, berr.Detail, tc.WantErrDetail)
  1320  				}
  1321  				test.AssertMetricWithLabelsEquals(
  1322  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
  1323  			}
  1324  		})
  1325  	}
  1326  }
  1327  
  1328  func TestValidPOSTForAccount(t *testing.T) {
  1329  	wfe, _, signer := setupWFE(t)
  1330  
  1331  	validJWS, _, validJWSBody := signer.byKeyID(1, nil, "http://localhost/test", `{"test":"passed"}`)
  1332  	validAccountPB, _ := wfe.sa.GetRegistration(context.Background(), &sapb.RegistrationID{Id: 1})
  1333  	validAccount, _ := bgrpc.PbToRegistration(validAccountPB)
  1334  
  1335  	// ID 102 is mocked to return missing
  1336  	_, _, missingJWSBody := signer.byKeyID(102, nil, "http://localhost/test", "{}")
  1337  
  1338  	// ID 3 is mocked to return deactivated
  1339  	key3 := loadKey(t, []byte(test3KeyPrivatePEM))
  1340  	_, _, deactivatedJWSBody := signer.byKeyID(3, key3, "http://localhost/test", "{}")
  1341  
  1342  	_, _, embeddedJWSBody := signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
  1343  
  1344  	testCases := []struct {
  1345  		Name          string
  1346  		Request       *http.Request
  1347  		WantPayload   string
  1348  		WantAcct      *core.Registration
  1349  		WantJWS       *jose.JSONWebSignature
  1350  		WantErrType   berrors.ErrorType
  1351  		WantErrDetail string
  1352  		WantStatType  string
  1353  	}{
  1354  		{
  1355  			Name:          "Invalid JWS",
  1356  			Request:       makePostRequestWithPath("test", "foo"),
  1357  			WantErrType:   berrors.Malformed,
  1358  			WantErrDetail: "Parse error reading JWS",
  1359  			WantStatType:  "JWSUnmarshalFailed",
  1360  		},
  1361  		{
  1362  			Name:          "Embedded Key JWS",
  1363  			Request:       makePostRequestWithPath("test", embeddedJWSBody),
  1364  			WantErrType:   berrors.Malformed,
  1365  			WantErrDetail: "No Key ID in JWS header",
  1366  			WantStatType:  "JWSAuthTypeWrong",
  1367  		},
  1368  		{
  1369  			Name:          "JWS signed by account that doesn't exist",
  1370  			Request:       makePostRequestWithPath("test", missingJWSBody),
  1371  			WantErrType:   berrors.AccountDoesNotExist,
  1372  			WantErrDetail: "Account \"http://localhost/acme/acct/102\" not found",
  1373  			WantStatType:  "JWSKeyIDNotFound",
  1374  		},
  1375  		{
  1376  			Name:          "JWS signed by account that's deactivated",
  1377  			Request:       makePostRequestWithPath("test", deactivatedJWSBody),
  1378  			WantErrType:   berrors.Unauthorized,
  1379  			WantErrDetail: "Account is not valid, has status \"deactivated\"",
  1380  			WantStatType:  "JWSKeyIDAccountInvalid",
  1381  		},
  1382  		{
  1383  			Name:        "Valid JWS for account",
  1384  			Request:     makePostRequestWithPath("test", validJWSBody),
  1385  			WantPayload: `{"test":"passed"}`,
  1386  			WantAcct:    &validAccount,
  1387  			WantJWS:     validJWS,
  1388  		},
  1389  	}
  1390  
  1391  	for _, tc := range testCases {
  1392  		t.Run(tc.Name, func(t *testing.T) {
  1393  			wfe.stats.joseErrorCount.Reset()
  1394  			inputLogEvent := newRequestEvent()
  1395  
  1396  			gotPayload, gotJWS, gotAcct, gotErr := wfe.validPOSTForAccount(tc.Request, context.Background(), inputLogEvent)
  1397  			if tc.WantErrDetail == "" {
  1398  				if gotErr != nil {
  1399  					t.Fatalf("validPOSTForAccount(%#v) = %#v, want nil", tc.Request, gotErr)
  1400  				}
  1401  				if string(gotPayload) != tc.WantPayload {
  1402  					t.Fatalf("validPOSTForAccount(%#v) = %q, want %q", tc.Request, string(gotPayload), tc.WantPayload)
  1403  				}
  1404  				test.AssertMarshaledEquals(t, gotJWS, tc.WantJWS)
  1405  				test.AssertMarshaledEquals(t, gotAcct, tc.WantAcct)
  1406  			} else {
  1407  				berr, ok := gotErr.(*berrors.BoulderError)
  1408  				if !ok {
  1409  					t.Fatalf("validPOSTForAccount(%#v) returned %T, want BoulderError", tc.Request, gotErr)
  1410  				}
  1411  				if berr.Type != tc.WantErrType {
  1412  					t.Errorf("validPOSTForAccount(%#v) = %#v, want %#v", tc.Request, berr.Type, tc.WantErrType)
  1413  				}
  1414  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
  1415  					t.Errorf("validPOSTForAccount(%#v) = %q, want %q", tc.Request, berr.Detail, tc.WantErrDetail)
  1416  				}
  1417  				test.AssertMetricWithLabelsEquals(
  1418  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
  1419  			}
  1420  		})
  1421  	}
  1422  }
  1423  
  1424  // TestValidPOSTAsGETForAccount tests POST-as-GET processing. Because
  1425  // wfe.validPOSTAsGETForAccount calls `wfe.validPOSTForAccount` to do all
  1426  // processing except the empty body test we do not duplicate the
  1427  // `TestValidPOSTForAccount` testcases here.
  1428  func TestValidPOSTAsGETForAccount(t *testing.T) {
  1429  	wfe, _, signer := setupWFE(t)
  1430  
  1431  	// an invalid POST-as-GET request contains a non-empty payload. In this case
  1432  	// we test with the empty JSON payload ("{}")
  1433  	_, _, invalidPayloadRequest := signer.byKeyID(1, nil, "http://localhost/test", "{}")
  1434  	// a valid POST-as-GET request contains an empty payload.
  1435  	_, _, validRequest := signer.byKeyID(1, nil, "http://localhost/test", "")
  1436  
  1437  	testCases := []struct {
  1438  		Name          string
  1439  		Request       *http.Request
  1440  		WantErrType   berrors.ErrorType
  1441  		WantErrDetail string
  1442  		WantLogEvent  web.RequestEvent
  1443  	}{
  1444  		{
  1445  			Name:          "Non-empty JWS payload",
  1446  			Request:       makePostRequestWithPath("test", invalidPayloadRequest),
  1447  			WantErrType:   berrors.Malformed,
  1448  			WantErrDetail: "POST-as-GET requests must have an empty payload",
  1449  			WantLogEvent:  web.RequestEvent{},
  1450  		},
  1451  		{
  1452  			Name:    "Valid POST-as-GET",
  1453  			Request: makePostRequestWithPath("test", validRequest),
  1454  			WantLogEvent: web.RequestEvent{
  1455  				Method: "POST-as-GET",
  1456  			},
  1457  		},
  1458  	}
  1459  
  1460  	for _, tc := range testCases {
  1461  		t.Run(tc.Name, func(t *testing.T) {
  1462  			ev := newRequestEvent()
  1463  			_, gotErr := wfe.validPOSTAsGETForAccount(tc.Request, context.Background(), ev)
  1464  			if tc.WantErrDetail == "" {
  1465  				if gotErr != nil {
  1466  					t.Fatalf("validPOSTAsGETForAccount(%#v) = %#v, want nil", tc.Request, gotErr)
  1467  				}
  1468  			} else {
  1469  				berr, ok := gotErr.(*berrors.BoulderError)
  1470  				if !ok {
  1471  					t.Fatalf("validPOSTAsGETForAccount(%#v) returned %T, want BoulderError", tc.Request, gotErr)
  1472  				}
  1473  				if berr.Type != tc.WantErrType {
  1474  					t.Errorf("validPOSTAsGETForAccount(%#v) = %#v, want %#v", tc.Request, berr.Type, tc.WantErrType)
  1475  				}
  1476  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
  1477  					t.Errorf("validPOSTAsGETForAccount(%#v) = %q, want %q", tc.Request, berr.Detail, tc.WantErrDetail)
  1478  				}
  1479  			}
  1480  			test.AssertMarshaledEquals(t, *ev, tc.WantLogEvent)
  1481  		})
  1482  	}
  1483  }
  1484  
  1485  type mockSADifferentStoredKey struct {
  1486  	sapb.StorageAuthorityReadOnlyClient
  1487  }
  1488  
  1489  // mockSADifferentStoredKey has a GetRegistration that will always return an
  1490  // account with the test 2 key, no matter the provided ID
  1491  func (sa mockSADifferentStoredKey) GetRegistration(_ context.Context, _ *sapb.RegistrationID, _ ...grpc.CallOption) (*corepb.Registration, error) {
  1492  	return &corepb.Registration{
  1493  		Key:    []byte(test2KeyPublicJSON),
  1494  		Status: string(core.StatusValid),
  1495  	}, nil
  1496  }
  1497  
  1498  func TestValidPOSTForAccountSwappedKey(t *testing.T) {
  1499  	wfe, _, signer := setupWFE(t)
  1500  	wfe.sa = &mockSADifferentStoredKey{}
  1501  	wfe.accountGetter = wfe.sa
  1502  	event := newRequestEvent()
  1503  
  1504  	payload := `{"resource":"ima-payload"}`
  1505  	// Sign a request using test1key
  1506  	_, _, body := signer.byKeyID(1, nil, "http://localhost:4001/test", payload)
  1507  	request := makePostRequestWithPath("test", body)
  1508  
  1509  	// Ensure that ValidPOSTForAccount produces an error since the
  1510  	// mockSADifferentStoredKey will return a different key than the one we used to
  1511  	// sign the request
  1512  	_, _, _, err := wfe.validPOSTForAccount(request, ctx, event)
  1513  	test.AssertError(t, err, "No error returned for request signed by wrong key")
  1514  	test.AssertErrorIs(t, err, berrors.Malformed)
  1515  	test.AssertContains(t, err.Error(), "JWS verification error")
  1516  }
  1517  
  1518  func TestValidSelfAuthenticatedPOSTGoodKeyErrors(t *testing.T) {
  1519  	wfe, _, signer := setupWFE(t)
  1520  
  1521  	timeoutErrCheckFunc := func(ctx context.Context, keyHash []byte) (bool, error) {
  1522  		return false, context.DeadlineExceeded
  1523  	}
  1524  
  1525  	kp, err := goodkey.NewPolicy(nil, timeoutErrCheckFunc)
  1526  	test.AssertNotError(t, err, "making key policy")
  1527  
  1528  	wfe.keyPolicy = kp
  1529  
  1530  	_, _, validJWSBody := signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
  1531  	request := makePostRequestWithPath("test", validJWSBody)
  1532  
  1533  	_, _, err = wfe.validSelfAuthenticatedPOST(context.Background(), request)
  1534  	test.AssertErrorIs(t, err, berrors.InternalServer)
  1535  
  1536  	badKeyCheckFunc := func(ctx context.Context, keyHash []byte) (bool, error) {
  1537  		return false, fmt.Errorf("oh no: %w", goodkey.ErrBadKey)
  1538  	}
  1539  
  1540  	kp, err = goodkey.NewPolicy(nil, badKeyCheckFunc)
  1541  	test.AssertNotError(t, err, "making key policy")
  1542  
  1543  	wfe.keyPolicy = kp
  1544  
  1545  	_, _, validJWSBody = signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
  1546  	request = makePostRequestWithPath("test", validJWSBody)
  1547  
  1548  	_, _, err = wfe.validSelfAuthenticatedPOST(context.Background(), request)
  1549  	test.AssertErrorIs(t, err, berrors.BadPublicKey)
  1550  }
  1551  
  1552  func TestValidSelfAuthenticatedPOST(t *testing.T) {
  1553  	wfe, _, signer := setupWFE(t)
  1554  
  1555  	_, validKey, validJWSBody := signer.embeddedJWK(nil, "http://localhost/test", `{"test":"passed"}`)
  1556  
  1557  	_, _, keyIDJWSBody := signer.byKeyID(1, nil, "http://localhost/test", `{"test":"passed"}`)
  1558  
  1559  	testCases := []struct {
  1560  		Name          string
  1561  		Request       *http.Request
  1562  		WantPayload   string
  1563  		WantJWK       *jose.JSONWebKey
  1564  		WantErrType   berrors.ErrorType
  1565  		WantErrDetail string
  1566  		WantStatType  string
  1567  	}{
  1568  		{
  1569  			Name:          "Invalid JWS",
  1570  			Request:       makePostRequestWithPath("test", "foo"),
  1571  			WantErrType:   berrors.Malformed,
  1572  			WantErrDetail: "Parse error reading JWS",
  1573  			WantStatType:  "JWSUnmarshalFailed",
  1574  		},
  1575  		{
  1576  			Name:          "JWS with key ID",
  1577  			Request:       makePostRequestWithPath("test", keyIDJWSBody),
  1578  			WantErrType:   berrors.Malformed,
  1579  			WantErrDetail: "No embedded JWK in JWS header",
  1580  			WantStatType:  "JWSAuthTypeWrong",
  1581  		},
  1582  		{
  1583  			Name:        "Valid JWS",
  1584  			Request:     makePostRequestWithPath("test", validJWSBody),
  1585  			WantPayload: `{"test":"passed"}`,
  1586  			WantJWK:     validKey,
  1587  		},
  1588  	}
  1589  
  1590  	for _, tc := range testCases {
  1591  		t.Run(tc.Name, func(t *testing.T) {
  1592  			wfe.stats.joseErrorCount.Reset()
  1593  			gotPayload, gotJWK, gotErr := wfe.validSelfAuthenticatedPOST(context.Background(), tc.Request)
  1594  			if tc.WantErrDetail == "" {
  1595  				if gotErr != nil {
  1596  					t.Fatalf("validSelfAuthenticatedPOST(%#v) = %#v, want nil", tc.Request, gotErr)
  1597  				}
  1598  				if string(gotPayload) != tc.WantPayload {
  1599  					t.Fatalf("validSelfAuthenticatedPOST(%#v) = %q, want %q", tc.Request, string(gotPayload), tc.WantPayload)
  1600  				}
  1601  				gotThumb, _ := gotJWK.Thumbprint(crypto.SHA256)
  1602  				wantThumb, _ := tc.WantJWK.Thumbprint(crypto.SHA256)
  1603  				if !slices.Equal(gotThumb, wantThumb) {
  1604  					t.Fatalf("validSelfAuthenticatedPOST(%#v) = %#v, want %#v", tc.Request, gotThumb, wantThumb)
  1605  				}
  1606  			} else {
  1607  				berr, ok := gotErr.(*berrors.BoulderError)
  1608  				if !ok {
  1609  					t.Fatalf("validSelfAuthenticatedPOST(%#v) returned %T, want BoulderError", tc.Request, gotErr)
  1610  				}
  1611  				if berr.Type != tc.WantErrType {
  1612  					t.Errorf("validSelfAuthenticatedPOST(%#v) = %#v, want %#v", tc.Request, berr.Type, tc.WantErrType)
  1613  				}
  1614  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
  1615  					t.Errorf("validSelfAuthenticatedPOST(%#v) = %q, want %q", tc.Request, berr.Detail, tc.WantErrDetail)
  1616  				}
  1617  				test.AssertMetricWithLabelsEquals(
  1618  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
  1619  			}
  1620  		})
  1621  	}
  1622  }
  1623  
  1624  func TestMatchJWSURLs(t *testing.T) {
  1625  	wfe, _, signer := setupWFE(t)
  1626  
  1627  	noURLJWS, _, _ := signer.embeddedJWK(nil, "", "")
  1628  	urlAJWS, _, _ := signer.embeddedJWK(nil, "example.com", "")
  1629  	urlBJWS, _, _ := signer.embeddedJWK(nil, "example.org", "")
  1630  
  1631  	testCases := []struct {
  1632  		Name          string
  1633  		Outer         *jose.JSONWebSignature
  1634  		Inner         *jose.JSONWebSignature
  1635  		WantErrType   berrors.ErrorType
  1636  		WantErrDetail string
  1637  		WantStatType  string
  1638  	}{
  1639  		{
  1640  			Name:          "Outer JWS without URL",
  1641  			Outer:         noURLJWS,
  1642  			Inner:         urlAJWS,
  1643  			WantErrType:   berrors.Malformed,
  1644  			WantErrDetail: "Outer JWS header parameter 'url' required",
  1645  			WantStatType:  "KeyRolloverOuterJWSNoURL",
  1646  		},
  1647  		{
  1648  			Name:          "Inner JWS without URL",
  1649  			Outer:         urlAJWS,
  1650  			Inner:         noURLJWS,
  1651  			WantErrType:   berrors.Malformed,
  1652  			WantErrDetail: "Inner JWS header parameter 'url' required",
  1653  			WantStatType:  "KeyRolloverInnerJWSNoURL",
  1654  		},
  1655  		{
  1656  			Name:          "Inner and outer JWS without URL",
  1657  			Outer:         noURLJWS,
  1658  			Inner:         noURLJWS,
  1659  			WantErrType:   berrors.Malformed,
  1660  			WantErrDetail: "Outer JWS header parameter 'url' required",
  1661  			WantStatType:  "KeyRolloverOuterJWSNoURL",
  1662  		},
  1663  		{
  1664  			Name:          "Mismatched inner and outer JWS URLs",
  1665  			Outer:         urlAJWS,
  1666  			Inner:         urlBJWS,
  1667  			WantErrType:   berrors.Malformed,
  1668  			WantErrDetail: "Outer JWS 'url' value \"example.com\" does not match inner JWS 'url' value \"example.org\"",
  1669  			WantStatType:  "KeyRolloverMismatchedURLs",
  1670  		},
  1671  		{
  1672  			Name:  "Matching inner and outer JWS URLs",
  1673  			Outer: urlAJWS,
  1674  			Inner: urlAJWS,
  1675  		},
  1676  	}
  1677  
  1678  	for _, tc := range testCases {
  1679  		t.Run(tc.Name, func(t *testing.T) {
  1680  			wfe.stats.joseErrorCount.Reset()
  1681  			outer := tc.Outer.Signatures[0].Header
  1682  			inner := tc.Inner.Signatures[0].Header
  1683  
  1684  			gotErr := wfe.matchJWSURLs(outer, inner)
  1685  			if tc.WantErrDetail == "" {
  1686  				if gotErr != nil {
  1687  					t.Fatalf("matchJWSURLs(%#v, %#v) = %#v, want nil", outer, inner, gotErr)
  1688  				}
  1689  			} else {
  1690  				berr, ok := gotErr.(*berrors.BoulderError)
  1691  				if !ok {
  1692  					t.Fatalf("matchJWSURLs(%#v, %#v) returned %T, want BoulderError", outer, inner, gotErr)
  1693  				}
  1694  				if berr.Type != tc.WantErrType {
  1695  					t.Errorf("matchJWSURLs(%#v, %#v) = %#v, want %#v", outer, inner, berr.Type, tc.WantErrType)
  1696  				}
  1697  				if !strings.Contains(berr.Detail, tc.WantErrDetail) {
  1698  					t.Errorf("matchJWSURLs(%#v, %#v) = %q, want %q", outer, inner, berr.Detail, tc.WantErrDetail)
  1699  				}
  1700  				test.AssertMetricWithLabelsEquals(
  1701  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.WantStatType}, 1)
  1702  			}
  1703  		})
  1704  	}
  1705  }