...

Source file src/github.com/letsencrypt/boulder/wfe2/wfe_test.go

Documentation: github.com/letsencrypt/boulder/wfe2

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

View as plain text