diff --git a/wfe2/wfe.go b/wfe2/wfe.go index da569fab6..04537a365 100644 --- a/wfe2/wfe.go +++ b/wfe2/wfe.go @@ -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. diff --git a/wfe2/wfe_test.go b/wfe2/wfe_test.go index 5dec52729..742b3ddd4 100644 --- a/wfe2/wfe_test.go +++ b/wfe2/wfe_test.go @@ -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": `;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, ";rel=\"next\""), true) test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true) - test.AssertEquals( - t, responseWriter.Header().Get("Link"), - `;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": `;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, ";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, ";rel=\"next\""), true) test.AssertEquals(t, contains(links, ";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) {