// MergeUpdate copies a subset of information from the input Registration // into the Registration r. It returns true if an update was performed and the base object // was changed, and false if no change was made. func mergeUpdate(r *core.Registration, input core.Registration) bool { var changed bool // Note: we allow input.Contact to overwrite r.Contact even if the former is // empty in order to allow users to remove the contact associated with // a registration. Since the field type is a pointer to slice of pointers we // can perform a nil check to differentiate between an empty value and a nil // (e.g. not provided) value if input.Contact != nil && !contactsEqual(r, input) { r.Contact = input.Contact changed = true } // If there is an agreement in the input and it's not the same as the base, // then we update the base if len(input.Agreement) > 0 && input.Agreement != r.Agreement { r.Agreement = input.Agreement changed = true } if features.Enabled(features.AllowKeyRollover) && input.Key != nil { sameKey, _ := core.PublicKeysEqual(r.Key.Key, input.Key.Key) if !sameKey { r.Key = input.Key changed = true } } return changed }
func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, request *http.Request) { if request.Method != "POST" { wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed) return } body, key, _, err := wfe.verifyPOST(request, false) if err != nil { wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest) return } var init, unmarshalled core.Registration err = json.Unmarshal(body, &unmarshalled) if err != nil { wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest) return } if len(unmarshalled.Agreement) > 0 && unmarshalled.Agreement != wfe.SubscriberAgreementURL { wfe.sendError(response, fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", unmarshalled.Agreement, wfe.SubscriberAgreementURL), nil, http.StatusBadRequest) return } init.MergeUpdate(unmarshalled) reg, err := wfe.RA.NewRegistration(init, *key) if err != nil { wfe.sendError(response, "Error creating new registration", err, http.StatusInternalServerError) return } regURL := fmt.Sprintf("%s%s", wfe.RegBase, string(reg.ID)) responseBody, err := json.Marshal(reg) if err != nil { wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError) return } response.Header().Add("Location", regURL) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.NewAuthz, "next")) if len(wfe.TermsPath) > 0 { response.Header().Add("Link", link(wfe.BaseURL+wfe.TermsPath, "terms-of-service")) } response.WriteHeader(http.StatusCreated) response.Write(responseBody) // incr reg stat wfe.Stats.Inc("Registrations", 1, 1.0) }
// newReg creates a reg model object from a core.Registration func registrationToModel(r *core.Registration) (*regModel, error) { key, err := json.Marshal(r.Key) if err != nil { return nil, err } sha, err := core.KeyDigest(r.Key) if err != nil { return nil, err } if r.InitialIP == nil { return nil, fmt.Errorf("initialIP was nil") } if r.Contact == nil { r.Contact = &[]*core.AcmeURL{} } rm := ®Model{ ID: r.ID, Key: key, KeySHA256: sha, Contact: *r.Contact, Agreement: r.Agreement, InitialIP: []byte(r.InitialIP.To16()), CreatedAt: r.CreatedAt, } return rm, nil }
// newReg creates a reg model object from a core.Registration func registrationToModel(r *core.Registration) (interface{}, error) { key, err := json.Marshal(r.Key) if err != nil { return nil, err } sha, err := core.KeyDigest(r.Key) if err != nil { return nil, err } if r.InitialIP == nil { return nil, fmt.Errorf("initialIP was nil") } if r.Contact == nil { r.Contact = &[]string{} } rm := regModelv1{ ID: r.ID, Key: key, KeySHA256: sha, Contact: *r.Contact, Agreement: r.Agreement, InitialIP: []byte(r.InitialIP.To16()), CreatedAt: r.CreatedAt, } if features.Enabled(features.AllowAccountDeactivation) { return ®Modelv2{ regModelv1: rm, Status: string(r.Status), }, nil } return &rm, nil }
// UpdateRegistration updates an existing Registration with new values. func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) { base.MergeUpdate(update) err = validateContacts(base.Contact, ra.DNSResolver) if err != nil { return } reg = base err = ra.SA.UpdateRegistration(base) if err != nil { // InternalServerError since the user-data was validated before being // passed to the SA. err = core.InternalServerError(fmt.Sprintf("Could not update registration: %s", err)) } return }
// UpdateRegistration updates an existing Registration with new values. func (ra *RegistrationAuthorityImpl) UpdateRegistration(ctx context.Context, base core.Registration, update core.Registration) (reg core.Registration, err error) { base.MergeUpdate(update) err = ra.validateContacts(ctx, base.Contact) if err != nil { return } reg = base err = ra.SA.UpdateRegistration(ctx, base) if err != nil { // InternalServerError since the user-data was validated before being // passed to the SA. err = core.InternalServerError(fmt.Sprintf("Could not update registration: %s", err)) } ra.stats.Inc("RA.UpdatedRegistrations", 1, 1.0) return }
func TestRegistration(t *testing.T) { contacts := []string{"email"} var key jose.JsonWebKey err := json.Unmarshal([]byte(` { "e": "AQAB", "kty": "RSA", "n": "tSwgy3ORGvc7YJI9B2qqkelZRUC6F1S5NwXFvM4w5-M0TsxbFsH5UH6adigV0jzsDJ5imAechcSoOhAh9POceCbPN1sTNwLpNbOLiQQ7RD5mY_pSUHWXNmS9R4NZ3t2fQAzPeW7jOfF0LKuJRGkekx6tXP1uSnNibgpJULNc4208dgBaCHo3mvaE2HV2GmVl1yxwWX5QZZkGQGjNDZYnjFfa2DKVvFs0QbAk21ROm594kAxlRlMMrvqlf24Eq4ERO0ptzpZgm_3j_e4hGRD39gJS7kAzK-j2cacFQ5Qi2Y6wZI2p-FCq_wiYsfEAIkATPBiLKl_6d_Jfcvs_impcXQ" } `), &key) test.AssertNotError(t, err, "Could not unmarshal testing key") inReg := core.Registration{ ID: 1, Key: &key, Contact: &contacts, Agreement: "yup", InitialIP: net.ParseIP("1.1.1.1"), CreatedAt: time.Now(), Status: core.StatusValid, } pbReg, err := registrationToPB(inReg) test.AssertNotError(t, err, "registrationToPB failed") outReg, err := pbToRegistration(pbReg) test.AssertNotError(t, err, "pbToRegistration failed") test.AssertDeepEquals(t, inReg, outReg) inReg.Contact = nil pbReg, err = registrationToPB(inReg) test.AssertNotError(t, err, "registrationToPB failed") pbReg.Contact = []string{} outReg, err = pbToRegistration(pbReg) test.AssertNotError(t, err, "pbToRegistration failed") test.AssertDeepEquals(t, inReg, outReg) var empty []string inReg.Contact = &empty pbReg, err = registrationToPB(inReg) test.AssertNotError(t, err, "registrationToPB failed") outReg, err = pbToRegistration(pbReg) test.AssertNotError(t, err, "pbToRegistration failed") test.Assert(t, *outReg.Contact != nil, "Empty slice was converted to a nil slice") }
// NewRegistration stores a new Registration func (ssa *SQLStorageAuthority) NewRegistration(ctx context.Context, reg core.Registration) (core.Registration, error) { reg.CreatedAt = ssa.clk.Now() rm, err := registrationToModel(®) if err != nil { return reg, err } err = ssa.dbMap.Insert(rm) if err != nil { return reg, err } return modelToRegistration(rm) }
func modelToRegistration(ri interface{}) (core.Registration, error) { var rm *regModelv1 if features.Enabled(features.AllowAccountDeactivation) { r2 := ri.(*regModelv2) rm = &r2.regModelv1 } else { rm = ri.(*regModelv1) } k := &jose.JsonWebKey{} err := json.Unmarshal(rm.Key, k) if err != nil { err = fmt.Errorf("unable to unmarshal JsonWebKey in db: %s", err) return core.Registration{}, err } var contact *[]string // Contact can be nil when the DB contains the literal string "null". We // prefer to represent this in memory as a pointer to an empty slice rather // than a nil pointer. if rm.Contact == nil { contact = &[]string{} } else { contact = &rm.Contact } r := core.Registration{ ID: rm.ID, Key: k, Contact: contact, Agreement: rm.Agreement, InitialIP: net.IP(rm.InitialIP), CreatedAt: rm.CreatedAt, } if features.Enabled(features.AllowAccountDeactivation) { r2 := ri.(*regModelv2) r.Status = core.AcmeStatus(r2.Status) } return r, nil }
// Registration is used by a client to submit an update to their registration. func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *http.Request) { logEvent := wfe.populateRequestEvent(request) defer wfe.logRequestDetails(&logEvent) body, _, currReg, err := wfe.verifyPOST(request, true, core.ResourceRegistration) if err != nil { logEvent.Error = err.Error() respMsg := malformedJWS respCode := http.StatusBadRequest if err == sql.ErrNoRows { respMsg = unknownKey respCode = http.StatusForbidden } wfe.sendError(response, respMsg, err, respCode) return } logEvent.Requester = currReg.ID logEvent.Contacts = currReg.Contact // Requests to this handler should have a path that leads to a known // registration idStr := parseIDFromPath(request.URL.Path) id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { logEvent.Error = err.Error() wfe.sendError(response, "Registration ID must be an integer", err, http.StatusBadRequest) return } else if id <= 0 { logEvent.Error = "Registration ID must be a positive non-zero integer" wfe.sendError(response, logEvent.Error, id, http.StatusBadRequest) return } else if id != currReg.ID { logEvent.Error = "Request signing key did not match registration key" wfe.sendError(response, logEvent.Error, "", http.StatusForbidden) return } var update core.Registration err = json.Unmarshal(body, &update) if err != nil { logEvent.Error = err.Error() wfe.sendError(response, "Error unmarshaling registration", err, http.StatusBadRequest) return } 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) wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest) return } // Registration objects contain a JWK object, which must be non-nil. We know // the key of the updated registration object is going to be the same as the // key of the current one, so we set it here. This ensures we can cleanly // serialize the update as JSON to send via AMQP to the RA. update.Key = currReg.Key // Ask the RA to update this authorization. updatedReg, err := wfe.RA.UpdateRegistration(currReg, update) if err != nil { logEvent.Error = err.Error() wfe.sendError(response, "Unable to update registration", err, statusCodeFromError(err)) return } jsonReply, err := json.Marshal(updatedReg) if err != nil { logEvent.Error = err.Error() // StatusInternalServerError because we just generated the reg, it should be OK wfe.sendError(response, "Failed to marshal registration", err, http.StatusInternalServerError) return } response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.NewAuthz, "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } response.WriteHeader(http.StatusAccepted) response.Write(jsonReply) }
// NewRegistration is used by clients to submit a new registration/account func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, request *http.Request) { logEvent := wfe.populateRequestEvent(request) defer wfe.logRequestDetails(&logEvent) body, key, _, err := wfe.verifyPOST(request, false, core.ResourceNewReg) if err != nil { logEvent.Error = err.Error() wfe.sendError(response, malformedJWS, err, http.StatusBadRequest) return } if existingReg, err := wfe.SA.GetRegistrationByKey(*key); err == nil { logEvent.Error = "Registration key is already in use" response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID)) wfe.sendError(response, logEvent.Error, nil, http.StatusConflict) return } var init core.Registration err = json.Unmarshal(body, &init) if err != nil { logEvent.Error = err.Error() wfe.sendError(response, "Error unmarshaling JSON", err, http.StatusBadRequest) return } 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) wfe.sendError(response, logEvent.Error, nil, http.StatusBadRequest) return } init.Key = *key reg, err := wfe.RA.NewRegistration(init) if err != nil { logEvent.Error = err.Error() wfe.sendError(response, "Error creating new registration", err, statusCodeFromError(err)) return } logEvent.Requester = reg.ID logEvent.Contacts = reg.Contact // Use an explicitly typed variable. Otherwise `go vet' incorrectly complains // that reg.ID is a string being passed to %d. var id int64 = reg.ID regURL := fmt.Sprintf("%s%d", wfe.RegBase, id) responseBody, err := json.Marshal(reg) if err != nil { logEvent.Error = err.Error() // StatusInternalServerError because we just created this registration, it should be OK. wfe.sendError(response, "Error marshaling registration", err, http.StatusInternalServerError) return } response.Header().Add("Location", regURL) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.NewAuthz, "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } response.WriteHeader(http.StatusCreated) response.Write(responseBody) // incr reg stat wfe.Stats.Inc("Registrations", 1, 1.0) }
// NewRegistration is used by clients to submit a new registration/account func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { body, key, _, err := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg) if err != nil { // verifyPOST handles its own setting of logEvent.Errors wfe.sendError(response, logEvent, malformedJWS, err, statusCodeFromError(err)) return } if existingReg, err := wfe.SA.GetRegistrationByKey(*key); err == nil { response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID)) wfe.sendError(response, logEvent, "Registration key is already in use", nil, http.StatusConflict) return } var init core.Registration err = json.Unmarshal(body, &init) if err != nil { wfe.sendError(response, logEvent, "Error unmarshaling JSON", err, http.StatusBadRequest) return } if len(init.Agreement) > 0 && 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, msg, nil, http.StatusBadRequest) return } init.Key = *key init.InitialIP = net.ParseIP(request.Header.Get("X-Real-IP")) if init.InitialIP == nil { host, _, err := net.SplitHostPort(request.RemoteAddr) if err == nil { init.InitialIP = net.ParseIP(host) } else { logEvent.AddError("Couldn't parse RemoteAddr: %s", request.RemoteAddr) wfe.sendError(response, logEvent, "couldn't parse the remote (that is, the client's) address", nil, http.StatusInternalServerError) return } } reg, err := wfe.RA.NewRegistration(init) if err != nil { logEvent.AddError("unable to create new registration: %s", err) wfe.sendError(response, logEvent, "Error creating new registration", err, statusCodeFromError(err)) return } logEvent.Requester = reg.ID logEvent.Contacts = reg.Contact // Use an explicitly typed variable. Otherwise `go vet' incorrectly complains // that reg.ID is a string being passed to %d. regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID) responseBody, err := json.Marshal(reg) if err != nil { // StatusInternalServerError because we just created this registration, it should be OK. logEvent.AddError("unable to marshal registration: %s", err) wfe.sendError(response, logEvent, "Error marshaling registration", err, http.StatusInternalServerError) return } response.Header().Add("Location", regURL) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.NewAuthz, "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } response.WriteHeader(http.StatusCreated) response.Write(responseBody) }
// Registration is used by a client to submit an update to their registration. func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration) if prob != nil { // verifyPOST handles its own setting of logEvent.Errors wfe.sendError(response, logEvent, prob, nil) return } // Requests to this handler should have a path that leads to a known // registration idStr := parseIDFromPath(request.URL.Path) id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { logEvent.AddError("registration ID must be an integer, was %#v", idStr) wfe.sendError(response, logEvent, probs.Malformed("Registration ID must be an integer"), err) return } else if id <= 0 { msg := fmt.Sprintf("Registration ID must be a positive non-zero integer, was %d", id) logEvent.AddError(msg) wfe.sendError(response, logEvent, probs.Malformed(msg), nil) return } else if id != currReg.ID { logEvent.AddError("Request signing key did not match registration key: %d != %d", id, currReg.ID) wfe.sendError(response, logEvent, probs.Unauthorized("Request signing key did not match registration key"), nil) return } var update core.Registration err = json.Unmarshal(body, &update) if err != nil { logEvent.AddError("unable to JSON parse registration: %s", err) wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling registration"), err) return } if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL { msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL) logEvent.AddError(msg) wfe.sendError(response, logEvent, probs.Malformed(msg), nil) return } // Registration objects contain a JWK object, which must be non-nil. We know // the key of the updated registration object is going to be the same as the // key of the current one, so we set it here. This ensures we can cleanly // serialize the update as JSON to send via AMQP to the RA. update.Key = currReg.Key // Ask the RA to update this authorization. updatedReg, err := wfe.RA.UpdateRegistration(currReg, update) if err != nil { logEvent.AddError("unable to update registration: %s", err) wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Unable to update registration"), err) return } jsonReply, err := json.Marshal(updatedReg) if err != nil { // ServerInternal because we just generated the reg, it should be OK logEvent.AddError("unable to marshal updated registration: %s", err) wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal registration"), err) return } response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.NewAuthz, "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } response.WriteHeader(http.StatusAccepted) response.Write(jsonReply) }
// NewRegistration is used by clients to submit a new registration/account func (wfe *WebFrontEndImpl) NewRegistration(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { body, key, _, prob := wfe.verifyPOST(ctx, logEvent, request, false, core.ResourceNewReg) addRequesterHeader(response, logEvent.Requester) if prob != nil { // verifyPOST handles its own setting of logEvent.Errors wfe.sendError(response, logEvent, prob, nil) return } if existingReg, err := wfe.SA.GetRegistrationByKey(ctx, *key); err == nil { response.Header().Set("Location", wfe.relativeEndpoint(request, fmt.Sprintf("%s%d", regPath, existingReg.ID))) // TODO(#595): check for missing registration err wfe.sendError(response, logEvent, probs.Conflict("Registration key is already in use"), err) return } var init core.Registration err := json.Unmarshal(body, &init) if err != nil { wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err) return } if len(init.Agreement) > 0 && 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, probs.Malformed(msg), nil) return } init.Key = *key init.InitialIP = net.ParseIP(request.Header.Get("X-Real-IP")) if init.InitialIP == nil { host, _, err := net.SplitHostPort(request.RemoteAddr) if err == nil { init.InitialIP = net.ParseIP(host) } else { logEvent.AddError("Couldn't parse RemoteAddr: %s", request.RemoteAddr) wfe.sendError(response, logEvent, probs.ServerInternal("couldn't parse the remote (that is, the client's) address"), nil) return } } reg, err := wfe.RA.NewRegistration(ctx, init) if err != nil { logEvent.AddError("unable to create new registration: %s", err) wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new registration"), err) return } logEvent.Requester = reg.ID addRequesterHeader(response, reg.ID) logEvent.Contacts = reg.Contact // Use an explicitly typed variable. Otherwise `go vet' incorrectly complains // that reg.ID is a string being passed to %d. regURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%d", regPath, reg.ID)) responseBody, err := marshalIndent(reg) if err != nil { // ServerInternal because we just created this registration, and it // should be OK. logEvent.AddError("unable to marshal registration: %s", err) wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling registration"), err) return } response.Header().Add("Location", regURL) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.relativeEndpoint(request, newAuthzPath), "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } response.WriteHeader(http.StatusCreated) response.Write(responseBody) }
func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) { base.MergeUpdate(update) reg = base err = ra.SA.UpdateRegistration(base) return }
func (ra *MockRegistrationAuthority) NewRegistration(reg core.Registration, jwk jose.JsonWebKey) (core.Registration, error) { reg.Key = jwk return reg, nil }