Disallow GETs for Registration.

Per the spec, authenticated requests must be signed by an account key, and
GET requests can't be signed under the current protocol. If the account holder
wishes to fetch their current registration, they can do so by posting a signed,
empty update to their registration resource.

Also fix a bug in generating registration URLs.
This commit is contained in:
Jacob Hoffman-Andrews 2015-05-19 12:14:12 -07:00
parent 1c251bc19a
commit 8dd4c650bd
3 changed files with 65 additions and 90 deletions

View File

@ -132,12 +132,17 @@ function parseLink(link) {
}
function post(url, body, callback) {
var jws = crypto.generateSignature(state.accountPrivateKey, new Buffer(JSON.stringify(body)));
var payload = JSON.stringify(jws);
var payload = JSON.stringify(body, null, 2);
var jws = crypto.generateSignature(state.accountPrivateKey, new Buffer(payload));
var signed = JSON.stringify(jws, null, 2);
var req = request.post(url, callback);
console.log('Posting to', url, ':\n', payload);
req.write(payload)
console.log('Posting to', url, ':\n', signed);
console.log('Payload:', payload);
req.on('response', function(response) {
console.log(response.headers)
})
req.write(signed)
req.end();
return req;
}
@ -276,13 +281,13 @@ function sendAgreement(answers) {
post(state.registrationURL, state.registration,
function(err, resp, body) {
if (err || Math.floor(resp.statusCode / 100) != 2) {
console.log("Couldn't POST agreement back to server, aborting.");
console.log("error: " + err);
console.log(body);
console.log("error: " + err);
console.log("Couldn't POST agreement back to server, aborting.");
process.exit(1);
} else {
inquirer.prompt(questions.domain, getChallenges);
}
inquirer.prompt(questions.domain, getChallenges);
});
}

View File

@ -268,7 +268,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
return
}
regURL := wfe.RegBase + string(reg.ID)
regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID)
responseBody, err := json.Marshal(reg)
if err != nil {
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
@ -554,6 +554,24 @@ func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.Re
}
func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *http.Request) {
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
}
body, _, currReg, err := wfe.verifyPOST(request, true)
if err != nil {
if err == sql.ErrNoRows {
wfe.sendError(response,
"No registration exists matching provided key",
err, http.StatusForbidden)
} else {
wfe.sendError(response,
"Unable to read/verify body", err, http.StatusBadRequest)
}
return
}
// Requests to this handler should have a path that leads to a known
// registration
idStr := parseIDFromPath(request.URL.Path)
@ -564,66 +582,38 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
} else if id <= 0 {
wfe.sendError(response, "Registration ID must be a positive non-zero integer", id, http.StatusBadRequest)
return
} else if id != currReg.ID {
wfe.sendError(response, "Request signing key did not match registration key", "", http.StatusForbidden)
return
}
reg, err := wfe.SA.GetRegistration(id)
var update core.Registration
err = json.Unmarshal(body, &update)
if err != nil {
wfe.sendError(response,
"Unable to find registration", err,
http.StatusNotFound)
wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest)
return
}
reg.ID = id
switch request.Method {
default:
wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed)
// MergeUpdate copies over only the fields that a client is allowed to modify.
// Note: The RA will also use MergeUpdate to filter out non-updateable fields,
// but we do it here too, so that/ input filtering happens as early in the
// request processing as possible.
currReg.MergeUpdate(update)
// Ask the RA to update this authorization.
updatedReg, err := wfe.RA.UpdateRegistration(currReg, currReg)
if err != nil {
wfe.sendError(response, "Unable to update registration", err, http.StatusInternalServerError)
return
case "GET":
jsonReply, err := json.Marshal(reg)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusOK)
response.Write(jsonReply)
case "POST":
body, _, currReg, err := wfe.verifyPOST(request, true)
if err != nil {
if err == sql.ErrNoRows {
wfe.sendError(response, "No registration exists matching provided key", err, http.StatusForbidden)
} else {
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
}
return
}
var update core.Registration
err = json.Unmarshal(body, &update)
if err != nil {
wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest)
return
}
// Ask the RA to update this authorization
updatedReg, err := wfe.RA.UpdateRegistration(currReg, update)
if err != nil {
wfe.sendError(response, "Unable to update registration", err, http.StatusInternalServerError)
return
}
jsonReply, err := json.Marshal(updatedReg)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusAccepted)
response.Write(jsonReply)
}
jsonReply, err := json.Marshal(updatedReg)
if err != nil {
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(http.StatusAccepted)
response.Write(jsonReply)
}
func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request *http.Request) {

View File

@ -91,7 +91,7 @@ func (sa *MockSA) GetRegistration(id int64) (core.Registration, error) {
var parsedKey jose.JsonWebKey
parsedKey.UnmarshalJSON(keyJSON)
return core.Registration{Key: parsedKey}, nil
return core.Registration{ID: id, Key: parsedKey}, nil
}
func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) {
@ -106,7 +106,7 @@ func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration,
if core.KeyDigestEquals(jwk, test2KeyPublic) {
// No key found
return core.Registration{}, sql.ErrNoRows
return core.Registration{ID: 2}, sql.ErrNoRows
}
// Return a fake registration
@ -602,35 +602,15 @@ func TestRegistration(t *testing.T) {
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Method not allowed\"}")
responseWriter.Body.Reset()
// Test GET missing entry
path, _ = url.Parse("/100")
wfe.Registration(responseWriter, &http.Request{
Method: "GET",
URL: path,
})
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Unable to find registration\"}")
responseWriter.Body.Reset()
// Test GET malformed entry
path, _ = url.Parse("/101")
wfe.Registration(responseWriter, &http.Request{
Method: "GET",
URL: path,
})
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:serverInternal\",\"detail\":\"Failed to marshal authz\"}")
responseWriter.Body.Reset()
// Test GET proper entry
// Test GET proper entry returns 405
path, _ = url.Parse("/1")
wfe.Registration(responseWriter, &http.Request{
Method: "GET",
URL: path,
})
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
test.AssertEquals(t,
responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Method not allowed\"}")
responseWriter.Body.Reset()
// Test POST invalid JSON
@ -646,7 +626,7 @@ func TestRegistration(t *testing.T) {
responseWriter.Body.Reset()
// Test POST valid JSON but key is not registered
path, _ = url.Parse("/1")
path, _ = url.Parse("/2")
wfe.Registration(responseWriter, &http.Request{
Method: "POST",
Body: makeBody(`{
@ -662,7 +642,7 @@ func TestRegistration(t *testing.T) {
responseWriter.Body.Reset()
// Test POST valid JSON with registration up in the mock
path, _ = url.Parse("/2")
path, _ = url.Parse("/1")
wfe.Registration(responseWriter, &http.Request{
Method: "POST",
Body: makeBody(`{