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

     1  package wfe2
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto"
     7  	"crypto/ecdsa"
     8  	"crypto/elliptic"
     9  	"crypto/rand"
    10  	"crypto/rsa"
    11  	"crypto/x509"
    12  	"encoding/asn1"
    13  	"encoding/base64"
    14  	"encoding/json"
    15  	"encoding/pem"
    16  	"errors"
    17  	"fmt"
    18  	"io"
    19  	"math/big"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"net/url"
    23  	"os"
    24  	"reflect"
    25  	"slices"
    26  	"sort"
    27  	"strconv"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/go-jose/go-jose/v4"
    33  	"github.com/jmhodges/clock"
    34  	"github.com/prometheus/client_golang/prometheus"
    35  	"google.golang.org/grpc"
    36  	"google.golang.org/protobuf/types/known/emptypb"
    37  	"google.golang.org/protobuf/types/known/timestamppb"
    38  
    39  	"github.com/letsencrypt/boulder/cmd"
    40  	"github.com/letsencrypt/boulder/config"
    41  	"github.com/letsencrypt/boulder/core"
    42  	corepb "github.com/letsencrypt/boulder/core/proto"
    43  	berrors "github.com/letsencrypt/boulder/errors"
    44  	"github.com/letsencrypt/boulder/features"
    45  	"github.com/letsencrypt/boulder/goodkey"
    46  	"github.com/letsencrypt/boulder/identifier"
    47  	"github.com/letsencrypt/boulder/issuance"
    48  	blog "github.com/letsencrypt/boulder/log"
    49  	"github.com/letsencrypt/boulder/metrics"
    50  	"github.com/letsencrypt/boulder/mocks"
    51  	"github.com/letsencrypt/boulder/must"
    52  	"github.com/letsencrypt/boulder/nonce"
    53  	noncepb "github.com/letsencrypt/boulder/nonce/proto"
    54  	"github.com/letsencrypt/boulder/probs"
    55  	rapb "github.com/letsencrypt/boulder/ra/proto"
    56  	"github.com/letsencrypt/boulder/ratelimits"
    57  	"github.com/letsencrypt/boulder/revocation"
    58  	sapb "github.com/letsencrypt/boulder/sa/proto"
    59  	"github.com/letsencrypt/boulder/test"
    60  	inmemnonce "github.com/letsencrypt/boulder/test/inmem/nonce"
    61  	"github.com/letsencrypt/boulder/unpause"
    62  	"github.com/letsencrypt/boulder/web"
    63  )
    64  
    65  const (
    66  	agreementURL = "http://example.invalid/terms"
    67  
    68  	test1KeyPublicJSON = `
    69  	{
    70  		"kty":"RSA",
    71  		"n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
    72  		"e":"AQAB"
    73  	}`
    74  
    75  	test1KeyPrivatePEM = `
    76  -----BEGIN RSA PRIVATE KEY-----
    77  MIIEowIBAAKCAQEAyNWVhtYEKJR21y9xsHV+PD/bYwbXSeNuFal46xYxVfRL5mqh
    78  a7vttvjB/vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K/klBYN8oYvTwwmeSkAz
    79  6ut7ZxPv+nZaT5TJhGk0NT2kh/zSpdriEJ/3vW+mqxYbbBmpvHqsa1/zx9fSuHYc
    80  tAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV+mzfMyboQjujPh7aNJxAWS
    81  q4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF+w8hOTI3XXohUdu
    82  29Se26k2B0PolDSuj0GIQU6+W9TdLXSjBb2SpQIDAQABAoIBAHw58SXYV/Yp72Cn
    83  jjFSW+U0sqWMY7rmnP91NsBjl9zNIe3C41pagm39bTIjB2vkBNR8ZRG7pDEB/QAc
    84  Cn9Keo094+lmTArjL407ien7Ld+koW7YS8TyKADYikZo0vAK3qOy14JfQNiFAF9r
    85  Bw61hG5/E58cK5YwQZe+YcyBK6/erM8fLrJEyw4CV49wWdq/QqmNYU1dx4OExAkl
    86  KMfvYXpjzpvyyTnZuS4RONfHsO8+JTyJVm+lUv2x+bTce6R4W++UhQY38HakJ0x3
    87  XRfXooRv1Bletu5OFlpXfTSGz/5gqsfemLSr5UHncsCcFMgoFBsk2t/5BVukBgC7
    88  PnHrAjkCgYEA887PRr7zu3OnaXKxylW5U5t4LzdMQLpslVW7cLPD4Y08Rye6fF5s
    89  O/jK1DNFXIoUB7iS30qR7HtaOnveW6H8/kTmMv/YAhLO7PAbRPCKxxcKtniEmP1x
    90  ADH0tF2g5uHB/zeZhCo9qJiF0QaJynvSyvSyJFmY6lLvYZsAW+C+PesCgYEA0uCi
    91  Q8rXLzLpfH2NKlLwlJTi5JjE+xjbabgja0YySwsKzSlmvYJqdnE2Xk+FHj7TCnSK
    92  KUzQKR7+rEk5flwEAf+aCCNh3W4+Hp9MmrdAcCn8ZsKmEW/o7oDzwiAkRCmLw/ck
    93  RSFJZpvFoxEg15riT37EjOJ4LBZ6SwedsoGA/a8CgYEA2Ve4sdGSR73/NOKZGc23
    94  q4/B4R2DrYRDPhEySnMGoPCeFrSU6z/lbsUIU4jtQWSaHJPu4n2AfncsZUx9WeSb
    95  OzTCnh4zOw33R4N4W8mvfXHODAJ9+kCc1tax1YRN5uTEYzb2dLqPQtfNGxygA1DF
    96  BkaC9CKnTeTnH3TlKgK8tUcCgYB7J1lcgh+9ntwhKinBKAL8ox8HJfkUM+YgDbwR
    97  sEM69E3wl1c7IekPFvsLhSFXEpWpq3nsuMFw4nsVHwaGtzJYAHByhEdpTDLXK21P
    98  heoKF1sioFbgJB1C/Ohe3OqRLDpFzhXOkawOUrbPjvdBM2Erz/r11GUeSlpNazs7
    99  vsoYXQKBgFwFM1IHmqOf8a2wEFa/a++2y/WT7ZG9nNw1W36S3P04K4lGRNRS2Y/S
   100  snYiqxD9nL7pVqQP2Qbqbn0yD6d3G5/7r86F7Wu2pihM8g6oyMZ3qZvvRIBvKfWo
   101  eROL1ve1vmQF3kjrMPhhK2kr6qdWnTE5XlPllVSZFQenSTzj98AO
   102  -----END RSA PRIVATE KEY-----
   103  `
   104  
   105  	test2KeyPublicJSON = `{
   106  		"kty":"RSA",
   107  		"n":"qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw",
   108  		"e":"AQAB"
   109  	}`
   110  
   111  	test2KeyPrivatePEM = `
   112  -----BEGIN RSA PRIVATE KEY-----
   113  MIIEpAIBAAKCAQEAqnARLrT7Xz4gRcKyLdydmCr+ey9OuPImX4X40thk3on26FkM
   114  znR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBr
   115  hR6uIoO4jAzJZR+ChzZuSDt7iHN+3xUVspu5XGwXU/MVJZshTwp4TaFx5elHIT/O
   116  bnTvTOU3Xhish07AbgZKmWsVbXh5s+CrIicU4OexJPgunWZ/YJJueOKmTvnLlTV4
   117  MzKR2oZlBKZ27S0+SfdV/QDx/ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY/2Uzi5
   118  eX0lTc7MPRwz6qR1kip+i59VcGcUQgqHV6FyqwIDAQABAoIBAG5m8Xpj2YC0aYtG
   119  tsxmX9812mpJFqFOmfS+f5N0gMJ2c+3F4TnKz6vE/ZMYkFnehAT0GErC4WrOiw68
   120  F/hLdtJM74gQ0LGh9dKeJmz67bKqngcAHWW5nerVkDGIBtzuMEsNwxofDcIxrjkr
   121  G0b7AHMRwXqrt0MI3eapTYxby7+08Yxm40mxpSsW87FSaI61LDxUDpeVkn7kolSN
   122  WifVat7CpZb/D2BfGAQDxiU79YzgztpKhbynPdGc/OyyU+CNgk9S5MgUX2m9Elh3
   123  aXrWh2bT2xzF+3KgZdNkJQcdIYVoGq/YRBxlGXPYcG4Do3xKhBmH79Io2BizevZv
   124  nHkbUGECgYEAydjb4rl7wYrElDqAYpoVwKDCZAgC6o3AKSGXfPX1Jd2CXgGR5Hkl
   125  ywP0jdSLbn2v/jgKQSAdRbYuEiP7VdroMb5M6BkBhSY619cH8etoRoLzFo1GxcE8
   126  Y7B598VXMq8TT+TQqw/XRvM18aL3YDZ3LSsR7Gl2jF/sl6VwQAaZToUCgYEA2Cn4
   127  fG58ME+M4IzlZLgAIJ83PlLb9ip6MeHEhUq2Dd0In89nss7Acu0IVg8ES88glJZy
   128  4SjDLGSiuQuoQVo9UBq/E5YghdMJFp5ovwVfEaJ+ruWqOeujvWzzzPVyIWSLXRQa
   129  N4kedtfrlqldMIXywxVru66Q1NOGvhDHm/Q8+28CgYEAkhLCbn3VNed7A9qidrkT
   130  7OdqRoIVujEDU8DfpKtK0jBP3EA+mJ2j4Bvoq4uZrEiBSPS9VwwqovyIstAfX66g
   131  Qv95IK6YDwfvpawUL9sxB3ZU/YkYIp0JWwun+Mtzo1ZYH4V0DZfVL59q9of9hj9k
   132  V+fHfNOF22jAC67KYUtlPxECgYEAwF6hj4L3rDqvQYrB/p8tJdrrW+B7dhgZRNkJ
   133  fiGd4LqLGUWHoH4UkHJXT9bvWNPMx88YDz6qapBoq8svAnHfTLFwyGp7KP1FAkcZ
   134  Kp4KG/SDTvx+QCtvPX1/fjAUUJlc2QmxxyiU3uiK9Tpl/2/FOk2O4aiZpX1VVUIz
   135  kZuKxasCgYBiVRkEBk2W4Ia0B7dDkr2VBrz4m23Y7B9cQLpNAapiijz/0uHrrCl8
   136  TkLlEeVOuQfxTadw05gzKX0jKkMC4igGxvEeilYc6NR6a4nvRulG84Q8VV9Sy9Ie
   137  wk6Oiadty3eQqSBJv0HnpmiEdQVffIK5Pg4M8Dd+aOBnEkbopAJOuA==
   138  -----END RSA PRIVATE KEY-----
   139  `
   140  	test3KeyPrivatePEM = `
   141  -----BEGIN RSA PRIVATE KEY-----
   142  MIIEpAIBAAKCAQEAuTQER6vUA1RDixS8xsfCRiKUNGRzzyIK0MhbS2biClShbb0h
   143  Sx2mPP7gBvis2lizZ9r+y9hL57kNQoYCKndOBg0FYsHzrQ3O9AcoV1z2Mq+XhHZb
   144  FrVYaXI0M3oY9BJCWog0dyi3XC0x8AxC1npd1U61cToHx+3uSvgZOuQA5ffEn5L3
   145  8Dz1Ti7OV3E4XahnRJvejadUmTkki7phLBUXm5MnnyFm0CPpf6ApV7zhLjN5W+nV
   146  0WL17o7v8aDgV/t9nIdi1Y26c3PlCEtiVHZcebDH5F1Deta3oLLg9+g6rWnTqPbY
   147  3knffhp4m0scLD6e33k8MtzxDX/D7vHsg0/X1wIDAQABAoIBAQCnFJpX3lhiuH5G
   148  1uqHmmdVxpRVv9oKn/eJ63cRSzvZfgg0bE/A6Hq0xGtvXqDySttvck4zsGqqHnQr
   149  86G4lfE53D1jnv4qvS5bUKnARwmFKIxU4EHE9s1QM8uMNTaV2nMqIX7TkVP6QHuw
   150  yB70R2inq15dS7EBWVGFKNX6HwAAdj8pFuF6o2vIwmAfee20aFzpWWf81jOH9Ai6
   151  hyJyV3NqrU1JzIwlXaeX67R1VroFdhN/lapp+2b0ZEcJJtFlcYFl99NjkQeVZyik
   152  izNv0GZZNWizc57wU0/8cv+jQ2f26ltvyrPz3QNK61bFfzy+/tfMvLq7sdCmztKJ
   153  tMxCBJOBAoGBAPKnIVQIS2nTvC/qZ8ajw1FP1rkvYblIiixegjgfFhM32HehQ+nu
   154  3TELi3I3LngLYi9o6YSqtNBmdBJB+DUAzIXp0TdOihOweGiv5dAEWwY9rjCzMT5S
   155  GP7dCWiJwoMUHrOs1Po3dwcjj/YsoAW+FC0jSvach2Ln2CvPgr5FP0ARAoGBAMNj
   156  64qUCzgeXiSyPKK69bCCGtHlTYUndwHQAZmABjbmxAXZNYgp/kBezFpKOwmICE8R
   157  kK8YALRrL0VWXl/yj85b0HAZGkquNFHPUDd1e6iiP5TrY+Hy4oqtlYApjH6f85CE
   158  lWjQ1iyUL7aT6fcSgzq65ZWD2hUzvNtWbTt6zQFnAoGAWS/EuDY0QblpOdNWQVR/
   159  vasyqO4ZZRiccKJsCmSioH2uOoozhBAfjJ9JqblOgyDr/bD546E6xD5j+zH0IMci
   160  ZTYDh+h+J659Ez1Topl3O1wAYjX6q4VRWpuzkZDQxYznm/KydSVdwmn3x+uvBW1P
   161  zSdjrjDqMhg1BCVJUNXy4YECgYEAjX1z+dwO68qB3gz7/9NnSzRL+6cTJdNYSIW6
   162  QtAEsAkX9iw+qaXPKgn77X5HljVd3vQXU9QL3pqnloxetxhNrt+p5yMmeOIBnSSF
   163  MEPxEkK7zDlRETPzfP0Kf86WoLNviz2XfFmOXqXIj2w5RuOvB/6DdmwOpr/aiPLj
   164  EulwPw0CgYAMSzsWOt6vU+y/G5NyhUCHvY50TdnGOj2btBk9rYVwWGWxCpg2QF0R
   165  pcKXgGzXEVZKFAqB8V1c/mmCo8ojPgmqGM+GzX2Bj4seVBW7PsTeZUjrHpADshjV
   166  F7o5b7y92NlxO5kwQzRKEAhwS5PbKJdx90iCuG+JlI1YgWlA1VcJMw==
   167  -----END RSA PRIVATE KEY-----
   168  `
   169  
   170  	testE1KeyPrivatePEM = `
   171  -----BEGIN EC PRIVATE KEY-----
   172  MHcCAQEEIH+p32RUnqT/iICBEGKrLIWFcyButv0S0lU/BLPOyHn2oAoGCCqGSM49
   173  AwEHoUQDQgAEFwvSZpu06i3frSk/mz9HcD9nETn4wf3mQ+zDtG21GapLytH7R1Zr
   174  ycBzDV9u6cX9qNLc9Bn5DAumz7Zp2AuA+Q==
   175  -----END EC PRIVATE KEY-----
   176  `
   177  
   178  	testE2KeyPrivatePEM = `
   179  -----BEGIN EC PRIVATE KEY-----
   180  MHcCAQEEIFRcPxQ989AY6se2RyIoF1ll9O6gHev4oY15SWJ+Jf5eoAoGCCqGSM49
   181  AwEHoUQDQgAES8FOmrZ3ywj4yyFqt0etAD90U+EnkNaOBSLfQmf7pNi8y+kPKoUN
   182  EeMZ9nWyIM6bktLrE11HnFOnKhAYsM5fZA==
   183  -----END EC PRIVATE KEY-----`
   184  )
   185  
   186  type MockRegistrationAuthority struct {
   187  	rapb.RegistrationAuthorityClient
   188  	clk                  clock.Clock
   189  	lastRevocationReason revocation.Reason
   190  }
   191  
   192  func (ra *MockRegistrationAuthority) NewRegistration(ctx context.Context, in *corepb.Registration, _ ...grpc.CallOption) (*corepb.Registration, error) {
   193  	in.Id = 1
   194  	created := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
   195  	in.CreatedAt = timestamppb.New(created)
   196  	return in, nil
   197  }
   198  
   199  func (ra *MockRegistrationAuthority) UpdateRegistrationKey(ctx context.Context, in *rapb.UpdateRegistrationKeyRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
   200  	return &corepb.Registration{
   201  		Status: string(core.StatusValid),
   202  		Key:    in.Jwk,
   203  	}, nil
   204  }
   205  
   206  func (ra *MockRegistrationAuthority) DeactivateRegistration(context.Context, *rapb.DeactivateRegistrationRequest, ...grpc.CallOption) (*corepb.Registration, error) {
   207  	return &corepb.Registration{
   208  		Status: string(core.StatusDeactivated),
   209  		Key:    []byte(test1KeyPublicJSON),
   210  	}, nil
   211  }
   212  
   213  func (ra *MockRegistrationAuthority) PerformValidation(context.Context, *rapb.PerformValidationRequest, ...grpc.CallOption) (*corepb.Authorization, error) {
   214  	return &corepb.Authorization{}, nil
   215  }
   216  
   217  func (ra *MockRegistrationAuthority) RevokeCertByApplicant(ctx context.Context, in *rapb.RevokeCertByApplicantRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
   218  	ra.lastRevocationReason = revocation.Reason(in.Code)
   219  	return &emptypb.Empty{}, nil
   220  }
   221  
   222  func (ra *MockRegistrationAuthority) RevokeCertByKey(ctx context.Context, in *rapb.RevokeCertByKeyRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
   223  	ra.lastRevocationReason = revocation.KeyCompromise
   224  	return &emptypb.Empty{}, nil
   225  }
   226  
   227  // GetAuthorization returns a different authorization depending on the requested
   228  // ID. All authorizations are associated with RegID 1, except for the one that isn't.
   229  func (ra *MockRegistrationAuthority) GetAuthorization(_ context.Context, in *rapb.GetAuthorizationRequest, _ ...grpc.CallOption) (*corepb.Authorization, error) {
   230  	switch in.Id {
   231  	case 1: // Return a valid authorization with a single valid challenge.
   232  		return &corepb.Authorization{
   233  			Id:             "1",
   234  			RegistrationID: 1,
   235  			Identifier:     identifier.NewDNS("not-an-example.com").ToProto(),
   236  			Status:         string(core.StatusValid),
   237  			Expires:        timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
   238  			Challenges: []*corepb.Challenge{
   239  				{Id: 1, Type: "http-01", Status: string(core.StatusValid), Token: "token"},
   240  			},
   241  		}, nil
   242  	case 2: // Return a pending authorization with three pending challenges.
   243  		return &corepb.Authorization{
   244  			Id:             "2",
   245  			RegistrationID: 1,
   246  			Identifier:     identifier.NewDNS("not-an-example.com").ToProto(),
   247  			Status:         string(core.StatusPending),
   248  			Expires:        timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
   249  			Challenges: []*corepb.Challenge{
   250  				{Id: 1, Type: "http-01", Status: string(core.StatusPending), Token: "token"},
   251  				{Id: 2, Type: "dns-01", Status: string(core.StatusPending), Token: "token"},
   252  				{Id: 3, Type: "tls-alpn-01", Status: string(core.StatusPending), Token: "token"},
   253  			},
   254  		}, nil
   255  	case 3: // Return an expired authorization with three pending (but expired) challenges.
   256  		return &corepb.Authorization{
   257  			Id:             "3",
   258  			RegistrationID: 1,
   259  			Identifier:     identifier.NewDNS("not-an-example.com").ToProto(),
   260  			Status:         string(core.StatusPending),
   261  			Expires:        timestamppb.New(ra.clk.Now().AddDate(-1, 0, 0)),
   262  			Challenges: []*corepb.Challenge{
   263  				{Id: 1, Type: "http-01", Status: string(core.StatusPending), Token: "token"},
   264  				{Id: 2, Type: "dns-01", Status: string(core.StatusPending), Token: "token"},
   265  				{Id: 3, Type: "tls-alpn-01", Status: string(core.StatusPending), Token: "token"},
   266  			},
   267  		}, nil
   268  	case 4: // Return an internal server error.
   269  		return nil, fmt.Errorf("unspecified error")
   270  	case 5: // Return a pending authorization as above, but associated with RegID 2.
   271  		return &corepb.Authorization{
   272  			Id:             "5",
   273  			RegistrationID: 2,
   274  			Identifier:     identifier.NewDNS("not-an-example.com").ToProto(),
   275  			Status:         string(core.StatusPending),
   276  			Expires:        timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
   277  			Challenges: []*corepb.Challenge{
   278  				{Id: 1, Type: "http-01", Status: string(core.StatusPending), Token: "token"},
   279  				{Id: 2, Type: "dns-01", Status: string(core.StatusPending), Token: "token"},
   280  				{Id: 3, Type: "tls-alpn-01", Status: string(core.StatusPending), Token: "token"},
   281  			},
   282  		}, nil
   283  	}
   284  
   285  	return nil, berrors.NotFoundError("no authorization found with id %q", in.Id)
   286  }
   287  
   288  func (ra *MockRegistrationAuthority) DeactivateAuthorization(context.Context, *corepb.Authorization, ...grpc.CallOption) (*emptypb.Empty, error) {
   289  	return &emptypb.Empty{}, nil
   290  }
   291  
   292  func (ra *MockRegistrationAuthority) NewOrder(ctx context.Context, in *rapb.NewOrderRequest, _ ...grpc.CallOption) (*corepb.Order, error) {
   293  	created := time.Date(2021, 1, 1, 1, 1, 1, 0, time.UTC)
   294  	expires := time.Date(2021, 2, 1, 1, 1, 1, 0, time.UTC)
   295  
   296  	return &corepb.Order{
   297  		Id:               1,
   298  		RegistrationID:   in.RegistrationID,
   299  		Created:          timestamppb.New(created),
   300  		Expires:          timestamppb.New(expires),
   301  		Identifiers:      in.Identifiers,
   302  		Status:           string(core.StatusPending),
   303  		V2Authorizations: []int64{1},
   304  	}, nil
   305  }
   306  
   307  func (ra *MockRegistrationAuthority) FinalizeOrder(ctx context.Context, in *rapb.FinalizeOrderRequest, _ ...grpc.CallOption) (*corepb.Order, error) {
   308  	in.Order.Status = string(core.StatusProcessing)
   309  	return in.Order, nil
   310  }
   311  
   312  func makeBody(s string) io.ReadCloser {
   313  	return io.NopCloser(strings.NewReader(s))
   314  }
   315  
   316  // loadKey loads a private key from PEM/DER-encoded data and returns
   317  // a `crypto.Signer`.
   318  func loadKey(t *testing.T, keyBytes []byte) crypto.Signer {
   319  	// pem.Decode does not return an error as its 2nd arg, but instead the "rest"
   320  	// that was leftover from parsing the PEM block. We only care if the decoded
   321  	// PEM block was empty for this test function.
   322  	block, _ := pem.Decode(keyBytes)
   323  	if block == nil {
   324  		t.Fatal("Unable to decode private key PEM bytes")
   325  	}
   326  
   327  	// Try decoding as an RSA private key
   328  	if rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
   329  		return rsaKey
   330  	}
   331  
   332  	// Try decoding as a PKCS8 private key
   333  	if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
   334  		// Determine the key's true type and return it as a crypto.Signer
   335  		switch k := key.(type) {
   336  		case *rsa.PrivateKey:
   337  			return k
   338  		case *ecdsa.PrivateKey:
   339  			return k
   340  		}
   341  	}
   342  
   343  	// Try as an ECDSA private key
   344  	if ecdsaKey, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
   345  		return ecdsaKey
   346  	}
   347  
   348  	// Nothing worked! Fail hard.
   349  	t.Fatalf("Unable to decode private key PEM bytes")
   350  	// NOOP - the t.Fatal() call will abort before this return
   351  	return nil
   352  }
   353  
   354  var ctx = context.Background()
   355  
   356  func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
   357  	features.Reset()
   358  
   359  	fc := clock.NewFake()
   360  	stats := metrics.NoopRegisterer
   361  	logger := blog.NewMock()
   362  
   363  	testKeyPolicy, err := goodkey.NewPolicy(nil, nil)
   364  	test.AssertNotError(t, err, "creating test keypolicy")
   365  
   366  	certChains := map[issuance.NameID][][]byte{}
   367  	issuerCertificates := map[issuance.NameID]*issuance.Certificate{}
   368  	for _, files := range [][]string{
   369  		{
   370  			"../test/hierarchy/int-r3.cert.pem",
   371  			"../test/hierarchy/root-x1.cert.pem",
   372  		},
   373  		{
   374  			"../test/hierarchy/int-r3-cross.cert.pem",
   375  			"../test/hierarchy/root-dst.cert.pem",
   376  		},
   377  		{
   378  			"../test/hierarchy/int-e1.cert.pem",
   379  			"../test/hierarchy/root-x2.cert.pem",
   380  		},
   381  		{
   382  			"../test/hierarchy/int-e1.cert.pem",
   383  			"../test/hierarchy/root-x2-cross.cert.pem",
   384  			"../test/hierarchy/root-x1-cross.cert.pem",
   385  			"../test/hierarchy/root-dst.cert.pem",
   386  		},
   387  	} {
   388  		certs, err := issuance.LoadChain(files)
   389  		test.AssertNotError(t, err, "Unable to load chain")
   390  		var buf bytes.Buffer
   391  		for _, cert := range certs {
   392  			buf.Write([]byte("\n"))
   393  			buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
   394  		}
   395  		id := certs[0].NameID()
   396  		certChains[id] = append(certChains[id], buf.Bytes())
   397  		issuerCertificates[id] = certs[0]
   398  	}
   399  
   400  	mockSA := mocks.NewStorageAuthorityReadOnly(fc)
   401  
   402  	// Use derived nonces.
   403  	rncKey := []byte("b8c758dd85e113ea340ce0b3a99f389d40a308548af94d1730a7692c1874f1f")
   404  	noncePrefix := nonce.DerivePrefix("192.168.1.1:8080", rncKey)
   405  	nonceService, err := nonce.NewNonceService(metrics.NoopRegisterer, 100, noncePrefix)
   406  	test.AssertNotError(t, err, "making nonceService")
   407  
   408  	inmemNonceService := &inmemnonce.NonceService{Impl: nonceService}
   409  	gnc := inmemNonceService
   410  	rnc := inmemNonceService
   411  
   412  	// Setup rate limiting.
   413  	limiter, err := ratelimits.NewLimiter(fc, ratelimits.NewInmemSource(), stats)
   414  	test.AssertNotError(t, err, "making limiter")
   415  	txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/ratelimit-defaults.yml", "", stats, logger)
   416  	test.AssertNotError(t, err, "making transaction composer")
   417  
   418  	unpauseSigner, err := unpause.NewJWTSigner(cmd.HMACKeyConfig{KeyFile: "../test/secrets/sfe_unpause_key"})
   419  	test.AssertNotError(t, err, "making unpause signer")
   420  	unpauseLifetime := time.Hour * 24 * 14
   421  	unpauseURL := "https://boulder.service.consul:4003"
   422  	wfe, err := NewWebFrontEndImpl(
   423  		stats,
   424  		fc,
   425  		testKeyPolicy,
   426  		certChains,
   427  		issuerCertificates,
   428  		logger,
   429  		10*time.Second,
   430  		10*time.Second,
   431  		2,
   432  		&MockRegistrationAuthority{clk: fc},
   433  		mockSA,
   434  		nil,
   435  		gnc,
   436  		rnc,
   437  		rncKey,
   438  		mockSA,
   439  		limiter,
   440  		txnBuilder,
   441  		map[string]string{"default": "a test profile"},
   442  		unpauseSigner,
   443  		unpauseLifetime,
   444  		unpauseURL,
   445  	)
   446  	test.AssertNotError(t, err, "Unable to create WFE")
   447  
   448  	wfe.SubscriberAgreementURL = agreementURL
   449  
   450  	return wfe, fc, requestSigner{t, inmemNonceService.AsSource()}
   451  }
   452  
   453  // makePostRequestWithPath creates an http.Request for localhost with method
   454  // POST, the provided body, and the correct Content-Length. The path provided
   455  // will be parsed as a URL and used to populate the request URL and RequestURI
   456  func makePostRequestWithPath(path string, body string) *http.Request {
   457  	request := &http.Request{
   458  		Method:     "POST",
   459  		RemoteAddr: "1.1.1.1:7882",
   460  		Header: map[string][]string{
   461  			"Content-Length": {strconv.Itoa(len(body))},
   462  			"Content-Type":   {expectedJWSContentType},
   463  		},
   464  		Body: makeBody(body),
   465  		Host: "localhost",
   466  	}
   467  	url := mustParseURL(path)
   468  	request.URL = url
   469  	request.RequestURI = url.Path
   470  	return request
   471  }
   472  
   473  // makeChunkedPostRequestWithPath is the same as makePostRequestWithPath, but
   474  // with a chunked encoded request body instead of a fixed content length.
   475  func makeChunkedPostRequestWithPath(path string, body string) *http.Request {
   476  	request := &http.Request{
   477  		Method:     "POST",
   478  		RemoteAddr: "1.1.1.1:7882",
   479  		Header: map[string][]string{
   480  			"Transfer-Encoding": {"chunked"},
   481  			"Content-Type":      {expectedJWSContentType},
   482  		},
   483  		Body: makeBody(body),
   484  		Host: "localhost",
   485  	}
   486  	url := mustParseURL(path)
   487  	request.URL = url
   488  	request.RequestURI = url.Path
   489  	return request
   490  }
   491  
   492  // signAndPost constructs a JWS signed by the account with ID 1, over the given
   493  // payload, with the protected URL set to the provided signedURL. An HTTP
   494  // request constructed to the provided path with the encoded JWS body as the
   495  // POST body is returned.
   496  func signAndPost(signer requestSigner, path, signedURL, payload string) *http.Request {
   497  	_, _, body := signer.byKeyID(1, nil, signedURL, payload)
   498  	return makePostRequestWithPath(path, body)
   499  }
   500  
   501  func mustParseURL(s string) *url.URL {
   502  	return must.Do(url.Parse(s))
   503  }
   504  
   505  func sortHeader(s string) string {
   506  	a := strings.Split(s, ", ")
   507  	sort.Strings(a)
   508  	return strings.Join(a, ", ")
   509  }
   510  
   511  func addHeadIfGet(s []string) []string {
   512  	if slices.Contains(s, "GET") {
   513  		return append(s, "HEAD")
   514  	}
   515  	return s
   516  }
   517  
   518  func TestHandleFunc(t *testing.T) {
   519  	wfe, _, _ := setupWFE(t)
   520  	var mux *http.ServeMux
   521  	var rw *httptest.ResponseRecorder
   522  	var stubCalled bool
   523  	runWrappedHandler := func(req *http.Request, pattern string, allowed ...string) {
   524  		mux = http.NewServeMux()
   525  		rw = httptest.NewRecorder()
   526  		stubCalled = false
   527  		wfe.HandleFunc(mux, pattern, func(context.Context, *web.RequestEvent, http.ResponseWriter, *http.Request) {
   528  			stubCalled = true
   529  		}, allowed...)
   530  		req.URL = mustParseURL(pattern)
   531  		mux.ServeHTTP(rw, req)
   532  	}
   533  
   534  	// Plain requests (no CORS)
   535  	type testCase struct {
   536  		allowed        []string
   537  		reqMethod      string
   538  		shouldCallStub bool
   539  		shouldSucceed  bool
   540  		pattern        string
   541  	}
   542  	var lastNonce string
   543  	for _, c := range []testCase{
   544  		{[]string{"GET", "POST"}, "GET", true, true, "/test"},
   545  		{[]string{"GET", "POST"}, "GET", true, true, newNoncePath},
   546  		{[]string{"GET", "POST"}, "POST", true, true, "/test"},
   547  		{[]string{"GET"}, "", false, false, "/test"},
   548  		{[]string{"GET"}, "POST", false, false, "/test"},
   549  		{[]string{"GET"}, "OPTIONS", false, true, "/test"},
   550  		{[]string{"GET"}, "MAKE-COFFEE", false, false, "/test"}, // 405, or 418?
   551  		{[]string{"GET"}, "GET", true, true, directoryPath},
   552  	} {
   553  		runWrappedHandler(&http.Request{Method: c.reqMethod}, c.pattern, c.allowed...)
   554  		test.AssertEquals(t, stubCalled, c.shouldCallStub)
   555  		if c.shouldSucceed {
   556  			test.AssertEquals(t, rw.Code, http.StatusOK)
   557  		} else {
   558  			test.AssertEquals(t, rw.Code, http.StatusMethodNotAllowed)
   559  			test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), sortHeader(strings.Join(addHeadIfGet(c.allowed), ", ")))
   560  			test.AssertUnmarshaledEquals(t,
   561  				rw.Body.String(),
   562  				`{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
   563  		}
   564  		if c.reqMethod == "GET" && c.pattern != newNoncePath {
   565  			nonce := rw.Header().Get("Replay-Nonce")
   566  			test.AssertEquals(t, nonce, "")
   567  		} else {
   568  			nonce := rw.Header().Get("Replay-Nonce")
   569  			test.AssertNotEquals(t, nonce, lastNonce)
   570  			test.AssertNotEquals(t, nonce, "")
   571  			lastNonce = nonce
   572  		}
   573  		linkHeader := rw.Header().Get("Link")
   574  		if c.pattern != directoryPath {
   575  			// If the pattern wasn't the directory there should be a Link header for the index
   576  			test.AssertEquals(t, linkHeader, `<http://localhost/directory>;rel="index"`)
   577  		} else {
   578  			// The directory resource shouldn't get a link header
   579  			test.AssertEquals(t, linkHeader, "")
   580  		}
   581  	}
   582  
   583  	// Disallowed method returns error JSON in body
   584  	runWrappedHandler(&http.Request{Method: "PUT"}, "/test", "GET", "POST")
   585  	test.AssertEquals(t, rw.Header().Get("Content-Type"), "application/problem+json")
   586  	test.AssertUnmarshaledEquals(t, rw.Body.String(), `{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
   587  	test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), "GET, HEAD, POST")
   588  
   589  	// Disallowed method special case: response to HEAD has got no body
   590  	runWrappedHandler(&http.Request{Method: "HEAD"}, "/test", "GET", "POST")
   591  	test.AssertEquals(t, stubCalled, true)
   592  	test.AssertEquals(t, rw.Body.String(), "")
   593  
   594  	// HEAD doesn't work with POST-only endpoints
   595  	runWrappedHandler(&http.Request{Method: "HEAD"}, "/test", "POST")
   596  	test.AssertEquals(t, stubCalled, false)
   597  	test.AssertEquals(t, rw.Code, http.StatusMethodNotAllowed)
   598  	test.AssertEquals(t, rw.Header().Get("Content-Type"), "application/problem+json")
   599  	test.AssertEquals(t, rw.Header().Get("Allow"), "POST")
   600  	test.AssertUnmarshaledEquals(t, rw.Body.String(), `{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
   601  
   602  	wfe.AllowOrigins = []string{"*"}
   603  	testOrigin := "https://example.com"
   604  
   605  	// CORS "actual" request for disallowed method
   606  	runWrappedHandler(&http.Request{
   607  		Method: "POST",
   608  		Header: map[string][]string{
   609  			"Origin": {testOrigin},
   610  		},
   611  	}, "/test", "GET")
   612  	test.AssertEquals(t, stubCalled, false)
   613  	test.AssertEquals(t, rw.Code, http.StatusMethodNotAllowed)
   614  
   615  	// CORS "actual" request for allowed method
   616  	runWrappedHandler(&http.Request{
   617  		Method: "GET",
   618  		Header: map[string][]string{
   619  			"Origin": {testOrigin},
   620  		},
   621  	}, "/test", "GET", "POST")
   622  	test.AssertEquals(t, stubCalled, true)
   623  	test.AssertEquals(t, rw.Code, http.StatusOK)
   624  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Methods"), "")
   625  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "*")
   626  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "Content-Type")
   627  	test.AssertEquals(t, sortHeader(rw.Header().Get("Access-Control-Expose-Headers")), "Link, Location, Replay-Nonce")
   628  
   629  	// CORS preflight request for disallowed method
   630  	runWrappedHandler(&http.Request{
   631  		Method: "OPTIONS",
   632  		Header: map[string][]string{
   633  			"Origin":                        {testOrigin},
   634  			"Access-Control-Request-Method": {"POST"},
   635  		},
   636  	}, "/test", "GET")
   637  	test.AssertEquals(t, stubCalled, false)
   638  	test.AssertEquals(t, rw.Code, http.StatusOK)
   639  	test.AssertEquals(t, rw.Header().Get("Allow"), "GET, HEAD")
   640  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "")
   641  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "")
   642  
   643  	// CORS preflight request for allowed method
   644  	runWrappedHandler(&http.Request{
   645  		Method: "OPTIONS",
   646  		Header: map[string][]string{
   647  			"Origin":                         {testOrigin},
   648  			"Access-Control-Request-Method":  {"POST"},
   649  			"Access-Control-Request-Headers": {"X-Accept-Header1, X-Accept-Header2", "X-Accept-Header3"},
   650  		},
   651  	}, "/test", "GET", "POST")
   652  	test.AssertEquals(t, rw.Code, http.StatusOK)
   653  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "*")
   654  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "Content-Type")
   655  	test.AssertEquals(t, rw.Header().Get("Access-Control-Max-Age"), "86400")
   656  	test.AssertEquals(t, sortHeader(rw.Header().Get("Access-Control-Allow-Methods")), "GET, HEAD, POST")
   657  	test.AssertEquals(t, sortHeader(rw.Header().Get("Access-Control-Expose-Headers")), "Link, Location, Replay-Nonce")
   658  
   659  	// OPTIONS request without an Origin header (i.e., not a CORS
   660  	// preflight request)
   661  	runWrappedHandler(&http.Request{
   662  		Method: "OPTIONS",
   663  		Header: map[string][]string{
   664  			"Access-Control-Request-Method": {"POST"},
   665  		},
   666  	}, "/test", "GET", "POST")
   667  	test.AssertEquals(t, rw.Code, http.StatusOK)
   668  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "")
   669  	test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "")
   670  	test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), "GET, HEAD, POST")
   671  
   672  	// CORS preflight request missing optional Request-Method
   673  	// header. The "actual" request will be GET.
   674  	for _, allowedMethod := range []string{"GET", "POST"} {
   675  		runWrappedHandler(&http.Request{
   676  			Method: "OPTIONS",
   677  			Header: map[string][]string{
   678  				"Origin": {testOrigin},
   679  			},
   680  		}, "/test", allowedMethod)
   681  		test.AssertEquals(t, rw.Code, http.StatusOK)
   682  		if allowedMethod == "GET" {
   683  			test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "*")
   684  			test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "Content-Type")
   685  			test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Methods"), "GET, HEAD")
   686  		} else {
   687  			test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "")
   688  			test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "")
   689  		}
   690  	}
   691  
   692  	// No CORS headers are given when configuration does not list
   693  	// "*" or the client-provided origin.
   694  	for _, wfe.AllowOrigins = range [][]string{
   695  		{},
   696  		{"http://example.com", "https://other.example"},
   697  		{""}, // Invalid origin is never matched
   698  	} {
   699  		runWrappedHandler(&http.Request{
   700  			Method: "OPTIONS",
   701  			Header: map[string][]string{
   702  				"Origin":                        {testOrigin},
   703  				"Access-Control-Request-Method": {"POST"},
   704  			},
   705  		}, "/test", "POST")
   706  		test.AssertEquals(t, rw.Code, http.StatusOK)
   707  		for _, h := range []string{
   708  			"Access-Control-Allow-Methods",
   709  			"Access-Control-Allow-Origin",
   710  			"Access-Control-Allow-Headers",
   711  			"Access-Control-Expose-Headers",
   712  			"Access-Control-Request-Headers",
   713  		} {
   714  			test.AssertEquals(t, rw.Header().Get(h), "")
   715  		}
   716  	}
   717  
   718  	// CORS headers are offered when configuration lists "*" or
   719  	// the client-provided origin.
   720  	for _, wfe.AllowOrigins = range [][]string{
   721  		{testOrigin, "http://example.org", "*"},
   722  		{"", "http://example.org", testOrigin}, // Invalid origin is harmless
   723  	} {
   724  		runWrappedHandler(&http.Request{
   725  			Method: "OPTIONS",
   726  			Header: map[string][]string{
   727  				"Origin":                        {testOrigin},
   728  				"Access-Control-Request-Method": {"POST"},
   729  			},
   730  		}, "/test", "POST")
   731  		test.AssertEquals(t, rw.Code, http.StatusOK)
   732  		test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), testOrigin)
   733  		// http://www.w3.org/TR/cors/ section 6.4:
   734  		test.AssertEquals(t, rw.Header().Get("Vary"), "Origin")
   735  	}
   736  }
   737  
   738  func TestPOST404(t *testing.T) {
   739  	wfe, _, _ := setupWFE(t)
   740  	responseWriter := httptest.NewRecorder()
   741  	url, _ := url.Parse("/foobar")
   742  	wfe.Index(ctx, newRequestEvent(), responseWriter, &http.Request{
   743  		Method: "POST",
   744  		URL:    url,
   745  	})
   746  	test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
   747  }
   748  
   749  func TestIndex(t *testing.T) {
   750  	wfe, _, _ := setupWFE(t)
   751  
   752  	responseWriter := httptest.NewRecorder()
   753  
   754  	url, _ := url.Parse("/")
   755  	wfe.Index(ctx, newRequestEvent(), responseWriter, &http.Request{
   756  		Method: "GET",
   757  		URL:    url,
   758  	})
   759  	test.AssertEquals(t, responseWriter.Code, http.StatusOK)
   760  	test.AssertNotEquals(t, responseWriter.Body.String(), "404 page not found\n")
   761  	test.Assert(t, strings.Contains(responseWriter.Body.String(), directoryPath),
   762  		"directory path not found")
   763  	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
   764  
   765  	responseWriter.Body.Reset()
   766  	responseWriter.Header().Del("Cache-Control")
   767  	url, _ = url.Parse("/foo")
   768  	wfe.Index(ctx, newRequestEvent(), responseWriter, &http.Request{
   769  		URL: url,
   770  	})
   771  	//test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
   772  	test.AssertEquals(t, responseWriter.Body.String(), "404 page not found\n")
   773  	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "")
   774  }
   775  
   776  // randomDirectoryKeyPresent unmarshals the given buf of JSON and returns true
   777  // if `randomDirKeyExplanationLink` appears as the value of a key in the directory
   778  // object.
   779  func randomDirectoryKeyPresent(t *testing.T, buf []byte) bool {
   780  	var dir map[string]any
   781  	err := json.Unmarshal(buf, &dir)
   782  	if err != nil {
   783  		t.Errorf("Failed to unmarshal directory: %s", err)
   784  	}
   785  	for _, v := range dir {
   786  		if v == randomDirKeyExplanationLink {
   787  			return true
   788  		}
   789  	}
   790  	return false
   791  }
   792  
   793  type fakeRand struct{}
   794  
   795  func (fr fakeRand) Read(p []byte) (int, error) {
   796  	return len(p), nil
   797  }
   798  
   799  func TestDirectory(t *testing.T) {
   800  	wfe, _, signer := setupWFE(t)
   801  	mux := wfe.Handler(metrics.NoopRegisterer)
   802  	core.RandReader = fakeRand{}
   803  	defer func() { core.RandReader = rand.Reader }()
   804  
   805  	dirURL, _ := url.Parse("/directory")
   806  
   807  	getReq := &http.Request{
   808  		Method: http.MethodGet,
   809  		URL:    dirURL,
   810  		Host:   "localhost:4300",
   811  	}
   812  
   813  	_, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/directory", "")
   814  	postAsGetReq := makePostRequestWithPath("/directory", jwsBody)
   815  
   816  	testCases := []struct {
   817  		name         string
   818  		caaIdent     string
   819  		website      string
   820  		expectedJSON string
   821  		request      *http.Request
   822  	}{
   823  		{
   824  			name:    "standard GET, no CAA ident/website meta",
   825  			request: getReq,
   826  			expectedJSON: `{
   827    "keyChange": "http://localhost:4300/acme/key-change",
   828    "meta": {
   829      "termsOfService": "http://example.invalid/terms",
   830  		"profiles": {
   831  			"default": "a test profile"
   832  		}
   833    },
   834    "newNonce": "http://localhost:4300/acme/new-nonce",
   835    "newAccount": "http://localhost:4300/acme/new-acct",
   836    "newOrder": "http://localhost:4300/acme/new-order",
   837    "revokeCert": "http://localhost:4300/acme/revoke-cert",
   838    "AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417"
   839  }`,
   840  		},
   841  		{
   842  			name:     "standard GET, CAA ident/website meta",
   843  			caaIdent: "Radiant Lock",
   844  			website:  "zombo.com",
   845  			request:  getReq,
   846  			expectedJSON: `{
   847    "AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
   848    "keyChange": "http://localhost:4300/acme/key-change",
   849    "meta": {
   850      "caaIdentities": [
   851        "Radiant Lock"
   852      ],
   853      "termsOfService": "http://example.invalid/terms",
   854      "website": "zombo.com",
   855  		"profiles": {
   856  			"default": "a test profile"
   857  		}
   858    },
   859    "newAccount": "http://localhost:4300/acme/new-acct",
   860    "newNonce": "http://localhost:4300/acme/new-nonce",
   861    "newOrder": "http://localhost:4300/acme/new-order",
   862    "revokeCert": "http://localhost:4300/acme/revoke-cert"
   863  }`,
   864  		},
   865  		{
   866  			name:     "POST-as-GET, CAA ident/website meta",
   867  			caaIdent: "Radiant Lock",
   868  			website:  "zombo.com",
   869  			request:  postAsGetReq,
   870  			expectedJSON: `{
   871    "AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
   872    "keyChange": "http://localhost/acme/key-change",
   873    "meta": {
   874      "caaIdentities": [
   875        "Radiant Lock"
   876      ],
   877      "termsOfService": "http://example.invalid/terms",
   878      "website": "zombo.com",
   879  		"profiles": {
   880  			"default": "a test profile"
   881  		}
   882    },
   883    "newAccount": "http://localhost/acme/new-acct",
   884    "newNonce": "http://localhost/acme/new-nonce",
   885    "newOrder": "http://localhost/acme/new-order",
   886    "revokeCert": "http://localhost/acme/revoke-cert"
   887  }`,
   888  		},
   889  	}
   890  
   891  	for _, tc := range testCases {
   892  		t.Run(tc.name, func(t *testing.T) {
   893  			// Configure a caaIdentity and website for the /directory meta based on the tc
   894  			wfe.DirectoryCAAIdentity = tc.caaIdent // "Radiant Lock"
   895  			wfe.DirectoryWebsite = tc.website      //"zombo.com"
   896  			responseWriter := httptest.NewRecorder()
   897  			// Serve the /directory response for this request into a recorder
   898  			mux.ServeHTTP(responseWriter, tc.request)
   899  			// We expect all directory requests to return a json object with a good HTTP status
   900  			test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
   901  			// We expect all requests to return status OK
   902  			test.AssertEquals(t, responseWriter.Code, http.StatusOK)
   903  			// The response should match expected
   904  			test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.expectedJSON)
   905  			// Check that the random directory key is present
   906  			test.AssertEquals(t,
   907  				randomDirectoryKeyPresent(t, responseWriter.Body.Bytes()),
   908  				true)
   909  		})
   910  	}
   911  }
   912  
   913  func TestRelativeDirectory(t *testing.T) {
   914  	wfe, _, _ := setupWFE(t)
   915  	mux := wfe.Handler(metrics.NoopRegisterer)
   916  	core.RandReader = fakeRand{}
   917  	defer func() { core.RandReader = rand.Reader }()
   918  
   919  	expectedDirectory := func(hostname string) string {
   920  		expected := new(bytes.Buffer)
   921  
   922  		fmt.Fprintf(expected, "{")
   923  		fmt.Fprintf(expected, `"keyChange":"%s/acme/key-change",`, hostname)
   924  		fmt.Fprintf(expected, `"newNonce":"%s/acme/new-nonce",`, hostname)
   925  		fmt.Fprintf(expected, `"newAccount":"%s/acme/new-acct",`, hostname)
   926  		fmt.Fprintf(expected, `"newOrder":"%s/acme/new-order",`, hostname)
   927  		fmt.Fprintf(expected, `"revokeCert":"%s/acme/revoke-cert",`, hostname)
   928  		fmt.Fprintf(expected, `"AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",`)
   929  		fmt.Fprintf(expected, `"meta":{`)
   930  		fmt.Fprintf(expected, `"termsOfService":"http://example.invalid/terms",`)
   931  		fmt.Fprintf(expected, `"profiles":{"default":"a test profile"}`)
   932  		fmt.Fprintf(expected, "}")
   933  		fmt.Fprintf(expected, "}")
   934  		return expected.String()
   935  	}
   936  
   937  	dirTests := []struct {
   938  		host        string
   939  		protoHeader string
   940  		result      string
   941  	}{
   942  		// Test '' (No host header) with no proto header
   943  		{"", "", expectedDirectory("http://localhost")},
   944  		// Test localhost:4300 with no proto header
   945  		{"localhost:4300", "", expectedDirectory("http://localhost:4300")},
   946  		// Test 127.0.0.1:4300 with no proto header
   947  		{"127.0.0.1:4300", "", expectedDirectory("http://127.0.0.1:4300")},
   948  		// Test localhost:4300 with HTTP proto header
   949  		{"localhost:4300", "http", expectedDirectory("http://localhost:4300")},
   950  		// Test localhost:4300 with HTTPS proto header
   951  		{"localhost:4300", "https", expectedDirectory("https://localhost:4300")},
   952  	}
   953  
   954  	for _, tt := range dirTests {
   955  		var headers map[string][]string
   956  		responseWriter := httptest.NewRecorder()
   957  
   958  		if tt.protoHeader != "" {
   959  			headers = map[string][]string{
   960  				"X-Forwarded-Proto": {tt.protoHeader},
   961  			}
   962  		}
   963  
   964  		mux.ServeHTTP(responseWriter, &http.Request{
   965  			Method: "GET",
   966  			Host:   tt.host,
   967  			URL:    mustParseURL(directoryPath),
   968  			Header: headers,
   969  		})
   970  		test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
   971  		test.AssertEquals(t, responseWriter.Code, http.StatusOK)
   972  		test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tt.result)
   973  	}
   974  }
   975  
   976  // TestNonceEndpoint tests requests to the WFE2's new-nonce endpoint
   977  func TestNonceEndpoint(t *testing.T) {
   978  	wfe, _, signer := setupWFE(t)
   979  	mux := wfe.Handler(metrics.NoopRegisterer)
   980  
   981  	getReq := &http.Request{
   982  		Method: http.MethodGet,
   983  		URL:    mustParseURL(newNoncePath),
   984  	}
   985  	headReq := &http.Request{
   986  		Method: http.MethodHead,
   987  		URL:    mustParseURL(newNoncePath),
   988  	}
   989  
   990  	_, _, jwsBody := signer.byKeyID(1, nil, fmt.Sprintf("http://localhost%s", newNoncePath), "")
   991  	postAsGetReq := makePostRequestWithPath(newNoncePath, jwsBody)
   992  
   993  	testCases := []struct {
   994  		name           string
   995  		request        *http.Request
   996  		expectedStatus int
   997  	}{
   998  		{
   999  			name:           "GET new-nonce request",
  1000  			request:        getReq,
  1001  			expectedStatus: http.StatusNoContent,
  1002  		},
  1003  		{
  1004  			name:           "HEAD new-nonce request",
  1005  			request:        headReq,
  1006  			expectedStatus: http.StatusOK,
  1007  		},
  1008  		{
  1009  			name:           "POST-as-GET new-nonce request",
  1010  			request:        postAsGetReq,
  1011  			expectedStatus: http.StatusOK,
  1012  		},
  1013  	}
  1014  
  1015  	for _, tc := range testCases {
  1016  		t.Run(tc.name, func(t *testing.T) {
  1017  			responseWriter := httptest.NewRecorder()
  1018  			mux.ServeHTTP(responseWriter, tc.request)
  1019  			// The response should have the expected HTTP status code
  1020  			test.AssertEquals(t, responseWriter.Code, tc.expectedStatus)
  1021  			// And the response should contain a valid nonce in the Replay-Nonce header
  1022  			nonce := responseWriter.Header().Get("Replay-Nonce")
  1023  			redeemResp, err := wfe.rnc.Redeem(context.Background(), &noncepb.NonceMessage{Nonce: nonce})
  1024  			test.AssertNotError(t, err, "redeeming nonce")
  1025  			test.AssertEquals(t, redeemResp.Valid, true)
  1026  			// The server MUST include a Cache-Control header field with the "no-store"
  1027  			// directive in responses for the newNonce resource, in order to prevent
  1028  			// caching of this resource.
  1029  			cacheControl := responseWriter.Header().Get("Cache-Control")
  1030  			test.AssertEquals(t, cacheControl, "no-store")
  1031  		})
  1032  	}
  1033  }
  1034  
  1035  func TestHTTPMethods(t *testing.T) {
  1036  	wfe, _, _ := setupWFE(t)
  1037  	mux := wfe.Handler(metrics.NoopRegisterer)
  1038  
  1039  	// NOTE: Boulder's muxer treats HEAD as implicitly allowed if GET is specified
  1040  	// so we include both here in `getOnly`
  1041  	getOnly := map[string]bool{http.MethodGet: true, http.MethodHead: true}
  1042  	postOnly := map[string]bool{http.MethodPost: true}
  1043  	getOrPost := map[string]bool{http.MethodGet: true, http.MethodHead: true, http.MethodPost: true}
  1044  
  1045  	testCases := []struct {
  1046  		Name    string
  1047  		Path    string
  1048  		Allowed map[string]bool
  1049  	}{
  1050  		{
  1051  			Name:    "Index path should be GET only",
  1052  			Path:    "/",
  1053  			Allowed: getOnly,
  1054  		},
  1055  		{
  1056  			Name:    "Directory path should be GET or POST only",
  1057  			Path:    directoryPath,
  1058  			Allowed: getOrPost,
  1059  		},
  1060  		{
  1061  			Name:    "NewAcct path should be POST only",
  1062  			Path:    newAcctPath,
  1063  			Allowed: postOnly,
  1064  		},
  1065  		{
  1066  			Name:    "Acct path should be POST only",
  1067  			Path:    acctPath,
  1068  			Allowed: postOnly,
  1069  		},
  1070  		// TODO(@cpu): Remove GET authz support, support only POST-as-GET
  1071  		{
  1072  			Name:    "Authz path should be GET or POST only",
  1073  			Path:    authzPath,
  1074  			Allowed: getOrPost,
  1075  		},
  1076  		// TODO(@cpu): Remove GET challenge support, support only POST-as-GET
  1077  		{
  1078  			Name:    "Challenge path should be GET or POST only",
  1079  			Path:    challengePath,
  1080  			Allowed: getOrPost,
  1081  		},
  1082  		// TODO(@cpu): Remove GET certificate support, support only POST-as-GET
  1083  		{
  1084  			Name:    "Certificate path should be GET or POST only",
  1085  			Path:    certPath,
  1086  			Allowed: getOrPost,
  1087  		},
  1088  		{
  1089  			Name:    "RevokeCert path should be POST only",
  1090  			Path:    revokeCertPath,
  1091  			Allowed: postOnly,
  1092  		},
  1093  		{
  1094  			Name:    "Build ID path should be GET only",
  1095  			Path:    buildIDPath,
  1096  			Allowed: getOnly,
  1097  		},
  1098  		{
  1099  			Name:    "Health path should be GET only",
  1100  			Path:    healthzPath,
  1101  			Allowed: getOnly,
  1102  		},
  1103  		{
  1104  			Name:    "Rollover path should be POST only",
  1105  			Path:    rolloverPath,
  1106  			Allowed: postOnly,
  1107  		},
  1108  		{
  1109  			Name:    "New order path should be POST only",
  1110  			Path:    newOrderPath,
  1111  			Allowed: postOnly,
  1112  		},
  1113  		// TODO(@cpu): Remove GET order support, support only POST-as-GET
  1114  		{
  1115  			Name:    "Order path should be GET or POST only",
  1116  			Path:    orderPath,
  1117  			Allowed: getOrPost,
  1118  		},
  1119  		{
  1120  			Name:    "Nonce path should be GET or POST only",
  1121  			Path:    newNoncePath,
  1122  			Allowed: getOrPost,
  1123  		},
  1124  	}
  1125  
  1126  	// NOTE: We omit http.MethodOptions because all requests with this method are
  1127  	// redirected to a special endpoint for CORS headers
  1128  	allMethods := []string{
  1129  		http.MethodGet,
  1130  		http.MethodHead,
  1131  		http.MethodPost,
  1132  		http.MethodPut,
  1133  		http.MethodPatch,
  1134  		http.MethodDelete,
  1135  		http.MethodConnect,
  1136  		http.MethodTrace,
  1137  	}
  1138  
  1139  	responseWriter := httptest.NewRecorder()
  1140  
  1141  	for _, tc := range testCases {
  1142  		t.Run(tc.Name, func(t *testing.T) {
  1143  			// For every possible HTTP method check what the mux serves for the test
  1144  			// case path
  1145  			for _, method := range allMethods {
  1146  				responseWriter.Body.Reset()
  1147  				mux.ServeHTTP(responseWriter, &http.Request{
  1148  					Method: method,
  1149  					URL:    mustParseURL(tc.Path),
  1150  				})
  1151  				// If the method isn't one that is intended to be allowed by the path,
  1152  				// check that the response was the not allowed response
  1153  				if _, ok := tc.Allowed[method]; !ok {
  1154  					var prob probs.ProblemDetails
  1155  					// Unmarshal the body into a problem
  1156  					body := responseWriter.Body.String()
  1157  					err := json.Unmarshal([]byte(body), &prob)
  1158  					test.AssertNotError(t, err, fmt.Sprintf("Error unmarshalling resp body: %q", body))
  1159  					// TODO(@cpu): It seems like the mux should be returning
  1160  					// http.StatusMethodNotAllowed here, but instead it returns StatusOK
  1161  					// with a problem that has a StatusMethodNotAllowed HTTPStatus. Is
  1162  					// this a bug?
  1163  					test.AssertEquals(t, responseWriter.Code, http.StatusOK)
  1164  					test.AssertEquals(t, prob.HTTPStatus, http.StatusMethodNotAllowed)
  1165  					test.AssertEquals(t, prob.Detail, "Method not allowed")
  1166  				} else {
  1167  					// Otherwise if it was an allowed method, ensure that the response was
  1168  					// *not* StatusMethodNotAllowed
  1169  					test.AssertNotEquals(t, responseWriter.Code, http.StatusMethodNotAllowed)
  1170  				}
  1171  			}
  1172  		})
  1173  	}
  1174  }
  1175  
  1176  func TestGetChallengeHandler(t *testing.T) {
  1177  	wfe, _, _ := setupWFE(t)
  1178  
  1179  	// The slug "7TyhFQ" is the StringID of a challenge with type "http-01" and
  1180  	// token "token".
  1181  	challSlug := "7TyhFQ"
  1182  
  1183  	for _, method := range []string{"GET", "HEAD"} {
  1184  		resp := httptest.NewRecorder()
  1185  
  1186  		// We set req.URL.Path separately to emulate the path-stripping that
  1187  		// Boulder's request handler does.
  1188  		challengeURL := fmt.Sprintf("http://localhost/acme/chall/1/1/%s", challSlug)
  1189  		req, err := http.NewRequest(method, challengeURL, nil)
  1190  		test.AssertNotError(t, err, "Could not make NewRequest")
  1191  		req.URL.Path = fmt.Sprintf("1/1/%s", challSlug)
  1192  
  1193  		wfe.ChallengeHandler(ctx, newRequestEvent(), resp, req)
  1194  		test.AssertEquals(t, resp.Code, http.StatusOK)
  1195  		test.AssertEquals(t, resp.Header().Get("Location"), challengeURL)
  1196  		test.AssertEquals(t, resp.Header().Get("Content-Type"), "application/json")
  1197  		test.AssertEquals(t, resp.Header().Get("Link"), `<http://localhost/acme/authz/1/1>;rel="up"`)
  1198  
  1199  		// Body is only relevant for GET. For HEAD, body will
  1200  		// be discarded by HandleFunc() anyway, so it doesn't
  1201  		// matter what Challenge() writes to it.
  1202  		if method == "GET" {
  1203  			test.AssertUnmarshaledEquals(
  1204  				t, resp.Body.String(),
  1205  				`{"status": "valid", "type":"http-01","token":"token","url":"http://localhost/acme/chall/1/1/7TyhFQ"}`)
  1206  		}
  1207  	}
  1208  }
  1209  
  1210  func TestChallengeHandler(t *testing.T) {
  1211  	wfe, _, signer := setupWFE(t)
  1212  
  1213  	post := func(path string) *http.Request {
  1214  		signedURL := fmt.Sprintf("http://localhost/%s", path)
  1215  		_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
  1216  		return makePostRequestWithPath(path, jwsBody)
  1217  	}
  1218  	postAsGet := func(keyID int64, path, body string) *http.Request {
  1219  		_, _, jwsBody := signer.byKeyID(keyID, nil, fmt.Sprintf("http://localhost/%s", path), body)
  1220  		return makePostRequestWithPath(path, jwsBody)
  1221  	}
  1222  
  1223  	testCases := []struct {
  1224  		Name            string
  1225  		Request         *http.Request
  1226  		ExpectedStatus  int
  1227  		ExpectedHeaders map[string]string
  1228  		ExpectedBody    string
  1229  	}{
  1230  		{
  1231  			Name:           "Valid challenge",
  1232  			Request:        post("1/1/7TyhFQ"),
  1233  			ExpectedStatus: http.StatusOK,
  1234  			ExpectedHeaders: map[string]string{
  1235  				"Content-Type": "application/json",
  1236  				"Location":     "http://localhost/acme/chall/1/1/7TyhFQ",
  1237  				"Link":         `<http://localhost/acme/authz/1/1>;rel="up"`,
  1238  			},
  1239  			ExpectedBody: `{"status": "valid", "type":"http-01","token":"token","url":"http://localhost/acme/chall/1/1/7TyhFQ"}`,
  1240  		},
  1241  		{
  1242  			Name:           "Expired challenge",
  1243  			Request:        post("1/3/7TyhFQ"),
  1244  			ExpectedStatus: http.StatusNotFound,
  1245  			ExpectedBody:   `{"type":"` + probs.ErrorNS + `malformed","detail":"Expired authorization","status":404}`,
  1246  		},
  1247  		{
  1248  			Name:           "Missing challenge",
  1249  			Request:        post("1/1/"),
  1250  			ExpectedStatus: http.StatusNotFound,
  1251  			ExpectedBody:   `{"type":"` + probs.ErrorNS + `malformed","detail":"No such challenge","status":404}`,
  1252  		},
  1253  		{
  1254  			Name:           "Unspecified database error",
  1255  			Request:        post("1/4/7TyhFQ"),
  1256  			ExpectedStatus: http.StatusInternalServerError,
  1257  			ExpectedBody:   `{"type":"` + probs.ErrorNS + `serverInternal","detail":"Problem getting authorization","status":500}`,
  1258  		},
  1259  		{
  1260  			Name:           "POST-as-GET, wrong owner",
  1261  			Request:        postAsGet(1, "1/5/7TyhFQ", ""),
  1262  			ExpectedStatus: http.StatusForbidden,
  1263  			ExpectedBody:   `{"type":"` + probs.ErrorNS + `unauthorized","detail":"User account ID doesn't match account ID in authorization","status":403}`,
  1264  		},
  1265  		{
  1266  			Name:           "Valid POST-as-GET",
  1267  			Request:        postAsGet(1, "1/1/7TyhFQ", ""),
  1268  			ExpectedStatus: http.StatusOK,
  1269  			ExpectedBody:   `{"status": "valid", "type":"http-01", "token":"token", "url": "http://localhost/acme/chall/1/1/7TyhFQ"}`,
  1270  		},
  1271  	}
  1272  
  1273  	for _, tc := range testCases {
  1274  		t.Run(tc.Name, func(t *testing.T) {
  1275  			responseWriter := httptest.NewRecorder()
  1276  			wfe.ChallengeHandler(ctx, newRequestEvent(), responseWriter, tc.Request)
  1277  			// Check the response code, headers and body match expected
  1278  			headers := responseWriter.Header()
  1279  			body := responseWriter.Body.String()
  1280  			test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus)
  1281  			for h, v := range tc.ExpectedHeaders {
  1282  				test.AssertEquals(t, headers.Get(h), v)
  1283  			}
  1284  			test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody)
  1285  		})
  1286  	}
  1287  }
  1288  
  1289  // MockRAPerformValidationError is a mock RA that just returns an error on
  1290  // PerformValidation.
  1291  type MockRAPerformValidationError struct {
  1292  	MockRegistrationAuthority
  1293  }
  1294  
  1295  func (ra *MockRAPerformValidationError) PerformValidation(context.Context, *rapb.PerformValidationRequest, ...grpc.CallOption) (*corepb.Authorization, error) {
  1296  	return nil, errors.New("broken on purpose")
  1297  }
  1298  
  1299  // TestUpdateChallengeHandlerFinalizedAuthz tests that POSTing a challenge associated
  1300  // with an already valid authorization just returns the challenge without calling
  1301  // the RA.
  1302  func TestUpdateChallengeHandlerFinalizedAuthz(t *testing.T) {
  1303  	wfe, fc, signer := setupWFE(t)
  1304  	wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}}
  1305  	responseWriter := httptest.NewRecorder()
  1306  
  1307  	signedURL := "http://localhost/1/1/7TyhFQ"
  1308  	_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
  1309  	request := makePostRequestWithPath("1/1/7TyhFQ", jwsBody)
  1310  	wfe.ChallengeHandler(ctx, newRequestEvent(), responseWriter, request)
  1311  
  1312  	body := responseWriter.Body.String()
  1313  	test.AssertUnmarshaledEquals(t, body, `{
  1314  	  "status": "valid",
  1315  		"type": "http-01",
  1316  		"token": "token",
  1317  		"url": "http://localhost/acme/chall/1/1/7TyhFQ"
  1318  	  }`)
  1319  }
  1320  
  1321  // TestUpdateChallengeHandlerRAError tests that when the RA returns an error from
  1322  // PerformValidation that the WFE returns an internal server error as expected
  1323  // and does not panic or otherwise bug out.
  1324  func TestUpdateChallengeHandlerRAError(t *testing.T) {
  1325  	wfe, fc, signer := setupWFE(t)
  1326  	// Mock the RA to always fail PerformValidation
  1327  	wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}}
  1328  
  1329  	// Update a pending challenge
  1330  	signedURL := "http://localhost/1/2/7TyhFQ"
  1331  	_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
  1332  	responseWriter := httptest.NewRecorder()
  1333  	request := makePostRequestWithPath("1/2/7TyhFQ", jwsBody)
  1334  
  1335  	wfe.ChallengeHandler(ctx, newRequestEvent(), responseWriter, request)
  1336  
  1337  	// The result should be an internal server error problem.
  1338  	body := responseWriter.Body.String()
  1339  	test.AssertUnmarshaledEquals(t, body, `{
  1340  		"type": "urn:ietf:params:acme:error:serverInternal",
  1341  	  "detail": "Unable to update challenge",
  1342  		"status": 500
  1343  	}`)
  1344  }
  1345  
  1346  func TestBadNonce(t *testing.T) {
  1347  	wfe, _, _ := setupWFE(t)
  1348  
  1349  	key := loadKey(t, []byte(test2KeyPrivatePEM))
  1350  	rsaKey, ok := key.(*rsa.PrivateKey)
  1351  	test.Assert(t, ok, "Couldn't load RSA key")
  1352  	// NOTE: We deliberately do not set the NonceSource in the jose.SignerOptions
  1353  	// for this test in order to provoke a bad nonce error
  1354  	noNonceSigner, err := jose.NewSigner(jose.SigningKey{
  1355  		Key:       rsaKey,
  1356  		Algorithm: jose.RS256,
  1357  	}, &jose.SignerOptions{
  1358  		EmbedJWK: true,
  1359  	})
  1360  	test.AssertNotError(t, err, "Failed to make signer")
  1361  
  1362  	responseWriter := httptest.NewRecorder()
  1363  	result, err := noNonceSigner.Sign([]byte(`{"contact":["mailto:person@mail.com"]}`))
  1364  	test.AssertNotError(t, err, "Failed to sign body")
  1365  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter,
  1366  		makePostRequestWithPath("nonce", result.FullSerialize()))
  1367  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `{"type":"`+probs.ErrorNS+`badNonce","detail":"JWS has an invalid anti-replay nonce","status":400}`)
  1368  }
  1369  
  1370  func TestNewECDSAAccount(t *testing.T) {
  1371  	wfe, _, signer := setupWFE(t)
  1372  
  1373  	// E1 always exists; E2 never exists
  1374  	key := loadKey(t, []byte(testE2KeyPrivatePEM))
  1375  	_, ok := key.(*ecdsa.PrivateKey)
  1376  	test.Assert(t, ok, "Couldn't load ECDSA key")
  1377  
  1378  	payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
  1379  	path := newAcctPath
  1380  	signedURL := fmt.Sprintf("http://localhost%s", path)
  1381  	_, _, body := signer.embeddedJWK(key, signedURL, payload)
  1382  	request := makePostRequestWithPath(path, body)
  1383  
  1384  	responseWriter := httptest.NewRecorder()
  1385  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
  1386  
  1387  	var acct core.Registration
  1388  	responseBody := responseWriter.Body.String()
  1389  	err := json.Unmarshal([]byte(responseBody), &acct)
  1390  	test.AssertNotError(t, err, "Couldn't unmarshal returned account object")
  1391  	test.AssertEquals(t, acct.Agreement, "")
  1392  
  1393  	test.AssertEquals(t, responseWriter.Header().Get("Location"), "http://localhost/acme/acct/1")
  1394  
  1395  	key = loadKey(t, []byte(testE1KeyPrivatePEM))
  1396  	_, ok = key.(*ecdsa.PrivateKey)
  1397  	test.Assert(t, ok, "Couldn't load ECDSA key")
  1398  
  1399  	_, _, body = signer.embeddedJWK(key, signedURL, payload)
  1400  	request = makePostRequestWithPath(path, body)
  1401  
  1402  	// Reset the body and status code
  1403  	responseWriter = httptest.NewRecorder()
  1404  	// POST, Valid JSON, Key already in use
  1405  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
  1406  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
  1407  		`{
  1408  		"key": {
  1409  			"kty": "EC",
  1410  			"crv": "P-256",
  1411  			"x": "FwvSZpu06i3frSk_mz9HcD9nETn4wf3mQ-zDtG21Gao",
  1412  			"y": "S8rR-0dWa8nAcw1fbunF_ajS3PQZ-QwLps-2adgLgPk"
  1413  		},
  1414  		"status": ""
  1415  		}`)
  1416  	test.AssertEquals(t, responseWriter.Header().Get("Location"), "http://localhost/acme/acct/3")
  1417  	test.AssertEquals(t, responseWriter.Code, 200)
  1418  
  1419  	// test3KeyPrivatePEM is a private key corresponding to a deactivated account in the mock SA's GetRegistration test data.
  1420  	key = loadKey(t, []byte(test3KeyPrivatePEM))
  1421  	_, ok = key.(*rsa.PrivateKey)
  1422  	test.Assert(t, ok, "Couldn't load test3 key")
  1423  
  1424  	// Reset the body and status code
  1425  	responseWriter = httptest.NewRecorder()
  1426  
  1427  	// Test POST valid JSON with deactivated account
  1428  	payload = `{}`
  1429  	path = "1"
  1430  	signedURL = "http://localhost/1"
  1431  	_, _, body = signer.embeddedJWK(key, signedURL, payload)
  1432  	request = makePostRequestWithPath(path, body)
  1433  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
  1434  	test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
  1435  }
  1436  
  1437  // Test that the WFE handling of the "empty update" POST is correct. The ACME
  1438  // spec describes how when clients wish to query the server for information
  1439  // about an account an empty account update should be sent, and
  1440  // a populated acct object will be returned.
  1441  func TestEmptyAccount(t *testing.T) {
  1442  	wfe, _, signer := setupWFE(t)
  1443  
  1444  	// Test Key 1 is mocked in the mock StorageAuthority used in setupWFE to
  1445  	// return a populated account for GetRegistrationByKey when test key 1 is
  1446  	// used.
  1447  	key := loadKey(t, []byte(test1KeyPrivatePEM))
  1448  	_, ok := key.(*rsa.PrivateKey)
  1449  	test.Assert(t, ok, "Couldn't load RSA key")
  1450  
  1451  	path := "1"
  1452  	signedURL := "http://localhost/1"
  1453  
  1454  	testCases := []struct {
  1455  		Name           string
  1456  		Payload        string
  1457  		ExpectedStatus int
  1458  	}{
  1459  		{
  1460  			Name:           "POST empty string to acct",
  1461  			Payload:        "",
  1462  			ExpectedStatus: http.StatusOK,
  1463  		},
  1464  		{
  1465  			Name:           "POST empty JSON object to acct",
  1466  			Payload:        "{}",
  1467  			ExpectedStatus: http.StatusOK,
  1468  		},
  1469  		{
  1470  			Name:           "POST invalid empty JSON string to acct",
  1471  			Payload:        "\"\"",
  1472  			ExpectedStatus: http.StatusBadRequest,
  1473  		},
  1474  		{
  1475  			Name:           "POST invalid empty JSON array to acct",
  1476  			Payload:        "[]",
  1477  			ExpectedStatus: http.StatusBadRequest,
  1478  		},
  1479  	}
  1480  
  1481  	for _, tc := range testCases {
  1482  		t.Run(tc.Name, func(t *testing.T) {
  1483  			responseWriter := httptest.NewRecorder()
  1484  
  1485  			_, _, body := signer.byKeyID(1, key, signedURL, tc.Payload)
  1486  			request := makePostRequestWithPath(path, body)
  1487  
  1488  			// Send an account update with the trivial body
  1489  			wfe.Account(
  1490  				ctx,
  1491  				newRequestEvent(),
  1492  				responseWriter,
  1493  				request)
  1494  
  1495  			responseBody := responseWriter.Body.String()
  1496  			test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus)
  1497  
  1498  			// If success is expected, we should get back a populated Account
  1499  			if tc.ExpectedStatus == http.StatusOK {
  1500  				var acct core.Registration
  1501  				err := json.Unmarshal([]byte(responseBody), &acct)
  1502  				test.AssertNotError(t, err, "Couldn't unmarshal returned account object")
  1503  				test.AssertEquals(t, acct.Agreement, "")
  1504  			}
  1505  
  1506  			responseWriter.Body.Reset()
  1507  		})
  1508  	}
  1509  }
  1510  
  1511  func TestNewAccount(t *testing.T) {
  1512  	wfe, _, signer := setupWFE(t)
  1513  	mux := wfe.Handler(metrics.NoopRegisterer)
  1514  	key := loadKey(t, []byte(test2KeyPrivatePEM))
  1515  	_, ok := key.(*rsa.PrivateKey)
  1516  	test.Assert(t, ok, "Couldn't load test2 key")
  1517  
  1518  	path := newAcctPath
  1519  	signedURL := fmt.Sprintf("http://localhost%s", path)
  1520  
  1521  	wrongAgreementAcct := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":false}`
  1522  	// An acct with the terms not agreed to
  1523  	_, _, wrongAgreementBody := signer.embeddedJWK(key, signedURL, wrongAgreementAcct)
  1524  
  1525  	// A non-JSON payload
  1526  	_, _, fooBody := signer.embeddedJWK(key, signedURL, `foo`)
  1527  
  1528  	type newAcctErrorTest struct {
  1529  		r        *http.Request
  1530  		respBody string
  1531  	}
  1532  
  1533  	acctErrTests := []newAcctErrorTest{
  1534  		// POST, but no body.
  1535  		{
  1536  			&http.Request{
  1537  				Method: "POST",
  1538  				URL:    mustParseURL(newAcctPath),
  1539  				Header: map[string][]string{
  1540  					"Content-Length": {"0"},
  1541  					"Content-Type":   {expectedJWSContentType},
  1542  				},
  1543  			},
  1544  			`{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: No body on POST","status":400}`,
  1545  		},
  1546  
  1547  		// POST, but body that isn't valid JWS
  1548  		{
  1549  			makePostRequestWithPath(newAcctPath, "hi"),
  1550  			`{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: Parse error reading JWS","status":400}`,
  1551  		},
  1552  
  1553  		// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
  1554  		{
  1555  			makePostRequestWithPath(newAcctPath, fooBody),
  1556  			`{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: Request payload did not parse as JSON","status":400}`,
  1557  		},
  1558  
  1559  		// Same signed body, but payload modified by one byte, breaking signature.
  1560  		// should fail JWS verification.
  1561  		{
  1562  			makePostRequestWithPath(newAcctPath,
  1563  				`{"payload":"Zm9x","protected":"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoicW5BUkxyVDdYejRnUmNLeUxkeWRtQ3ItZXk5T3VQSW1YNFg0MHRoazNvbjI2RmtNem5SM2ZSanM2NmVMSzdtbVBjQlo2dU9Kc2VVUlU2d0FhWk5tZW1vWXgxZE12cXZXV0l5aVFsZUhTRDdROHZCcmhSNnVJb080akF6SlpSLUNoelp1U0R0N2lITi0zeFVWc3B1NVhHd1hVX01WSlpzaFR3cDRUYUZ4NWVsSElUX09iblR2VE9VM1hoaXNoMDdBYmdaS21Xc1ZiWGg1cy1DcklpY1U0T2V4SlBndW5XWl9ZSkp1ZU9LbVR2bkxsVFY0TXpLUjJvWmxCS1oyN1MwLVNmZFZfUUR4X3lkbGU1b01BeUtWdGxBVjM1Y3lQTUlzWU53Z1VHQkNkWV8yVXppNWVYMGxUYzdNUFJ3ejZxUjFraXAtaTU5VmNHY1VRZ3FIVjZGeXF3IiwiZSI6IkFRQUIifSwia2lkIjoiIiwibm9uY2UiOiJyNHpuenZQQUVwMDlDN1JwZUtYVHhvNkx3SGwxZVBVdmpGeXhOSE1hQnVvIiwidXJsIjoiaHR0cDovL2xvY2FsaG9zdC9hY21lL25ldy1yZWcifQ","signature":"jcTdxSygm_cvD7KbXqsxgnoPApCTSkV4jolToSOd2ciRkg5W7Yl0ZKEEKwOc-dYIbQiwGiDzisyPCicwWsOUA1WSqHylKvZ3nxSMc6KtwJCW2DaOqcf0EEjy5VjiZJUrOt2c-r6b07tbn8sfOJKwlF2lsOeGi4s-rtvvkeQpAU-AWauzl9G4bv2nDUeCviAZjHx_PoUC-f9GmZhYrbDzAvXZ859ktM6RmMeD0OqPN7bhAeju2j9Gl0lnryZMtq2m0J2m1ucenQBL1g4ZkP1JiJvzd2cAz5G7Ftl2YeJJyWhqNd3qq0GVOt1P11s8PTGNaSoM0iR9QfUxT9A6jxARtg"}`),
  1564  			`{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: JWS verification error","status":400}`,
  1565  		},
  1566  		{
  1567  			makePostRequestWithPath(newAcctPath, wrongAgreementBody),
  1568  			`{"type":"` + probs.ErrorNS + `malformed","detail":"must agree to terms of service","status":400}`,
  1569  		},
  1570  	}
  1571  	for _, rt := range acctErrTests {
  1572  		responseWriter := httptest.NewRecorder()
  1573  		mux.ServeHTTP(responseWriter, rt.r)
  1574  		test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), rt.respBody)
  1575  	}
  1576  
  1577  	responseWriter := httptest.NewRecorder()
  1578  
  1579  	payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
  1580  	_, _, body := signer.embeddedJWK(key, signedURL, payload)
  1581  	request := makePostRequestWithPath(path, body)
  1582  
  1583  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
  1584  
  1585  	var acct core.Registration
  1586  	responseBody := responseWriter.Body.String()
  1587  	err := json.Unmarshal([]byte(responseBody), &acct)
  1588  	test.AssertNotError(t, err, "Couldn't unmarshal returned account object")
  1589  	// Agreement is an ACMEv1 field and should not be present
  1590  	test.AssertEquals(t, acct.Agreement, "")
  1591  
  1592  	test.AssertEquals(
  1593  		t, responseWriter.Header().Get("Location"),
  1594  		"http://localhost/acme/acct/1")
  1595  
  1596  	// Load an existing key
  1597  	key = loadKey(t, []byte(test1KeyPrivatePEM))
  1598  	_, ok = key.(*rsa.PrivateKey)
  1599  	test.Assert(t, ok, "Couldn't load test1 key")
  1600  
  1601  	// Reset the body and status code
  1602  	responseWriter = httptest.NewRecorder()
  1603  	// POST, Valid JSON, Key already in use
  1604  	_, _, body = signer.embeddedJWK(key, signedURL, payload)
  1605  	request = makePostRequestWithPath(path, body)
  1606  	// POST the NewAccount request
  1607  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
  1608  	// We expect a Location header and a 200 response with an empty body
  1609  	test.AssertEquals(
  1610  		t, responseWriter.Header().Get("Location"),
  1611  		"http://localhost/acme/acct/1")
  1612  	test.AssertEquals(t, responseWriter.Code, 200)
  1613  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
  1614  		`{
  1615  		"key": {
  1616  			"kty": "RSA",
  1617  			"n": "yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
  1618  			"e": "AQAB"
  1619  		},
  1620  		"status": "valid"
  1621  	}`)
  1622  }
  1623  
  1624  func TestNewAccountWhenAccountHasBeenDeactivated(t *testing.T) {
  1625  	wfe, _, signer := setupWFE(t)
  1626  	signedURL := fmt.Sprintf("http://localhost%s", newAcctPath)
  1627  	// test3KeyPrivatePEM is a private key corresponding to a deactivated account in the mock SA's GetRegistration test data.
  1628  	k := loadKey(t, []byte(test3KeyPrivatePEM))
  1629  	_, ok := k.(*rsa.PrivateKey)
  1630  	test.Assert(t, ok, "Couldn't load test3 key")
  1631  
  1632  	payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
  1633  	_, _, body := signer.embeddedJWK(k, signedURL, payload)
  1634  	request := makePostRequestWithPath(newAcctPath, body)
  1635  
  1636  	responseWriter := httptest.NewRecorder()
  1637  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
  1638  
  1639  	test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
  1640  }
  1641  
  1642  func TestNewAccountNoID(t *testing.T) {
  1643  	wfe, _, signer := setupWFE(t)
  1644  	key := loadKey(t, []byte(test2KeyPrivatePEM))
  1645  	_, ok := key.(*rsa.PrivateKey)
  1646  	test.Assert(t, ok, "Couldn't load test2 key")
  1647  	path := newAcctPath
  1648  	signedURL := fmt.Sprintf("http://localhost%s", path)
  1649  
  1650  	payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
  1651  	_, _, body := signer.embeddedJWK(key, signedURL, payload)
  1652  	request := makePostRequestWithPath(path, body)
  1653  
  1654  	responseWriter := httptest.NewRecorder()
  1655  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
  1656  
  1657  	responseBody := responseWriter.Body.String()
  1658  	test.AssertUnmarshaledEquals(t, responseBody, `{
  1659  		"key": {
  1660  			"kty": "RSA",
  1661  			"n": "qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw",
  1662  			"e": "AQAB"
  1663  		},
  1664  		"createdAt": "2021-01-01T00:00:00Z",
  1665  		"status": ""
  1666  	}`)
  1667  }
  1668  
  1669  func TestContactsToEmails(t *testing.T) {
  1670  	t.Parallel()
  1671  	wfe, _, _ := setupWFE(t)
  1672  
  1673  	for _, tc := range []struct {
  1674  		name     string
  1675  		contacts []string
  1676  		want     []string
  1677  		wantErr  string
  1678  	}{
  1679  		{
  1680  			name:     "no contacts",
  1681  			contacts: []string{},
  1682  			want:     []string{},
  1683  		},
  1684  		{
  1685  			name:     "happy path",
  1686  			contacts: []string{"mailto:one@mail.com", "mailto:two@mail.com"},
  1687  			want:     []string{"one@mail.com", "two@mail.com"},
  1688  		},
  1689  		{
  1690  			name:     "empty url",
  1691  			contacts: []string{""},
  1692  			wantErr:  "empty contact",
  1693  		},
  1694  		{
  1695  			name:     "too many contacts",
  1696  			contacts: []string{"mailto:one@mail.com", "mailto:two@mail.com", "mailto:three@mail.com"},
  1697  			wantErr:  "too many contacts",
  1698  		},
  1699  		{
  1700  			name:     "unknown scheme",
  1701  			contacts: []string{"ansible:earth.sol.milkyway.laniakea/letsencrypt"},
  1702  			wantErr:  "contact scheme",
  1703  		},
  1704  		{
  1705  			name:     "malformed email",
  1706  			contacts: []string{"mailto:admin.com"},
  1707  			wantErr:  "unable to parse email address",
  1708  		},
  1709  		{
  1710  			name:     "non-ascii email",
  1711  			contacts: []string{"mailto:señor@email.com"},
  1712  			wantErr:  "contains non-ASCII characters",
  1713  		},
  1714  		{
  1715  			name:     "unarseable email",
  1716  			contacts: []string{"mailto:a@mail.com, b@mail.com"},
  1717  			wantErr:  "unable to parse email address",
  1718  		},
  1719  		{
  1720  			name:     "forbidden example domain",
  1721  			contacts: []string{"mailto:a@example.org"},
  1722  			wantErr:  "forbidden",
  1723  		},
  1724  		{
  1725  			name:     "forbidden non-public domain",
  1726  			contacts: []string{"mailto:admin@localhost"},
  1727  			wantErr:  "needs at least one dot",
  1728  		},
  1729  		{
  1730  			name:     "forbidden non-iana domain",
  1731  			contacts: []string{"mailto:admin@non.iana.suffix"},
  1732  			wantErr:  "does not end with a valid public suffix",
  1733  		},
  1734  		{
  1735  			name:     "forbidden ip domain",
  1736  			contacts: []string{"mailto:admin@1.2.3.4"},
  1737  			wantErr:  "value is an IP address",
  1738  		},
  1739  		{
  1740  			name:     "forbidden bracketed ip domain",
  1741  			contacts: []string{"mailto:admin@[1.2.3.4]"},
  1742  			wantErr:  "contains an invalid character",
  1743  		},
  1744  		{
  1745  			name:     "query parameter",
  1746  			contacts: []string{"mailto:admin@a.com?no-reminder-emails"},
  1747  			wantErr:  "contains a question mark",
  1748  		},
  1749  		{
  1750  			name:     "empty query parameter",
  1751  			contacts: []string{"mailto:admin@a.com?"},
  1752  			wantErr:  "contains a question mark",
  1753  		},
  1754  		{
  1755  			name:     "fragment url",
  1756  			contacts: []string{"mailto:admin@a.com#optional"},
  1757  			wantErr:  "contains a '#'",
  1758  		},
  1759  		{
  1760  			name:     "empty fragment url",
  1761  			contacts: []string{"mailto:admin@a.com#"},
  1762  			wantErr:  "contains a '#'",
  1763  		},
  1764  	} {
  1765  		t.Run(tc.name, func(t *testing.T) {
  1766  			t.Parallel()
  1767  			got, err := wfe.contactsToEmails(tc.contacts)
  1768  			if tc.wantErr != "" {
  1769  				if err == nil {
  1770  					t.Fatalf("contactsToEmails(%#v) = nil, but want %q", tc.contacts, tc.wantErr)
  1771  				}
  1772  				if !strings.Contains(err.Error(), tc.wantErr) {
  1773  					t.Errorf("contactsToEmails(%#v) = %q, but want %q", tc.contacts, err.Error(), tc.wantErr)
  1774  				}
  1775  			} else {
  1776  				if err != nil {
  1777  					t.Fatalf("contactsToEmails(%#v) = %q, but want %#v", tc.contacts, err.Error(), tc.want)
  1778  				}
  1779  				if !slices.Equal(got, tc.want) {
  1780  					t.Errorf("contactsToEmails(%#v) = %#v, but want %#v", tc.contacts, got, tc.want)
  1781  				}
  1782  			}
  1783  		})
  1784  	}
  1785  }
  1786  
  1787  func TestGetAuthorizationHandler(t *testing.T) {
  1788  	wfe, _, signer := setupWFE(t)
  1789  
  1790  	// Expired authorizations should be inaccessible
  1791  	authzURL := "1/3"
  1792  	responseWriter := httptest.NewRecorder()
  1793  	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{
  1794  		Method: "GET",
  1795  		URL:    mustParseURL(authzURL),
  1796  	})
  1797  	test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
  1798  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
  1799  		`{"type":"`+probs.ErrorNS+`malformed","detail":"Expired authorization","status":404}`)
  1800  	responseWriter.Body.Reset()
  1801  
  1802  	// Ensure that a valid authorization can't be reached with an invalid URL
  1803  	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{
  1804  		URL:    mustParseURL("1/1d"),
  1805  		Method: "GET",
  1806  	})
  1807  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
  1808  		`{"type":"`+probs.ErrorNS+`malformed","detail":"Invalid authorization ID","status":400}`)
  1809  
  1810  	_, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/1/1", "")
  1811  	postAsGet := makePostRequestWithPath("1/1", jwsBody)
  1812  
  1813  	responseWriter = httptest.NewRecorder()
  1814  	// Ensure that a POST-as-GET to an authorization works
  1815  	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, postAsGet)
  1816  	test.AssertEquals(t, responseWriter.Code, http.StatusOK)
  1817  	body := responseWriter.Body.String()
  1818  	test.AssertUnmarshaledEquals(t, body, `
  1819  	{
  1820  		"identifier": {
  1821  			"type": "dns",
  1822  			"value": "not-an-example.com"
  1823  		},
  1824  		"status": "valid",
  1825  		"expires": "2070-01-01T00:00:00Z",
  1826  		"challenges": [
  1827  			{
  1828  			  "status": "valid",
  1829  				"type": "http-01",
  1830  				"token":"token",
  1831  				"url": "http://localhost/acme/chall/1/1/7TyhFQ"
  1832  			}
  1833  		]
  1834  	}`)
  1835  }
  1836  
  1837  // TestAuthorizationHandler500 tests that internal errors on GetAuthorization result in
  1838  // a 500.
  1839  func TestAuthorizationHandler500(t *testing.T) {
  1840  	wfe, _, _ := setupWFE(t)
  1841  
  1842  	responseWriter := httptest.NewRecorder()
  1843  	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{
  1844  		Method: "GET",
  1845  		URL:    mustParseURL("1/4"),
  1846  	})
  1847  	expected := `{
  1848           "type": "urn:ietf:params:acme:error:serverInternal",
  1849  				 "detail": "Problem getting authorization",
  1850  				 "status": 500
  1851    }`
  1852  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), expected)
  1853  }
  1854  
  1855  // RAWithFailedChallenges is a fake RA whose GetAuthorization method returns
  1856  // an authz with a failed challenge.
  1857  type RAWithFailedChallenge struct {
  1858  	rapb.RegistrationAuthorityClient
  1859  	clk clock.Clock
  1860  }
  1861  
  1862  func (ra *RAWithFailedChallenge) GetAuthorization(ctx context.Context, id *rapb.GetAuthorizationRequest, _ ...grpc.CallOption) (*corepb.Authorization, error) {
  1863  	return &corepb.Authorization{
  1864  		Id:             "6",
  1865  		RegistrationID: 1,
  1866  		Identifier:     identifier.NewDNS("not-an-example.com").ToProto(),
  1867  		Status:         string(core.StatusInvalid),
  1868  		Expires:        timestamppb.New(ra.clk.Now().AddDate(100, 0, 0)),
  1869  		Challenges: []*corepb.Challenge{
  1870  			{
  1871  				Id:     1,
  1872  				Type:   "http-01",
  1873  				Status: string(core.StatusInvalid),
  1874  				Token:  "token",
  1875  				Error: &corepb.ProblemDetails{
  1876  					ProblemType: "things:are:whack",
  1877  					Detail:      "whack attack",
  1878  					HttpStatus:  555,
  1879  				},
  1880  			},
  1881  		},
  1882  	}, nil
  1883  }
  1884  
  1885  // TestAuthorizationChallengeHandlerNamespace tests that the runtime prefixing of
  1886  // Challenge Problem Types works as expected
  1887  func TestAuthorizationChallengeHandlerNamespace(t *testing.T) {
  1888  	wfe, clk, _ := setupWFE(t)
  1889  	wfe.ra = &RAWithFailedChallenge{clk: clk}
  1890  
  1891  	responseWriter := httptest.NewRecorder()
  1892  	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{
  1893  		Method: "GET",
  1894  		URL:    mustParseURL("1/6"),
  1895  	})
  1896  
  1897  	var authz core.Authorization
  1898  	err := json.Unmarshal(responseWriter.Body.Bytes(), &authz)
  1899  	test.AssertNotError(t, err, "Couldn't unmarshal returned authorization object")
  1900  	test.AssertEquals(t, len(authz.Challenges), 1)
  1901  	// The Challenge Error Type should have had the probs.ErrorNS prefix added
  1902  	test.AssertEquals(t, string(authz.Challenges[0].Error.Type), probs.ErrorNS+"things:are:whack")
  1903  	responseWriter.Body.Reset()
  1904  }
  1905  
  1906  func TestAccount(t *testing.T) {
  1907  	wfe, _, signer := setupWFE(t)
  1908  	mux := wfe.Handler(metrics.NoopRegisterer)
  1909  	responseWriter := httptest.NewRecorder()
  1910  
  1911  	// Test GET proper entry returns 405
  1912  	mux.ServeHTTP(responseWriter, &http.Request{
  1913  		Method: "GET",
  1914  		URL:    mustParseURL(acctPath),
  1915  	})
  1916  	test.AssertUnmarshaledEquals(t,
  1917  		responseWriter.Body.String(),
  1918  		`{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
  1919  	responseWriter.Body.Reset()
  1920  
  1921  	// Test POST invalid JSON
  1922  	wfe.Account(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("2", "invalid"))
  1923  	test.AssertUnmarshaledEquals(t,
  1924  		responseWriter.Body.String(),
  1925  		`{"type":"`+probs.ErrorNS+`malformed","detail":"Unable to validate JWS :: Parse error reading JWS","status":400}`)
  1926  	responseWriter.Body.Reset()
  1927  
  1928  	key := loadKey(t, []byte(test2KeyPrivatePEM))
  1929  	_, ok := key.(*rsa.PrivateKey)
  1930  	test.Assert(t, ok, "Couldn't load RSA key")
  1931  
  1932  	signedURL := fmt.Sprintf("http://localhost%s%d", acctPath, 102)
  1933  	path := fmt.Sprintf("%s%d", acctPath, 102)
  1934  	payload := `{}`
  1935  	// ID 102 is used by the mock for missing acct
  1936  	_, _, body := signer.byKeyID(102, nil, signedURL, payload)
  1937  	request := makePostRequestWithPath(path, body)
  1938  
  1939  	// Test POST valid JSON but key is not registered
  1940  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  1941  	test.AssertUnmarshaledEquals(t,
  1942  		responseWriter.Body.String(),
  1943  		`{"type":"`+probs.ErrorNS+`accountDoesNotExist","detail":"Unable to validate JWS :: Account \"http://localhost/acme/acct/102\" not found","status":400}`)
  1944  	responseWriter.Body.Reset()
  1945  
  1946  	key = loadKey(t, []byte(test1KeyPrivatePEM))
  1947  	_, ok = key.(*rsa.PrivateKey)
  1948  	test.Assert(t, ok, "Couldn't load RSA key")
  1949  
  1950  	// Test POST valid JSON with account up in the mock
  1951  	payload = `{}`
  1952  	path = "1"
  1953  	signedURL = "http://localhost/1"
  1954  	_, _, body = signer.byKeyID(1, nil, signedURL, payload)
  1955  	request = makePostRequestWithPath(path, body)
  1956  
  1957  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  1958  	test.AssertNotContains(t, responseWriter.Body.String(), probs.ErrorNS)
  1959  	links := responseWriter.Header()["Link"]
  1960  	test.AssertEquals(t, slices.Contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true)
  1961  	responseWriter.Body.Reset()
  1962  
  1963  	// Test POST valid JSON with garbage in URL but valid account ID
  1964  	payload = `{}`
  1965  	signedURL = "http://localhost/a/bunch/of/garbage/1"
  1966  	_, _, body = signer.byKeyID(1, nil, signedURL, payload)
  1967  	request = makePostRequestWithPath("/a/bunch/of/garbage/1", body)
  1968  
  1969  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  1970  	test.AssertContains(t, responseWriter.Body.String(), "400")
  1971  	test.AssertContains(t, responseWriter.Body.String(), probs.ErrorNS+"malformed")
  1972  	responseWriter.Body.Reset()
  1973  
  1974  	// Test valid POST-as-GET request
  1975  	responseWriter = httptest.NewRecorder()
  1976  	_, _, body = signer.byKeyID(1, nil, "http://localhost/1", "")
  1977  	request = makePostRequestWithPath("1", body)
  1978  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  1979  	// It should not error
  1980  	test.AssertNotContains(t, responseWriter.Body.String(), probs.ErrorNS)
  1981  	test.AssertEquals(t, responseWriter.Code, http.StatusOK)
  1982  
  1983  	altKey := loadKey(t, []byte(test2KeyPrivatePEM))
  1984  	_, ok = altKey.(*rsa.PrivateKey)
  1985  	test.Assert(t, ok, "Couldn't load altKey RSA key")
  1986  
  1987  	// Test POST-as-GET request signed with wrong account key
  1988  	responseWriter = httptest.NewRecorder()
  1989  	_, _, body = signer.byKeyID(2, altKey, "http://localhost/1", "")
  1990  	request = makePostRequestWithPath("1", body)
  1991  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  1992  	// It should error
  1993  	test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
  1994  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `{
  1995  		"type": "urn:ietf:params:acme:error:unauthorized",
  1996  		"detail": "Request signing key did not match account key",
  1997  		"status": 403
  1998  	}`)
  1999  }
  2000  
  2001  func TestUpdateAccount(t *testing.T) {
  2002  	t.Parallel()
  2003  	wfe, _, _ := setupWFE(t)
  2004  
  2005  	for _, tc := range []struct {
  2006  		name     string
  2007  		req      string
  2008  		wantAcct *core.Registration
  2009  		wantErr  string
  2010  	}{
  2011  		{
  2012  			name:     "empty status",
  2013  			req:      `{}`,
  2014  			wantAcct: &core.Registration{Status: core.StatusValid},
  2015  		},
  2016  		{
  2017  			name:     "empty status with contact",
  2018  			req:      `{"contact": ["mailto:admin@example.com"]}`,
  2019  			wantAcct: &core.Registration{Status: core.StatusValid},
  2020  		},
  2021  		{
  2022  			name:     "valid",
  2023  			req:      `{"status": "valid"}`,
  2024  			wantAcct: &core.Registration{Status: core.StatusValid},
  2025  		},
  2026  		{
  2027  			name:     "valid with contact",
  2028  			req:      `{"status": "valid", "contact": ["mailto:admin@example.com"]}`,
  2029  			wantAcct: &core.Registration{Status: core.StatusValid},
  2030  		},
  2031  		{
  2032  			name:     "deactivate",
  2033  			req:      `{"status": "deactivated"}`,
  2034  			wantAcct: &core.Registration{Status: core.StatusDeactivated},
  2035  		},
  2036  		{
  2037  			name:     "deactivate with contact",
  2038  			req:      `{"status": "deactivated", "contact": ["mailto:admin@example.com"]}`,
  2039  			wantAcct: &core.Registration{Status: core.StatusDeactivated},
  2040  		},
  2041  		{
  2042  			name:    "unrecognized status",
  2043  			req:     `{"status": "foo"}`,
  2044  			wantErr: "invalid status",
  2045  		},
  2046  		{
  2047  			// We're happy to ignore fields we don't recognize; they might be useful
  2048  			// for other CAs.
  2049  			name:     "unrecognized request field",
  2050  			req:      `{"foo": "bar"}`,
  2051  			wantAcct: &core.Registration{Status: core.StatusValid},
  2052  		},
  2053  	} {
  2054  		t.Run(tc.name, func(t *testing.T) {
  2055  			t.Parallel()
  2056  
  2057  			currAcct := core.Registration{Status: core.StatusValid}
  2058  
  2059  			gotAcct, gotProb := wfe.updateAccount(context.Background(), []byte(tc.req), &currAcct)
  2060  			if tc.wantAcct != nil {
  2061  				if gotAcct.Status != tc.wantAcct.Status {
  2062  					t.Errorf("want status %s, got %s", tc.wantAcct.Status, gotAcct.Status)
  2063  				}
  2064  				if !reflect.DeepEqual(gotAcct.Contact, tc.wantAcct.Contact) {
  2065  					t.Errorf("want contact %v, got %v", tc.wantAcct.Contact, gotAcct.Contact)
  2066  				}
  2067  			}
  2068  			if tc.wantErr != "" {
  2069  				if gotProb == nil {
  2070  					t.Fatalf("want error %q, got nil", tc.wantErr)
  2071  				}
  2072  				if !strings.Contains(gotProb.Error(), tc.wantErr) {
  2073  					t.Errorf("want error %q, got %q", tc.wantErr, gotProb.Error())
  2074  				}
  2075  			}
  2076  		})
  2077  	}
  2078  }
  2079  
  2080  type mockSAWithCert struct {
  2081  	sapb.StorageAuthorityReadOnlyClient
  2082  	cert   *x509.Certificate
  2083  	status core.OCSPStatus
  2084  }
  2085  
  2086  func newMockSAWithCert(t *testing.T, sa sapb.StorageAuthorityReadOnlyClient) *mockSAWithCert {
  2087  	cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
  2088  	test.AssertNotError(t, err, "Failed to load test cert")
  2089  	return &mockSAWithCert{sa, cert, core.OCSPStatusGood}
  2090  }
  2091  
  2092  // GetCertificate returns the mock SA's hard-coded certificate, issued by the
  2093  // account with regID 1, if the given serial matches. Otherwise, returns not found.
  2094  func (sa *mockSAWithCert) GetCertificate(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
  2095  	if req.Serial != core.SerialToString(sa.cert.SerialNumber) {
  2096  		return nil, berrors.NotFoundError("Certificate with serial %q not found", req.Serial)
  2097  	}
  2098  
  2099  	return &corepb.Certificate{
  2100  		RegistrationID: 1,
  2101  		Serial:         core.SerialToString(sa.cert.SerialNumber),
  2102  		Issued:         timestamppb.New(sa.cert.NotBefore),
  2103  		Expires:        timestamppb.New(sa.cert.NotAfter),
  2104  		Der:            sa.cert.Raw,
  2105  	}, nil
  2106  }
  2107  
  2108  // GetCertificateStatus returns the mock SA's status, if the given serial matches.
  2109  // Otherwise, returns not found.
  2110  func (sa *mockSAWithCert) GetCertificateStatus(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.CertificateStatus, error) {
  2111  	if req.Serial != core.SerialToString(sa.cert.SerialNumber) {
  2112  		return nil, berrors.NotFoundError("Status for certificate with serial %q not found", req.Serial)
  2113  	}
  2114  
  2115  	return &corepb.CertificateStatus{
  2116  		Serial: core.SerialToString(sa.cert.SerialNumber),
  2117  		Status: string(sa.status),
  2118  	}, nil
  2119  }
  2120  
  2121  type mockSAWithIncident struct {
  2122  	sapb.StorageAuthorityReadOnlyClient
  2123  	incidents map[string]*sapb.Incidents
  2124  }
  2125  
  2126  // newMockSAWithIncident returns a mock SA with an enabled (ongoing) incident
  2127  // for each of the provided serials.
  2128  func newMockSAWithIncident(sa sapb.StorageAuthorityReadOnlyClient, serial []string) *mockSAWithIncident {
  2129  	incidents := make(map[string]*sapb.Incidents)
  2130  	for _, s := range serial {
  2131  		incidents[s] = &sapb.Incidents{
  2132  			Incidents: []*sapb.Incident{
  2133  				{
  2134  					Id:          0,
  2135  					SerialTable: "incident_foo",
  2136  					Url:         "http://big.bad/incident",
  2137  					RenewBy:     nil,
  2138  					Enabled:     true,
  2139  				},
  2140  			},
  2141  		}
  2142  	}
  2143  	return &mockSAWithIncident{sa, incidents}
  2144  }
  2145  
  2146  func (sa *mockSAWithIncident) IncidentsForSerial(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*sapb.Incidents, error) {
  2147  	incidents, ok := sa.incidents[req.Serial]
  2148  	if ok {
  2149  		return incidents, nil
  2150  	}
  2151  	return &sapb.Incidents{}, nil
  2152  }
  2153  
  2154  type mockSAWithSerialMetadata struct {
  2155  	sapb.StorageAuthorityReadOnlyClient
  2156  }
  2157  
  2158  func (sa *mockSAWithSerialMetadata) GetSerialMetadata(ctx context.Context, serial *sapb.Serial, _ ...grpc.CallOption) (*sapb.SerialMetadata, error) {
  2159  	return &sapb.SerialMetadata{
  2160  		Expires: timestamppb.New(time.Date(2025, 7, 29, 0, 0, 0, 0, time.UTC)),
  2161  	}, nil
  2162  }
  2163  
  2164  func TestGetCertInfo(t *testing.T) {
  2165  	wfe, _, _ := setupWFE(t)
  2166  	wfe.sa = &mockSAWithSerialMetadata{}
  2167  	responseWriter := httptest.NewRecorder()
  2168  
  2169  	wfe.CertificateInfo(context.Background(), newRequestEvent(), responseWriter, &http.Request{
  2170  		Method: "GET",
  2171  		URL:    &url.URL{Path: "aabbccddeeffaabbccddeeff000102030405"},
  2172  	})
  2173  
  2174  	if responseWriter.Code != http.StatusOK {
  2175  		t.Errorf("got HTTP status code %d, want %d", responseWriter.Code, http.StatusOK)
  2176  	}
  2177  	expected := `{
  2178    "notAfter": "2025-07-29T00:00:00Z"
  2179  }`
  2180  	if responseWriter.Body.String() != expected {
  2181  		t.Errorf("got response body %q, want %q", responseWriter.Body.String(), expected)
  2182  	}
  2183  }
  2184  
  2185  func TestGetCertificate(t *testing.T) {
  2186  	wfe, _, signer := setupWFE(t)
  2187  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  2188  	mux := wfe.Handler(metrics.NoopRegisterer)
  2189  
  2190  	makeGet := func(path string) *http.Request {
  2191  		return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
  2192  	}
  2193  
  2194  	makePost := func(keyID int64, key any, path, body string) *http.Request {
  2195  		_, _, jwsBody := signer.byKeyID(keyID, key, fmt.Sprintf("http://localhost%s", path), body)
  2196  		return makePostRequestWithPath(path, jwsBody)
  2197  	}
  2198  
  2199  	altKey := loadKey(t, []byte(test2KeyPrivatePEM))
  2200  	_, ok := altKey.(*rsa.PrivateKey)
  2201  	test.Assert(t, ok, "Couldn't load RSA key")
  2202  
  2203  	certPemBytes, _ := os.ReadFile("../test/hierarchy/ee-r3.cert.pem")
  2204  	cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
  2205  	test.AssertNotError(t, err, "failed to load test certificate")
  2206  
  2207  	chainPemBytes, err := os.ReadFile("../test/hierarchy/int-r3.cert.pem")
  2208  	test.AssertNotError(t, err, "Error reading ../test/hierarchy/int-r3.cert.pem")
  2209  
  2210  	chainCrossPemBytes, err := os.ReadFile("../test/hierarchy/int-r3-cross.cert.pem")
  2211  	test.AssertNotError(t, err, "Error reading ../test/hierarchy/int-r3-cross.cert.pem")
  2212  
  2213  	reqPath := fmt.Sprintf("/acme/cert/%s", core.SerialToString(cert.SerialNumber))
  2214  	pkixContent := "application/pem-certificate-chain"
  2215  	noCache := "public, max-age=0, no-cache"
  2216  	notFound := `{"type":"` + probs.ErrorNS + `malformed","detail":"Certificate not found","status":404}`
  2217  
  2218  	testCases := []struct {
  2219  		Name            string
  2220  		Request         *http.Request
  2221  		ExpectedStatus  int
  2222  		ExpectedHeaders map[string]string
  2223  		ExpectedLink    string
  2224  		ExpectedBody    string
  2225  		ExpectedCert    []byte
  2226  		AnyCert         bool
  2227  	}{
  2228  		{
  2229  			Name:           "Valid serial",
  2230  			Request:        makeGet(reqPath),
  2231  			ExpectedStatus: http.StatusOK,
  2232  			ExpectedHeaders: map[string]string{
  2233  				"Content-Type": pkixContent,
  2234  			},
  2235  			ExpectedCert: append(certPemBytes, append([]byte("\n"), chainPemBytes...)...),
  2236  			ExpectedLink: fmt.Sprintf(`<http://localhost%s/1>;rel="alternate"`, reqPath),
  2237  		},
  2238  		{
  2239  			Name:           "Valid serial, POST-as-GET",
  2240  			Request:        makePost(1, nil, reqPath, ""),
  2241  			ExpectedStatus: http.StatusOK,
  2242  			ExpectedHeaders: map[string]string{
  2243  				"Content-Type": pkixContent,
  2244  			},
  2245  			ExpectedCert: append(certPemBytes, append([]byte("\n"), chainPemBytes...)...),
  2246  		},
  2247  		{
  2248  			Name:           "Valid serial, bad POST-as-GET",
  2249  			Request:        makePost(1, nil, reqPath, "{}"),
  2250  			ExpectedStatus: http.StatusBadRequest,
  2251  			ExpectedBody: `{
  2252  				"type": "urn:ietf:params:acme:error:malformed",
  2253  				"status": 400,
  2254  				"detail": "Unable to validate JWS :: POST-as-GET requests must have an empty payload"
  2255  			}`,
  2256  		},
  2257  		{
  2258  			Name:           "Valid serial, POST-as-GET from wrong account",
  2259  			Request:        makePost(2, altKey, reqPath, ""),
  2260  			ExpectedStatus: http.StatusForbidden,
  2261  			ExpectedBody: `{
  2262  				"type": "urn:ietf:params:acme:error:unauthorized",
  2263  				"status": 403,
  2264  				"detail": "Account in use did not issue specified certificate"
  2265  			}`,
  2266  		},
  2267  		{
  2268  			Name:           "Unused serial, no cache",
  2269  			Request:        makeGet("/acme/cert/000000000000000000000000000000000001"),
  2270  			ExpectedStatus: http.StatusNotFound,
  2271  			ExpectedBody:   notFound,
  2272  		},
  2273  		{
  2274  			Name:           "Invalid serial, no cache",
  2275  			Request:        makeGet("/acme/cert/nothex"),
  2276  			ExpectedStatus: http.StatusNotFound,
  2277  			ExpectedBody:   notFound,
  2278  		},
  2279  		{
  2280  			Name:           "Another invalid serial, no cache",
  2281  			Request:        makeGet("/acme/cert/00000000000000"),
  2282  			ExpectedStatus: http.StatusNotFound,
  2283  			ExpectedBody:   notFound,
  2284  		},
  2285  		{
  2286  			Name:           "Valid serial (explicit default chain)",
  2287  			Request:        makeGet(reqPath + "/0"),
  2288  			ExpectedStatus: http.StatusOK,
  2289  			ExpectedHeaders: map[string]string{
  2290  				"Content-Type": pkixContent,
  2291  			},
  2292  			ExpectedLink: fmt.Sprintf(`<http://localhost%s/1>;rel="alternate"`, reqPath),
  2293  			ExpectedCert: append(certPemBytes, append([]byte("\n"), chainPemBytes...)...),
  2294  		},
  2295  		{
  2296  			Name:           "Valid serial (explicit alternate chain)",
  2297  			Request:        makeGet(reqPath + "/1"),
  2298  			ExpectedStatus: http.StatusOK,
  2299  			ExpectedHeaders: map[string]string{
  2300  				"Content-Type": pkixContent,
  2301  			},
  2302  			ExpectedLink: fmt.Sprintf(`<http://localhost%s/0>;rel="alternate"`, reqPath),
  2303  			ExpectedCert: append(certPemBytes, append([]byte("\n"), chainCrossPemBytes...)...),
  2304  		},
  2305  		{
  2306  			Name:           "Valid serial (explicit non-existent alternate chain)",
  2307  			Request:        makeGet(reqPath + "/2"),
  2308  			ExpectedStatus: http.StatusNotFound,
  2309  			ExpectedBody:   `{"type":"` + probs.ErrorNS + `malformed","detail":"Unknown issuance chain","status":404}`,
  2310  		},
  2311  		{
  2312  			Name:           "Valid serial (explicit negative alternate chain)",
  2313  			Request:        makeGet(reqPath + "/-1"),
  2314  			ExpectedStatus: http.StatusBadRequest,
  2315  			ExpectedBody:   `{"type":"` + probs.ErrorNS + `malformed","detail":"Chain ID must be a non-negative integer","status":400}`,
  2316  		},
  2317  	}
  2318  
  2319  	for _, tc := range testCases {
  2320  		t.Run(tc.Name, func(t *testing.T) {
  2321  			responseWriter := httptest.NewRecorder()
  2322  			mockLog := wfe.log.(*blog.Mock)
  2323  			mockLog.Clear()
  2324  
  2325  			// Mux a request for a certificate
  2326  			mux.ServeHTTP(responseWriter, tc.Request)
  2327  			headers := responseWriter.Header()
  2328  
  2329  			// Assert that the status code written is as expected
  2330  			test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus)
  2331  
  2332  			// All of the responses should have the correct cache control header
  2333  			test.AssertEquals(t, headers.Get("Cache-Control"), noCache)
  2334  
  2335  			// If the test cases expects additional headers, check those too
  2336  			for h, v := range tc.ExpectedHeaders {
  2337  				test.AssertEquals(t, headers.Get(h), v)
  2338  			}
  2339  
  2340  			if tc.ExpectedLink != "" {
  2341  				found := false
  2342  				links := headers["Link"]
  2343  				if slices.Contains(links, tc.ExpectedLink) {
  2344  					found = true
  2345  				}
  2346  				if !found {
  2347  					t.Errorf("Expected link '%s', but did not find it in (%v)",
  2348  						tc.ExpectedLink, links)
  2349  				}
  2350  			}
  2351  
  2352  			if tc.AnyCert { // Certificate is randomly generated, don't match it
  2353  				return
  2354  			}
  2355  
  2356  			if len(tc.ExpectedCert) > 0 {
  2357  				// If the expectation was to return a certificate, check that it was the one expected
  2358  				bodyBytes := responseWriter.Body.Bytes()
  2359  				test.Assert(t, bytes.Equal(bodyBytes, tc.ExpectedCert), "Certificates don't match")
  2360  
  2361  				// Successful requests should be logged as such
  2362  				reqlogs := mockLog.GetAllMatching(`INFO: [^ ]+ [^ ]+ [^ ]+ 200 .*`)
  2363  				if len(reqlogs) != 1 {
  2364  					t.Errorf("Didn't find info logs with code 200. Instead got:\n%s\n",
  2365  						strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
  2366  				}
  2367  			} else {
  2368  				// Otherwise if the expectation wasn't a certificate, check that the body matches the expected
  2369  				body := responseWriter.Body.String()
  2370  				test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody)
  2371  
  2372  				// Unsuccessful requests should be logged as such
  2373  				reqlogs := mockLog.GetAllMatching(fmt.Sprintf(`INFO: [^ ]+ [^ ]+ [^ ]+ %d .*`, tc.ExpectedStatus))
  2374  				if len(reqlogs) != 1 {
  2375  					t.Errorf("Didn't find info logs with code %d. Instead got:\n%s\n",
  2376  						tc.ExpectedStatus, strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
  2377  				}
  2378  			}
  2379  		})
  2380  	}
  2381  }
  2382  
  2383  type mockSAWithNewCert struct {
  2384  	sapb.StorageAuthorityReadOnlyClient
  2385  	clk clock.Clock
  2386  }
  2387  
  2388  func (sa *mockSAWithNewCert) GetCertificate(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
  2389  	issuer, err := core.LoadCert("../test/hierarchy/int-e1.cert.pem")
  2390  	if err != nil {
  2391  		return nil, fmt.Errorf("failed to load test issuer cert: %w", err)
  2392  	}
  2393  
  2394  	issuerKeyPem, err := os.ReadFile("../test/hierarchy/int-e1.key.pem")
  2395  	if err != nil {
  2396  		return nil, fmt.Errorf("failed to load test issuer key: %w", err)
  2397  	}
  2398  	issuerKey := loadKey(&testing.T{}, issuerKeyPem)
  2399  
  2400  	newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  2401  	if err != nil {
  2402  		return nil, fmt.Errorf("failed to create test key: %w", err)
  2403  	}
  2404  
  2405  	sn, err := core.StringToSerial(req.Serial)
  2406  	if err != nil {
  2407  		return nil, fmt.Errorf("failed to parse test serial: %w", err)
  2408  	}
  2409  
  2410  	template := &x509.Certificate{
  2411  		SerialNumber: sn,
  2412  		DNSNames:     []string{"new.ee.boulder.test"},
  2413  	}
  2414  
  2415  	certDER, err := x509.CreateCertificate(rand.Reader, template, issuer, &newKey.PublicKey, issuerKey)
  2416  	if err != nil {
  2417  		return nil, fmt.Errorf("failed to issue test cert: %w", err)
  2418  	}
  2419  
  2420  	cert, err := x509.ParseCertificate(certDER)
  2421  	if err != nil {
  2422  		return nil, fmt.Errorf("failed to parse test cert: %w", err)
  2423  	}
  2424  
  2425  	return &corepb.Certificate{
  2426  		RegistrationID: 1,
  2427  		Serial:         core.SerialToString(cert.SerialNumber),
  2428  		Issued:         timestamppb.New(sa.clk.Now().Add(-1 * time.Second)),
  2429  		Der:            cert.Raw,
  2430  	}, nil
  2431  }
  2432  
  2433  // TestGetCertificateNew tests for the case when the certificate is new (by
  2434  // dynamically generating it at test time), and therefore isn't served by the
  2435  // GET api.
  2436  func TestGetCertificateNew(t *testing.T) {
  2437  	wfe, fc, signer := setupWFE(t)
  2438  	wfe.sa = &mockSAWithNewCert{wfe.sa, fc}
  2439  	mux := wfe.Handler(metrics.NoopRegisterer)
  2440  
  2441  	makeGet := func(path string) *http.Request {
  2442  		return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
  2443  	}
  2444  
  2445  	makePost := func(keyID int64, key any, path, body string) *http.Request {
  2446  		_, _, jwsBody := signer.byKeyID(keyID, key, fmt.Sprintf("http://localhost%s", path), body)
  2447  		return makePostRequestWithPath(path, jwsBody)
  2448  	}
  2449  
  2450  	altKey := loadKey(t, []byte(test2KeyPrivatePEM))
  2451  	_, ok := altKey.(*rsa.PrivateKey)
  2452  	test.Assert(t, ok, "Couldn't load RSA key")
  2453  
  2454  	pkixContent := "application/pem-certificate-chain"
  2455  	noCache := "public, max-age=0, no-cache"
  2456  
  2457  	testCases := []struct {
  2458  		Name            string
  2459  		Request         *http.Request
  2460  		ExpectedStatus  int
  2461  		ExpectedHeaders map[string]string
  2462  		ExpectedBody    string
  2463  	}{
  2464  		{
  2465  			Name:           "Get",
  2466  			Request:        makeGet("/get/cert/000000000000000000000000000000000001"),
  2467  			ExpectedStatus: http.StatusForbidden,
  2468  			ExpectedBody: `{
  2469  				"type": "` + probs.ErrorNS + `unauthorized",
  2470  				"detail": "Certificate is too new for GET API. You should only use this non-standard API to access resources created more than 10s ago",
  2471  				"status": 403
  2472  			}`,
  2473  		},
  2474  		{
  2475  			Name:           "ACME Get",
  2476  			Request:        makeGet("/acme/cert/000000000000000000000000000000000002"),
  2477  			ExpectedStatus: http.StatusOK,
  2478  			ExpectedHeaders: map[string]string{
  2479  				"Content-Type": pkixContent,
  2480  			},
  2481  		},
  2482  		{
  2483  			Name:           "ACME POST-as-GET",
  2484  			Request:        makePost(1, nil, "/acme/cert/000000000000000000000000000000000003", ""),
  2485  			ExpectedStatus: http.StatusOK,
  2486  			ExpectedHeaders: map[string]string{
  2487  				"Content-Type": pkixContent,
  2488  			},
  2489  		},
  2490  	}
  2491  
  2492  	for _, tc := range testCases {
  2493  		t.Run(tc.Name, func(t *testing.T) {
  2494  			responseWriter := httptest.NewRecorder()
  2495  			mockLog := wfe.log.(*blog.Mock)
  2496  			mockLog.Clear()
  2497  
  2498  			// Mux a request for a certificate
  2499  			mux.ServeHTTP(responseWriter, tc.Request)
  2500  			headers := responseWriter.Header()
  2501  
  2502  			// Assert that the status code written is as expected
  2503  			test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus)
  2504  
  2505  			// All of the responses should have the correct cache control header
  2506  			test.AssertEquals(t, headers.Get("Cache-Control"), noCache)
  2507  
  2508  			// If the test cases expects additional headers, check those too
  2509  			for h, v := range tc.ExpectedHeaders {
  2510  				test.AssertEquals(t, headers.Get(h), v)
  2511  			}
  2512  
  2513  			// If we're expecting a particular body (because of an error), check that.
  2514  			if tc.ExpectedBody != "" {
  2515  				body := responseWriter.Body.String()
  2516  				test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody)
  2517  
  2518  				// Unsuccessful requests should be logged as such
  2519  				reqlogs := mockLog.GetAllMatching(fmt.Sprintf(`INFO: [^ ]+ [^ ]+ [^ ]+ %d .*`, tc.ExpectedStatus))
  2520  				if len(reqlogs) != 1 {
  2521  					t.Errorf("Didn't find info logs with code %d. Instead got:\n%s\n",
  2522  						tc.ExpectedStatus, strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
  2523  				}
  2524  			}
  2525  		})
  2526  	}
  2527  }
  2528  
  2529  // This uses httptest.NewServer because ServeMux.ServeHTTP won't prevent the
  2530  // body from being sent like the net/http Server's actually do.
  2531  func TestGetCertificateHEADHasCorrectBodyLength(t *testing.T) {
  2532  	wfe, _, _ := setupWFE(t)
  2533  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  2534  
  2535  	certPemBytes, _ := os.ReadFile("../test/hierarchy/ee-r3.cert.pem")
  2536  	cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
  2537  	test.AssertNotError(t, err, "failed to load test certificate")
  2538  
  2539  	chainPemBytes, err := os.ReadFile("../test/hierarchy/int-r3.cert.pem")
  2540  	test.AssertNotError(t, err, "Error reading ../test/hierarchy/int-r3.cert.pem")
  2541  	chain := fmt.Sprintf("%s\n%s", string(certPemBytes), string(chainPemBytes))
  2542  	chainLen := strconv.Itoa(len(chain))
  2543  
  2544  	mockLog := wfe.log.(*blog.Mock)
  2545  	mockLog.Clear()
  2546  
  2547  	mux := wfe.Handler(metrics.NoopRegisterer)
  2548  	s := httptest.NewServer(mux)
  2549  	defer s.Close()
  2550  	req, _ := http.NewRequest(
  2551  		"HEAD", fmt.Sprintf("%s/acme/cert/%s", s.URL, core.SerialToString(cert.SerialNumber)), nil)
  2552  	resp, err := http.DefaultClient.Do(req)
  2553  	if err != nil {
  2554  		test.AssertNotError(t, err, "do error")
  2555  	}
  2556  	body, err := io.ReadAll(resp.Body)
  2557  	if err != nil {
  2558  		test.AssertNotEquals(t, err, "readall error")
  2559  	}
  2560  	err = resp.Body.Close()
  2561  	if err != nil {
  2562  		test.AssertNotEquals(t, err, "readall error")
  2563  	}
  2564  	test.AssertEquals(t, resp.StatusCode, 200)
  2565  	test.AssertEquals(t, chainLen, resp.Header.Get("Content-Length"))
  2566  	test.AssertEquals(t, 0, len(body))
  2567  }
  2568  
  2569  type mockSAWithError struct {
  2570  	sapb.StorageAuthorityReadOnlyClient
  2571  }
  2572  
  2573  func (sa *mockSAWithError) GetCertificate(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
  2574  	return nil, errors.New("Oops")
  2575  }
  2576  
  2577  func TestGetCertificateServerError(t *testing.T) {
  2578  	// TODO: add tests for failure to parse the retrieved cert, a cert whose
  2579  	// IssuerNameID is unknown, and a cert whose signature can't be verified.
  2580  	wfe, _, _ := setupWFE(t)
  2581  	wfe.sa = &mockSAWithError{wfe.sa}
  2582  	mux := wfe.Handler(metrics.NoopRegisterer)
  2583  
  2584  	cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
  2585  	test.AssertNotError(t, err, "failed to load test certificate")
  2586  
  2587  	reqPath := fmt.Sprintf("/acme/cert/%s", core.SerialToString(cert.SerialNumber))
  2588  	req := &http.Request{URL: &url.URL{Path: reqPath}, Method: "GET"}
  2589  
  2590  	// Mux a request for a certificate
  2591  	responseWriter := httptest.NewRecorder()
  2592  	mux.ServeHTTP(responseWriter, req)
  2593  
  2594  	test.AssertEquals(t, responseWriter.Code, http.StatusInternalServerError)
  2595  
  2596  	noCache := "public, max-age=0, no-cache"
  2597  	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), noCache)
  2598  
  2599  	body := `{
  2600  		"type": "urn:ietf:params:acme:error:serverInternal",
  2601  		"status": 500,
  2602  		"detail": "Failed to retrieve certificate"
  2603  	}`
  2604  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), body)
  2605  }
  2606  
  2607  func newRequestEvent() *web.RequestEvent {
  2608  	return &web.RequestEvent{Extra: make(map[string]any)}
  2609  }
  2610  
  2611  func TestHeaderBoulderRequester(t *testing.T) {
  2612  	wfe, _, signer := setupWFE(t)
  2613  	mux := wfe.Handler(metrics.NoopRegisterer)
  2614  	responseWriter := httptest.NewRecorder()
  2615  
  2616  	key := loadKey(t, []byte(test1KeyPrivatePEM))
  2617  	_, ok := key.(*rsa.PrivateKey)
  2618  	test.Assert(t, ok, "Failed to load test 1 RSA key")
  2619  
  2620  	payload := `{}`
  2621  	path := fmt.Sprintf("%s%d", acctPath, 1)
  2622  	signedURL := fmt.Sprintf("http://localhost%s", path)
  2623  	_, _, body := signer.byKeyID(1, nil, signedURL, payload)
  2624  	request := makePostRequestWithPath(path, body)
  2625  
  2626  	mux.ServeHTTP(responseWriter, request)
  2627  	test.AssertEquals(t, responseWriter.Header().Get("Boulder-Requester"), "1")
  2628  
  2629  	// requests that do call sendError() also should have the requester header
  2630  	payload = `{"agreement":"https://letsencrypt.org/im-bad"}`
  2631  	_, _, body = signer.byKeyID(1, nil, signedURL, payload)
  2632  	request = makePostRequestWithPath(path, body)
  2633  	mux.ServeHTTP(responseWriter, request)
  2634  	test.AssertEquals(t, responseWriter.Header().Get("Boulder-Requester"), "1")
  2635  }
  2636  
  2637  func TestDeactivateAuthorizationHandler(t *testing.T) {
  2638  	wfe, _, signer := setupWFE(t)
  2639  	responseWriter := httptest.NewRecorder()
  2640  
  2641  	responseWriter.Body.Reset()
  2642  
  2643  	payload := `{"status":""}`
  2644  	_, _, body := signer.byKeyID(1, nil, "http://localhost/1/1", payload)
  2645  	request := makePostRequestWithPath("1/1", body)
  2646  
  2647  	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, request)
  2648  	test.AssertUnmarshaledEquals(t,
  2649  		responseWriter.Body.String(),
  2650  		`{"type": "`+probs.ErrorNS+`malformed","detail": "Invalid status value","status": 400}`)
  2651  
  2652  	responseWriter.Body.Reset()
  2653  	payload = `{"status":"deactivated"}`
  2654  	_, _, body = signer.byKeyID(1, nil, "http://localhost/1/1", payload)
  2655  	request = makePostRequestWithPath("1/1", body)
  2656  
  2657  	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, request)
  2658  	test.AssertUnmarshaledEquals(t,
  2659  		responseWriter.Body.String(),
  2660  		`{
  2661  		  "identifier": {
  2662  		    "type": "dns",
  2663  		    "value": "not-an-example.com"
  2664  		  },
  2665  		  "status": "deactivated",
  2666  		  "expires": "2070-01-01T00:00:00Z",
  2667  		  "challenges": [
  2668  		    {
  2669  					"status": "valid",
  2670  					"type": "http-01",
  2671  					"token": "token",
  2672  					"url": "http://localhost/acme/chall/1/1/7TyhFQ"
  2673  		    }
  2674  		  ]
  2675  		}`)
  2676  }
  2677  
  2678  func TestDeactivateAccount(t *testing.T) {
  2679  	responseWriter := httptest.NewRecorder()
  2680  	wfe, _, signer := setupWFE(t)
  2681  
  2682  	responseWriter.Body.Reset()
  2683  	payload := `{"status":"asd"}`
  2684  	signedURL := "http://localhost/1"
  2685  	path := "1"
  2686  	_, _, body := signer.byKeyID(1, nil, signedURL, payload)
  2687  	request := makePostRequestWithPath(path, body)
  2688  
  2689  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  2690  	test.AssertUnmarshaledEquals(t,
  2691  		responseWriter.Body.String(),
  2692  		`{"type": "`+probs.ErrorNS+`malformed","detail": "Unable to update account :: invalid status \"asd\" for account update request, must be \"valid\" or \"deactivated\"","status": 400}`)
  2693  
  2694  	responseWriter.Body.Reset()
  2695  	payload = `{"status":"deactivated"}`
  2696  	_, _, body = signer.byKeyID(1, nil, signedURL, payload)
  2697  	request = makePostRequestWithPath(path, body)
  2698  
  2699  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  2700  	test.AssertUnmarshaledEquals(t,
  2701  		responseWriter.Body.String(),
  2702  		`{
  2703  		  "key": {
  2704  		    "kty": "RSA",
  2705  		    "n": "yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
  2706  		    "e": "AQAB"
  2707  		  },
  2708  		  "status": "deactivated"
  2709  		}`)
  2710  
  2711  	responseWriter.Body.Reset()
  2712  	payload = `{"status":"deactivated", "contact":[]}`
  2713  	_, _, body = signer.byKeyID(1, nil, signedURL, payload)
  2714  	request = makePostRequestWithPath(path, body)
  2715  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  2716  	test.AssertUnmarshaledEquals(t,
  2717  		responseWriter.Body.String(),
  2718  		`{
  2719  		  "key": {
  2720  		    "kty": "RSA",
  2721  		    "n": "yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
  2722  		    "e": "AQAB"
  2723  		  },
  2724  		  "status": "deactivated"
  2725  		}`)
  2726  
  2727  	responseWriter.Body.Reset()
  2728  	key := loadKey(t, []byte(test3KeyPrivatePEM))
  2729  	_, ok := key.(*rsa.PrivateKey)
  2730  	test.Assert(t, ok, "Couldn't load test3 RSA key")
  2731  
  2732  	payload = `{"status":"deactivated"}`
  2733  	path = "3"
  2734  	signedURL = "http://localhost/3"
  2735  	_, _, body = signer.byKeyID(3, key, signedURL, payload)
  2736  	request = makePostRequestWithPath(path, body)
  2737  
  2738  	wfe.Account(ctx, newRequestEvent(), responseWriter, request)
  2739  
  2740  	test.AssertUnmarshaledEquals(t,
  2741  		responseWriter.Body.String(),
  2742  		`{
  2743  		  "type": "`+probs.ErrorNS+`unauthorized",
  2744  		  "detail": "Unable to validate JWS :: Account is not valid, has status \"deactivated\"",
  2745  		  "status": 403
  2746  		}`)
  2747  }
  2748  
  2749  func TestNewOrder(t *testing.T) {
  2750  	wfe, _, signer := setupWFE(t)
  2751  	responseWriter := httptest.NewRecorder()
  2752  
  2753  	targetHost := "localhost"
  2754  	targetPath := "new-order"
  2755  	signedURL := fmt.Sprintf("http://%s/%s", targetHost, targetPath)
  2756  
  2757  	invalidIdentifierBody := `
  2758  	{
  2759  		"Identifiers": [
  2760  			{"type": "dns",    "value": "not-example.com"},
  2761  			{"type": "dns",    "value": "www.not-example.com"},
  2762  			{"type": "fakeID", "value": "www.i-am-21.com"}
  2763  		]
  2764  	}
  2765  	`
  2766  
  2767  	validOrderBody := `
  2768  	{
  2769  		"Identifiers": [
  2770  			{"type": "dns", "value": "not-example.com"},
  2771  			{"type": "dns", "value": "www.not-example.com"},
  2772  			{"type": "ip", "value": "9.9.9.9"}
  2773  		]
  2774  	}`
  2775  
  2776  	validOrderBodyWithMixedCaseIdentifiers := `
  2777  	{
  2778  		"Identifiers": [
  2779  			{"type": "dns", "value": "Not-Example.com"},
  2780  			{"type": "dns", "value": "WWW.Not-example.com"},
  2781  			{"type": "ip", "value": "9.9.9.9"}
  2782  		]
  2783  	}`
  2784  
  2785  	// Body with a SAN that is longer than 64 bytes. This one is 65 bytes.
  2786  	tooLongCNBody := `
  2787  	{
  2788  		"Identifiers": [
  2789  			{
  2790  				"type": "dns",
  2791  				"value": "thisreallylongexampledomainisabytelongerthanthemaxcnbytelimit.com"
  2792  			}
  2793  		]
  2794  	}`
  2795  
  2796  	oneLongOneShortCNBody := `
  2797  	{
  2798  		"Identifiers": [
  2799  			{
  2800  				"type": "dns",
  2801  				"value": "thisreallylongexampledomainisabytelongerthanthemaxcnbytelimit.com"
  2802  			},
  2803  			{
  2804  				"type": "dns",
  2805  				"value": "not-example.com"
  2806  			}
  2807  		]
  2808  	}`
  2809  
  2810  	testCases := []struct {
  2811  		Name            string
  2812  		Request         *http.Request
  2813  		ExpectedBody    string
  2814  		ExpectedHeaders map[string]string
  2815  	}{
  2816  		{
  2817  			Name: "POST, but no body",
  2818  			Request: &http.Request{
  2819  				Method: "POST",
  2820  				Header: map[string][]string{
  2821  					"Content-Length": {"0"},
  2822  					"Content-Type":   {expectedJWSContentType},
  2823  				},
  2824  			},
  2825  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: No body on POST","status":400}`,
  2826  		},
  2827  		{
  2828  			Name:         "POST, with an invalid JWS body",
  2829  			Request:      makePostRequestWithPath("hi", "hi"),
  2830  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: Parse error reading JWS","status":400}`,
  2831  		},
  2832  		{
  2833  			Name:         "POST, properly signed JWS, payload isn't valid",
  2834  			Request:      signAndPost(signer, targetPath, signedURL, "foo"),
  2835  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: Request payload did not parse as JSON","status":400}`,
  2836  		},
  2837  		{
  2838  			Name:         "POST, empty DNS identifier",
  2839  			Request:      signAndPost(signer, targetPath, signedURL, `{"identifiers":[{"type":"dns","value":""}]}`),
  2840  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"NewOrder request included empty identifier","status":400}`,
  2841  		},
  2842  		{
  2843  			Name:         "POST, empty IP identifier",
  2844  			Request:      signAndPost(signer, targetPath, signedURL, `{"identifiers":[{"type":"ip","value":""}]}`),
  2845  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"NewOrder request included empty identifier","status":400}`,
  2846  		},
  2847  		{
  2848  			Name:         "POST, invalid DNS identifier",
  2849  			Request:      signAndPost(signer, targetPath, signedURL, `{"identifiers":[{"type":"dns","value":"example.invalid"}]}`),
  2850  			ExpectedBody: `{"type":"` + probs.ErrorNS + `rejectedIdentifier","detail":"Invalid identifiers requested :: Cannot issue for \"example.invalid\": Domain name does not end with a valid public suffix (TLD)","status":400}`,
  2851  		},
  2852  		{
  2853  			Name:         "POST, invalid IP identifier",
  2854  			Request:      signAndPost(signer, targetPath, signedURL, `{"identifiers":[{"type":"ip","value":"127.0.0.0.0.0.0.1"}]}`),
  2855  			ExpectedBody: `{"type":"` + probs.ErrorNS + `rejectedIdentifier","detail":"Invalid identifiers requested :: Cannot issue for \"127.0.0.0.0.0.0.1\": IP address is invalid","status":400}`,
  2856  		},
  2857  		{
  2858  			Name:         "POST, no identifiers in payload",
  2859  			Request:      signAndPost(signer, targetPath, signedURL, "{}"),
  2860  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"NewOrder request did not specify any identifiers","status":400}`,
  2861  		},
  2862  		{
  2863  			Name:         "POST, invalid identifier type in payload",
  2864  			Request:      signAndPost(signer, targetPath, signedURL, invalidIdentifierBody),
  2865  			ExpectedBody: `{"type":"` + probs.ErrorNS + `unsupportedIdentifier","detail":"NewOrder request included unsupported identifier: type \"fakeID\", value \"www.i-am-21.com\"","status":400}`,
  2866  		},
  2867  		{
  2868  			Name:         "POST, notAfter and notBefore in payload",
  2869  			Request:      signAndPost(signer, targetPath, signedURL, `{"identifiers":[{"type": "dns", "value": "not-example.com"}], "notBefore":"now", "notAfter": "later"}`),
  2870  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"NotBefore and NotAfter are not supported","status":400}`,
  2871  		},
  2872  		{
  2873  			Name:    "POST, good payload, all names too long to fit in CN",
  2874  			Request: signAndPost(signer, targetPath, signedURL, tooLongCNBody),
  2875  			ExpectedBody: `
  2876  			{
  2877  				"status": "pending",
  2878  				"expires": "2021-02-01T01:01:01Z",
  2879  				"identifiers": [
  2880  					{ "type": "dns", "value": "thisreallylongexampledomainisabytelongerthanthemaxcnbytelimit.com"}
  2881  				],
  2882  				"authorizations": [
  2883  					"http://localhost/acme/authz/1/1"
  2884  				],
  2885  				"finalize": "http://localhost/acme/finalize/1/1"
  2886  			}`,
  2887  		},
  2888  		{
  2889  			Name:    "POST, good payload, one potential CNs less than 64 bytes and one longer",
  2890  			Request: signAndPost(signer, targetPath, signedURL, oneLongOneShortCNBody),
  2891  			ExpectedBody: `
  2892  			{
  2893  				"status": "pending",
  2894  				"expires": "2021-02-01T01:01:01Z",
  2895  				"identifiers": [
  2896  					{ "type": "dns", "value": "not-example.com"},
  2897  					{ "type": "dns", "value": "thisreallylongexampledomainisabytelongerthanthemaxcnbytelimit.com"}
  2898  				],
  2899  				"authorizations": [
  2900  					"http://localhost/acme/authz/1/1"
  2901  				],
  2902  				"finalize": "http://localhost/acme/finalize/1/1"
  2903  			}`,
  2904  		},
  2905  		{
  2906  			Name:    "POST, good payload",
  2907  			Request: signAndPost(signer, targetPath, signedURL, validOrderBody),
  2908  			ExpectedBody: `
  2909  					{
  2910  						"status": "pending",
  2911  						"expires": "2021-02-01T01:01:01Z",
  2912  						"identifiers": [
  2913  							{ "type": "dns", "value": "not-example.com"},
  2914  							{ "type": "dns", "value": "www.not-example.com"},
  2915  							{ "type": "ip", "value": "9.9.9.9"}
  2916  						],
  2917  						"authorizations": [
  2918  							"http://localhost/acme/authz/1/1"
  2919  						],
  2920  						"finalize": "http://localhost/acme/finalize/1/1"
  2921  					}`,
  2922  		},
  2923  		{
  2924  			Name:    "POST, good payload, but when the input had mixed case",
  2925  			Request: signAndPost(signer, targetPath, signedURL, validOrderBodyWithMixedCaseIdentifiers),
  2926  			ExpectedBody: `
  2927  					{
  2928  						"status": "pending",
  2929  						"expires": "2021-02-01T01:01:01Z",
  2930  						"identifiers": [
  2931  							{ "type": "dns", "value": "not-example.com"},
  2932  							{ "type": "dns", "value": "www.not-example.com"},
  2933  							{ "type": "ip", "value": "9.9.9.9"}
  2934  						],
  2935  						"authorizations": [
  2936  							"http://localhost/acme/authz/1/1"
  2937  						],
  2938  						"finalize": "http://localhost/acme/finalize/1/1"
  2939  					}`,
  2940  		},
  2941  	}
  2942  
  2943  	for _, tc := range testCases {
  2944  		t.Run(tc.Name, func(t *testing.T) {
  2945  			responseWriter.Body.Reset()
  2946  
  2947  			wfe.NewOrder(ctx, newRequestEvent(), responseWriter, tc.Request)
  2948  			test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedBody)
  2949  
  2950  			headers := responseWriter.Header()
  2951  			for k, v := range tc.ExpectedHeaders {
  2952  				test.AssertEquals(t, headers.Get(k), v)
  2953  			}
  2954  		})
  2955  	}
  2956  
  2957  	// Test that we log the "Created" field.
  2958  	responseWriter.Body.Reset()
  2959  	request := signAndPost(signer, targetPath, signedURL, validOrderBody)
  2960  	requestEvent := newRequestEvent()
  2961  	wfe.NewOrder(ctx, requestEvent, responseWriter, request)
  2962  
  2963  	if requestEvent.Created != "1" {
  2964  		t.Errorf("Expected to log Created field when creating Order: %#v", requestEvent)
  2965  	}
  2966  }
  2967  
  2968  func TestFinalizeOrder(t *testing.T) {
  2969  	wfe, _, signer := setupWFE(t)
  2970  	responseWriter := httptest.NewRecorder()
  2971  
  2972  	targetHost := "localhost"
  2973  	targetPath := "1/1"
  2974  	signedURL := fmt.Sprintf("http://%s/%s", targetHost, targetPath)
  2975  
  2976  	// This example is a well-formed CSR for the name "example.com".
  2977  	goodCertCSRPayload := `{
  2978  		"csr": "MIHRMHgCAQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ2hlvArQl5k0L1eF1vF5dwr7ASm2iKqibmauund-z3QJpuudnNEjlyOXi-IY1rxyhehRrtbm_bbcNCtZLgbkPvoAAwCgYIKoZIzj0EAwIDSQAwRgIhAJ8z2EDll2BvoNRotAknEfrqeP6K5CN1NeVMB4QOu0G1AiEAqAVpiGwNyV7SEZ67vV5vyuGsKPAGnqrisZh5Vg5JKHE="
  2979  	}`
  2980  
  2981  	egUrl := mustParseURL("1/1")
  2982  
  2983  	testCases := []struct {
  2984  		Name            string
  2985  		Request         *http.Request
  2986  		ExpectedHeaders map[string]string
  2987  		ExpectedBody    string
  2988  	}{
  2989  		{
  2990  			Name: "POST, but no body",
  2991  			Request: &http.Request{
  2992  				URL:        egUrl,
  2993  				RequestURI: targetPath,
  2994  				Method:     "POST",
  2995  				Header: map[string][]string{
  2996  					"Content-Length": {"0"},
  2997  					"Content-Type":   {expectedJWSContentType},
  2998  				},
  2999  			},
  3000  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: No body on POST","status":400}`,
  3001  		},
  3002  		{
  3003  			Name:         "POST, with an invalid JWS body",
  3004  			Request:      makePostRequestWithPath(targetPath, "hi"),
  3005  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: Parse error reading JWS","status":400}`,
  3006  		},
  3007  		{
  3008  			Name:         "POST, properly signed JWS, payload isn't valid",
  3009  			Request:      signAndPost(signer, targetPath, signedURL, "foo"),
  3010  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: Request payload did not parse as JSON","status":400}`,
  3011  		},
  3012  		{
  3013  			Name:         "Invalid path",
  3014  			Request:      signAndPost(signer, "1", "http://localhost/1", "{}"),
  3015  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid request path","status":404}`,
  3016  		},
  3017  		{
  3018  			Name:         "Bad acct ID in path",
  3019  			Request:      signAndPost(signer, "a/1", "http://localhost/a/1", "{}"),
  3020  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid account ID","status":400}`,
  3021  		},
  3022  		{
  3023  			Name: "Mismatched acct ID in path/JWS",
  3024  			// Note(@cpu): We use "http://localhost/2/1" here not
  3025  			// "http://localhost/order/2/1" because we are calling the Order
  3026  			// handler directly and it normally has the initial path component
  3027  			// stripped by the global WFE2 handler. We need the JWS URL to match the request
  3028  			// URL so we fudge both such that the finalize-order prefix has been removed.
  3029  			Request:      signAndPost(signer, "2/1", "http://localhost/2/1", "{}"),
  3030  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Mismatched account ID","status":400}`,
  3031  		},
  3032  		{
  3033  			Name:         "Order ID is invalid",
  3034  			Request:      signAndPost(signer, "1/okwhatever/finalize-order", "http://localhost/1/okwhatever/finalize-order", "{}"),
  3035  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid order ID","status":400}`,
  3036  		},
  3037  		{
  3038  			Name: "Order doesn't exist",
  3039  			// mocks/mocks.go's StorageAuthority's GetOrder mock treats ID 2 as missing
  3040  			Request:      signAndPost(signer, "1/2", "http://localhost/1/2", "{}"),
  3041  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order for ID 2","status":404}`,
  3042  		},
  3043  		{
  3044  			Name: "Order is already finalized",
  3045  			// mocks/mocks.go's StorageAuthority's GetOrder mock treats ID 1 as an Order with a Serial
  3046  			Request:      signAndPost(signer, "1/1", "http://localhost/1/1", goodCertCSRPayload),
  3047  			ExpectedBody: `{"type":"` + probs.ErrorNS + `orderNotReady","detail":"Order's status (\"valid\") is not acceptable for finalization","status":403}`,
  3048  		},
  3049  		{
  3050  			Name: "Order is expired",
  3051  			// mocks/mocks.go's StorageAuthority's GetOrder mock treats ID 7 as an Order that has already expired
  3052  			Request:      signAndPost(signer, "1/7", "http://localhost/1/7", goodCertCSRPayload),
  3053  			ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Order 7 is expired","status":404}`,
  3054  		},
  3055  		{
  3056  			Name:         "Good CSR, Pending Order",
  3057  			Request:      signAndPost(signer, "1/4", "http://localhost/1/4", goodCertCSRPayload),
  3058  			ExpectedBody: `{"type":"` + probs.ErrorNS + `orderNotReady","detail":"Order's status (\"pending\") is not acceptable for finalization","status":403}`,
  3059  		},
  3060  		{
  3061  			Name:    "Good CSR, Ready Order",
  3062  			Request: signAndPost(signer, "1/8", "http://localhost/1/8", goodCertCSRPayload),
  3063  			ExpectedHeaders: map[string]string{
  3064  				"Location":    "http://localhost/acme/order/1/8",
  3065  				"Retry-After": "3",
  3066  			},
  3067  			ExpectedBody: `
  3068  {
  3069    "status": "processing",
  3070    "expires": "2000-01-01T00:00:00Z",
  3071    "identifiers": [
  3072      {"type":"dns","value":"example.com"}
  3073    ],
  3074  	"profile": "default",
  3075    "authorizations": [
  3076      "http://localhost/acme/authz/1/1"
  3077    ],
  3078    "finalize": "http://localhost/acme/finalize/1/8"
  3079  }`,
  3080  		},
  3081  	}
  3082  
  3083  	for _, tc := range testCases {
  3084  		t.Run(tc.Name, func(t *testing.T) {
  3085  			responseWriter.Body.Reset()
  3086  			wfe.FinalizeOrder(ctx, newRequestEvent(), responseWriter, tc.Request)
  3087  			for k, v := range tc.ExpectedHeaders {
  3088  				got := responseWriter.Header().Get(k)
  3089  				if v != got {
  3090  					t.Errorf("Header %q: Expected %q, got %q", k, v, got)
  3091  				}
  3092  			}
  3093  			test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedBody)
  3094  		})
  3095  	}
  3096  
  3097  	// Check a bad CSR request separately from the above testcases. We don't want
  3098  	// to match the whole response body because the "detail" of a bad CSR problem
  3099  	// contains a verbose Go error message that can change between versions (e.g.
  3100  	// Go 1.10.4 to 1.11 changed the expected format)
  3101  	badCSRReq := signAndPost(signer, "1/8", "http://localhost/1/8", `{"CSR": "ABCD"}`)
  3102  	responseWriter.Body.Reset()
  3103  	wfe.FinalizeOrder(ctx, newRequestEvent(), responseWriter, badCSRReq)
  3104  	responseBody := responseWriter.Body.String()
  3105  	test.AssertContains(t, responseBody, "Error parsing certificate request")
  3106  }
  3107  
  3108  func TestKeyRollover(t *testing.T) {
  3109  	responseWriter := httptest.NewRecorder()
  3110  	wfe, _, signer := setupWFE(t)
  3111  
  3112  	existingKey, err := rsa.GenerateKey(rand.Reader, 2048)
  3113  	test.AssertNotError(t, err, "Error creating random 2048 RSA key")
  3114  
  3115  	newKeyBytes, err := os.ReadFile("../test/test-key-5.der")
  3116  	test.AssertNotError(t, err, "Failed to read ../test/test-key-5.der")
  3117  	newKeyPriv, err := x509.ParsePKCS1PrivateKey(newKeyBytes)
  3118  	test.AssertNotError(t, err, "Failed parsing private key")
  3119  	newJWKJSON, err := jose.JSONWebKey{Key: newKeyPriv.Public()}.MarshalJSON()
  3120  	test.AssertNotError(t, err, "Failed to marshal JWK JSON")
  3121  
  3122  	wfe.KeyRollover(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("", "{}"))
  3123  	test.AssertUnmarshaledEquals(t,
  3124  		responseWriter.Body.String(),
  3125  		`{
  3126  		  "type": "`+probs.ErrorNS+`malformed",
  3127  		  "detail": "Unable to validate JWS :: Parse error reading JWS",
  3128  		  "status": 400
  3129  		}`)
  3130  
  3131  	testCases := []struct {
  3132  		Name             string
  3133  		Payload          string
  3134  		ExpectedResponse string
  3135  		NewKey           crypto.Signer
  3136  		ErrorStatType    string
  3137  	}{
  3138  		{
  3139  			Name:    "Missing account URL",
  3140  			Payload: `{"oldKey":` + test1KeyPublicJSON + `}`,
  3141  			ExpectedResponse: `{
  3142  		     "type": "` + probs.ErrorNS + `malformed",
  3143  		     "detail": "Inner key rollover request specified Account \"\", but outer JWS has Key ID \"http://localhost/acme/acct/1\"",
  3144  		     "status": 400
  3145  		   }`,
  3146  			NewKey:        newKeyPriv,
  3147  			ErrorStatType: "KeyRolloverMismatchedAccount",
  3148  		},
  3149  		{
  3150  			Name:    "incorrect old key",
  3151  			Payload: `{"oldKey":` + string(newJWKJSON) + `,"account":"http://localhost/acme/acct/1"}`,
  3152  			ExpectedResponse: `{
  3153  		     "type": "` + probs.ErrorNS + `malformed",
  3154  		     "detail": "Unable to validate JWS :: Inner JWS does not contain old key field matching current account key",
  3155  		     "status": 400
  3156  		   }`,
  3157  			NewKey:        newKeyPriv,
  3158  			ErrorStatType: "KeyRolloverWrongOldKey",
  3159  		},
  3160  		{
  3161  			Name:    "Valid key rollover request, key exists",
  3162  			Payload: `{"oldKey":` + test1KeyPublicJSON + `,"account":"http://localhost/acme/acct/1"}`,
  3163  			ExpectedResponse: `{
  3164                            "type": "urn:ietf:params:acme:error:conflict",
  3165                            "detail": "New key is already in use for a different account",
  3166                            "status": 409
  3167                          }`,
  3168  			NewKey: existingKey,
  3169  		},
  3170  		{
  3171  			Name:    "Valid key rollover request",
  3172  			Payload: `{"oldKey":` + test1KeyPublicJSON + `,"account":"http://localhost/acme/acct/1"}`,
  3173  			ExpectedResponse: `{
  3174  		     "key": ` + string(newJWKJSON) + `,
  3175  		     "status": "valid"
  3176  		   }`,
  3177  			NewKey: newKeyPriv,
  3178  		},
  3179  	}
  3180  
  3181  	for _, tc := range testCases {
  3182  		t.Run(tc.Name, func(t *testing.T) {
  3183  			wfe.stats.joseErrorCount.Reset()
  3184  			responseWriter.Body.Reset()
  3185  			_, _, inner := signer.embeddedJWK(tc.NewKey, "http://localhost/key-change", tc.Payload)
  3186  			_, _, outer := signer.byKeyID(1, nil, "http://localhost/key-change", inner)
  3187  			wfe.KeyRollover(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("key-change", outer))
  3188  			t.Log(responseWriter.Body.String())
  3189  			t.Log(tc.ExpectedResponse)
  3190  			test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedResponse)
  3191  			if tc.ErrorStatType != "" {
  3192  				test.AssertMetricWithLabelsEquals(
  3193  					t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
  3194  			}
  3195  		})
  3196  	}
  3197  }
  3198  
  3199  func TestKeyRolloverMismatchedJWSURLs(t *testing.T) {
  3200  	responseWriter := httptest.NewRecorder()
  3201  	wfe, _, signer := setupWFE(t)
  3202  
  3203  	newKeyBytes, err := os.ReadFile("../test/test-key-5.der")
  3204  	test.AssertNotError(t, err, "Failed to read ../test/test-key-5.der")
  3205  	newKeyPriv, err := x509.ParsePKCS1PrivateKey(newKeyBytes)
  3206  	test.AssertNotError(t, err, "Failed parsing private key")
  3207  
  3208  	_, _, inner := signer.embeddedJWK(newKeyPriv, "http://localhost/wrong-url", "{}")
  3209  	_, _, outer := signer.byKeyID(1, nil, "http://localhost/key-change", inner)
  3210  	wfe.KeyRollover(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("key-change", outer))
  3211  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `
  3212  		{
  3213  			"type": "urn:ietf:params:acme:error:malformed",
  3214  			"detail": "Unable to validate JWS :: Outer JWS 'url' value \"http://localhost/key-change\" does not match inner JWS 'url' value \"http://localhost/wrong-url\"",
  3215  			"status": 400
  3216  		}`)
  3217  }
  3218  
  3219  func TestGetOrder(t *testing.T) {
  3220  	wfe, _, signer := setupWFE(t)
  3221  
  3222  	makeGet := func(path string) *http.Request {
  3223  		return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
  3224  	}
  3225  
  3226  	makePost := func(keyID int64, path, body string) *http.Request {
  3227  		_, _, jwsBody := signer.byKeyID(keyID, nil, fmt.Sprintf("http://localhost/%s", path), body)
  3228  		return makePostRequestWithPath(path, jwsBody)
  3229  	}
  3230  
  3231  	testCases := []struct {
  3232  		Name     string
  3233  		Request  *http.Request
  3234  		Response string
  3235  		Headers  map[string]string
  3236  	}{
  3237  		{
  3238  			Name:     "Good request",
  3239  			Request:  makeGet("1/1"),
  3240  			Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "profile": "default", "authorizations":["http://localhost/acme/authz/1/1"],"finalize":"http://localhost/acme/finalize/1/1","certificate":"http://localhost/acme/cert/serial"}`,
  3241  		},
  3242  		{
  3243  			Name:     "404 request",
  3244  			Request:  makeGet("1/2"),
  3245  			Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order for ID 2", "status":404}`,
  3246  		},
  3247  		{
  3248  			Name:     "Invalid request path",
  3249  			Request:  makeGet("asd"),
  3250  			Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid request path","status":404}`,
  3251  		},
  3252  		{
  3253  			Name:     "Invalid account ID",
  3254  			Request:  makeGet("asd/asd"),
  3255  			Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid account ID","status":400}`,
  3256  		},
  3257  		{
  3258  			Name:     "Invalid order ID",
  3259  			Request:  makeGet("1/asd"),
  3260  			Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid order ID","status":400}`,
  3261  		},
  3262  		{
  3263  			Name:     "Real request, wrong account",
  3264  			Request:  makeGet("2/1"),
  3265  			Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order found for account ID 2", "status":404}`,
  3266  		},
  3267  		{
  3268  			Name:     "Internal error request",
  3269  			Request:  makeGet("1/3"),
  3270  			Response: `{"type":"` + probs.ErrorNS + `serverInternal","detail":"Failed to retrieve order for ID 3","status":500}`,
  3271  		},
  3272  		{
  3273  			Name:     "Invalid POST-as-GET",
  3274  			Request:  makePost(1, "1/1", "{}"),
  3275  			Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unable to validate JWS :: POST-as-GET requests must have an empty payload", "status":400}`,
  3276  		},
  3277  		{
  3278  			Name:     "Valid POST-as-GET, wrong account",
  3279  			Request:  makePost(1, "2/1", ""),
  3280  			Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order found for account ID 2", "status":404}`,
  3281  		},
  3282  		{
  3283  			Name:     "Valid POST-as-GET",
  3284  			Request:  makePost(1, "1/1", ""),
  3285  			Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "profile": "default", "authorizations":["http://localhost/acme/authz/1/1"],"finalize":"http://localhost/acme/finalize/1/1","certificate":"http://localhost/acme/cert/serial"}`,
  3286  		},
  3287  		{
  3288  			Name:     "GET new order from old endpoint",
  3289  			Request:  makeGet("1/9"),
  3290  			Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "profile": "default", "authorizations":["http://localhost/acme/authz/1/1"],"finalize":"http://localhost/acme/finalize/1/9","certificate":"http://localhost/acme/cert/serial"}`,
  3291  		},
  3292  		{
  3293  			Name:     "POST-as-GET new order",
  3294  			Request:  makePost(1, "1/9", ""),
  3295  			Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "profile": "default", "authorizations":["http://localhost/acme/authz/1/1"],"finalize":"http://localhost/acme/finalize/1/9","certificate":"http://localhost/acme/cert/serial"}`,
  3296  		},
  3297  		{
  3298  			Name:     "POST-as-GET processing order",
  3299  			Request:  makePost(1, "1/10", ""),
  3300  			Response: `{"status": "processing","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "profile": "default", "authorizations":["http://localhost/acme/authz/1/1"],"finalize":"http://localhost/acme/finalize/1/10"}`,
  3301  			Headers:  map[string]string{"Retry-After": "3"},
  3302  		},
  3303  	}
  3304  
  3305  	for _, tc := range testCases {
  3306  		t.Run(tc.Name, func(t *testing.T) {
  3307  			responseWriter := httptest.NewRecorder()
  3308  			wfe.GetOrder(ctx, newRequestEvent(), responseWriter, tc.Request)
  3309  			t.Log(tc.Name)
  3310  			t.Log("actual:", responseWriter.Body.String())
  3311  			t.Log("expect:", tc.Response)
  3312  			test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.Response)
  3313  			for k, v := range tc.Headers {
  3314  				test.AssertEquals(t, responseWriter.Header().Get(k), v)
  3315  			}
  3316  		})
  3317  	}
  3318  }
  3319  
  3320  func makeRevokeRequestJSON(reason *revocation.Reason) ([]byte, error) {
  3321  	certPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.cert.pem")
  3322  	if err != nil {
  3323  		return nil, err
  3324  	}
  3325  	certBlock, _ := pem.Decode(certPemBytes)
  3326  	return makeRevokeRequestJSONForCert(certBlock.Bytes, reason)
  3327  }
  3328  
  3329  func makeRevokeRequestJSONForCert(der []byte, reason *revocation.Reason) ([]byte, error) {
  3330  	revokeRequest := struct {
  3331  		CertificateDER core.JSONBuffer    `json:"certificate"`
  3332  		Reason         *revocation.Reason `json:"reason"`
  3333  	}{
  3334  		CertificateDER: der,
  3335  		Reason:         reason,
  3336  	}
  3337  	revokeRequestJSON, err := json.Marshal(revokeRequest)
  3338  	if err != nil {
  3339  		return nil, err
  3340  	}
  3341  	return revokeRequestJSON, nil
  3342  }
  3343  
  3344  // Valid revocation request for existing, non-revoked cert, signed using the
  3345  // issuing account key.
  3346  func TestRevokeCertificateByApplicantValid(t *testing.T) {
  3347  	wfe, _, signer := setupWFE(t)
  3348  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  3349  
  3350  	mockLog := wfe.log.(*blog.Mock)
  3351  	mockLog.Clear()
  3352  
  3353  	revokeRequestJSON, err := makeRevokeRequestJSON(nil)
  3354  	test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
  3355  	_, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/revoke-cert", string(revokeRequestJSON))
  3356  
  3357  	responseWriter := httptest.NewRecorder()
  3358  	wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
  3359  		makePostRequestWithPath("revoke-cert", jwsBody))
  3360  
  3361  	test.AssertEquals(t, responseWriter.Code, 200)
  3362  	test.AssertEquals(t, responseWriter.Body.String(), "")
  3363  	test.AssertDeepEquals(t, mockLog.GetAllMatching("Authenticated revocation"), []string{
  3364  		`INFO: [AUDIT] Authenticated revocation JSON={"Serial":"000000000000000000001d72443db5189821","Reason":0,"RegID":1,"Method":"applicant"}`,
  3365  	})
  3366  }
  3367  
  3368  // Valid revocation request for existing, non-revoked cert, signed using the
  3369  // certificate private key.
  3370  func TestRevokeCertificateByKeyValid(t *testing.T) {
  3371  	wfe, _, signer := setupWFE(t)
  3372  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  3373  
  3374  	mockLog := wfe.log.(*blog.Mock)
  3375  	mockLog.Clear()
  3376  
  3377  	keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.key.pem")
  3378  	test.AssertNotError(t, err, "Failed to load key")
  3379  	key := loadKey(t, keyPemBytes)
  3380  
  3381  	revocationReason := revocation.KeyCompromise
  3382  	revokeRequestJSON, err := makeRevokeRequestJSON(&revocationReason)
  3383  	test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
  3384  	_, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
  3385  
  3386  	responseWriter := httptest.NewRecorder()
  3387  	wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
  3388  		makePostRequestWithPath("revoke-cert", jwsBody))
  3389  
  3390  	test.AssertEquals(t, responseWriter.Code, 200)
  3391  	test.AssertEquals(t, responseWriter.Body.String(), "")
  3392  	test.AssertDeepEquals(t, mockLog.GetAllMatching("Authenticated revocation"), []string{
  3393  		`INFO: [AUDIT] Authenticated revocation JSON={"Serial":"000000000000000000001d72443db5189821","Reason":1,"RegID":0,"Method":"privkey"}`,
  3394  	})
  3395  }
  3396  
  3397  // Invalid revocation request: although signed with the cert key, the cert
  3398  // wasn't issued by any issuer the Boulder is aware of.
  3399  func TestRevokeCertificateNotIssued(t *testing.T) {
  3400  	wfe, _, signer := setupWFE(t)
  3401  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  3402  
  3403  	// Make a self-signed junk certificate
  3404  	k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  3405  	test.AssertNotError(t, err, "unexpected error making random private key")
  3406  	// Use a known serial from the mockSAWithValidCert mock.
  3407  	// This ensures that any failures here are due to the certificate's issuer
  3408  	// not matching up with issuers known by the mock, rather than due to the
  3409  	// certificate's serial not matching up with serials known by the mock.
  3410  	knownCert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
  3411  	test.AssertNotError(t, err, "Unexpected error loading test cert")
  3412  	template := &x509.Certificate{
  3413  		SerialNumber: knownCert.SerialNumber,
  3414  	}
  3415  	certDER, err := x509.CreateCertificate(rand.Reader, template, template, k.Public(), k)
  3416  	test.AssertNotError(t, err, "Unexpected error creating self-signed junk cert")
  3417  
  3418  	keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.key.pem")
  3419  	test.AssertNotError(t, err, "Failed to load key")
  3420  	key := loadKey(t, keyPemBytes)
  3421  
  3422  	revokeRequestJSON, err := makeRevokeRequestJSONForCert(certDER, nil)
  3423  	test.AssertNotError(t, err, "Failed to make revokeRequestJSON for certDER")
  3424  	_, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
  3425  
  3426  	responseWriter := httptest.NewRecorder()
  3427  	wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
  3428  		makePostRequestWithPath("revoke-cert", jwsBody))
  3429  	// It should result in a 404 response with a problem body
  3430  	test.AssertEquals(t, responseWriter.Code, 404)
  3431  	test.AssertEquals(t, responseWriter.Body.String(), "{\n  \"type\": \"urn:ietf:params:acme:error:malformed\",\n  \"detail\": \"Unable to revoke :: Certificate from unrecognized issuer\",\n  \"status\": 404\n}")
  3432  }
  3433  
  3434  func TestRevokeCertificateExpired(t *testing.T) {
  3435  	wfe, fc, signer := setupWFE(t)
  3436  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  3437  
  3438  	keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.key.pem")
  3439  	test.AssertNotError(t, err, "Failed to load key")
  3440  	key := loadKey(t, keyPemBytes)
  3441  
  3442  	revokeRequestJSON, err := makeRevokeRequestJSON(nil)
  3443  	test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
  3444  
  3445  	_, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
  3446  
  3447  	cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
  3448  	test.AssertNotError(t, err, "Failed to load test certificate")
  3449  
  3450  	fc.Set(cert.NotAfter.Add(time.Hour))
  3451  
  3452  	responseWriter := httptest.NewRecorder()
  3453  	wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
  3454  		makePostRequestWithPath("revoke-cert", jwsBody))
  3455  	test.AssertEquals(t, responseWriter.Code, 403)
  3456  	test.AssertEquals(t, responseWriter.Body.String(), "{\n  \"type\": \"urn:ietf:params:acme:error:unauthorized\",\n  \"detail\": \"Unable to revoke :: Certificate is expired\",\n  \"status\": 403\n}")
  3457  }
  3458  
  3459  func TestRevokeCertificateReasons(t *testing.T) {
  3460  	wfe, _, signer := setupWFE(t)
  3461  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  3462  	ra := wfe.ra.(*MockRegistrationAuthority)
  3463  
  3464  	reason0 := revocation.Unspecified
  3465  	reason1 := revocation.KeyCompromise
  3466  	reason2 := revocation.CACompromise
  3467  	reason100 := revocation.Reason(100)
  3468  
  3469  	testCases := []struct {
  3470  		Name             string
  3471  		Reason           *revocation.Reason
  3472  		ExpectedHTTPCode int
  3473  		ExpectedBody     string
  3474  		ExpectedReason   *revocation.Reason
  3475  	}{
  3476  		{
  3477  			Name:             "Valid reason",
  3478  			Reason:           &reason1,
  3479  			ExpectedHTTPCode: http.StatusOK,
  3480  			ExpectedReason:   &reason1,
  3481  		},
  3482  		{
  3483  			Name:             "No reason",
  3484  			ExpectedHTTPCode: http.StatusOK,
  3485  			ExpectedReason:   &reason0,
  3486  		},
  3487  		{
  3488  			Name:             "Unsupported reason",
  3489  			Reason:           &reason2,
  3490  			ExpectedHTTPCode: http.StatusBadRequest,
  3491  			ExpectedBody:     `{"type":"` + probs.ErrorNS + `badRevocationReason","detail":"Unable to revoke :: disallowed revocation reason: 2","status":400}`,
  3492  		},
  3493  		{
  3494  			Name:             "Non-existent reason",
  3495  			Reason:           &reason100,
  3496  			ExpectedHTTPCode: http.StatusBadRequest,
  3497  			ExpectedBody:     `{"type":"` + probs.ErrorNS + `badRevocationReason","detail":"Unable to revoke :: disallowed revocation reason: 100","status":400}`,
  3498  		},
  3499  	}
  3500  
  3501  	for _, tc := range testCases {
  3502  		t.Run(tc.Name, func(t *testing.T) {
  3503  			revokeRequestJSON, err := makeRevokeRequestJSON(tc.Reason)
  3504  			test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
  3505  			_, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/revoke-cert", string(revokeRequestJSON))
  3506  
  3507  			responseWriter := httptest.NewRecorder()
  3508  			wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
  3509  				makePostRequestWithPath("revoke-cert", jwsBody))
  3510  
  3511  			test.AssertEquals(t, responseWriter.Code, tc.ExpectedHTTPCode)
  3512  			if tc.ExpectedBody != "" {
  3513  				test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedBody)
  3514  			} else {
  3515  				test.AssertEquals(t, responseWriter.Body.String(), tc.ExpectedBody)
  3516  			}
  3517  			if tc.ExpectedReason != nil {
  3518  				test.AssertEquals(t, ra.lastRevocationReason, *tc.ExpectedReason)
  3519  			}
  3520  		})
  3521  	}
  3522  }
  3523  
  3524  // A revocation request signed by an incorrect certificate private key.
  3525  func TestRevokeCertificateWrongCertificateKey(t *testing.T) {
  3526  	wfe, _, signer := setupWFE(t)
  3527  	wfe.sa = newMockSAWithCert(t, wfe.sa)
  3528  
  3529  	keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-e1.key.pem")
  3530  	test.AssertNotError(t, err, "Failed to load key")
  3531  	key := loadKey(t, keyPemBytes)
  3532  
  3533  	revocationReason := revocation.KeyCompromise
  3534  	revokeRequestJSON, err := makeRevokeRequestJSON(&revocationReason)
  3535  	test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
  3536  	_, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
  3537  
  3538  	responseWriter := httptest.NewRecorder()
  3539  	wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
  3540  		makePostRequestWithPath("revoke-cert", jwsBody))
  3541  	test.AssertEquals(t, responseWriter.Code, 403)
  3542  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
  3543  		`{"type":"`+probs.ErrorNS+`unauthorized","detail":"Unable to revoke :: JWK embedded in revocation request must be the same public key as the cert to be revoked","status":403}`)
  3544  }
  3545  
  3546  type mockSAGetRegByKeyFails struct {
  3547  	sapb.StorageAuthorityReadOnlyClient
  3548  }
  3549  
  3550  func (sa *mockSAGetRegByKeyFails) GetRegistrationByKey(_ context.Context, req *sapb.JSONWebKey, _ ...grpc.CallOption) (*corepb.Registration, error) {
  3551  	return nil, fmt.Errorf("whoops")
  3552  }
  3553  
  3554  // When SA.GetRegistrationByKey errors (e.g. gRPC timeout), NewAccount should
  3555  // return internal server errors.
  3556  func TestNewAccountWhenGetRegByKeyFails(t *testing.T) {
  3557  	wfe, _, signer := setupWFE(t)
  3558  	wfe.sa = &mockSAGetRegByKeyFails{wfe.sa}
  3559  	key := loadKey(t, []byte(testE2KeyPrivatePEM))
  3560  	_, ok := key.(*ecdsa.PrivateKey)
  3561  	test.Assert(t, ok, "Couldn't load ECDSA key")
  3562  	payload := `{"contact":["mailto:person@mail.com"],"agreement":"` + agreementURL + `"}`
  3563  	responseWriter := httptest.NewRecorder()
  3564  	_, _, body := signer.embeddedJWK(key, "http://localhost/new-account", payload)
  3565  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("/new-account", body))
  3566  	if responseWriter.Code != 500 {
  3567  		t.Fatalf("Wrong response code %d for NewAccount with failing GetRegByKey (wanted 500)", responseWriter.Code)
  3568  	}
  3569  	var prob probs.ProblemDetails
  3570  	err := json.Unmarshal(responseWriter.Body.Bytes(), &prob)
  3571  	test.AssertNotError(t, err, "unmarshalling response")
  3572  	if prob.Type != probs.ErrorNS+probs.ServerInternalProblem {
  3573  		t.Errorf("Wrong type for returned problem: %#v", prob.Type)
  3574  	}
  3575  }
  3576  
  3577  type mockSAGetRegByKeyNotFound struct {
  3578  	sapb.StorageAuthorityReadOnlyClient
  3579  }
  3580  
  3581  func (sa *mockSAGetRegByKeyNotFound) GetRegistrationByKey(_ context.Context, req *sapb.JSONWebKey, _ ...grpc.CallOption) (*corepb.Registration, error) {
  3582  	return nil, berrors.NotFoundError("not found")
  3583  }
  3584  
  3585  func TestNewAccountWhenGetRegByKeyNotFound(t *testing.T) {
  3586  	wfe, _, signer := setupWFE(t)
  3587  	wfe.sa = &mockSAGetRegByKeyNotFound{wfe.sa}
  3588  	key := loadKey(t, []byte(testE2KeyPrivatePEM))
  3589  	_, ok := key.(*ecdsa.PrivateKey)
  3590  	test.Assert(t, ok, "Couldn't load ECDSA key")
  3591  	// When SA.GetRegistrationByKey returns NotFound, and no onlyReturnExisting
  3592  	// field is sent, NewAccount should succeed.
  3593  	payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
  3594  	signedURL := "http://localhost/new-account"
  3595  	responseWriter := httptest.NewRecorder()
  3596  	_, _, body := signer.embeddedJWK(key, signedURL, payload)
  3597  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("/new-account", body))
  3598  	if responseWriter.Code != http.StatusCreated {
  3599  		t.Errorf("Bad response to NewRegistration: %d, %s", responseWriter.Code, responseWriter.Body)
  3600  	}
  3601  
  3602  	// When SA.GetRegistrationByKey returns NotFound, and onlyReturnExisting
  3603  	// field **is** sent, NewAccount should fail with the expected error.
  3604  	payload = `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true,"onlyReturnExisting":true}`
  3605  	responseWriter = httptest.NewRecorder()
  3606  	_, _, body = signer.embeddedJWK(key, signedURL, payload)
  3607  	// Process the new account request
  3608  	wfe.NewAccount(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("/new-account", body))
  3609  	test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
  3610  	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `
  3611  	{
  3612  		"type": "urn:ietf:params:acme:error:accountDoesNotExist",
  3613  		"detail": "No account exists with the provided key",
  3614  		"status": 400
  3615  	}`)
  3616  }
  3617  
  3618  func TestPrepAuthzForDisplay(t *testing.T) {
  3619  	t.Parallel()
  3620  	wfe, _, _ := setupWFE(t)
  3621  
  3622  	authz := &core.Authorization{
  3623  		ID:             "12345",
  3624  		Status:         core.StatusPending,
  3625  		RegistrationID: 1,
  3626  		Identifier:     identifier.NewDNS("example.com"),
  3627  		Challenges: []core.Challenge{
  3628  			{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
  3629  			{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"},
  3630  			{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"},
  3631  		},
  3632  	}
  3633  
  3634  	// This modifies the authz in-place.
  3635  	wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
  3636  
  3637  	// Ensure ID and RegID are omitted.
  3638  	authzJSON, err := json.Marshal(authz)
  3639  	test.AssertNotError(t, err, "Failed to marshal authz")
  3640  	test.AssertNotContains(t, string(authzJSON), "\"id\":\"12345\"")
  3641  	test.AssertNotContains(t, string(authzJSON), "\"registrationID\":\"1\"")
  3642  }
  3643  
  3644  func TestPrepRevokedAuthzForDisplay(t *testing.T) {
  3645  	t.Parallel()
  3646  	wfe, _, _ := setupWFE(t)
  3647  
  3648  	authz := &core.Authorization{
  3649  		ID:             "12345",
  3650  		Status:         core.StatusInvalid,
  3651  		RegistrationID: 1,
  3652  		Identifier:     identifier.NewDNS("example.com"),
  3653  		Challenges: []core.Challenge{
  3654  			{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
  3655  			{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"},
  3656  			{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"},
  3657  		},
  3658  	}
  3659  
  3660  	// This modifies the authz in-place.
  3661  	wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
  3662  
  3663  	// All of the challenges should be revoked as well.
  3664  	for _, chall := range authz.Challenges {
  3665  		test.AssertEquals(t, chall.Status, core.StatusInvalid)
  3666  	}
  3667  }
  3668  
  3669  func TestPrepWildcardAuthzForDisplay(t *testing.T) {
  3670  	t.Parallel()
  3671  	wfe, _, _ := setupWFE(t)
  3672  
  3673  	authz := &core.Authorization{
  3674  		ID:             "12345",
  3675  		Status:         core.StatusPending,
  3676  		RegistrationID: 1,
  3677  		Identifier:     identifier.NewDNS("*.example.com"),
  3678  		Challenges: []core.Challenge{
  3679  			{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
  3680  		},
  3681  	}
  3682  
  3683  	// This modifies the authz in-place.
  3684  	wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
  3685  
  3686  	// The identifier should not start with a star, but the authz should be marked
  3687  	// as a wildcard.
  3688  	test.AssertEquals(t, strings.HasPrefix(authz.Identifier.Value, "*."), false)
  3689  	test.AssertEquals(t, authz.Wildcard, true)
  3690  }
  3691  
  3692  func TestPrepAuthzForDisplayShuffle(t *testing.T) {
  3693  	t.Parallel()
  3694  	wfe, _, _ := setupWFE(t)
  3695  
  3696  	authz := &core.Authorization{
  3697  		ID:             "12345",
  3698  		Status:         core.StatusPending,
  3699  		RegistrationID: 1,
  3700  		Identifier:     identifier.NewDNS("example.com"),
  3701  		Challenges: []core.Challenge{
  3702  			{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"},
  3703  			{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"},
  3704  			{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"},
  3705  		},
  3706  	}
  3707  
  3708  	// The challenges should be presented in an unpredictable order.
  3709  
  3710  	// Create a structure to count how many times each challenge type ends up in
  3711  	// each position in the output authz.Challenges list.
  3712  	counts := make(map[core.AcmeChallenge]map[int]int)
  3713  	counts[core.ChallengeTypeDNS01] = map[int]int{0: 0, 1: 0, 2: 0}
  3714  	counts[core.ChallengeTypeHTTP01] = map[int]int{0: 0, 1: 0, 2: 0}
  3715  	counts[core.ChallengeTypeTLSALPN01] = map[int]int{0: 0, 1: 0, 2: 0}
  3716  
  3717  	// Prep the authz 100 times, and count where each challenge ended up each time.
  3718  	for range 100 {
  3719  		// This modifies the authz in place
  3720  		wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
  3721  		for i, chall := range authz.Challenges {
  3722  			counts[chall.Type][i] += 1
  3723  		}
  3724  	}
  3725  
  3726  	// Ensure that at least some amount of randomization is happening.
  3727  	for challType, indices := range counts {
  3728  		for index, count := range indices {
  3729  			test.Assert(t, count > 10, fmt.Sprintf("challenge type %s did not appear in position %d as often as expected", challType, index))
  3730  		}
  3731  	}
  3732  }
  3733  
  3734  // noSCTMockRA is a mock RA that always returns a `berrors.MissingSCTsError` from `FinalizeOrder`
  3735  type noSCTMockRA struct {
  3736  	MockRegistrationAuthority
  3737  }
  3738  
  3739  func (ra *noSCTMockRA) FinalizeOrder(context.Context, *rapb.FinalizeOrderRequest, ...grpc.CallOption) (*corepb.Order, error) {
  3740  	return nil, berrors.MissingSCTsError("noSCTMockRA missing scts error")
  3741  }
  3742  
  3743  func TestFinalizeSCTError(t *testing.T) {
  3744  	wfe, _, signer := setupWFE(t)
  3745  
  3746  	// Set up an RA mock that always returns a berrors.MissingSCTsError from
  3747  	// `FinalizeOrder`
  3748  	wfe.ra = &noSCTMockRA{}
  3749  
  3750  	// Create a response writer to capture the WFE response
  3751  	responseWriter := httptest.NewRecorder()
  3752  
  3753  	// This example is a well-formed CSR for the name "example.com".
  3754  	goodCertCSRPayload := `{
  3755  		"csr": "MIHRMHgCAQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ2hlvArQl5k0L1eF1vF5dwr7ASm2iKqibmauund-z3QJpuudnNEjlyOXi-IY1rxyhehRrtbm_bbcNCtZLgbkPvoAAwCgYIKoZIzj0EAwIDSQAwRgIhAJ8z2EDll2BvoNRotAknEfrqeP6K5CN1NeVMB4QOu0G1AiEAqAVpiGwNyV7SEZ67vV5vyuGsKPAGnqrisZh5Vg5JKHE="
  3756  	}`
  3757  
  3758  	// Create a finalization request with the above payload
  3759  	request := signAndPost(signer, "1/8", "http://localhost/1/8", goodCertCSRPayload)
  3760  
  3761  	// POST the finalize order request.
  3762  	wfe.FinalizeOrder(ctx, newRequestEvent(), responseWriter, request)
  3763  
  3764  	// We expect the berrors.MissingSCTsError error to have been converted into
  3765  	// a serverInternal error with the right message.
  3766  	test.AssertUnmarshaledEquals(t,
  3767  		responseWriter.Body.String(),
  3768  		`{"type":"`+probs.ErrorNS+`serverInternal","detail":"Error finalizing order :: Unable to meet CA SCT embedding requirements","status":500}`)
  3769  }
  3770  
  3771  func TestOrderToOrderJSONV2Authorizations(t *testing.T) {
  3772  	wfe, fc, _ := setupWFE(t)
  3773  	expires := fc.Now()
  3774  	orderJSON := wfe.orderToOrderJSON(&http.Request{}, &corepb.Order{
  3775  		Id:               1,
  3776  		RegistrationID:   1,
  3777  		Identifiers:      []*corepb.Identifier{identifier.NewDNS("a").ToProto()},
  3778  		Status:           string(core.StatusPending),
  3779  		Expires:          timestamppb.New(expires),
  3780  		V2Authorizations: []int64{1, 2},
  3781  	})
  3782  	test.AssertDeepEquals(t, orderJSON.Authorizations, []string{
  3783  		"http://localhost/acme/authz/1/1",
  3784  		"http://localhost/acme/authz/1/2",
  3785  	})
  3786  }
  3787  
  3788  func TestAccountMarshaling(t *testing.T) {
  3789  	acct := &core.Registration{
  3790  		ID:        1987,
  3791  		Agreement: "disagreement",
  3792  		Status:    core.StatusValid,
  3793  	}
  3794  
  3795  	marshaled, err := json.Marshal(acct)
  3796  	if err != nil {
  3797  		t.Fatalf("marshalling account object: %s", err)
  3798  	}
  3799  
  3800  	var got core.Registration
  3801  	err = json.Unmarshal(marshaled, &got)
  3802  	if err != nil {
  3803  		t.Fatalf("unmarshaling account object: %s", err)
  3804  	}
  3805  
  3806  	// The Agreement should always be cleared.
  3807  	test.AssertEquals(t, got.Agreement, "")
  3808  	// The ID field should be zeroed.
  3809  	test.AssertEquals(t, got.ID, int64(0))
  3810  	// The Status field should be preserved.
  3811  	test.AssertEquals(t, got.Status, core.StatusValid)
  3812  }
  3813  
  3814  // TestGet404 tests that a 404 is served and that the expected endpoint of
  3815  // "/" is logged when an unknown path is requested. This will test the
  3816  // codepath to the wfe.Index() handler which handles "/" and all non-api
  3817  // endpoint requests to make sure the endpoint is set properly in the logs.
  3818  func TestIndexGet404(t *testing.T) {
  3819  	// Setup
  3820  	wfe, _, _ := setupWFE(t)
  3821  	path := "/nopathhere/nope/nofilehere"
  3822  	req := &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
  3823  	logEvent := &web.RequestEvent{}
  3824  	responseWriter := httptest.NewRecorder()
  3825  
  3826  	// Send a request to wfe.Index()
  3827  	wfe.Index(context.Background(), logEvent, responseWriter, req)
  3828  
  3829  	// Test that a 404 is received as expected
  3830  	test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
  3831  	// Test that we logged the "/" endpoint
  3832  	test.AssertEquals(t, logEvent.Endpoint, "/")
  3833  	// Test that the rest of the path is logged as the slug
  3834  	test.AssertEquals(t, logEvent.Slug, path[1:])
  3835  }
  3836  
  3837  // TestARI tests that requests for real certs result in renewal info, while
  3838  // requests for certs that don't exist result in errors.
  3839  func TestARI(t *testing.T) {
  3840  	wfe, _, _ := setupWFE(t)
  3841  	msa := newMockSAWithCert(t, wfe.sa)
  3842  	wfe.sa = msa
  3843  
  3844  	features.Set(features.Config{ServeRenewalInfo: true})
  3845  	defer features.Reset()
  3846  
  3847  	makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
  3848  		return &http.Request{URL: &url.URL{Path: path}, Method: "GET"},
  3849  			&web.RequestEvent{Endpoint: endpoint, Extra: map[string]any{}}
  3850  	}
  3851  
  3852  	// Load the leaf certificate.
  3853  	cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
  3854  	test.AssertNotError(t, err, "failed to load test certificate")
  3855  
  3856  	// Ensure that a correct draft-ietf-acme-ari03 query results in a 200.
  3857  	certID := fmt.Sprintf("%s.%s",
  3858  		base64.RawURLEncoding.EncodeToString(cert.AuthorityKeyId),
  3859  		base64.RawURLEncoding.EncodeToString(cert.SerialNumber.Bytes()),
  3860  	)
  3861  	req, event := makeGet(certID, renewalInfoPath)
  3862  	resp := httptest.NewRecorder()
  3863  	wfe.RenewalInfo(context.Background(), event, resp, req)
  3864  	test.AssertEquals(t, resp.Code, http.StatusOK)
  3865  	test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
  3866  	var ri core.RenewalInfo
  3867  	err = json.Unmarshal(resp.Body.Bytes(), &ri)
  3868  	test.AssertNotError(t, err, "unmarshalling renewal info")
  3869  	test.Assert(t, ri.SuggestedWindow.Start.After(cert.NotBefore), "suggested window begins before cert issuance")
  3870  	test.Assert(t, ri.SuggestedWindow.End.Before(cert.NotAfter), "suggested window ends after cert expiry")
  3871  
  3872  	// Ensure that a correct draft-ietf-acme-ari03 query for a revoked cert
  3873  	// results in a renewal window in the past.
  3874  	msa.status = core.OCSPStatusRevoked
  3875  	req, event = makeGet(certID, renewalInfoPath)
  3876  	resp = httptest.NewRecorder()
  3877  	wfe.RenewalInfo(context.Background(), event, resp, req)
  3878  	test.AssertEquals(t, resp.Code, http.StatusOK)
  3879  	test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
  3880  	err = json.Unmarshal(resp.Body.Bytes(), &ri)
  3881  	test.AssertNotError(t, err, "unmarshalling renewal info")
  3882  	test.Assert(t, ri.SuggestedWindow.End.Before(wfe.clk.Now()), "suggested window should end in the past")
  3883  	test.Assert(t, ri.SuggestedWindow.Start.Before(ri.SuggestedWindow.End), "suggested window should start before it ends")
  3884  
  3885  	// Ensure that a draft-ietf-acme-ari03 query for a non-existent serial
  3886  	// results in a 404.
  3887  	certID = fmt.Sprintf("%s.%s",
  3888  		base64.RawURLEncoding.EncodeToString(cert.AuthorityKeyId),
  3889  		base64.RawURLEncoding.EncodeToString(
  3890  			big.NewInt(0).Add(cert.SerialNumber, big.NewInt(1)).Bytes(),
  3891  		),
  3892  	)
  3893  	req, event = makeGet(certID, renewalInfoPath)
  3894  	resp = httptest.NewRecorder()
  3895  	wfe.RenewalInfo(context.Background(), event, resp, req)
  3896  	test.AssertEquals(t, resp.Code, http.StatusNotFound)
  3897  	test.AssertEquals(t, resp.Header().Get("Retry-After"), "")
  3898  
  3899  	// Ensure that a query with a non-CertID path fails.
  3900  	req, event = makeGet("lolwutsup", renewalInfoPath)
  3901  	resp = httptest.NewRecorder()
  3902  	wfe.RenewalInfo(context.Background(), event, resp, req)
  3903  	test.AssertEquals(t, resp.Code, http.StatusBadRequest)
  3904  	test.AssertContains(t, resp.Body.String(), "Invalid path")
  3905  
  3906  	// Ensure that a query with no path slug at all bails out early.
  3907  	req, event = makeGet("", renewalInfoPath)
  3908  	resp = httptest.NewRecorder()
  3909  	wfe.RenewalInfo(context.Background(), event, resp, req)
  3910  	test.AssertEquals(t, resp.Code, http.StatusNotFound)
  3911  	test.AssertContains(t, resp.Body.String(), "Must specify a request path")
  3912  }
  3913  
  3914  // TestIncidentARI tests that requests certs impacted by an ongoing revocation
  3915  // incident result in a 200 with a retry-after header and a suggested retry
  3916  // window in the past.
  3917  func TestIncidentARI(t *testing.T) {
  3918  	wfe, _, _ := setupWFE(t)
  3919  	expectSerial := big.NewInt(12345)
  3920  	expectSerialString := core.SerialToString(big.NewInt(12345))
  3921  	wfe.sa = newMockSAWithIncident(wfe.sa, []string{expectSerialString})
  3922  
  3923  	features.Set(features.Config{ServeRenewalInfo: true})
  3924  	defer features.Reset()
  3925  
  3926  	makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
  3927  		return &http.Request{URL: &url.URL{Path: path}, Method: "GET"},
  3928  			&web.RequestEvent{Endpoint: endpoint, Extra: map[string]any{}}
  3929  	}
  3930  
  3931  	var issuer issuance.NameID
  3932  	for k := range wfe.issuerCertificates {
  3933  		// Grab the first known issuer.
  3934  		issuer = k
  3935  		break
  3936  	}
  3937  	certID := fmt.Sprintf("%s.%s",
  3938  		base64.RawURLEncoding.EncodeToString(wfe.issuerCertificates[issuer].SubjectKeyId),
  3939  		base64.RawURLEncoding.EncodeToString(expectSerial.Bytes()),
  3940  	)
  3941  	req, event := makeGet(certID, renewalInfoPath)
  3942  	resp := httptest.NewRecorder()
  3943  	wfe.RenewalInfo(context.Background(), event, resp, req)
  3944  	test.AssertEquals(t, resp.Code, 200)
  3945  	test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
  3946  	var ri core.RenewalInfo
  3947  	err := json.Unmarshal(resp.Body.Bytes(), &ri)
  3948  	test.AssertNotError(t, err, "unmarshalling renewal info")
  3949  	// The start of the window should be in the past.
  3950  	test.AssertEquals(t, ri.SuggestedWindow.Start.Before(wfe.clk.Now()), true)
  3951  	// The end of the window should be after the start.
  3952  	test.AssertEquals(t, ri.SuggestedWindow.End.After(ri.SuggestedWindow.Start), true)
  3953  	// The end of the window should also be in the past.
  3954  	test.AssertEquals(t, ri.SuggestedWindow.End.Before(wfe.clk.Now()), true)
  3955  	// The explanationURL should be set.
  3956  	test.AssertEquals(t, ri.ExplanationURL, "http://big.bad/incident")
  3957  }
  3958  
  3959  func Test_sendError(t *testing.T) {
  3960  	features.Reset()
  3961  	wfe, _, _ := setupWFE(t)
  3962  	testResponse := httptest.NewRecorder()
  3963  
  3964  	testErr := berrors.RateLimitError(0, "test")
  3965  	wfe.sendError(testResponse, &web.RequestEvent{Endpoint: "test"}, probs.RateLimited("test"), testErr)
  3966  	// Ensure a 0 value RetryAfter results in no Retry-After header.
  3967  	test.AssertEquals(t, testResponse.Header().Get("Retry-After"), "")
  3968  	// Ensure the Link header isn't populatsed.
  3969  	test.AssertEquals(t, testResponse.Header().Get("Link"), "")
  3970  
  3971  	testErr = berrors.RateLimitError(time.Millisecond*500, "test")
  3972  	wfe.sendError(testResponse, &web.RequestEvent{Endpoint: "test"}, probs.RateLimited("test"), testErr)
  3973  	// Ensure a 500ms RetryAfter is rounded up to a 1s Retry-After header.
  3974  	test.AssertEquals(t, testResponse.Header().Get("Retry-After"), "1")
  3975  	// Ensure the Link header is populated.
  3976  	test.AssertEquals(t, testResponse.Header().Get("Link"), "<https://letsencrypt.org/docs/rate-limits>;rel=\"help\"")
  3977  
  3978  	// Clear headers for the next test.
  3979  	testResponse.Header().Del("Retry-After")
  3980  	testResponse.Header().Del("Link")
  3981  
  3982  	testErr = berrors.RateLimitError(time.Millisecond*499, "test")
  3983  	wfe.sendError(testResponse, &web.RequestEvent{Endpoint: "test"}, probs.RateLimited("test"), testErr)
  3984  	// Ensure a 499ms RetryAfter results in no Retry-After header.
  3985  	test.AssertEquals(t, testResponse.Header().Get("Retry-After"), "")
  3986  	// Ensure the Link header isn't populatsed.
  3987  	test.AssertEquals(t, testResponse.Header().Get("Link"), "")
  3988  }
  3989  
  3990  func Test_sendErrorInternalServerError(t *testing.T) {
  3991  	features.Reset()
  3992  	wfe, _, _ := setupWFE(t)
  3993  	testResponse := httptest.NewRecorder()
  3994  
  3995  	wfe.sendError(testResponse, &web.RequestEvent{}, probs.ServerInternal("oh no"), nil)
  3996  	test.AssertEquals(t, testResponse.Header().Get("Retry-After"), "60")
  3997  }
  3998  
  3999  // mockSAForARI provides a mock SA with the methods required for an issuance and
  4000  // a renewal with the ARI `Replaces` field.
  4001  //
  4002  // Note that FQDNSetTimestampsForWindow always return an empty list, which allows us to act
  4003  // as if a certificate is not getting the renewal exemption, even when we are repeatedly
  4004  // issuing for the same names.
  4005  type mockSAForARI struct {
  4006  	sapb.StorageAuthorityReadOnlyClient
  4007  	cert *corepb.Certificate
  4008  }
  4009  
  4010  func (sa *mockSAForARI) FQDNSetTimestampsForWindow(ctx context.Context, in *sapb.CountFQDNSetsRequest, opts ...grpc.CallOption) (*sapb.Timestamps, error) {
  4011  	return &sapb.Timestamps{Timestamps: nil}, nil
  4012  }
  4013  
  4014  // GetCertificate returns the inner certificate if it matches the given serial.
  4015  func (sa *mockSAForARI) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
  4016  	if req.Serial == sa.cert.Serial {
  4017  		return sa.cert, nil
  4018  	}
  4019  	return nil, berrors.NotFoundError("certificate with serial %q not found", req.Serial)
  4020  }
  4021  
  4022  func (sa *mockSAForARI) ReplacementOrderExists(ctx context.Context, in *sapb.Serial, opts ...grpc.CallOption) (*sapb.Exists, error) {
  4023  	if in.Serial == sa.cert.Serial {
  4024  		return &sapb.Exists{Exists: false}, nil
  4025  
  4026  	}
  4027  	return &sapb.Exists{Exists: true}, nil
  4028  }
  4029  
  4030  func (sa *mockSAForARI) IncidentsForSerial(ctx context.Context, in *sapb.Serial, opts ...grpc.CallOption) (*sapb.Incidents, error) {
  4031  	return &sapb.Incidents{}, nil
  4032  }
  4033  
  4034  func (sa *mockSAForARI) GetCertificateStatus(ctx context.Context, in *sapb.Serial, opts ...grpc.CallOption) (*corepb.CertificateStatus, error) {
  4035  	return &corepb.CertificateStatus{Serial: in.Serial, Status: string(core.OCSPStatusGood)}, nil
  4036  }
  4037  
  4038  func TestOrderMatchesReplacement(t *testing.T) {
  4039  	wfe, _, _ := setupWFE(t)
  4040  
  4041  	expectExpiry := time.Now().AddDate(0, 0, 1)
  4042  	expectSerial := big.NewInt(1337)
  4043  	testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
  4044  	rawCert := x509.Certificate{
  4045  		NotAfter:     expectExpiry,
  4046  		DNSNames:     []string{"example.com", "example-a.com"},
  4047  		SerialNumber: expectSerial,
  4048  	}
  4049  	mockDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
  4050  	test.AssertNotError(t, err, "failed to create test certificate")
  4051  
  4052  	wfe.sa = &mockSAForARI{
  4053  		cert: &corepb.Certificate{
  4054  			RegistrationID: 1,
  4055  			Serial:         expectSerial.String(),
  4056  			Der:            mockDer,
  4057  		},
  4058  	}
  4059  
  4060  	// Working with a single matching identifier.
  4061  	err = wfe.orderMatchesReplacement(context.Background(), &core.Registration{ID: 1}, identifier.ACMEIdentifiers{identifier.NewDNS("example.com")}, expectSerial.String())
  4062  	test.AssertNotError(t, err, "failed to check order is replacement")
  4063  
  4064  	// Working with a different matching identifier.
  4065  	err = wfe.orderMatchesReplacement(context.Background(), &core.Registration{ID: 1}, identifier.ACMEIdentifiers{identifier.NewDNS("example-a.com")}, expectSerial.String())
  4066  	test.AssertNotError(t, err, "failed to check order is replacement")
  4067  
  4068  	// No matching identifiers.
  4069  	err = wfe.orderMatchesReplacement(context.Background(), &core.Registration{ID: 1}, identifier.ACMEIdentifiers{identifier.NewDNS("example-b.com")}, expectSerial.String())
  4070  	test.AssertErrorIs(t, err, berrors.Malformed)
  4071  
  4072  	// RegID for predecessor order does not match.
  4073  	err = wfe.orderMatchesReplacement(context.Background(), &core.Registration{ID: 2}, identifier.ACMEIdentifiers{identifier.NewDNS("example.com")}, expectSerial.String())
  4074  	test.AssertErrorIs(t, err, berrors.Unauthorized)
  4075  
  4076  	// Predecessor certificate not found.
  4077  	err = wfe.orderMatchesReplacement(context.Background(), &core.Registration{ID: 1}, identifier.ACMEIdentifiers{identifier.NewDNS("example.com")}, "1")
  4078  	test.AssertErrorIs(t, err, berrors.NotFound)
  4079  }
  4080  
  4081  type mockRA struct {
  4082  	rapb.RegistrationAuthorityClient
  4083  	expectProfileName string
  4084  }
  4085  
  4086  // NewOrder returns an error if the ""
  4087  func (sa *mockRA) NewOrder(ctx context.Context, in *rapb.NewOrderRequest, opts ...grpc.CallOption) (*corepb.Order, error) {
  4088  	if in.CertificateProfileName != sa.expectProfileName {
  4089  		return nil, errors.New("not expected profile name")
  4090  	}
  4091  	now := time.Now().UTC()
  4092  	created := now.AddDate(-30, 0, 0)
  4093  	exp := now.AddDate(30, 0, 0)
  4094  	return &corepb.Order{
  4095  		Id:                     123456789,
  4096  		RegistrationID:         987654321,
  4097  		Created:                timestamppb.New(created),
  4098  		Expires:                timestamppb.New(exp),
  4099  		Identifiers:            []*corepb.Identifier{identifier.NewDNS("example.com").ToProto()},
  4100  		Status:                 string(core.StatusValid),
  4101  		V2Authorizations:       []int64{1},
  4102  		CertificateSerial:      "serial",
  4103  		Error:                  nil,
  4104  		CertificateProfileName: in.CertificateProfileName,
  4105  	}, nil
  4106  }
  4107  
  4108  func TestNewOrderWithProfile(t *testing.T) {
  4109  	wfe, _, signer := setupWFE(t)
  4110  	expectProfileName := "test-profile"
  4111  	wfe.ra = &mockRA{expectProfileName: expectProfileName}
  4112  	mux := wfe.Handler(metrics.NoopRegisterer)
  4113  	wfe.certProfiles = map[string]string{expectProfileName: "description"}
  4114  
  4115  	// Test that the newOrder endpoint returns the proper error if an invalid
  4116  	// profile is specified.
  4117  	invalidOrderBody := `
  4118  	{
  4119  		"Identifiers": [
  4120  		  {"type": "dns", "value": "example.com"}
  4121  		],
  4122  		"Profile": "bad-profile"
  4123  	}`
  4124  
  4125  	responseWriter := httptest.NewRecorder()
  4126  	r := signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath, invalidOrderBody)
  4127  	mux.ServeHTTP(responseWriter, r)
  4128  	test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
  4129  	var errorResp map[string]any
  4130  	err := json.Unmarshal(responseWriter.Body.Bytes(), &errorResp)
  4131  	test.AssertNotError(t, err, "Failed to unmarshal error response")
  4132  	test.AssertEquals(t, errorResp["type"], "urn:ietf:params:acme:error:invalidProfile")
  4133  	test.AssertEquals(t, errorResp["detail"], "profile name \"bad-profile\" not recognized")
  4134  
  4135  	// Test that the newOrder endpoint returns no error if the valid profile is specified.
  4136  	validOrderBody := `
  4137  	{
  4138  		"Identifiers": [
  4139  		  {"type": "dns", "value": "example.com"}
  4140  		],
  4141  		"Profile": "test-profile"
  4142  	}`
  4143  	responseWriter = httptest.NewRecorder()
  4144  	r = signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath, validOrderBody)
  4145  	mux.ServeHTTP(responseWriter, r)
  4146  	test.AssertEquals(t, responseWriter.Code, http.StatusCreated)
  4147  	var errorResp1 map[string]any
  4148  	err = json.Unmarshal(responseWriter.Body.Bytes(), &errorResp1)
  4149  	test.AssertNotError(t, err, "Failed to unmarshal order response")
  4150  	test.AssertEquals(t, errorResp1["status"], "valid")
  4151  
  4152  	// Set the acceptable profiles to the empty set, the WFE should no longer accept any profiles.
  4153  	wfe.certProfiles = map[string]string{}
  4154  	responseWriter = httptest.NewRecorder()
  4155  	r = signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath, validOrderBody)
  4156  	mux.ServeHTTP(responseWriter, r)
  4157  	test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
  4158  	var errorResp2 map[string]any
  4159  	err = json.Unmarshal(responseWriter.Body.Bytes(), &errorResp2)
  4160  	test.AssertNotError(t, err, "Failed to unmarshal error response")
  4161  	test.AssertEquals(t, errorResp2["type"], "urn:ietf:params:acme:error:invalidProfile")
  4162  	test.AssertEquals(t, errorResp2["detail"], "profile name \"test-profile\" not recognized")
  4163  }
  4164  
  4165  func makeARICertID(leaf *x509.Certificate) (string, error) {
  4166  	if leaf == nil {
  4167  		return "", errors.New("leaf certificate is nil")
  4168  	}
  4169  
  4170  	// Marshal the Serial Number into DER.
  4171  	der, err := asn1.Marshal(leaf.SerialNumber)
  4172  	if err != nil {
  4173  		return "", err
  4174  	}
  4175  
  4176  	// Check if the DER encoded bytes are sufficient (at least 3 bytes: tag,
  4177  	// length, and value).
  4178  	if len(der) < 3 {
  4179  		return "", errors.New("invalid DER encoding of serial number")
  4180  	}
  4181  
  4182  	// Extract only the integer bytes from the DER encoded Serial Number
  4183  	// Skipping the first 2 bytes (tag and length). The result is base64url
  4184  	// encoded without padding.
  4185  	serial := base64.RawURLEncoding.EncodeToString(der[2:])
  4186  
  4187  	// Convert the Authority Key Identifier to base64url encoding without
  4188  	// padding.
  4189  	aki := base64.RawURLEncoding.EncodeToString(leaf.AuthorityKeyId)
  4190  
  4191  	// Construct the final identifier by concatenating AKI and Serial Number.
  4192  	return fmt.Sprintf("%s.%s", aki, serial), nil
  4193  }
  4194  
  4195  func TestCountNewOrderWithReplaces(t *testing.T) {
  4196  	wfe, fc, signer := setupWFE(t)
  4197  
  4198  	// Pick a random issuer to "issue" expectCert.
  4199  	var issuer *issuance.Certificate
  4200  	for _, v := range wfe.issuerCertificates {
  4201  		issuer = v
  4202  		break
  4203  	}
  4204  	testKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  4205  	expectSerial := big.NewInt(1337)
  4206  	expectCert := &x509.Certificate{
  4207  		NotBefore:      fc.Now(),
  4208  		NotAfter:       fc.Now().AddDate(0, 0, 90),
  4209  		DNSNames:       []string{"example.com"},
  4210  		SerialNumber:   expectSerial,
  4211  		AuthorityKeyId: issuer.SubjectKeyId,
  4212  	}
  4213  	expectCertId, err := makeARICertID(expectCert)
  4214  	test.AssertNotError(t, err, "failed to create test cert id")
  4215  	expectDer, err := x509.CreateCertificate(rand.Reader, expectCert, expectCert, &testKey.PublicKey, testKey)
  4216  	test.AssertNotError(t, err, "failed to create test certificate")
  4217  
  4218  	// MockSA that returns the certificate with the expected serial.
  4219  	wfe.sa = &mockSAForARI{
  4220  		cert: &corepb.Certificate{
  4221  			RegistrationID: 1,
  4222  			Serial:         core.SerialToString(expectSerial),
  4223  			Der:            expectDer,
  4224  			Issued:         timestamppb.New(expectCert.NotBefore),
  4225  			Expires:        timestamppb.New(expectCert.NotAfter),
  4226  		},
  4227  	}
  4228  	mux := wfe.Handler(metrics.NoopRegisterer)
  4229  	responseWriter := httptest.NewRecorder()
  4230  
  4231  	// Set the fake clock forward to 1s past the suggested renewal window start
  4232  	// time.
  4233  	renewalWindowStart := core.RenewalInfoSimple(expectCert.NotBefore, expectCert.NotAfter).SuggestedWindow.Start
  4234  	fc.Set(renewalWindowStart.Add(time.Second))
  4235  
  4236  	body := fmt.Sprintf(`
  4237  	{
  4238  		"Identifiers": [
  4239  		  {"type": "dns", "value": "example.com"}
  4240  		],
  4241  		"Replaces": %q
  4242  	}`, expectCertId)
  4243  
  4244  	r := signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath, body)
  4245  	mux.ServeHTTP(responseWriter, r)
  4246  	test.AssertEquals(t, responseWriter.Code, http.StatusCreated)
  4247  	test.AssertMetricWithLabelsEquals(t, wfe.stats.ariReplacementOrders, prometheus.Labels{"isReplacement": "true", "limitsExempt": "true"}, 1)
  4248  }
  4249  
  4250  func TestNewOrderRateLimits(t *testing.T) {
  4251  	wfe, fc, signer := setupWFE(t)
  4252  
  4253  	// Set the default ratelimits to only allow one new order per account per 24
  4254  	// hours.
  4255  	txnBuilder, err := ratelimits.NewTransactionBuilder(ratelimits.LimitConfigs{
  4256  		ratelimits.NewOrdersPerAccount.String(): &ratelimits.LimitConfig{
  4257  			Burst:  1,
  4258  			Count:  1,
  4259  			Period: config.Duration{Duration: time.Hour * 24}},
  4260  	}, nil, metrics.NoopRegisterer, blog.NewMock())
  4261  	test.AssertNotError(t, err, "making transaction composer")
  4262  	wfe.txnBuilder = txnBuilder
  4263  
  4264  	// Pick a random issuer to "issue" extantCert.
  4265  	var issuer *issuance.Certificate
  4266  	for _, v := range wfe.issuerCertificates {
  4267  		issuer = v
  4268  		break
  4269  	}
  4270  	testKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  4271  	test.AssertNotError(t, err, "failed to create test key")
  4272  	extantCert := &x509.Certificate{
  4273  		NotBefore:      fc.Now(),
  4274  		NotAfter:       fc.Now().AddDate(0, 0, 90),
  4275  		DNSNames:       []string{"example.com"},
  4276  		SerialNumber:   big.NewInt(1337),
  4277  		AuthorityKeyId: issuer.SubjectKeyId,
  4278  	}
  4279  	extantCertId, err := makeARICertID(extantCert)
  4280  	test.AssertNotError(t, err, "failed to create test cert id")
  4281  	extantDer, err := x509.CreateCertificate(rand.Reader, extantCert, extantCert, &testKey.PublicKey, testKey)
  4282  	test.AssertNotError(t, err, "failed to create test certificate")
  4283  
  4284  	// Mock SA that returns the certificate with the expected serial.
  4285  	wfe.sa = &mockSAForARI{
  4286  		cert: &corepb.Certificate{
  4287  			RegistrationID: 1,
  4288  			Serial:         core.SerialToString(extantCert.SerialNumber),
  4289  			Der:            extantDer,
  4290  			Issued:         timestamppb.New(extantCert.NotBefore),
  4291  			Expires:        timestamppb.New(extantCert.NotAfter),
  4292  		},
  4293  	}
  4294  
  4295  	// Set the fake clock forward to 1s past the suggested renewal window start
  4296  	// time.
  4297  	renewalWindowStart := core.RenewalInfoSimple(extantCert.NotBefore, extantCert.NotAfter).SuggestedWindow.Start
  4298  	fc.Set(renewalWindowStart.Add(time.Second))
  4299  
  4300  	mux := wfe.Handler(metrics.NoopRegisterer)
  4301  
  4302  	// Request the certificate for the first time. Because we mocked together
  4303  	// the certificate, it will have been issued 60 days ago.
  4304  	r := signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath,
  4305  		`{"Identifiers": [{"type": "dns", "value": "example.com"}]}`)
  4306  	responseWriter := httptest.NewRecorder()
  4307  	mux.ServeHTTP(responseWriter, r)
  4308  	test.AssertEquals(t, responseWriter.Code, http.StatusCreated)
  4309  
  4310  	// Request another, identical certificate. This should fail for violating
  4311  	// the NewOrdersPerAccount rate limit.
  4312  	r = signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath,
  4313  		`{"Identifiers": [{"type": "dns", "value": "example.com"}]}`)
  4314  	responseWriter = httptest.NewRecorder()
  4315  	mux.ServeHTTP(responseWriter, r)
  4316  	features.Set(features.Config{
  4317  		UseKvLimitsForNewOrder: true,
  4318  	})
  4319  	test.AssertEquals(t, responseWriter.Code, http.StatusTooManyRequests)
  4320  
  4321  	// Make a request with the "Replaces" field, which should satisfy ARI checks
  4322  	// and therefore bypass the rate limit.
  4323  	r = signAndPost(signer, newOrderPath, "http://localhost"+newOrderPath,
  4324  		fmt.Sprintf(`{"Identifiers": [{"type": "dns", "value": "example.com"}],	"Replaces": %q}`, extantCertId))
  4325  	responseWriter = httptest.NewRecorder()
  4326  	mux.ServeHTTP(responseWriter, r)
  4327  	test.AssertEquals(t, responseWriter.Code, http.StatusCreated)
  4328  }
  4329  
  4330  func TestNewAccountCreatesContacts(t *testing.T) {
  4331  	t.Parallel()
  4332  
  4333  	key := loadKey(t, []byte(test2KeyPrivatePEM))
  4334  	_, ok := key.(*rsa.PrivateKey)
  4335  	test.Assert(t, ok, "Couldn't load test2 key")
  4336  
  4337  	path := newAcctPath
  4338  	signedURL := fmt.Sprintf("http://localhost%s", path)
  4339  
  4340  	testCases := []struct {
  4341  		name     string
  4342  		contacts []string
  4343  		expected []string
  4344  	}{
  4345  		{
  4346  			name:     "No email",
  4347  			contacts: []string{},
  4348  			expected: []string{},
  4349  		},
  4350  		{
  4351  			name:     "One email",
  4352  			contacts: []string{"mailto:person@mail.com"},
  4353  			expected: []string{"person@mail.com"},
  4354  		},
  4355  		{
  4356  			name:     "Two emails",
  4357  			contacts: []string{"mailto:person1@mail.com", "mailto:person2@mail.com"},
  4358  			expected: []string{"person1@mail.com", "person2@mail.com"},
  4359  		},
  4360  		{
  4361  			name:     "Invalid email",
  4362  			contacts: []string{"mailto:lol@%mail.com"},
  4363  			expected: []string{},
  4364  		},
  4365  		{
  4366  			name:     "One valid email, one invalid email",
  4367  			contacts: []string{"mailto:person@mail.com", "mailto:lol@%mail.com"},
  4368  			expected: []string{},
  4369  		},
  4370  		{
  4371  			name:     "Valid email with non-email prefix",
  4372  			contacts: []string{"heliograph:person@mail.com"},
  4373  			expected: []string{},
  4374  		},
  4375  		{
  4376  			name: "Non-email prefix with correct field signal instructions",
  4377  			contacts: []string{`heliograph:STATION OF RECEPTION: High Ridge above Black Hollow, near Lone Pine.
  4378  AZIMUTH TO SIGNAL STATION: Due West, bearing Twin Peaks.
  4379  WATCH PERIOD: Third hour post-zenith; observation maintained for 30 minutes.
  4380  SIGNAL CODE: Standard Morse, three-flash attention signal.
  4381  ALTERNATE SITE: If no reply, move to Observation Point B at Broken Cairn.`},
  4382  			expected: []string{},
  4383  		},
  4384  	}
  4385  
  4386  	for _, tc := range testCases {
  4387  		t.Run(tc.name, func(t *testing.T) {
  4388  			t.Parallel()
  4389  
  4390  			wfe, _, signer := setupWFE(t)
  4391  
  4392  			mockImpl := mocks.NewMockSalesforceClientImpl()
  4393  			wfe.ee = mocks.NewMockExporterImpl(mockImpl)
  4394  
  4395  			contactsJSON, err := json.Marshal(tc.contacts)
  4396  			test.AssertNotError(t, err, "Failed to marshal contacts")
  4397  
  4398  			payload := fmt.Sprintf(`{"contact":%s,"termsOfServiceAgreed":true}`, contactsJSON)
  4399  			_, _, body := signer.embeddedJWK(key, signedURL, payload)
  4400  			request := makePostRequestWithPath(path, body)
  4401  
  4402  			responseWriter := httptest.NewRecorder()
  4403  			wfe.NewAccount(context.Background(), newRequestEvent(), responseWriter, request)
  4404  
  4405  			for _, email := range tc.expected {
  4406  				test.AssertSliceContains(t, mockImpl.GetCreatedContacts(), email)
  4407  			}
  4408  		})
  4409  	}
  4410  }