diff --git a/core/objects.go b/core/objects.go index 723f28a01..ad2eb0172 100644 --- a/core/objects.go +++ b/core/objects.go @@ -143,14 +143,10 @@ type AcmeIdentifier struct { // CertificateRequest is just a CSR together with // URIs pointing to authorizations that should collectively // authorize the certificate being requsted. -// -// This type is never marshaled, since we only ever receive -// it from the client. So it carries some additional information -// that is useful internally. (We rely on Go's case-insensitive -// JSON unmarshal to properly unmarshal client requests.) type CertificateRequest struct { CSR *x509.CertificateRequest // The CSR Authorizations []AcmeURL // Links to Authorization over the account key + Bytes []byte // The original bytes of the CSR, for logging. } type rawCertificateRequest struct { @@ -172,6 +168,7 @@ func (cr *CertificateRequest) UnmarshalJSON(data []byte) error { cr.CSR = csr cr.Authorizations = raw.Authorizations + cr.Bytes = raw.CSR return nil } diff --git a/mocks/log.go b/mocks/log.go index 8313615bf..6187e0d20 100644 --- a/mocks/log.go +++ b/mocks/log.go @@ -126,47 +126,47 @@ func (msw *MockSyslogWriter) Clear() { } // Close releases resources. No other methods may be called after this. -func (msw *MockSyslogWriter) Close() error { +func (msw MockSyslogWriter) Close() error { msw.closeChan <- struct{}{} return nil } // Alert logs at LOG_ALERT -func (msw *MockSyslogWriter) Alert(m string) error { +func (msw MockSyslogWriter) Alert(m string) error { return msw.write(m, syslog.LOG_ALERT) } // Crit logs at LOG_CRIT -func (msw *MockSyslogWriter) Crit(m string) error { +func (msw MockSyslogWriter) Crit(m string) error { return msw.write(m, syslog.LOG_CRIT) } // Debug logs at LOG_DEBUG -func (msw *MockSyslogWriter) Debug(m string) error { +func (msw MockSyslogWriter) Debug(m string) error { return msw.write(m, syslog.LOG_DEBUG) } // Emerg logs at LOG_EMERG -func (msw *MockSyslogWriter) Emerg(m string) error { +func (msw MockSyslogWriter) Emerg(m string) error { return msw.write(m, syslog.LOG_EMERG) } // Err logs at LOG_ERR -func (msw *MockSyslogWriter) Err(m string) error { +func (msw MockSyslogWriter) Err(m string) error { return msw.write(m, syslog.LOG_ERR) } // Info logs at LOG_INFO -func (msw *MockSyslogWriter) Info(m string) error { +func (msw MockSyslogWriter) Info(m string) error { return msw.write(m, syslog.LOG_INFO) } // Notice logs at LOG_NOTICE -func (msw *MockSyslogWriter) Notice(m string) error { +func (msw MockSyslogWriter) Notice(m string) error { return msw.write(m, syslog.LOG_NOTICE) } // Warning logs at LOG_WARNING -func (msw *MockSyslogWriter) Warning(m string) error { +func (msw MockSyslogWriter) Warning(m string) error { return msw.write(m, syslog.LOG_WARNING) } diff --git a/wfe/web-front-end.go b/wfe/web-front-end.go index aa599f0c4..f6fab39ed 100644 --- a/wfe/web-front-end.go +++ b/wfe/web-front-end.go @@ -9,6 +9,7 @@ import ( "bytes" "crypto/x509" "database/sql" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -624,6 +625,11 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ } } +func (wfe *WebFrontEndImpl) logCsr(cr core.CertificateRequest) { + wfe.log.Audit(fmt.Sprintf("Certificate request CSR=%s", + base64.StdEncoding.EncodeToString(cr.Bytes))) +} + // NewCertificate is used by clients to request the issuance of a cert for an // authorized identifier. func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request *http.Request) { @@ -659,6 +665,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest) return } + wfe.logCsr(init) logEvent.Extra["Authorizations"] = init.Authorizations logEvent.Extra["CSRDNSNames"] = init.CSR.DNSNames logEvent.Extra["CSREmailAddresses"] = init.CSR.EmailAddresses diff --git a/wfe/web-front-end_test.go b/wfe/web-front-end_test.go index 74652f0a1..9355b7d0f 100644 --- a/wfe/web-front-end_test.go +++ b/wfe/web-front-end_test.go @@ -14,6 +14,7 @@ import ( "encoding/json" "encoding/pem" "errors" + "fmt" "io" "io/ioutil" "log/syslog" @@ -128,8 +129,6 @@ wk6Oiadty3eQqSBJv0HnpmiEdQVffIK5Pg4M8Dd+aOBnEkbopAJOuA== "5dd9c885526136d810fc7640f5ba56281e2b75fa3ff7c91a7d23bab7fd4" ) -var log = mocks.UseMockLog() - type MockSA struct { // empty } @@ -332,6 +331,7 @@ func setupWFE(t *testing.T) WebFrontEndImpl { wfe.NewCert = wfe.BaseURL + NewCertPath wfe.CertBase = wfe.BaseURL + CertPath wfe.SubscriberAgreementURL = agreementURL + wfe.log.SyslogWriter = mocks.NewSyslogWriter() return wfe } @@ -496,6 +496,7 @@ func TestIssueCertificate(t *testing.T) { wfe := setupWFE(t) mux, err := wfe.Handler() test.AssertNotError(t, err, "Problem setting up HTTP handlers") + mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter) // TODO: Use a mock RA so we can test various conditions of authorized, not authorized, etc. ra := ra.NewRegistrationAuthorityImpl() @@ -572,6 +573,7 @@ func TestIssueCertificate(t *testing.T) { "{\"type\":\"urn:acme:error:unauthorized\",\"detail\":\"Error creating new cert :: Invalid signature on CSR\"}") // Valid, signed JWS body, payload has a CSR with no DNS names + mockLog.Clear() responseWriter.Body.Reset() wfe.NewCertificate(responseWriter, &http.Request{ Method: "POST", @@ -583,12 +585,14 @@ func TestIssueCertificate(t *testing.T) { test.AssertEquals(t, responseWriter.Body.String(), "{\"type\":\"urn:acme:error:unauthorized\",\"detail\":\"Error creating new cert :: Key not authorized for name Oh hi\"}") + assertCsrLogged(t, mockLog) // Valid, signed JWS body, payload has a valid CSR but no authorizations: // { // "csr": "MIIBK...", // "authorizations: [] // } + mockLog.Clear() responseWriter.Body.Reset() wfe.NewCertificate(responseWriter, &http.Request{ Method: "POST", @@ -600,8 +604,9 @@ func TestIssueCertificate(t *testing.T) { test.AssertEquals(t, responseWriter.Body.String(), "{\"type\":\"urn:acme:error:unauthorized\",\"detail\":\"Error creating new cert :: Key not authorized for name meep.com\"}") + assertCsrLogged(t, mockLog) - log.Clear() + mockLog.Clear() responseWriter.Body.Reset() wfe.NewCertificate(responseWriter, &http.Request{ Method: "POST", @@ -610,6 +615,7 @@ func TestIssueCertificate(t *testing.T) { "authorizations": ["valid"] }`, &wfe.nonceService)), }) + assertCsrLogged(t, mockLog) randomCertDer, _ := hex.DecodeString(GoodTestCert) test.AssertEquals(t, responseWriter.Body.String(), @@ -623,7 +629,7 @@ func TestIssueCertificate(t *testing.T) { test.AssertEquals( t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert") - reqlogs := log.GetAllMatching(`Certificate request - successful`) + reqlogs := mockLog.GetAllMatching(`Certificate request - successful`) test.AssertEquals(t, len(reqlogs), 1) test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_NOTICE) test.AssertContains(t, reqlogs[0].Message, `[AUDIT] `) @@ -1206,5 +1212,29 @@ func TestGetCertificate(t *testing.T) { test.AssertEquals(t, responseWriter.Code, 404) test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`) - +} + +func assertCsrLogged(t *testing.T, mockLog *mocks.MockSyslogWriter) { + matches := mockLog.GetAllMatching("^\\[AUDIT\\] Certificate request CSR=") + test.Assert(t, len(matches) == 1, + fmt.Sprintf("Incorrect number of certificate request log entries: %d", + len(matches))) + test.AssertEquals(t, matches[0].Priority, syslog.LOG_NOTICE) +} + +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 := setupWFE(t) + var certificateRequest core.CertificateRequest + err := json.Unmarshal([]byte(certificateRequestJson), &certificateRequest) + test.AssertNotError(t, err, "Unable to parse certificateRequest") + wfe.logCsr(certificateRequest) + mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter) + matches := mockLog.GetAllMatching("Certificate request") + test.Assert(t, len(matches) == 1, + "Incorrect number of certificate request log entries") + test.AssertEquals(t, matches[0].Priority, syslog.LOG_NOTICE) + test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request CSR=MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=`) }