add context to WFE errors

This change makes the use of requestEvent more prominent and provides
some nice handler types to make that better.

In order to avoid stomping on Error, requestEvent gains an Errors string
slice.

Fixes #552
This commit is contained in:
Jeff Hodges 2015-10-15 17:53:50 -07:00
parent 98fea98f4f
commit 7d04ea98cf
3 changed files with 317 additions and 282 deletions

85
wfe/context.go Normal file
View File

@ -0,0 +1,85 @@
package wfe
import (
"fmt"
"net/http"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
)
type requestEvent struct {
ID string `json:",omitempty"`
RealIP string `json:",omitempty"`
ClientAddr string `json:",omitempty"`
Endpoint string `json:",omitempty"`
Method string `json:",omitempty"`
RequestTime time.Time `json:",omitempty"`
ResponseTime time.Time `json:",omitempty"`
Errors []string
Requester int64 `json:",omitempty"`
Contacts []*core.AcmeURL `json:",omitempty"`
Extra map[string]interface{} `json:",omitempty"`
}
func (e *requestEvent) AddError(msg string, args ...interface{}) {
e.Errors = append(e.Errors, fmt.Sprintf(msg, args...))
}
type wfeHandlerFunc func(*requestEvent, http.ResponseWriter, *http.Request)
func (f wfeHandlerFunc) ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request) {
f(e, w, r)
}
type wfeHandler interface {
ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request)
}
type wfeTopHandler struct {
h wfeHandler
log *blog.AuditLogger
clk clock.Clock
}
func (t *wfeTopHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logEvent := &requestEvent{
ID: core.NewToken(),
RealIP: r.Header.Get("X-Real-IP"),
ClientAddr: getClientAddr(r),
Method: r.Method,
RequestTime: time.Now(),
Extra: make(map[string]interface{}, 0),
}
if r.URL != nil {
logEvent.Endpoint = r.URL.String()
}
defer t.logEvent(logEvent)
t.h.ServeHTTP(logEvent, w, r)
}
func (t *wfeTopHandler) logEvent(logEvent *requestEvent) {
logEvent.ResponseTime = t.clk.Now()
var msg string
if len(logEvent.Errors) != 0 {
msg = "Terminated request"
} else {
msg = "Successful request"
}
t.log.InfoObject(msg, logEvent)
}
// Comma-separated list of HTTP clients involved in making this
// request, starting with the original requestor and ending with the
// remote end of our TCP connection (which is typically our own
// proxy).
func getClientAddr(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return xff + "," + r.RemoteAddr
}
return r.RemoteAddr
}

View File

