Remove new-cert and new-authz handlers from wfe2 (#2987)

This commit is contained in:
Roland Bracewell Shoemaker 2017-08-16 12:50:55 -07:00 committed by Daniel McCarney
parent 6962bfe1a6
commit e17175a851
2 changed files with 5 additions and 498 deletions

View File

@ -36,10 +36,8 @@ const (
directoryPath = "/directory"
newRegPath = "/acme/new-reg"
regPath = "/acme/reg/"
newAuthzPath = "/acme/new-authz"
authzPath = "/acme/authz/"
challengePath = "/acme/challenge/"
newCertPath = "/acme/new-cert"
certPath = "/acme/cert/"
revokeCertPath = "/acme/revoke-cert"
termsPath = "/terms"
@ -300,8 +298,6 @@ func (wfe *WebFrontEndImpl) Handler() http.Handler {
m := http.NewServeMux()
wfe.HandleFunc(m, directoryPath, wfe.Directory, "GET")
wfe.HandleFunc(m, newRegPath, wfe.NewRegistration, "POST")
wfe.HandleFunc(m, newAuthzPath, wfe.NewAuthorization, "POST")
wfe.HandleFunc(m, newCertPath, wfe.NewCertificate, "POST")
wfe.HandleFunc(m, regPath, wfe.Registration, "POST")
wfe.HandleFunc(m, authzPath, wfe.Authorization, "GET", "POST")
wfe.HandleFunc(m, challengePath, wfe.Challenge, "GET", "POST")
@ -372,8 +368,6 @@ func addRequesterHeader(w http.ResponseWriter, requester int64) {
func (wfe *WebFrontEndImpl) Directory(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
directoryEndpoints := map[string]interface{}{
"new-reg": newRegPath,
"new-authz": newAuthzPath,
"new-cert": newCertPath,
"revoke-cert": revokeCertPath,
}
@ -503,7 +497,6 @@ func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *reque
regURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%d", regPath, reg.ID))
response.Header().Add("Location", regURL)
response.Header().Add("Link", link(wfe.relativeEndpoint(request, newAuthzPath), "next"))
if len(wfe.SubscriberAgreementURL) > 0 {
response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service"))
}
@ -518,55 +511,6 @@ func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *reque
}
}
// NewAuthorization is used by clients to submit a new ID Authorization
func (wfe *WebFrontEndImpl) NewAuthorization(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
body, currReg, prob := wfe.validPOSTForAccount(request, ctx, logEvent)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
// validPOSTforAccount handles its own setting of logEvent.Errors
wfe.sendError(response, logEvent, prob, nil)
return
}
// Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update.
if currReg.Agreement == "" {
wfe.sendError(response, logEvent, probs.Unauthorized("Must agree to subscriber agreement before any further actions"), nil)
return
}
var init core.Authorization
if err := json.Unmarshal(body, &init); err != nil {
logEvent.AddError("unable to JSON unmarshal Authorization: %s", err)
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err)
return
}
logEvent.Extra["Identifier"] = init.Identifier
// Create new authz and return
authz, err := wfe.RA.NewAuthorization(ctx, init, currReg.ID)
if err != nil {
logEvent.AddError("unable to create new authz: %s", err)
wfe.sendError(response, logEvent, problemDetailsForError(err, "Error creating new authz"), err)
return
}
logEvent.Extra["AuthzID"] = authz.ID
// Make a URL for this authz, then blow away the ID and RegID before serializing
authzURL := wfe.relativeEndpoint(request, authzPath+string(authz.ID))
wfe.prepAuthorizationForDisplay(request, &authz)
response.Header().Add("Location", authzURL)
response.Header().Add("Link", link(wfe.relativeEndpoint(request, newCertPath), "next"))
err = wfe.writeJsonResponse(response, logEvent, http.StatusCreated, authz)
if err != nil {
// ServerInternal because we generated the authz, it should be OK
wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling authz"), err)
return
}
}
func (wfe *WebFrontEndImpl) regHoldsAuthorizations(ctx context.Context, regID int64, names []string) (bool, error) {
authz, err := wfe.SA.GetValidAuthorizations(ctx, regID, names, wfe.clk.Now())
if err != nil {
@ -606,113 +550,6 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
wfe.log.AuditObject("Certificate request", csrLog)
}
// NewCertificate is used by clients to request the issuance of a cert for an
// authorized identifier.
func (wfe *WebFrontEndImpl) NewCertificate(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
body, reg, prob := wfe.validPOSTForAccount(request, ctx, logEvent)
addRequesterHeader(response, logEvent.Requester)
if prob != nil {
// validPOSTForAccount handles its own setting of logEvent.Errors
wfe.sendError(response, logEvent, prob, nil)
return
}
// Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update.
if reg.Agreement == "" {
wfe.sendError(response, logEvent, probs.Unauthorized("Must agree to subscriber agreement before any further actions"), nil)
return
}
var rawCSR core.RawCertificateRequest
err := json.Unmarshal(body, &rawCSR)
if err != nil {
logEvent.AddError("unable to JSON unmarshal CertificateRequest: %s", err)
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling certificate request"), err)
return
}
// Assuming a properly formatted CSR there should be two four byte SEQUENCE
// declarations then a two byte integer declaration which defines the version
// of the CSR. If those two bytes (at offset 8 and 9) and equal to 2 and 0
// then the CSR was generated by a pre-1.0.2 version of OpenSSL with a client
// which didn't explicitly set the version causing the integer to be malformed
// and encoding/asn1 will refuse to parse it. If this is the case exit early
// with a more useful error message.
if len(rawCSR.CSR) >= 10 && rawCSR.CSR[8] == 2 && rawCSR.CSR[9] == 0 {
logEvent.AddError("Pre-1.0.2 OpenSSL malformed CSR")
wfe.sendError(
response,
logEvent,
probs.Malformed("CSR generated using a pre-1.0.2 OpenSSL with a client that doesn't properly specify the CSR version. See https://community.letsencrypt.org/t/openssl-bug-information/19591"),
nil,
)
return
}
certificateRequest := core.CertificateRequest{Bytes: rawCSR.CSR}
certificateRequest.CSR, err = x509.ParseCertificateRequest(rawCSR.CSR)
if err != nil {
logEvent.AddError("unable to parse certificate request: %s", err)
wfe.sendError(response, logEvent, probs.Malformed("Error parsing certificate request: %s", err), err)
return
}
wfe.logCsr(request, certificateRequest, *reg)
// Check that the key in the CSR is good. This will also be checked in the CA
// component, but we want to discard CSRs with bad keys as early as possible
// because (a) it's an easy check and we can save unnecessary requests and
// bytes on the wire, and (b) the CA logs all rejections as audit events, but
// a bad key from the client is just a malformed request and doesn't need to
// be audited.
if err := wfe.keyPolicy.GoodKey(certificateRequest.CSR.PublicKey); err != nil {
logEvent.AddError("CSR public key failed GoodKey: %s", err)
wfe.sendError(response, logEvent, probs.Malformed("Invalid key in certificate request :: %s", err), err)
return
}
logEvent.Extra["CSRDNSNames"] = certificateRequest.CSR.DNSNames
logEvent.Extra["CSREmailAddresses"] = certificateRequest.CSR.EmailAddresses
logEvent.Extra["CSRIPAddresses"] = certificateRequest.CSR.IPAddresses
// Create new certificate and return
// TODO IMPORTANT: The RA trusts the WFE to provide the correct key. If the
// WFE is compromised, *and* the attacker knows the public key of an account
// authorized for target site, they could cause issuance for that site by
// lying to the RA. We should probably pass a copy of the whole request to the
// RA for secondary validation.
cert, err := wfe.RA.NewCertificate(ctx, certificateRequest, reg.ID)
if err != nil {
logEvent.AddError("unable to create new cert: %s", err)
wfe.sendError(response, logEvent, problemDetailsForError(err, "Error creating new cert"), err)
return
}
// Make a URL for this certificate.
// We use only the sequential part of the serial number, because it should
// uniquely identify the certificate, and this makes it easy for anybody to
// enumerate and mirror our certificates.
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil {
logEvent.AddError("unable to parse certificate: %s", err)
wfe.sendError(response, logEvent, probs.ServerInternal("Unable to parse certificate"), err)
return
}
serial := parsedCertificate.SerialNumber
certURL := wfe.relativeEndpoint(request, certPath+core.SerialToString(serial))
// TODO Content negotiation
response.Header().Add("Location", certURL)
if err = wfe.addIssuingCertificateURLs(response, parsedCertificate.IssuingCertificateURL); err != nil {
logEvent.AddError("unable to parse IssuingCertificateURL: %s", err)
wfe.sendError(response, logEvent, probs.ServerInternal("unable to parse IssuingCertificateURL"), err)
return
}
response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusCreated)
if _, err = response.Write(cert.DER); err != nil {
logEvent.AddError(err.Error())
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}
// Challenge handles POST requests to challenge URLs. Such requests are clients'
// responses to the server's challenges.
func (wfe *WebFrontEndImpl) Challenge(
@ -980,7 +817,6 @@ func (wfe *WebFrontEndImpl) Registration(
return
}
response.Header().Add("Link", link(wfe.relativeEndpoint(request, newAuthzPath), "next"))
if len(wfe.SubscriberAgreementURL) > 0 {
response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service"))
}
@ -1074,8 +910,6 @@ func (wfe *WebFrontEndImpl) Authorization(ctx context.Context, logEvent *request
wfe.prepAuthorizationForDisplay(request, &authz)
response.Header().Add("Link", link(wfe.relativeEndpoint(request, newCertPath), "next"))
err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, authz)
if err != nil {
// InternalServerError because this is a failure to decode from our DB.

View File

@ -33,7 +33,6 @@ import (
"github.com/letsencrypt/boulder/mocks"
"github.com/letsencrypt/boulder/nonce"
"github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/ra"
rapb "github.com/letsencrypt/boulder/ra/proto"
"github.com/letsencrypt/boulder/revocation"
"github.com/letsencrypt/boulder/test"
@ -661,8 +660,6 @@ func TestDirectory(t *testing.T) {
"meta": {
"terms-of-service": "http://example.invalid/terms"
},
"new-authz": "http://localhost:4300/acme/new-authz",
"new-cert": "http://localhost:4300/acme/new-cert",
"new-reg": "http://localhost:4300/acme/new-reg",
"revoke-cert": "http://localhost:4300/acme/revoke-cert",
"AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417"
@ -702,15 +699,15 @@ func TestRelativeDirectory(t *testing.T) {
result string
}{
// Test '' (No host header) with no proto header
{"", "", `{"key-change":"http://localhost/acme/key-change","new-authz":"http://localhost/acme/new-authz","new-cert":"http://localhost/acme/new-cert","new-reg":"http://localhost/acme/new-reg","revoke-cert":"http://localhost/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
{"", "", `{"key-change":"http://localhost/acme/key-change","new-reg":"http://localhost/acme/new-reg","revoke-cert":"http://localhost/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
// Test localhost:4300 with no proto header
{"localhost:4300", "", `{"key-change":"http://localhost:4300/acme/key-change","new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
{"localhost:4300", "", `{"key-change":"http://localhost:4300/acme/key-change","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
// Test 127.0.0.1:4300 with no proto header
{"127.0.0.1:4300", "", `{"key-change":"http://127.0.0.1:4300/acme/key-change","new-authz":"http://127.0.0.1:4300/acme/new-authz","new-cert":"http://127.0.0.1:4300/acme/new-cert","new-reg":"http://127.0.0.1:4300/acme/new-reg","revoke-cert":"http://127.0.0.1:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
{"127.0.0.1:4300", "", `{"key-change":"http://127.0.0.1:4300/acme/key-change","new-reg":"http://127.0.0.1:4300/acme/new-reg","revoke-cert":"http://127.0.0.1:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
// Test localhost:4300 with HTTP proto header
{"localhost:4300", "http", `{"key-change":"http://localhost:4300/acme/key-change","new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
{"localhost:4300", "http", `{"key-change":"http://localhost:4300/acme/key-change","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
// Test localhost:4300 with HTTPS proto header
{"localhost:4300", "https", `{"key-change":"https://localhost:4300/acme/key-change","new-authz":"https://localhost:4300/acme/new-authz","new-cert":"https://localhost:4300/acme/new-cert","new-reg":"https://localhost:4300/acme/new-reg","revoke-cert":"https://localhost:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
{"localhost:4300", "https", `{"key-change":"https://localhost:4300/acme/key-change","new-reg":"https://localhost:4300/acme/new-reg","revoke-cert":"https://localhost:4300/acme/revoke-cert","AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417","meta":{"terms-of-service": "http://example.invalid/terms"}}`},
}
for _, tt := range dirTests {
@ -765,11 +762,6 @@ func TestHTTPMethods(t *testing.T) {
Path: newRegPath,
Allowed: postOnly,
},
{
Name: "NewAuthz path should be POST only",
Path: newAuthzPath,
Allowed: postOnly,
},
{
Name: "NewReg path should be POST only",
Path: newRegPath,
@ -877,188 +869,6 @@ func TestHTTPMethods(t *testing.T) {
}
}
// TODO: Write additional test cases for:
// - RA returns with a failure
func TestIssueCertificate(t *testing.T) {
wfe, fc := setupWFE(t)
mockLog := wfe.log.(*blog.Mock)
// The mock CA we use always returns the same test certificate, with a Not
// Before of 2015-09-22. Since we're currently using a real RA instead of a
// mock (see below), that date would trigger failures for excessive
// backdating. So we set the fake clock's time to a time that matches that
// test certificate.
testTime := time.Date(2015, 9, 9, 22, 56, 0, 0, time.UTC)
fc.Add(fc.Now().Sub(testTime))
mockCertPEM, err := ioutil.ReadFile("test/not-an-example.com.crt")
test.AssertNotError(t, err, "Could not load mock cert")
// TODO: Use a mock RA so we can test various conditions of authorized, not
// authorized, etc.
stats := metrics.NewNoopScope()
ra := ra.NewRegistrationAuthorityImpl(
fc,
wfe.log,
stats,
0,
testKeyPolicy,
0,
true,
false,
300*24*time.Hour,
7*24*time.Hour,
nil,
0,
)
ra.SA = mocks.NewStorageAuthority(fc)
ra.CA = &mocks.MockCA{
PEM: mockCertPEM,
}
ra.PA = &mockPA{}
wfe.RA = ra
targetHost := "localhost"
targetPath := "new-cert"
signedURL := fmt.Sprintf("http://%s/%s", targetHost, targetPath)
// Valid, signed JWS body, payload has an invalid signature on CSR and no authorizations:
// alias b64url="base64 -w0 | sed -e 's,+,-,g' -e 's,/,_,g'"
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=foo.com | \
// sed 's/foo.com/fob.com/' | b64url
brokenCSRSignaturePayload := `{
"csr": "MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHZm9iLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKzHhqcMSTVjBu61vufGVmIYM4mMbWXgndHOUWnIqSKcNtFtPQ465tcZRT5ITIZWXGjsmgDrj31qvG3t5qLwyaF5hsTvFHK72nLMAQhdgM6481Qe9yaoaulWpkGr_9LVz4jQ9pGAaLVamXGpSxV-ipTOo79Sev4aZE8ksD9atEfWtcOD9w8_zj74vpWjTAHN49Q88chlChVqakn0zSfHPfS-jF8g0UTddBuF0Ti3sZChjxzbo6LwZ4182xX7XPnOLav3AGj0Su7j5XMl3OpenOrlWulWJeZIHq5itGW321j306XiGdbrdWH4K7JygICFds6oolwQRGBY6yinAtCgkTcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBxPiHOtKuBxtvecMNtLkTSuTyEkusQGnjoFDaKe5oqwGYQgy0YBii2-BbaPmqS4ZaDc-vDz_RLeKH5ZiH-NliYR1V_CRtpFLQi18g_2pLQnZLVO3ENs-SM37nU_nBGn9O93t2bkssoM3fZmtgp3R2W7I_wvx7Z8oWKa4boTeBAg_q9Gmi6QskZBddK7A4S_vOR0frU6QSPK_ksPhvovp9fwb6CVKrlJWf556UwRPWgbkW39hvTxK2KHhrUEg3oawNkWde2jZtnZ9e-9zpw8-_5O0X7-YN0ucbFTfQybce_ReuLlGepiHT5bvVavBZoIvqw1XOgSMvGgZFU8tAWMBlj"
}`
// Valid, signed JWS body, payload has a valid CSR but no authorizations:
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=meep.com | b64url
noAuthorizationsCSRPayload := `{
"resource":"new-cert",
"csr": "MIICWDCCAUACAQAwEzERMA8GA1UEAwwIbWVlcC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaqzue57mgXEoGTZZoVkkCZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12HX01rUwhj41UuBxJvI1w4wvdstssTAaa9c9tsQ5-UED2bFRL1MsyBdbmCF_-pu3i-ZIYqWgiKbjVBe3nlAVbo77zizwp3Y4Tp1_TBOwTAuFkHePmkNT63uPm9My_hNzsSm1o-Q519Cf7ry-JQmOVgz_jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoXRUlQjXXkNTj23OxxdT_cVLcLJjytyG6e5izME2R2aCkDBWIc1a4_sRJ0R396auPXG6KhJ7o_AgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEALu046p76aKgvoAEHFINkMTgKokPXf9mZ4IZx_BKz-qs1MPMxVtPIrQDVweBH6tYT7Hfj2naLry6SpZ3vUNP_FYeTFWgW1V03LiqacX-QQgbEYtn99Dt3ScGyzb7EH833ztb3vDJ_-ha_CJplIrg-kHBBrlLFWXhh-I9K1qLRTNpbhZ18ooFde4Sbhkw9o9fKivGhx9aYr7ZbjRsNtKit_DsG1nwEXz53TMJ2vB9IQY29coJv_n5NFLkvBfzbG5faRNiFcimPYBO2jFdaA2mWzfxltLtwMF_dBwzTXDpMo3TVT9zEdV8YpsWqr63igqGDZVpKenlkqvRTeGJVayVuMA"
}`
// CSR from an < 1.0.2 OpenSSL
oldOpenSSLCSRPayload := `{
"resource": "new-cert",
"csr": "MIICWjCCAUICADAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMpwCSKfLhKC3SnvLNpVayAEyAHVixkusgProAPZRBH0VAog_r4JOfoJez7ABiZ2ZIXXA2gg65_05HkGNl9ww-sa0EY8eCty_8WcHxqzafUnyXOJZuLMPJjaJ2oiBv_3BM7PZgpFzyNZ0_0ZuRKdFGtEY-vX9GXZUV0A3sxZMOpce0lhHAiBk_vNARJyM2-O-cZ7WjzZ7R1T9myAyxtsFhWy3QYvIwiKVVF3lDp3KXlPZ_7wBhVIBcVSk0bzhseotyUnKg-aL5qZIeB1ci7IT5qA_6C1_bsCSJSbQ5gnQwIQ0iaUV_SgUBpKNqYbmnSdZmDxvvW8FzhuL6JSDLfBR2kCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBxxkchTXfjv07aSWU9brHnRziNYOLvsSNiOWmWLNlZg9LKdBy6j1xwM8IQRCfTOVSkbuxVV-kU5p-Cg9UF_UGoerl3j8SiupurTovK9-L_PdX0wTKbK9xkh7OUq88jp32Rw0eAT87gODJRD-M1NXlTvm-j896e60hUmL-DIe3iPbFl8auUS-KROAWjci-LJZYVdomm9Iw47E-zr4Hg27EdZhvCZvSyPMK8ioys9mNg5TthHB6ExepKP1YW3HpQa1EdUVYWGEvyVL4upQZOxuEA1WJqHv6iVDzsQqkl5kkahK87NKTPS59k1TFetjw2GLnQ09-g_L7kT8dpq3Bk5Wo="
}`
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=not-an-example.com | b64url
// a valid CSR with authorizations for all of its names
goodCertCSRPayload := `{
"resource":"new-cert",
"csr": "MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSbm90LWFuLWV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmqs7nue5oFxKBk2WaFZJAma2nm1oFyPIq19gYEAdQN4mWvaJ8RjzHFkDMYUrlIrGxCYuFJDHFUk9dh19Na1MIY-NVLgcSbyNcOML3bLbLEwGmvXPbbEOflBA9mxUS9TLMgXW5ghf_qbt4vmSGKloIim41QXt55QFW6O-84s8Kd2OE6df0wTsEwLhZB3j5pDU-t7j5vTMv4Tc7EptaPkOdfQn-68viUJjlYM_4yIBVRhWCdexFdylCKVLg0obsghQEwULKYCUjdg6F0VJUI115DU49tzscXU_3FS3CyY8rchunuYszBNkdmgpAwViHNWuP7ESdEd_emrj1xuioSe6PwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAE_T1nWU38XVYL28hNVSXU0rW5IBUKtbvr0qAkD4kda4HmQRTYkt-LNSuvxoZCC9lxijjgtJi-OJe_DCTdZZpYzewlVvcKToWSYHYQ6Wm1-fxxD_XzphvZOujpmBySchdiz7QSVWJmVZu34XD5RJbIcrmj_cjRt42J1hiTFjNMzQu9U6_HwIMmliDL-soFY2RTvvZf-dAFvOUQ-Wbxt97eM1PbbmxJNWRhbAmgEpe9PWDPTpqV5AK56VAa991cQ1P8ZVmPss5hvwGWhOtpnpTZVHN3toGNYFKqxWPboirqushQlfKiFqT9rpRgM3-mFjOHidGqsKEkTdmfSVlVEk3oo="
}`
cert, err := core.LoadCert("test/not-an-example.com.crt")
test.AssertNotError(t, err, "Could not load cert")
goodCertAIAHeaders := map[string]string{
"Location": "http://localhost/acme/cert/0000ff0000000000000e4b4f67d86e818c46",
"Link": `<https://localhost:4000/acme/issuer-cert>;rel="up"`,
"Content-Type": "application/pkix-cert",
}
goodCertLogParts := []string{
`INFO: `,
`[AUDIT] `,
`"CommonName":"not-an-example.com",`,
}
testCases := []struct {
Name string
Request *http.Request
ExpectedProblem string
ExpectedCert string
AssertCSRLogged bool
ExpectedHeaders map[string]string
ExpectedLogParts []string
}{
{
Name: "POST, but no body",
Request: &http.Request{
Method: "POST",
Header: map[string][]string{
"Content-Length": {"0"},
},
},
ExpectedProblem: `{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`,
},
{
Name: "POST, with an invalid JWS body",
Request: makePostRequestWithPath("hi", "hi"),
ExpectedProblem: `{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`,
},
{
Name: "POST, properly signed JWS, payload isn't valid",
Request: signAndPost(t, targetPath, signedURL, "foo", 1, wfe.nonceService),
ExpectedProblem: `{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`,
},
{
Name: "POST, properly signed JWS, trivial JSON payload",
Request: signAndPost(t, targetPath, signedURL, "{}", 1, wfe.nonceService),
ExpectedProblem: `{"type":"urn:acme:error:malformed","detail":"Error parsing certificate request: asn1: syntax error: sequence truncated","status":400}`,
},
{
Name: "POST, properly signed JWS, broken signature on CSR",
Request: signAndPost(t, targetPath, signedURL, brokenCSRSignaturePayload, 1, wfe.nonceService),
ExpectedProblem: `{"type":"urn:acme:error:malformed","detail":"Error creating new cert :: invalid signature on CSR","status":400}`,
},
{
Name: "POST, properly signed JWS, CSR from an old OpenSSL",
Request: signAndPost(t, targetPath, signedURL, oldOpenSSLCSRPayload, 1, wfe.nonceService),
ExpectedProblem: `{"type":"urn:acme:error:malformed","detail":"CSR generated using a pre-1.0.2 OpenSSL with a client that doesn't properly specify the CSR version. See https://community.letsencrypt.org/t/openssl-bug-information/19591","status":400}`,
},
{
Name: "POST, properly signed JWS, no authorizations for names in CSR",
Request: signAndPost(t, targetPath, signedURL, noAuthorizationsCSRPayload, 1, wfe.nonceService),
ExpectedProblem: `{"type":"urn:acme:error:unauthorized","detail":"Error creating new cert :: authorizations for these names not found or expired: meep.com","status":403}`,
AssertCSRLogged: true,
},
{
Name: "POST, properly signed JWS, authorizations for all names in CSR, using AIAIssuer",
Request: signAndPost(t, targetPath, signedURL, goodCertCSRPayload, 1, wfe.nonceService),
ExpectedCert: string(cert.Raw),
AssertCSRLogged: true,
ExpectedHeaders: goodCertAIAHeaders,
ExpectedLogParts: goodCertLogParts,
},
}
responseWriter := httptest.NewRecorder()
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
responseWriter.Body.Reset()
responseWriter.HeaderMap = http.Header{}
mockLog.Clear()
wfe.NewCertificate(ctx, newRequestEvent(), responseWriter, tc.Request)
if len(tc.ExpectedProblem) > 0 {
test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedProblem)
} else if len(tc.ExpectedCert) > 0 {
test.AssertEquals(t, responseWriter.Body.String(), tc.ExpectedCert)
}
if tc.AssertCSRLogged {
assertCsrLogged(t, mockLog)
}
headers := responseWriter.Header()
for k, v := range tc.ExpectedHeaders {
test.AssertEquals(t, headers.Get(k), v)
}
if len(tc.ExpectedLogParts) > 0 {
reqlogs := mockLog.GetAllMatching(`Certificate request - successful`)
test.AssertEquals(t, len(reqlogs), 1)
for _, msg := range tc.ExpectedLogParts {
test.AssertContains(t, reqlogs[0], msg)
}
}
})
}
}
func TestGetChallenge(t *testing.T) {
wfe, _ := setupWFE(t)
@ -1356,13 +1166,8 @@ func TestNewRegistration(t *testing.T) {
t, responseWriter.Header().Get("Location"),
"http://localhost/acme/reg/0")
links := responseWriter.Header()["Link"]
test.AssertEquals(t, contains(links, "<http://localhost/acme/new-authz>;rel=\"next\""), true)
test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true)
test.AssertEquals(
t, responseWriter.Header().Get("Link"),
`<http://localhost/acme/new-authz>;rel="next"`)
key = loadKeys(t, []byte(test1KeyPrivatePEM))
_, ok = key.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load test1 key")
@ -1409,80 +1214,6 @@ func makeRevokeRequestJSON(reason *revocation.Reason) ([]byte, error) {
return revokeRequestJSON, nil
}
func TestAuthorization(t *testing.T) {
wfe, _ := setupWFE(t)
authzURL := fmt.Sprintf("http://localhost%s", authzPath)
_, _, jwsInvalidBody := signRequestKeyID(t, 1, nil, authzURL, "foo", wfe.nonceService)
jwsInvalidSig := `{"payload":"Zm9x","protected":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjEiLCJub25jZSI6ImdMTE90QlJCeXl4V3Y2RExQRUJFUERsS05DV294NzdjYzBRLXdTSVdYY1UiLCJ1cmwiOiJodHRwOi8vbG9jYWxob3N0L2FjbWUvYXV0aHovIn0","signature":"Xn2yTyn1eIgbv1HOnl8_OuDMrHRlacje_fhAYyOlVmFGrb9vzTTYQ-TXdXpjrtQzcIRX7z0v1Wv3DJJryMAMwm-I9DN-Gmd_h1HG6KXK61vHIqMWlUen2WoBBNdeE00S5UI0iP-zY5E_jfW0ByLzAqoZeMN_vTJG40Ji9hfvaPaQBGLEcz6xw0Mc7EgacY6m_JGPBkCDvrDstHTTqIu5tZ3Dw7tif33aakONDKXVE0WOHjfHNP-jW4tkeCrlIDajWpU074AGmCMyBDXK5ii6Pv6tztcMPqg8SvL9_I7RyCsjQGCymNCr_XoJkRwS6GBoaKS5Jqmb-qt-O_-rcpq-Fw"}`
goodPayload := `{"identifier":{"type":"dns","value":"test.com"}}`
_, _, goodJWSBody := signRequestKeyID(t, 1, nil, authzURL, goodPayload, wfe.nonceService)
testCases := []struct {
Name string
Request *http.Request
ExpectedBody string
ExpectedHeaders map[string]string
UnmarshalAuthz bool
}{
{
Name: "POST with no body",
Request: &http.Request{
Method: "POST",
Header: map[string][]string{
"Content-Length": {"0"},
},
},
ExpectedBody: `{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`,
},
{
Name: "POST with invalid JWS",
Request: makePostRequestWithPath("hi", "hi"),
ExpectedBody: `{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`,
},
{
Name: "POST JWS with invalid payload",
Request: makePostRequestWithPath(authzPath, jwsInvalidBody),
ExpectedBody: `{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`,
},
{
Name: "POST JWS with a broken signature",
Request: makePostRequestWithPath(authzPath, jwsInvalidSig),
ExpectedBody: `{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}`,
},
{
Name: "Valid POST",
Request: makePostRequestWithPath(authzPath, goodJWSBody),
ExpectedBody: `{"identifier":{"type":"dns","value":"test.com"}}`,
ExpectedHeaders: map[string]string{
"Location": "http://localhost/acme/authz/bkrPh2u0JUf18-rVBZtOOWWb3GuIiliypL-hBM9Ak1Q",
"Link": `<http://localhost/acme/new-cert>;rel="next"`,
},
UnmarshalAuthz: true,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
responseWriter := httptest.NewRecorder()
wfe.NewAuthorization(ctx, newRequestEvent(), responseWriter, tc.Request)
body := responseWriter.Body.String()
headers := responseWriter.Header()
test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody)
for h, v := range tc.ExpectedHeaders {
test.AssertEquals(t, headers.Get(h), v)
}
// If the test case instructs, also ensure the response body unmarshals into an authz
if tc.UnmarshalAuthz {
var authz core.Authorization
err := json.Unmarshal([]byte(body), &authz)
test.AssertNotError(t, err, "Couldn't unmarshal returned authorization object")
}
})
}
}
func TestGetAuthorization(t *testing.T) {
wfe, _ := setupWFE(t)
@ -1581,7 +1312,6 @@ func TestRegistration(t *testing.T) {
wfe.Registration(ctx, newRequestEvent(), responseWriter, request)
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
links := responseWriter.Header()["Link"]
test.AssertEquals(t, contains(links, "<http://localhost/acme/new-authz>;rel=\"next\""), true)
test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true)
responseWriter.Body.Reset()
@ -1607,7 +1337,6 @@ func TestRegistration(t *testing.T) {
wfe.Registration(ctx, newRequestEvent(), responseWriter, request)
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
links = responseWriter.Header()["Link"]
test.AssertEquals(t, contains(links, "<http://localhost/acme/new-authz>;rel=\"next\""), true)
test.AssertEquals(t, contains(links, "<http://example.invalid/new-terms>;rel=\"terms-of-service\""), true)
responseWriter.Body.Reset()
}
@ -1741,62 +1470,6 @@ func TestGetCertificate(t *testing.T) {
}
}
func assertCsrLogged(t *testing.T, mockLog *blog.Mock) {
matches := mockLog.GetAllMatching("^INFO: \\[AUDIT\\] Certificate request JSON=")
test.Assert(t, len(matches) == 1,
fmt.Sprintf("Incorrect number of certificate request log entries: %d",
len(matches)))
}
func TestLogCsrPem(t *testing.T) {
const certificateRequestJSON = `{
"csr": "MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca-fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB-6dp48xkcX7Z_KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD-eeu8-z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7_C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT_DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq_Bbe7TBGVYZabUEh-LOskYwhgcOuThVN7tHnH5rhN-gb7cEdysjTb1QL-vOUwYgV75CB6PE5JVYK-cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub-fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G_W-Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd-L3IcyvHVKxNhIJXZVH0AOqh_1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY"
}`
wfe, fc := setupWFE(t)
var certificateRequest core.CertificateRequest
err := json.Unmarshal([]byte(certificateRequestJSON), &certificateRequest)
test.AssertNotError(t, err, "Unable to parse certificateRequest")
mockSA := mocks.NewStorageAuthority(fc)
reg, err := mockSA.GetRegistration(ctx, 789)
test.AssertNotError(t, err, "Unable to get registration")
req, err := http.NewRequest("GET", "http://[::1]/", nil)
test.AssertNotError(t, err, "NewRequest failed")
req.RemoteAddr = "12.34.98.76"
req.Header.Set("X-Forwarded-For", "10.0.0.1,172.16.0.1")
mockLog := wfe.log.(*blog.Mock)
mockLog.Clear()
wfe.logCsr(req, certificateRequest, reg)
assertCsrLogged(t, mockLog)
}
func TestBadKeyCSR(t *testing.T) {
wfe, _ := setupWFE(t)
responseWriter := httptest.NewRecorder()
payload := `{
"resource":"new-cert",
"csr": "MIHLMHcCAQAwEjEQMA4GA1UEAwwHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDCZftp4x4owgjBnwOKfzihIPedT-BUmV2fuQPMqaUlc8yJUp13vcO5uxUlaBm8leM7Dj_sgTDP_JgykorlYo73AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAEaQ2QBhweK-kp1ejQCedUhMit_wG-uTBtKnc3M82f6_fztLkhg1vWQ782nmhbEI5orXp6QtNHgJYnBpqA9Ut00"
}`
signedURL := fmt.Sprintf("http://localhost%s", newCertPath)
// Sign a request using test1key
_, _, body := signRequestKeyID(t, 1, nil, signedURL, payload, wfe.nonceService)
request := makePostRequestWithPath(newCertPath, body)
// CSR with a bad (512 bit RSA) key.
// openssl req -outform der -new -newkey rsa:512 -nodes -keyout foo.com.key
// -subj /CN=foo.com | base64 -w0 | sed -e 's,+,-,g' -e 's,/,_,g'
wfe.NewCertificate(ctx, newRequestEvent(), responseWriter, request)
test.AssertUnmarshaledEquals(t,
responseWriter.Body.String(),
`{"type":"urn:acme:error:malformed","detail":"Invalid key in certificate request :: key too small: 512","status":400}`)
}
// This uses httptest.NewServer because ServeMux.ServeHTTP won't prevent the
// body from being sent like the net/http Server's actually do.
func TestGetCertificateHEADHasCorrectBodyLength(t *testing.T) {