@ -19,6 +19,7 @@ import (
"time" "time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log" blog "github.com/letsencrypt/boulder/log"
@ -115,21 +116,6 @@ func statusCodeFromError(err interface{}) int {
} }
} }
type requestEvent struct {
ID string `json:",omitempty"`
RealIP string `json:",omitempty"`
ClientAddr string `json:",omitempty"`
Endpoint string `json:",omitempty"`
Method string `json:",omitempty"`
RequestTime time.Time `json:",omitempty"`
ResponseTime time.Time `json:",omitempty"`
Error string `json:",omitempty"`
Requester int64 `json:",omitempty"`
Contacts []*core.AcmeURL `json:",omitempty"`
Extra map[string]interface{} `json:",omitempty"`
}
// NewWebFrontEndImpl constructs a web service for Boulder // NewWebFrontEndImpl constructs a web service for Boulder
func NewWebFrontEndImpl(stats statsd.Statter) (WebFrontEndImpl, error) { func NewWebFrontEndImpl(stats statsd.Statter) (WebFrontEndImpl, error) {
logger := blog.GetAuditLogger() logger := blog.GetAuditLogger()
@ -173,7 +159,7 @@ func (mrw BodylessResponseWriter) Write(buf []byte) (int, error) {
// * Never send a body in response to a HEAD request. Anything // * Never send a body in response to a HEAD request. Anything
// written by the handler will be discarded if the method is HEAD. // written by the handler will be discarded if the method is HEAD.
// Also, all handlers that accept GET automatically accept HEAD. // Also, all handlers that accept GET automatically accept HEAD.
func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h func(http.ResponseWriter, *http.Request), methods ...string) { func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfeHandlerFunc, methods ...string) {
methodsMap := make(map[string]bool) methodsMap := make(map[string]bool)
for _, m := range methods { for _, m := range methods {
methodsMap[m] = true methodsMap[m] = true
@ -184,38 +170,41 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h fun
methodsMap["HEAD"] = true methodsMap["HEAD"] = true
} }
methodsStr := strings.Join(methods, ", ") methodsStr := strings.Join(methods, ", ")
mux.HandleFunc(pattern, func(response http.ResponseWriter, request *http.Request) { mux.Handle(pattern, &wfeTopHandler{
// We do not propagate errors here, because (1) they should be log: wfe.log,
// transient, and (2) they fail closed. clk: clock.Default(),
nonce, err := wfe.nonceService.Nonce() h: wfeHandlerFunc(func(e *requestEvent, response http.ResponseWriter, request *http.Request) {
if err == nil { // We do not propagate errors here, because (1) they should be
response.Header().Set("Replay-Nonce", nonce) // transient, and (2) they fail closed.
} nonce, err := wfe.nonceService.Nonce()
if err == nil {
response.Header().Set("Replay-Nonce", nonce)
}
switch request.Method { switch request.Method {
case "HEAD": case "HEAD":
// Whether or not we're sending a 405 error, // Whether or not we're sending a 405 error,
// we should comply with HTTP spec by not // we should comply with HTTP spec by not
// sending a body. // sending a body.
response = BodylessResponseWriter{response} response = BodylessResponseWriter{response}
case "OPTIONS": case "OPTIONS":
wfe.Options(response, request, methodsStr, methodsMap) wfe.Options(response, request, methodsStr, methodsMap)
return return
} }
if !methodsMap[request.Method] { if !methodsMap[request.Method] {
logEvent := wfe.populateRequestEvent(request) msg := "Method not allowed"
defer wfe.logRequestDetails(&logEvent) e.AddError(msg)
logEvent.Error = "Method not allowed" response.Header().Set("Allow", methodsStr)
response.Header().Set("Allow", methodsStr) wfe.sendError(response, msg, request.Method, http.StatusMethodNotAllowed)
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed) return
return }
}
wfe.setCORSHeaders(response, request, "") wfe.setCORSHeaders(response, request, "")
// Call the wrapped handler. // Call the wrapped handler.
h(response, request) h(e, response, request)
}),
}) })
} }
@ -259,29 +248,30 @@ func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) {
// We don't use our special HandleFunc for "/" because it matches everything, // We don't use our special HandleFunc for "/" because it matches everything,
// meaning we can wind up returning 405 when we mean to return 404. See // meaning we can wind up returning 405 when we mean to return 404. See
// https://github.com/letsencrypt/boulder/issues/717 // https://github.com/letsencrypt/boulder/issues/717
m.HandleFunc("/", wfe.Index) m.Handle("/", &wfeTopHandler{
log: wfe.log,
clk: clock.Default(),
h: wfeHandlerFunc(wfe.Index),
})
return m, nil return m, nil
} }
// Method implementations // Method implementations
// Index serves a simple identification page. It is not part of the ACME spec. // Index serves a simple identification page. It is not part of the ACME spec.
func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) Index(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
// http://golang.org/pkg/net/http/#example_ServeMux_Handle // http://golang.org/pkg/net/http/#example_ServeMux_Handle
// The "/" pattern matches everything, so we need to check // The "/" pattern matches everything, so we need to check
// that we're at the root here. // that we're at the root here.
if request.URL.Path != "/" { if request.URL.Path != "/" {
logEvent.Error = "Resource not found" logEvent.AddError("Resource not found")
http.NotFound(response, request) http.NotFound(response, request)
response.Header().Set("Content-Type", "application/problem+json") response.Header().Set("Content-Type", "application/problem+json")
return return
} }
if request.Method != "GET" { if request.Method != "GET" {
logEvent.Error = "Bad method" logEvent.AddError("Bad method")
response.Header().Set("Allow", "GET") response.Header().Set("Allow", "GET")
response.WriteHeader(http.StatusMethodNotAllowed) response.WriteHeader(http.StatusMethodNotAllowed)
return return
@ -309,7 +299,7 @@ func addCacheHeader(w http.ResponseWriter, age float64) {
// Directory is an HTTP request handler that simply provides the directory // Directory is an HTTP request handler that simply provides the directory
// object stored in the WFE's DirectoryJSON member. // object stored in the WFE's DirectoryJSON member.
func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) Directory(e *requestEvent, response http.ResponseWriter, request *http.Request) {
response.Header().Set("Content-Type", "application/json") response.Header().Set("Content-Type", "application/json")
response.Write(wfe.DirectoryJSON) response.Write(wfe.DirectoryJSON)
} }
@ -326,39 +316,44 @@ const (
) )
// verifyPOST reads and parses the request body, looks up the Registration // verifyPOST reads and parses the request body, looks up the Registration
// corresponding to its JWK, verifies the JWS signature, // corresponding to its JWK, verifies the JWS signature, checks that the
// checks that the resource field is present and correct in the JWS protected // resource field is present and correct in the JWS protected header, and
// header, and returns the JWS payload bytes, the key used to verify, and the // returns the JWS payload bytes, the key used to verify, and the corresponding
// corresponding Registration (or error). // Registration (or error). If regCheck is false, verifyPOST will still try to
// If regCheck is false, verifyPOST will still try to look up a registration // look up a registration object, and will return it if found. However, if no
// object, and will return it if found. However, if no registration object is // registration object is found, verifyPOST will attempt to verify the JWS using
// found, verifyPOST will attempt to verify the JWS using the key in the JWS // the key in the JWS headers, and return the key plus a dummy registration if
// headers, and return the key plus a dummy registration if successful. If a // successful. If a caller passes regCheck = false, it should plan on validating
// caller passes regCheck = false, it should plan on validating the key itself. // the key itself. verifyPOST also appends its errors to requestEvent.Errors so
func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) { // code calling it does not need to if they imediately return a response to the
// user.
func (wfe *WebFrontEndImpl) verifyPOST(e *requestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) {
var err error var err error
// TODO: We should return a pointer to a registration, which can be nil, // TODO: We should return a pointer to a registration, which can be nil,
// rather the a registration value with a sentinel value. // rather the a registration value with a sentinel value.
// https://github.com/letsencrypt/boulder/issues/877 // https://github.com/letsencrypt/boulder/issues/877
reg := core.Registration{ID: -1} reg := core.Registration{ID: 0}
if _, ok := request.Header["Content-Length"]; !ok { if _, ok := request.Header["Content-Length"]; !ok {
err = core.LengthRequiredError("Content-Length header is required for POST.") err = core.LengthRequiredError("Content-Length header is required for POST.")
wfe.log.Debug(err.Error()) wfe.stats.Inc("WFE.HTTP.ClientErrors.LengthRequiredError", 1, 1.0)
e.AddError("missing Content-Length header on POST")
return nil, nil, reg, err return nil, nil, reg, err
} }
// Read body // Read body
if request.Body == nil { if request.Body == nil {
err = core.MalformedRequestError("No body on POST") err = core.MalformedRequestError("No body on POST")
wfe.log.Debug(err.Error()) wfe.stats.Inc("WFE.Errors.NoPOSTBody", 1, 1.0)
e.AddError("no body on POST")
return nil, nil, reg, err return nil, nil, reg, err
} }
bodyBytes, err := ioutil.ReadAll(request.Body) bodyBytes, err := ioutil.ReadAll(request.Body)
if err != nil { if err != nil {
err = core.InternalServerError(err.Error()) err = core.InternalServerError("unable to read request body")
wfe.log.Debug(err.Error()) wfe.stats.Inc("WFE.Errors.UnableToReadRequestBody", 1, 1.0)
e.AddError("unable to read request body")
return nil, nil, reg, err return nil, nil, reg, err
} }
@ -367,7 +362,8 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
parsedJws, err := jose.ParseSigned(body) parsedJws, err := jose.ParseSigned(body)
if err != nil { if err != nil {
puberr := core.SignatureValidationError("Parse error reading JWS") puberr := core.SignatureValidationError("Parse error reading JWS")
wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) wfe.stats.Inc("WFE.Errors.UnableToParseJWS", 1, 1.0)
e.AddError("could not JSON parse body into JWS: %s", err)
return nil, nil, reg, puberr return nil, nil, reg, puberr
} }
@ -378,19 +374,22 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
// *anyway*, so it could always lie about what key was used by faking // *anyway*, so it could always lie about what key was used by faking
// the signature itself. // the signature itself.
if len(parsedJws.Signatures) > 1 { if len(parsedJws.Signatures) > 1 {
err = core.SignatureValidationError("Too many signatures on POST") err = core.SignatureValidationError("Too many signatures in POST body")
wfe.log.Debug(err.Error()) wfe.stats.Inc("WFE.Errors.TooManyJWSSignaturesInPOST", 1, 1.0)
e.AddError("too many signatures in POST body: %d", len(parsedJws.Signatures))
return nil, nil, reg, err return nil, nil, reg, err
} }
if len(parsedJws.Signatures) == 0 { if len(parsedJws.Signatures) == 0 {
err = core.SignatureValidationError("POST JWS not signed") err = core.SignatureValidationError("POST JWS not signed")
wfe.log.Debug(err.Error()) wfe.stats.Inc("WFE.Errors.JWSNotSignedInPOST", 1, 1.0)
e.AddError("no signatures in POST body")
return nil, nil, reg, err return nil, nil, reg, err
} }
submittedKey := parsedJws.Signatures[0].Header.JsonWebKey submittedKey := parsedJws.Signatures[0].Header.JsonWebKey
if submittedKey == nil { if submittedKey == nil {
err = core.SignatureValidationError("No JWK in JWS header") err = core.SignatureValidationError("No JWK in JWS header")
wfe.log.Debug(err.Error()) wfe.stats.Inc("WFE.Errors.NoJWKInJWSSignatureHeader", 1, 1.0)
e.AddError("no JWK in JWS signature header in POST body")
return nil, nil, reg, err return nil, nil, reg, err
} }
@ -404,34 +403,46 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
// are "good". But when we are verifying against any submitted key, we want // are "good". But when we are verifying against any submitted key, we want
// to check its quality before doing the verify. // to check its quality before doing the verify.
if err = core.GoodKey(submittedKey.Key); err != nil { if err = core.GoodKey(submittedKey.Key); err != nil {
wfe.stats.Inc("WFE.Errors.JWKRejectedByGoodKey", 1, 1.0)
e.AddError("JWK in request was rejected by GoodKey: %s", err)
return nil, nil, reg, err return nil, nil, reg, err
} }
key = submittedKey key = submittedKey
} else if err != nil { } else if err != nil {
// For all other errors, or if regCheck is true, return error immediately. // For all other errors, or if regCheck is true, return error immediately.
wfe.stats.Inc("WFE.Errors.UnableToGetRegistrationByKey", 1, 1.0)
e.AddError("unable to fetch registration by the given JWK: %s", err)
return nil, nil, reg, err return nil, nil, reg, err
} else { } else {
// If the lookup was successful, use that key. // If the lookup was successful, use that key.
key = &reg.Key key = &reg.Key
e.Requester = reg.ID
e.Contacts = reg.Contact
} }
payload, header, err := parsedJws.Verify(key) payload, header, err := parsedJws.Verify(key)
if err != nil { if err != nil {
puberr := core.SignatureValidationError("JWS verification error") puberr := core.SignatureValidationError("JWS verification error")
wfe.log.Debug(string(body)) wfe.stats.Inc("WFE.Errors.JWSVerificationFailed", 1, 1.0)
wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) n := len(body)
if n > 100 {
n = 100
}
e.AddError("verification of JWS with the JWK failed: %v; body: %s", err, body[:n])
return nil, nil, reg, puberr return nil, nil, reg, puberr
} }
// Check that the request has a known anti-replay nonce // Check that the request has a known anti-replay nonce
// i.e., Nonce is in protected header and // i.e., Nonce is in protected header and
if err != nil || len(header.Nonce) == 0 { if err != nil || len(header.Nonce) == 0 {
wfe.stats.Inc("WFE.Errors.JWSMissingNonce", 1, 1.0)
e.AddError("JWS is missing an anti-replay nonce")
err = core.SignatureValidationError("JWS has no anti-replay nonce") err = core.SignatureValidationError("JWS has no anti-replay nonce")
wfe.log.Debug(err.Error())
return nil, nil, reg, err return nil, nil, reg, err
} else if !wfe.nonceService.Valid(header.Nonce) { } else if !wfe.nonceService.Valid(header.Nonce) {
wfe.stats.Inc("WFE.Errors.JWSInvalidNonce", 1, 1.0)
e.AddError("JWS has an invalid anti-replay nonce")
err = core.SignatureValidationError(fmt.Sprintf("JWS has invalid anti-replay nonce")) err = core.SignatureValidationError(fmt.Sprintf("JWS has invalid anti-replay nonce"))
wfe.log.Debug(err.Error())
return nil, nil, reg, err return nil, nil, reg, err
} }
@ -441,17 +452,20 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, res
} }
err = json.Unmarshal([]byte(payload), &parsedRequest) err = json.Unmarshal([]byte(payload), &parsedRequest)
if err != nil { if err != nil {
wfe.stats.Inc("WFE.Errors.UnparsableJWSPayload", 1, 1.0)
e.AddError("unable to JSON parse resource from JWS payload: %s", err)
puberr := core.SignatureValidationError("Request payload did not parse as JSON") puberr := core.SignatureValidationError("Request payload did not parse as JSON")
wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error()))
return nil, nil, reg, puberr return nil, nil, reg, puberr
} }
if parsedRequest.Resource == "" { if parsedRequest.Resource == "" {
wfe.stats.Inc("WFE.Errors.NoResourceInJWSPayload", 1, 1.0)
e.AddError("JWS request payload does not specifiy a resource")
err = core.MalformedRequestError("Request payload does not specify a resource") err = core.MalformedRequestError("Request payload does not specify a resource")
wfe.log.Debug(err.Error())
return nil, nil, reg, err return nil, nil, reg, err
} else if resource != core.AcmeResource(parsedRequest.Resource) { } else if resource != core.AcmeResource(parsedRequest.Resource) {
err = core.MalformedRequestError(fmt.Sprintf("Request payload has invalid resource: %s != %s", parsedRequest.Resource, resource)) wfe.stats.Inc("WFE.Errors.MismatchedResourceInJWSPayload", 1, 1.0)
wfe.log.Debug(err.Error()) e.AddError("JWS request payload does not match resource")
err = core.MalformedRequestError(fmt.Sprintf("JWS resource payload does not match the HTTP resource: %s != %s", parsedRequest.Resource, resource))
return nil, nil, reg, err return nil, nil, reg, err
} }
@ -517,34 +531,33 @@ func link(url, relation string) string {
} }
// NewRegistration is used by clients to submit a new registration/account // NewRegistration is used by clients to submit a new registration/account
func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
body, key, _, err := wfe.verifyPOST(request, false, core.ResourceNewReg) body, key, _, err := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg)
if err != nil { if err != nil {
logEvent.Error = err.Error() // verifyPOST handles its own setting of logEvent.Errors
wfe.sendError(response, malformedJWS, err, statusCodeFromError(err)) wfe.sendError(response, malformedJWS, err, statusCodeFromError(err))
return return
} }
if existingReg, err := wfe.SA.GetRegistrationByKey(*key); err == nil { if existingReg, err := wfe.SA.GetRegistrationByKey(*key); err == nil {
logEvent.Error = "Registration key is already in use" logEvent.AddError("Registration key is already in use")
response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID)) response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID))
wfe.sendError(response, logEvent.Error, nil, http.StatusConflict) wfe.sendError(response, "Registration key is already in use", nil, http.StatusConflict)
return return
} }
var init core.Registration var init core.Registration
err = json.Unmarshal(body, &init) err = json.Unmarshal(body, &init)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to unmarshal Registration: %s", err)
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest) wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
return return
} }
if len(init.Agreement) > 0 && init.Agreement != wfe.SubscriberAgreementURL { if len(init.Agreement) > 0 && init.Agreement != wfe.SubscriberAgreementURL {
logEvent.Error = fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL) msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL)
wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest) logEvent.AddError(msg)
wfe.sendError(response, msg, nil, http.StatusBadRequest)
return return
} }
init.Key = *key init.Key = *key
@ -554,15 +567,15 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
if err == nil { if err == nil {
init.InitialIP = net.ParseIP(host) init.InitialIP = net.ParseIP(host)
} else { } else {
logEvent.Error = "Couldn't parse RemoteAddr" logEvent.AddError("Couldn't parse RemoteAddr: %s", request.RemoteAddr)
wfe.sendError(response, logEvent.Error, nil, http.StatusInternalServerError) wfe.sendError(response, "couldn't parse the remote (that is, the client's) address", nil, http.StatusInternalServerError)
return return
} }
} }
reg, err := wfe.RA.NewRegistration(init) reg, err := wfe.RA.NewRegistration(init)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to create new registration: %s", err)
wfe.sendError(response, "Error creating new registration", err, statusCodeFromError(err)) wfe.sendError(response, "Error creating new registration", err, statusCodeFromError(err))
return return
} }
@ -574,7 +587,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID) regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID)
responseBody, err := json.Marshal(reg) responseBody, err := json.Marshal(reg)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to marsh registration: %s", err)
// StatusInternalServerError because we just created this registration, it should be OK. // StatusInternalServerError because we just created this registration, it should be OK.
wfe.sendError(response, "Error marshaling registration", err, http.StatusInternalServerError) wfe.sendError(response, "Error marshaling registration", err, http.StatusInternalServerError)
return return
@ -592,13 +605,10 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
} }
// NewAuthorization is used by clients to submit a new ID Authorization // NewAuthorization is used by clients to submit a new ID Authorization
func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request) body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewAuthz)
defer wfe.logRequestDetails(&logEvent)
body, _, currReg, err := wfe.verifyPOST(request, true, core.ResourceNewAuthz)
if err != nil { if err != nil {
logEvent.Error = err.Error() // verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS respMsg := malformedJWS
respCode := statusCodeFromError(err) respCode := statusCodeFromError(err)
if _, ok := err.(core.NoSuchRegistrationError); ok { if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -608,20 +618,18 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
wfe.sendError(response, respMsg, err, respCode) wfe.sendError(response, respMsg, err, respCode)
return return
} }
logEvent.Requester = currReg.ID
logEvent.Contacts = currReg.Contact
// Any version of the agreement is acceptable here. Version match is enforced in // Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen // wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update. // by mailing subscribers and don't require a registration update.
if currReg.Agreement == "" { if currReg.Agreement == "" {
logEvent.Error = "Must agree to subscriber agreement before any further actions" logEvent.AddError("Must agree to subscriber agreement before any further actions")
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden) wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
return return
} }
var init core.Authorization var init core.Authorization
if err = json.Unmarshal(body, &init); err != nil { if err = json.Unmarshal(body, &init); err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to JSON unmarshal Authorization: %s", err)
wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest) wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest)
return return
} }
@ -630,7 +638,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
// Create new authz and return // Create new authz and return
authz, err := wfe.RA.NewAuthorization(init, currReg.ID) authz, err := wfe.RA.NewAuthorization(init, currReg.ID)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to create new authz: %s", err)
wfe.sendError(response, "Error creating new authz", err, statusCodeFromError(err)) wfe.sendError(response, "Error creating new authz", err, statusCodeFromError(err))
return return
} }
@ -641,7 +649,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
wfe.prepAuthorizationForDisplay(&authz) wfe.prepAuthorizationForDisplay(&authz)
responseBody, err := json.Marshal(authz) responseBody, err := json.Marshal(authz)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to marshal authz: %s", err)
// StatusInternalServerError because we generated the authz, it should be OK // StatusInternalServerError because we generated the authz, it should be OK
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError) wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
return return
@ -652,40 +660,36 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
response.Header().Set("Content-Type", "application/json") response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusCreated) response.WriteHeader(http.StatusCreated)
if _, err = response.Write(responseBody); err != nil { if _, err = response.Write(responseBody); err != nil {
logEvent.Error = err.Error() logEvent.AddError(err.Error())
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
} }
} }
// RevokeCertificate is used by clients to request the revocation of a cert. // RevokeCertificate is used by clients to request the revocation of a cert.
func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
// We don't ask verifyPOST to verify there is a correponding registration, // We don't ask verifyPOST to verify there is a correponding registration,
// because anyone with the right private key can revoke a certificate. // because anyone with the right private key can revoke a certificate.
body, requestKey, registration, err := wfe.verifyPOST(request, false, core.ResourceRevokeCert) body, requestKey, registration, err := wfe.verifyPOST(logEvent, request, false, core.ResourceRevokeCert)
if err != nil { if err != nil {
logEvent.Error = err.Error() // verifyPOST handles its own setting of logEvent.Errors
wfe.sendError(response, malformedJWS, err, statusCodeFromError(err)) wfe.sendError(response, malformedJWS, err, statusCodeFromError(err))
return return
} }
logEvent.Requester = registration.ID
logEvent.Contacts = registration.Contact
type RevokeRequest struct { type RevokeRequest struct {
CertificateDER core.JSONBuffer `json:"certificate"` CertificateDER core.JSONBuffer `json:"certificate"`
} }
var revokeRequest RevokeRequest var revokeRequest RevokeRequest
if err = json.Unmarshal(body, &revokeRequest); err != nil { if err = json.Unmarshal(body, &revokeRequest); err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to JSON unmarshal RevokeRequest: %s", err)
wfe.log.Debug(fmt.Sprintf("Couldn't unmarshal in revoke request %s", string(body))) wfe.log.Debug(fmt.Sprintf("Couldn't unmarshal in revoke request %s", string(body)))
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest) wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return return
} }
providedCert, err := x509.ParseCertificate(revokeRequest.CertificateDER) providedCert, err := x509.ParseCertificate(revokeRequest.CertificateDER)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to parse revoke certificate DER: %s", err)
wfe.log.Debug("Couldn't parse cert in revoke request.") wfe.log.Debug("Couldn't parse cert in revoke request.")
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest) wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return return
@ -700,7 +704,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
} }
parsedCertificate, err := x509.ParseCertificate(cert.DER) parsedCertificate, err := x509.ParseCertificate(cert.DER)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to parse certificate DER: %s", err)
// InternalServerError because this is a failure to decode from our DB. // InternalServerError because this is a failure to decode from our DB.
wfe.sendError(response, "Invalid certificate", err, http.StatusInternalServerError) wfe.sendError(response, "Invalid certificate", err, http.StatusInternalServerError)
return return
@ -712,25 +716,24 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
certStatus, err := wfe.SA.GetCertificateStatus(serial) certStatus, err := wfe.SA.GetCertificateStatus(serial)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to get certificate status: %s", err)
wfe.sendError(response, "Certificate status not yet available", err, http.StatusNotFound) wfe.sendError(response, "Certificate status not yet available", err, http.StatusNotFound)
return return
} }
logEvent.Extra["CertificateStatus"] = certStatus.Status logEvent.Extra["CertificateStatus"] = certStatus.Status
if certStatus.Status == core.OCSPStatusRevoked { if certStatus.Status == core.OCSPStatusRevoked {
logEvent.Error = "Certificate already revoked" logEvent.AddError("Certificate already revoked: %#v", serial)
wfe.sendError(response, logEvent.Error, "", http.StatusConflict) wfe.sendError(response, "Certificate already revoked", "", http.StatusConflict)
return return
} }
// TODO: Implement method of revocation by authorizations on account. // TODO: Implement method of revocation by authorizations on account.
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) || if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
registration.ID == cert.RegistrationID) { registration.ID == cert.RegistrationID) {
logEvent.Error = "Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it." logEvent.AddError("Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it.")
wfe.log.Debug("Key mismatch for revoke")
wfe.sendError(response, wfe.sendError(response,
logEvent.Error, "Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it.",
requestKey, requestKey,
http.StatusForbidden) http.StatusForbidden)
return return
@ -739,7 +742,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
// Use revocation code 0, meaning "unspecified" // Use revocation code 0, meaning "unspecified"
err = wfe.RA.RevokeCertificateWithReg(*parsedCertificate, 0, registration.ID) err = wfe.RA.RevokeCertificateWithReg(*parsedCertificate, 0, registration.ID)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("failed to revoke certificate: %s", err)
wfe.sendError(response, "Failed to revoke certificate", err, statusCodeFromError(err)) wfe.sendError(response, "Failed to revoke certificate", err, statusCodeFromError(err))
} else { } else {
wfe.log.Debug(fmt.Sprintf("Revoked %v", serial)) wfe.log.Debug(fmt.Sprintf("Revoked %v", serial))
@ -762,13 +765,10 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
// NewCertificate is used by clients to request the issuance of a cert for an // NewCertificate is used by clients to request the issuance of a cert for an
// authorized identifier. // authorized identifier.
func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request) body, _, reg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewCert)
defer wfe.logRequestDetails(&logEvent)
body, _, reg, err := wfe.verifyPOST(request, true, core.ResourceNewCert)
if err != nil { if err != nil {
logEvent.Error = err.Error() // verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS respMsg := malformedJWS
respCode := statusCodeFromError(err) respCode := statusCodeFromError(err)
if _, ok := err.(core.NoSuchRegistrationError); ok { if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -778,20 +778,18 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
wfe.sendError(response, respMsg, err, respCode) wfe.sendError(response, respMsg, err, respCode)
return return
} }
logEvent.Requester = reg.ID
logEvent.Contacts = reg.Contact
// Any version of the agreement is acceptable here. Version match is enforced in // Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen // wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update. // by mailing subscribers and don't require a registration update.
if reg.Agreement == "" { if reg.Agreement == "" {
logEvent.Error = "Must agree to subscriber agreement before any further actions" logEvent.AddError("Must agree to subscriber agreement before any further actions")
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden) wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
return return
} }
var certificateRequest core.CertificateRequest var certificateRequest core.CertificateRequest
if err = json.Unmarshal(body, &certificateRequest); err != nil { if err = json.Unmarshal(body, &certificateRequest); err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to JSON unmarshal CertificateRequest: %s", err)
wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest) wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest)
return return
} }
@ -803,7 +801,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// a bad key from the client is just a malformed request and doesn't need to // a bad key from the client is just a malformed request and doesn't need to
// be audited. // be audited.
if err = core.GoodKey(certificateRequest.CSR.PublicKey); err != nil { if err = core.GoodKey(certificateRequest.CSR.PublicKey); err != nil {
logEvent.Error = err.Error() logEvent.AddError("CSR public key failed GoodKey: %s", err)
wfe.sendError(response, "Invalid key in certificate request", err, http.StatusBadRequest) wfe.sendError(response, "Invalid key in certificate request", err, http.StatusBadRequest)
return return
} }
@ -819,7 +817,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// RA for secondary validation. // RA for secondary validation.
cert, err := wfe.RA.NewCertificate(certificateRequest, reg.ID) cert, err := wfe.RA.NewCertificate(certificateRequest, reg.ID)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to create new cert: %s", err)
wfe.sendError(response, "Error creating new cert", err, statusCodeFromError(err)) wfe.sendError(response, "Error creating new cert", err, statusCodeFromError(err))
return return
} }
@ -830,7 +828,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// enumerate and mirror our certificates. // enumerate and mirror our certificates.
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER)) parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to parse certificate: %s", err)
wfe.sendError(response, wfe.sendError(response,
"Error creating new cert", err, "Error creating new cert", err,
http.StatusBadRequest) http.StatusBadRequest)
@ -845,7 +843,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
response.Header().Set("Content-Type", "application/pkix-cert") response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusCreated) response.WriteHeader(http.StatusCreated)
if _, err = response.Write(cert.DER); err != nil { if _, err = response.Write(cert.DER); err != nil {
logEvent.Error = err.Error() logEvent.AddError(err.Error())
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
} }
} }
@ -853,10 +851,9 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
// Challenge handles POST requests to challenge URLs. Such requests are clients' // Challenge handles POST requests to challenge URLs. Such requests are clients'
// responses to the server's challenges. // responses to the server's challenges.
func (wfe *WebFrontEndImpl) Challenge( func (wfe *WebFrontEndImpl) Challenge(
logEvent *requestEvent,
response http.ResponseWriter, response http.ResponseWriter,
request *http.Request) { request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
notFound := func() { notFound := func() {
wfe.sendError(response, "No such registration", request.URL.Path, http.StatusNotFound) wfe.sendError(response, "No such registration", request.URL.Path, http.StatusNotFound)
@ -900,10 +897,10 @@ func (wfe *WebFrontEndImpl) Challenge(
switch request.Method { switch request.Method {
case "GET", "HEAD": case "GET", "HEAD":
wfe.getChallenge(response, request, authz, &challenge, &logEvent) wfe.getChallenge(response, request, authz, &challenge, logEvent)
case "POST": case "POST":
wfe.postChallenge(response, request, authz, challengeIndex, &logEvent) wfe.postChallenge(response, request, authz, challengeIndex, logEvent)
} }
} }
@ -941,7 +938,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
jsonReply, err := json.Marshal(challenge) jsonReply, err := json.Marshal(challenge)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to marshal challenge: %s", err)
// InternalServerError because this is a failure to decode data passed in // InternalServerError because this is a failure to decode data passed in
// by the caller, which got it from the DB. // by the caller, which got it from the DB.
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError) wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
@ -955,7 +952,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
if _, err := response.Write(jsonReply); err != nil { if _, err := response.Write(jsonReply); err != nil {
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
logEvent.Error = err.Error() logEvent.AddError(err.Error())
return return
} }
} }
@ -966,9 +963,9 @@ func (wfe *WebFrontEndImpl) postChallenge(
authz core.Authorization, authz core.Authorization,
challengeIndex int, challengeIndex int,
logEvent *requestEvent) { logEvent *requestEvent) {
body, _, currReg, err := wfe.verifyPOST(request, true, core.ResourceChallenge) body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceChallenge)
if err != nil { if err != nil {
logEvent.Error = err.Error() // verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS respMsg := malformedJWS
respCode := http.StatusBadRequest respCode := http.StatusBadRequest
if _, ok := err.(core.NoSuchRegistrationError); ok { if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -978,30 +975,28 @@ func (wfe *WebFrontEndImpl) postChallenge(
wfe.sendError(response, respMsg, err, respCode) wfe.sendError(response, respMsg, err, respCode)
return return
} }
logEvent.Requester = currReg.ID
logEvent.Contacts = currReg.Contact
// Any version of the agreement is acceptable here. Version match is enforced in // Any version of the agreement is acceptable here. Version match is enforced in
// wfe.Registration when agreeing the first time. Agreement updates happen // wfe.Registration when agreeing the first time. Agreement updates happen
// by mailing subscribers and don't require a registration update. // by mailing subscribers and don't require a registration update.
if currReg.Agreement == "" { if currReg.Agreement == "" {
logEvent.Error = "Must agree to subscriber agreement before any further actions" logEvent.AddError("Registration didn't agree to subscriber agreement before any further actions")
wfe.sendError(response, logEvent.Error, nil, http.StatusForbidden) wfe.sendError(response, "Registration didn't agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
return return
} }
// Check that the registration ID matching the key used matches // Check that the registration ID matching the key used matches
// the registration ID on the authz object // the registration ID on the authz object
if currReg.ID != authz.RegistrationID { if currReg.ID != authz.RegistrationID {
logEvent.Error = fmt.Sprintf("User: %v != Authorization: %v", currReg.ID, authz.RegistrationID) logEvent.AddError("User registration id: %d != Authorization registration id: %v", currReg.ID, authz.RegistrationID)
wfe.sendError(response, "User registration ID doesn't match registration ID in authorization", wfe.sendError(response, "User registration ID doesn't match registration ID in authorization",
logEvent.Error, "",
http.StatusForbidden) http.StatusForbidden)
return return
} }
var challengeUpdate core.Challenge var challengeUpdate core.Challenge
if err = json.Unmarshal(body, &challengeUpdate); err != nil { if err = json.Unmarshal(body, &challengeUpdate); err != nil {
logEvent.Error = err.Error() logEvent.AddError("error JSON unmarshalling challenge response: %s", err)
wfe.sendError(response, "Error unmarshaling challenge response", err, http.StatusBadRequest) wfe.sendError(response, "Error unmarshaling challenge response", err, http.StatusBadRequest)
return return
} }
@ -1009,7 +1004,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
// Ask the RA to update this authorization // Ask the RA to update this authorization
updatedAuthorization, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeUpdate) updatedAuthorization, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeUpdate)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to update challenge: %s", err)
wfe.sendError(response, "Unable to update challenge", err, statusCodeFromError(err)) wfe.sendError(response, "Unable to update challenge", err, statusCodeFromError(err))
return return
} }
@ -1019,7 +1014,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
wfe.prepChallengeForDisplay(authz, &challenge) wfe.prepChallengeForDisplay(authz, &challenge)
jsonReply, err := json.Marshal(challenge) jsonReply, err := json.Marshal(challenge)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("failed to marshal challenge: %s", err)
// StatusInternalServerError because we made the challenges, they should be OK // StatusInternalServerError because we made the challenges, they should be OK
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError) wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
return return
@ -1031,20 +1026,18 @@ func (wfe *WebFrontEndImpl) postChallenge(
response.Header().Add("Link", link(authzURL, "up")) response.Header().Add("Link", link(authzURL, "up"))
response.WriteHeader(http.StatusAccepted) response.WriteHeader(http.StatusAccepted)
if _, err = response.Write(jsonReply); err != nil { if _, err = response.Write(jsonReply); err != nil {
logEvent.Error = err.Error() logEvent.AddError(err.Error())
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
return return
} }
} }
// Registration is used by a client to submit an update to their registration. // Registration is used by a client to submit an update to their registration.
func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
body, _, currReg, err := wfe.verifyPOST(request, true, core.ResourceRegistration) body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration)
if err != nil { if err != nil {
logEvent.Error = err.Error() // verifyPOST handles its own setting of logEvent.Errors
respMsg := malformedJWS respMsg := malformedJWS
respCode := statusCodeFromError(err) respCode := statusCodeFromError(err)
if _, ok := err.(core.NoSuchRegistrationError); ok { if _, ok := err.(core.NoSuchRegistrationError); ok {
@ -1054,38 +1047,37 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
wfe.sendError(response, respMsg, err, respCode) wfe.sendError(response, respMsg, err, respCode)
return return
} }
logEvent.Requester = currReg.ID
logEvent.Contacts = currReg.Contact
// Requests to this handler should have a path that leads to a known // Requests to this handler should have a path that leads to a known
// registration // registration
idStr := parseIDFromPath(request.URL.Path) idStr := parseIDFromPath(request.URL.Path)
id, err := strconv.ParseInt(idStr, 10, 64) id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("registration ID must be an integer, was %#v", idStr)
wfe.sendError(response, "Registration ID must be an integer", err, http.StatusBadRequest) wfe.sendError(response, "Registration ID must be an integer", err, http.StatusBadRequest)
return return
} else if id <= 0 { } else if id <= 0 {
logEvent.Error = "Registration ID must be a positive non-zero integer" logEvent.AddError("Registration ID must be a positive non-zero integer, was %d", id)
wfe.sendError(response, logEvent.Error, id, http.StatusBadRequest) wfe.sendError(response, "Registration ID must be a positive non-zero integer", id, http.StatusBadRequest)
return return
} else if id != currReg.ID { } else if id != currReg.ID {
logEvent.Error = "Request signing key did not match registration key" logEvent.AddError("Request signing key did not match registration key: %d != %d", id, currReg.ID)
wfe.sendError(response, logEvent.Error, "", http.StatusForbidden) wfe.sendError(response, "Request signing key did not match registration key", "", http.StatusForbidden)
return return
} }
var update core.Registration var update core.Registration
err = json.Unmarshal(body, &update) err = json.Unmarshal(body, &update)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to JSON parse registration: %s", err)
wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest) wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest)
return return
} }
if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL { if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL {
logEvent.Error = fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL) msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL)
wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest) logEvent.AddError(msg)
wfe.sendError(response, msg, nil, http.StatusBadRequest)
return return
} }
@ -1098,14 +1090,14 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
// Ask the RA to update this authorization. // Ask the RA to update this authorization.
updatedReg, err := wfe.RA.UpdateRegistration(currReg, update) updatedReg, err := wfe.RA.UpdateRegistration(currReg, update)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to update registration: %s", err)
wfe.sendError(response, "Unable to update registration", err, statusCodeFromError(err)) wfe.sendError(response, "Unable to update registration", err, statusCodeFromError(err))
return return
} }
jsonReply, err := json.Marshal(updatedReg) jsonReply, err := json.Marshal(updatedReg)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to marshal updated registration: %s", err)
// StatusInternalServerError because we just generated the reg, it should be OK // StatusInternalServerError because we just generated the reg, it should be OK
wfe.sendError(response, "Failed to marshal registration", err, http.StatusInternalServerError) wfe.sendError(response, "Failed to marshal registration", err, http.StatusInternalServerError)
return return
@ -1121,14 +1113,12 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
// Authorization is used by clients to submit an update to one of their // Authorization is used by clients to submit an update to one of their
// authorizations. // authorizations.
func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
// Requests to this handler should have a path that leads to a known authz // Requests to this handler should have a path that leads to a known authz
id := parseIDFromPath(request.URL.Path) id := parseIDFromPath(request.URL.Path)
authz, err := wfe.SA.GetAuthorization(id) authz, err := wfe.SA.GetAuthorization(id)
if err != nil { if err != nil {
logEvent.AddError("No such authorization at id %s", id)
wfe.sendError(response, wfe.sendError(response,
"Unable to find authorization", err, "Unable to find authorization", err,
http.StatusNotFound) http.StatusNotFound)
@ -1144,16 +1134,16 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
jsonReply, err := json.Marshal(authz) jsonReply, err := json.Marshal(authz)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("Failed to JSON marshal authz: %s", err)
// InternalServerError because this is a failure to decode from our DB. // InternalServerError because this is a failure to decode from our DB.
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError) wfe.sendError(response, "Failed to JSON marshal authz", err, http.StatusInternalServerError)
return return
} }
response.Header().Add("Link", link(wfe.NewCert, "next")) response.Header().Add("Link", link(wfe.NewCert, "next"))
response.Header().Set("Content-Type", "application/json") response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusOK) response.WriteHeader(http.StatusOK)
if _, err = response.Write(jsonReply); err != nil { if _, err = response.Write(jsonReply); err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to write authorization update response: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
} }
} }
@ -1162,32 +1152,29 @@ var allHex = regexp.MustCompile("^[0-9a-f]+$")
// Certificate is used by clients to request a copy of their current certificate, or to // Certificate is used by clients to request a copy of their current certificate, or to
// request a reissuance of the certificate. // request a reissuance of the certificate.
func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) Certificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
path := request.URL.Path path := request.URL.Path
// Certificate paths consist of the CertBase path, plus exactly sixteen hex // Certificate paths consist of the CertBase path, plus exactly sixteen hex
// digits. // digits.
if !strings.HasPrefix(path, CertPath) { if !strings.HasPrefix(path, CertPath) {
logEvent.Error = "Certificate not found" logEvent.AddError("this request path should not have gotten to Certificate: %#v is not a prefix of %#v", path, CertPath)
wfe.sendError(response, logEvent.Error, path, http.StatusNotFound) wfe.sendError(response, "Certificate not found", path, http.StatusNotFound)
addNoCacheHeader(response) addNoCacheHeader(response)
return return
} }
serial := path[len(CertPath):] serial := path[len(CertPath):]
if !core.ValidSerial(serial) { if !core.ValidSerial(serial) {
logEvent.Error = "Certificate not found" logEvent.AddError("certificate serial provided was not valid: %s", serial)
wfe.sendError(response, logEvent.Error, serial, http.StatusNotFound) wfe.sendError(response, "Certificate not found", serial, http.StatusNotFound)
addNoCacheHeader(response) addNoCacheHeader(response)
return return
} }
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
logEvent.Extra["RequestedSerial"] = serial logEvent.Extra["RequestedSerial"] = serial
cert, err := wfe.SA.GetCertificate(serial) cert, err := wfe.SA.GetCertificate(serial)
if err != nil { if err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to get certificate by serial id %#v: %s", serial, err)
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") { if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict) wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
} else { } else {
@ -1204,7 +1191,7 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
response.Header().Add("Link", link(IssuerPath, "up")) response.Header().Add("Link", link(IssuerPath, "up"))
response.WriteHeader(http.StatusOK) response.WriteHeader(http.StatusOK)
if _, err = response.Write(cert.DER); err != nil { if _, err = response.Write(cert.DER); err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to write new certificate response: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
} }
return return
@ -1212,39 +1199,30 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
// Terms is used by the client to obtain the current Terms of Service / // Terms is used by the client to obtain the current Terms of Service /
// Subscriber Agreement to which the subscriber must agree. // Subscriber Agreement to which the subscriber must agree.
func (wfe *WebFrontEndImpl) Terms(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) Terms(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
http.Redirect(response, request, wfe.SubscriberAgreementURL, http.StatusFound) http.Redirect(response, request, wfe.SubscriberAgreementURL, http.StatusFound)
} }
// Issuer obtains the issuer certificate used by this instance of Boulder. // Issuer obtains the issuer certificate used by this instance of Boulder.
func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) Issuer(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
addCacheHeader(response, wfe.IssuerCacheDuration.Seconds()) addCacheHeader(response, wfe.IssuerCacheDuration.Seconds())
// TODO Content negotiation // TODO Content negotiation
response.Header().Set("Content-Type", "application/pkix-cert") response.Header().Set("Content-Type", "application/pkix-cert")
response.WriteHeader(http.StatusOK) response.WriteHeader(http.StatusOK)
if _, err := response.Write(wfe.IssuerCert); err != nil { if _, err := response.Write(wfe.IssuerCert); err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to write issuer certificate response: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
} }
} }
// BuildID tells the requestor what build we're running. // BuildID tells the requestor what build we're running.
func (wfe *WebFrontEndImpl) BuildID(response http.ResponseWriter, request *http.Request) { func (wfe *WebFrontEndImpl) BuildID(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
logEvent := wfe.populateRequestEvent(request)
defer wfe.logRequestDetails(&logEvent)
response.Header().Set("Content-Type", "text/plain") response.Header().Set("Content-Type", "text/plain")
response.WriteHeader(http.StatusOK) response.WriteHeader(http.StatusOK)
detailsString := fmt.Sprintf("Boulder=(%s %s)", core.GetBuildID(), core.GetBuildTime()) detailsString := fmt.Sprintf("Boulder=(%s %s)", core.GetBuildID(), core.GetBuildTime())
if _, err := fmt.Fprintln(response, detailsString); err != nil { if _, err := fmt.Fprintln(response, detailsString); err != nil {
logEvent.Error = err.Error() logEvent.AddError("unable to print build information: %s", err)
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
} }
} }
@ -1303,40 +1281,3 @@ func (wfe *WebFrontEndImpl) setCORSHeaders(response http.ResponseWriter, request
response.Header().Set("Access-Control-Expose-Headers", "Link, Replay-Nonce") response.Header().Set("Access-Control-Expose-Headers", "Link, Replay-Nonce")
response.Header().Set("Access-Control-Max-Age", "86400") response.Header().Set("Access-Control-Max-Age", "86400")
} }
func (wfe *WebFrontEndImpl) logRequestDetails(logEvent *requestEvent) {
logEvent.ResponseTime = time.Now()
var msg string
if logEvent.Error != "" {
msg = "Terminated request"
} else {
msg = "Successful request"
}
wfe.log.InfoObject(msg, logEvent)
}
func (wfe *WebFrontEndImpl) populateRequestEvent(request *http.Request) (logEvent requestEvent) {
logEvent = requestEvent{
ID: core.NewToken(),
RealIP: request.Header.Get("X-Real-IP"),
ClientAddr: getClientAddr(request),
Method: request.Method,
RequestTime: time.Now(),
Extra: make(map[string]interface{}, 0),
}
if request.URL != nil {
logEvent.Endpoint = request.URL.String()
}
return
}
// Comma-separated list of HTTP clients involved in making this
// request, starting with the original requestor and ending with the
// remote end of our TCP connection (which is typically our own
// proxy).
func getClientAddr(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return xff + "," + r.RemoteAddr
}
return r.RemoteAddr
}

View File

@ -271,7 +271,7 @@ func TestHandleFunc(t *testing.T) {
mux = http.NewServeMux() mux = http.NewServeMux()
rw = httptest.NewRecorder() rw = httptest.NewRecorder()
stubCalled = false stubCalled = false
wfe.HandleFunc(mux, "/test", func(http.ResponseWriter, *http.Request) { wfe.HandleFunc(mux, "/test", func(*requestEvent, http.ResponseWriter, *http.Request) {
stubCalled = true stubCalled = true
}, allowed...) }, allowed...)
req.URL = mustParseURL("/test") req.URL = mustParseURL("/test")
@ -463,7 +463,7 @@ func TestIndexPOST(t *testing.T) {
wfe := setupWFE(t) wfe := setupWFE(t)
responseWriter := httptest.NewRecorder() responseWriter := httptest.NewRecorder()
url, _ := url.Parse("/") url, _ := url.Parse("/")
wfe.Index(responseWriter, &http.Request{ wfe.Index(newRequestEvent(), responseWriter, &http.Request{
Method: "POST", Method: "POST",
URL: url, URL: url,
}) })
@ -474,7 +474,7 @@ func TestPOST404(t *testing.T) {
wfe := setupWFE(t) wfe := setupWFE(t)
responseWriter := httptest.NewRecorder() responseWriter := httptest.NewRecorder()
url, _ := url.Parse("/foobar") url, _ := url.Parse("/foobar")
wfe.Index(responseWriter, &http.Request{ wfe.Index(newRequestEvent(), responseWriter, &http.Request{
Method: "POST", Method: "POST",
URL: url, URL: url,
}) })
@ -488,7 +488,7 @@ func TestIndex(t *testing.T) {
responseWriter := httptest.NewRecorder() responseWriter := httptest.NewRecorder()
url, _ := url.Parse("/") url, _ := url.Parse("/")
wfe.Index(responseWriter, &http.Request{ wfe.Index(newRequestEvent(), responseWriter, &http.Request{
Method: "GET", Method: "GET",
URL: url, URL: url,
}) })
@ -501,7 +501,7 @@ func TestIndex(t *testing.T) {
responseWriter.Body.Reset() responseWriter.Body.Reset()
responseWriter.Header().Del("Cache-Control") responseWriter.Header().Del("Cache-Control")
url, _ = url.Parse("/foo") url, _ = url.Parse("/foo")
wfe.Index(responseWriter, &http.Request{ wfe.Index(newRequestEvent(), responseWriter, &http.Request{
URL: url, URL: url,
}) })
//test.AssertEquals(t, responseWriter.Code, http.StatusNotFound) //test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
@ -566,7 +566,7 @@ func TestIssueCertificate(t *testing.T) {
// POST, but no body. // POST, but no body.
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, &http.Request{ wfe.NewCertificate(newRequestEvent(), responseWriter, &http.Request{
Method: "POST", Method: "POST",
Header: map[string][]string{ Header: map[string][]string{
"Content-Length": []string{"0"}, "Content-Length": []string{"0"},
@ -578,14 +578,14 @@ func TestIssueCertificate(t *testing.T) {
// POST, but body that isn't valid JWS // POST, but body that isn't valid JWS
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, makePostRequest("hi")) wfe.NewCertificate(newRequestEvent(), responseWriter, makePostRequest("hi"))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`) `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON. // POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, "foo", &wfe.nonceService))) makePostRequest(signRequest(t, "foo", &wfe.nonceService)))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
@ -593,7 +593,7 @@ func TestIssueCertificate(t *testing.T) {
// Valid, signed JWS body, payload is '{}' // Valid, signed JWS body, payload is '{}'
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest( makePostRequest(
signRequest(t, "{}", &wfe.nonceService))) signRequest(t, "{}", &wfe.nonceService)))
test.AssertEquals(t, test.AssertEquals(t,
@ -602,7 +602,7 @@ func TestIssueCertificate(t *testing.T) {
// Valid, signed JWS body, payload is '{"resource":"new-cert"}' // Valid, signed JWS body, payload is '{"resource":"new-cert"}'
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{"resource":"new-cert"}`, &wfe.nonceService))) makePostRequest(signRequest(t, `{"resource":"new-cert"}`, &wfe.nonceService)))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
@ -613,7 +613,8 @@ func TestIssueCertificate(t *testing.T) {
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=foo.com | \ // openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=foo.com | \
// sed 's/foo.com/fob.com/' | b64url // sed 's/foo.com/fob.com/' | b64url
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, wfe.NewCertificate(newRequestEvent(),
responseWriter,
makePostRequest(signRequest(t, `{ makePostRequest(signRequest(t, `{
"resource":"new-cert", "resource":"new-cert",
"csr": "MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHZm9iLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKzHhqcMSTVjBu61vufGVmIYM4mMbWXgndHOUWnIqSKcNtFtPQ465tcZRT5ITIZWXGjsmgDrj31qvG3t5qLwyaF5hsTvFHK72nLMAQhdgM6481Qe9yaoaulWpkGr_9LVz4jQ9pGAaLVamXGpSxV-ipTOo79Sev4aZE8ksD9atEfWtcOD9w8_zj74vpWjTAHN49Q88chlChVqakn0zSfHPfS-jF8g0UTddBuF0Ti3sZChjxzbo6LwZ4182xX7XPnOLav3AGj0Su7j5XMl3OpenOrlWulWJeZIHq5itGW321j306XiGdbrdWH4K7JygICFds6oolwQRGBY6yinAtCgkTcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBxPiHOtKuBxtvecMNtLkTSuTyEkusQGnjoFDaKe5oqwGYQgy0YBii2-BbaPmqS4ZaDc-vDz_RLeKH5ZiH-NliYR1V_CRtpFLQi18g_2pLQnZLVO3ENs-SM37nU_nBGn9O93t2bkssoM3fZmtgp3R2W7I_wvx7Z8oWKa4boTeBAg_q9Gmi6QskZBddK7A4S_vOR0frU6QSPK_ksPhvovp9fwb6CVKrlJWf556UwRPWgbkW39hvTxK2KHhrUEg3oawNkWde2jZtnZ9e-9zpw8-_5O0X7-YN0ucbFTfQybce_ReuLlGepiHT5bvVavBZoIvqw1XOgSMvGgZFU8tAWMBlj" "csr": "MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHZm9iLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKzHhqcMSTVjBu61vufGVmIYM4mMbWXgndHOUWnIqSKcNtFtPQ465tcZRT5ITIZWXGjsmgDrj31qvG3t5qLwyaF5hsTvFHK72nLMAQhdgM6481Qe9yaoaulWpkGr_9LVz4jQ9pGAaLVamXGpSxV-ipTOo79Sev4aZE8ksD9atEfWtcOD9w8_zj74vpWjTAHN49Q88chlChVqakn0zSfHPfS-jF8g0UTddBuF0Ti3sZChjxzbo6LwZ4182xX7XPnOLav3AGj0Su7j5XMl3OpenOrlWulWJeZIHq5itGW321j306XiGdbrdWH4K7JygICFds6oolwQRGBY6yinAtCgkTcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBxPiHOtKuBxtvecMNtLkTSuTyEkusQGnjoFDaKe5oqwGYQgy0YBii2-BbaPmqS4ZaDc-vDz_RLeKH5ZiH-NliYR1V_CRtpFLQi18g_2pLQnZLVO3ENs-SM37nU_nBGn9O93t2bkssoM3fZmtgp3R2W7I_wvx7Z8oWKa4boTeBAg_q9Gmi6QskZBddK7A4S_vOR0frU6QSPK_ksPhvovp9fwb6CVKrlJWf556UwRPWgbkW39hvTxK2KHhrUEg3oawNkWde2jZtnZ9e-9zpw8-_5O0X7-YN0ucbFTfQybce_ReuLlGepiHT5bvVavBZoIvqw1XOgSMvGgZFU8tAWMBlj"
@ -626,7 +627,7 @@ func TestIssueCertificate(t *testing.T) {
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=meep.com | b64url // openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=meep.com | b64url
mockLog.Clear() mockLog.Clear()
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewCertificate(responseWriter, wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{ makePostRequest(signRequest(t, `{
"resource":"new-cert", "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": "MIICWDCCAUACAQAwEzERMA8GA1UEAwwIbWVlcC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaqzue57mgXEoGTZZoVkkCZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12HX01rUwhj41UuBxJvI1w4wvdstssTAaa9c9tsQ5-UED2bFRL1MsyBdbmCF_-pu3i-ZIYqWgiKbjVBe3nlAVbo77zizwp3Y4Tp1_TBOwTAuFkHePmkNT63uPm9My_hNzsSm1o-Q519Cf7ry-JQmOVgz_jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoXRUlQjXXkNTj23OxxdT_cVLcLJjytyG6e5izME2R2aCkDBWIc1a4_sRJ0R396auPXG6KhJ7o_AgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEALu046p76aKgvoAEHFINkMTgKokPXf9mZ4IZx_BKz-qs1MPMxVtPIrQDVweBH6tYT7Hfj2naLry6SpZ3vUNP_FYeTFWgW1V03LiqacX-QQgbEYtn99Dt3ScGyzb7EH833ztb3vDJ_-ha_CJplIrg-kHBBrlLFWXhh-I9K1qLRTNpbhZ18ooFde4Sbhkw9o9fKivGhx9aYr7ZbjRsNtKit_DsG1nwEXz53TMJ2vB9IQY29coJv_n5NFLkvBfzbG5faRNiFcimPYBO2jFdaA2mWzfxltLtwMF_dBwzTXDpMo3TVT9zEdV8YpsWqr63igqGDZVpKenlkqvRTeGJVayVuMA"
@ -639,7 +640,7 @@ func TestIssueCertificate(t *testing.T) {
mockLog.Clear() mockLog.Clear()
responseWriter.Body.Reset() responseWriter.Body.Reset()
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=not-an-example.com | b64url // openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=not-an-example.com | b64url
wfe.NewCertificate(responseWriter, wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{ makePostRequest(signRequest(t, `{
"resource":"new-cert", "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=" "csr": "MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSbm90LWFuLWV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmqs7nue5oFxKBk2WaFZJAma2nm1oFyPIq19gYEAdQN4mWvaJ8RjzHFkDMYUrlIrGxCYuFJDHFUk9dh19Na1MIY-NVLgcSbyNcOML3bLbLEwGmvXPbbEOflBA9mxUS9TLMgXW5ghf_qbt4vmSGKloIim41QXt55QFW6O-84s8Kd2OE6df0wTsEwLhZB3j5pDU-t7j5vTMv4Tc7EptaPkOdfQn-68viUJjlYM_4yIBVRhWCdexFdylCKVLg0obsghQEwULKYCUjdg6F0VJUI115DU49tzscXU_3FS3CyY8rchunuYszBNkdmgpAwViHNWuP7ESdEd_emrj1xuioSe6PwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAE_T1nWU38XVYL28hNVSXU0rW5IBUKtbvr0qAkD4kda4HmQRTYkt-LNSuvxoZCC9lxijjgtJi-OJe_DCTdZZpYzewlVvcKToWSYHYQ6Wm1-fxxD_XzphvZOujpmBySchdiz7QSVWJmVZu34XD5RJbIcrmj_cjRt42J1hiTFjNMzQu9U6_HwIMmliDL-soFY2RTvvZf-dAFvOUQ-Wbxt97eM1PbbmxJNWRhbAmgEpe9PWDPTpqV5AK56VAa991cQ1P8ZVmPss5hvwGWhOtpnpTZVHN3toGNYFKqxWPboirqushQlfKiFqT9rpRgM3-mFjOHidGqsKEkTdmfSVlVEk3oo="
@ -680,7 +681,7 @@ func TestGetChallenge(t *testing.T) {
req, err := http.NewRequest(method, challengeURL, nil) req, err := http.NewRequest(method, challengeURL, nil)
test.AssertNotError(t, err, "Could not make NewRequest") test.AssertNotError(t, err, "Could not make NewRequest")
wfe.Challenge(resp, req) wfe.Challenge(newRequestEvent(), resp, req)
test.AssertEquals(t, test.AssertEquals(t,
resp.Code, resp.Code,
http.StatusAccepted) http.StatusAccepted)
@ -722,7 +723,7 @@ func TestChallenge(t *testing.T) {
test.AssertNotError(t, err, "Could not unmarshal testing key") test.AssertNotError(t, err, "Could not unmarshal testing key")
challengeURL := "/acme/challenge/valid/23" challengeURL := "/acme/challenge/valid/23"
wfe.Challenge(responseWriter, wfe.Challenge(newRequestEvent(), responseWriter,
makePostRequestWithPath(challengeURL, makePostRequestWithPath(challengeURL,
signRequest(t, `{"resource":"challenge"}`, &wfe.nonceService))) signRequest(t, `{"resource":"challenge"}`, &wfe.nonceService)))
@ -842,7 +843,7 @@ func TestNewRegistration(t *testing.T) {
nonce, err = wfe.nonceService.Nonce() nonce, err = wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce) result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce)
wfe.NewRegistration(responseWriter, wfe.NewRegistration(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
var reg core.Registration var reg core.Registration
@ -878,7 +879,7 @@ func TestNewRegistration(t *testing.T) {
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce) result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce)
wfe.NewRegistration(responseWriter, wfe.NewRegistration(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
@ -946,7 +947,7 @@ func TestRevokeCertificateCertKey(t *testing.T) {
nonce, err := wfe.nonceService.Nonce() nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, _ := signer.Sign(revokeRequestJSON, nonce) result, _ := signer.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter, wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200) test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "") test.AssertEquals(t, responseWriter.Body.String(), "")
@ -970,7 +971,7 @@ func TestRevokeCertificateAccountKey(t *testing.T) {
nonce, err := wfe.nonceService.Nonce() nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, _ := accountKeySigner.Sign(revokeRequestJSON, nonce) result, _ := accountKeySigner.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter, wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 200) test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "") test.AssertEquals(t, responseWriter.Body.String(), "")
@ -994,7 +995,7 @@ func TestRevokeCertificateWrongKey(t *testing.T) {
test.AssertNotError(t, err, "Unable to create revoke request") test.AssertNotError(t, err, "Unable to create revoke request")
result, _ := accountKeySigner2.Sign(revokeRequestJSON, nonce) result, _ := accountKeySigner2.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter, wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 403) test.AssertEquals(t, responseWriter.Code, 403)
test.AssertEquals(t, responseWriter.Body.String(), test.AssertEquals(t, responseWriter.Body.String(),
@ -1038,7 +1039,7 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
nonce, err := wfe.nonceService.Nonce() nonce, err := wfe.nonceService.Nonce()
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, _ := signer.Sign(revokeRequestJSON, nonce) result, _ := signer.Sign(revokeRequestJSON, nonce)
wfe.RevokeCertificate(responseWriter, wfe.RevokeCertificate(newRequestEvent(), responseWriter,
makePostRequest(result.FullSerialize())) makePostRequest(result.FullSerialize()))
test.AssertEquals(t, responseWriter.Code, 409) test.AssertEquals(t, responseWriter.Code, 409)
test.AssertEquals(t, responseWriter.Body.String(), test.AssertEquals(t, responseWriter.Body.String(),
@ -1064,7 +1065,7 @@ func TestAuthorization(t *testing.T) {
// POST, but no body. // POST, but no body.
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, &http.Request{ wfe.NewAuthorization(newRequestEvent(), responseWriter, &http.Request{
Method: "POST", Method: "POST",
Header: map[string][]string{ Header: map[string][]string{
"Content-Length": []string{"0"}, "Content-Length": []string{"0"},
@ -1074,12 +1075,12 @@ func TestAuthorization(t *testing.T) {
// POST, but body that isn't valid JWS // POST, but body that isn't valid JWS
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, makePostRequest("hi")) wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest("hi"))
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`) test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON. // POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, wfe.NewAuthorization(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, "foo", &wfe.nonceService))) makePostRequest(signRequest(t, "foo", &wfe.nonceService)))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
@ -1088,7 +1089,7 @@ func TestAuthorization(t *testing.T) {
// Same signed body, but payload modified by one byte, breaking signature. // Same signed body, but payload modified by one byte, breaking signature.
// should fail JWS verification. // should fail JWS verification.
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, makePostRequest(` wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest(`
{ {
"header": { "header": {
"alg": "RS256", "alg": "RS256",
@ -1107,7 +1108,7 @@ func TestAuthorization(t *testing.T) {
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error"}`) `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error"}`)
responseWriter.Body.Reset() responseWriter.Body.Reset()
wfe.NewAuthorization(responseWriter, wfe.NewAuthorization(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{"resource":"new-authz","identifier":{"type":"dns","value":"test.com"}}`, &wfe.nonceService))) makePostRequest(signRequest(t, `{"resource":"new-authz","identifier":{"type":"dns","value":"test.com"}}`, &wfe.nonceService)))
test.AssertEquals( test.AssertEquals(
@ -1166,7 +1167,7 @@ func TestRegistration(t *testing.T) {
responseWriter.Body.Reset() responseWriter.Body.Reset()
// Test POST invalid JSON // Test POST invalid JSON
wfe.Registration(responseWriter, makePostRequestWithPath("/2", "invalid")) wfe.Registration(newRequestEvent(), responseWriter, makePostRequestWithPath("/2", "invalid"))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`) `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
@ -1184,7 +1185,7 @@ func TestRegistration(t *testing.T) {
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, err := signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce) result, err := signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce)
test.AssertNotError(t, err, "Unable to sign") test.AssertNotError(t, err, "Unable to sign")
wfe.Registration(responseWriter, wfe.Registration(newRequestEvent(), responseWriter,
makePostRequestWithPath("/2", result.FullSerialize())) makePostRequestWithPath("/2", result.FullSerialize()))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
@ -1204,7 +1205,7 @@ func TestRegistration(t *testing.T) {
result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"https://letsencrypt.org/im-bad"}`), nonce) result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"https://letsencrypt.org/im-bad"}`), nonce)
// Test POST valid JSON with registration up in the mock // Test POST valid JSON with registration up in the mock
wfe.Registration(responseWriter, wfe.Registration(newRequestEvent(), responseWriter,
makePostRequestWithPath("/1", result.FullSerialize())) makePostRequestWithPath("/1", result.FullSerialize()))
test.AssertEquals(t, test.AssertEquals(t,
responseWriter.Body.String(), responseWriter.Body.String(),
@ -1216,7 +1217,7 @@ func TestRegistration(t *testing.T) {
test.AssertNotError(t, err, "Unable to create nonce") test.AssertNotError(t, err, "Unable to create nonce")
result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce) result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce)
test.AssertNotError(t, err, "Couldn't sign") test.AssertNotError(t, err, "Couldn't sign")
wfe.Registration(responseWriter, wfe.Registration(newRequestEvent(), responseWriter,
makePostRequestWithPath("/1", result.FullSerialize())) makePostRequestWithPath("/1", result.FullSerialize()))
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error") test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
links := responseWriter.Header()["Link"] links := responseWriter.Header()["Link"]
@ -1237,7 +1238,7 @@ func TestTermsRedirect(t *testing.T) {
responseWriter := httptest.NewRecorder() responseWriter := httptest.NewRecorder()
path, _ := url.Parse("/terms") path, _ := url.Parse("/terms")
wfe.Terms(responseWriter, &http.Request{ wfe.Terms(newRequestEvent(), responseWriter, &http.Request{
Method: "GET", Method: "GET",
URL: path, URL: path,
}) })
@ -1254,7 +1255,7 @@ func TestIssuer(t *testing.T) {
responseWriter := httptest.NewRecorder() responseWriter := httptest.NewRecorder()
wfe.Issuer(responseWriter, &http.Request{ wfe.Issuer(newRequestEvent(), responseWriter, &http.Request{
Method: "GET", Method: "GET",
}) })
test.AssertEquals(t, responseWriter.Code, http.StatusOK) test.AssertEquals(t, responseWriter.Code, http.StatusOK)
@ -1264,6 +1265,9 @@ func TestIssuer(t *testing.T) {
func TestGetCertificate(t *testing.T) { func TestGetCertificate(t *testing.T) {
wfe := setupWFE(t) wfe := setupWFE(t)
mux, err := wfe.Handler()
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
wfe.CertCacheDuration = time.Second * 10 wfe.CertCacheDuration = time.Second * 10
wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7 wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7
wfe.SA = &mocks.StorageAuthority{} wfe.SA = &mocks.StorageAuthority{}
@ -1279,12 +1283,13 @@ func TestGetCertificate(t *testing.T) {
// Valid serial, cached // Valid serial, cached
req, _ := http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000b2", nil) req, _ := http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000b2", nil)
req.RemoteAddr = "192.168.0.1" req.RemoteAddr = "192.168.0.1"
wfe.Certificate(responseWriter, req) mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 200) test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10") test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert") test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert")
test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match") test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match")
t.Logf("UGH %#v", mockLog.GetAll()[0])
reqlogs := mockLog.GetAllMatching(`Successful request`) reqlogs := mockLog.GetAllMatching(`Successful request`)
test.AssertEquals(t, len(reqlogs), 1) test.AssertEquals(t, len(reqlogs), 1)
test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO) test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO)
@ -1296,7 +1301,7 @@ func TestGetCertificate(t *testing.T) {
req, _ = http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000ff", nil) req, _ = http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000ff", nil)
req.RemoteAddr = "192.168.0.1" req.RemoteAddr = "192.168.0.1"
req.Header.Set("X-Forwarded-For", "192.168.99.99") req.Header.Set("X-Forwarded-For", "192.168.99.99")
wfe.Certificate(responseWriter, req) mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 404) test.AssertEquals(t, responseWriter.Code, 404)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") 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"}`) test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
@ -1309,7 +1314,7 @@ func TestGetCertificate(t *testing.T) {
// Invalid serial, no cache // Invalid serial, no cache
responseWriter = httptest.NewRecorder() responseWriter = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/acme/cert/nothex", nil) req, _ = http.NewRequest("GET", "/acme/cert/nothex", nil)
wfe.Certificate(responseWriter, req) mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 404) test.AssertEquals(t, responseWriter.Code, 404)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") 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"}`) test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
@ -1317,7 +1322,7 @@ func TestGetCertificate(t *testing.T) {
// Invalid serial, no cache // Invalid serial, no cache
responseWriter = httptest.NewRecorder() responseWriter = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/acme/cert/00000000000000", nil) req, _ = http.NewRequest("GET", "/acme/cert/00000000000000", nil)
wfe.Certificate(responseWriter, req) mux.ServeHTTP(responseWriter, req)
test.AssertEquals(t, responseWriter.Code, 404) test.AssertEquals(t, responseWriter.Code, 404)
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") 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"}`) test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
@ -1359,7 +1364,7 @@ func TestLogCsrPem(t *testing.T) {
func TestLengthRequired(t *testing.T) { func TestLengthRequired(t *testing.T) {
wfe := setupWFE(t) wfe := setupWFE(t)
_, _, _, err := wfe.verifyPOST(&http.Request{ _, _, _, err := wfe.verifyPOST(newRequestEvent(), &http.Request{
Method: "POST", Method: "POST",
URL: mustParseURL("/"), URL: mustParseURL("/"),
}, false, "resource") }, false, "resource")
@ -1387,7 +1392,7 @@ func TestVerifyPOSTUsesStoredKey(t *testing.T) {
wfe.SA = &mockSADifferentStoredKey{mocks.StorageAuthority{}} wfe.SA = &mockSADifferentStoredKey{mocks.StorageAuthority{}}
// signRequest signs with test1Key, but our special mock returns a // signRequest signs with test1Key, but our special mock returns a
// registration with test2Key // registration with test2Key
_, _, _, err := wfe.verifyPOST(makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo") _, _, _, err := wfe.verifyPOST(newRequestEvent(), makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo")
test.AssertError(t, err, "No error returned when provided key differed from stored key.") test.AssertError(t, err, "No error returned when provided key differed from stored key.")
} }
@ -1398,7 +1403,7 @@ func TestBadKeyCSR(t *testing.T) {
// CSR with a bad (512 bit RSA) key. // CSR with a bad (512 bit RSA) key.
// openssl req -outform der -new -newkey rsa:512 -nodes -keyout foo.com.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' // -subj /CN=foo.com | base64 -w0 | sed -e 's,+,-,g' -e 's,/,_,g'
wfe.NewCertificate(responseWriter, wfe.NewCertificate(newRequestEvent(), responseWriter,
makePostRequest(signRequest(t, `{ makePostRequest(signRequest(t, `{
"resource":"new-cert", "resource":"new-cert",
"csr": "MIHLMHcCAQAwEjEQMA4GA1UEAwwHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDCZftp4x4owgjBnwOKfzihIPedT-BUmV2fuQPMqaUlc8yJUp13vcO5uxUlaBm8leM7Dj_sgTDP_JgykorlYo73AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAEaQ2QBhweK-kp1ejQCedUhMit_wG-uTBtKnc3M82f6_fztLkhg1vWQ782nmhbEI5orXp6QtNHgJYnBpqA9Ut00" "csr": "MIHLMHcCAQAwEjEQMA4GA1UEAwwHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDCZftp4x4owgjBnwOKfzihIPedT-BUmV2fuQPMqaUlc8yJUp13vcO5uxUlaBm8leM7Dj_sgTDP_JgykorlYo73AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAEaQ2QBhweK-kp1ejQCedUhMit_wG-uTBtKnc3M82f6_fztLkhg1vWQ782nmhbEI5orXp6QtNHgJYnBpqA9Ut00"
@ -1431,3 +1436,7 @@ func TestStatusCodeFromError(t *testing.T) {
} }
} }
} }
func newRequestEvent() *requestEvent {
return &requestEvent{Extra: make(map[string]interface{})}
}