opacity/storage-node

Apply cryptographic verification on (nearly?) all endpoints

rfornea opened this issue · 0 comments

Instead of the client just sending their AccountID with requests, they will also send a signature so that we can verify that the correct user is the one who initiated the action.

The endpoint to create an account can be updated in the following manner:

  1. Remove AccountID from top level object and replace with Signature.
  2. Nest StorageLimit, DurationInMonths, and MetadataKey under an object living under the top level.

So this:

type accountCreateReq struct {
	AccountID        string `json:"accountID" binding:"required,len=64"`
	StorageLimit     int    `json:"storageLimit" binding:"required,gte=100"`
	DurationInMonths int    `json:"durationInMonths" binding:"required,gte=1"`
	MetadataKey      string `json:"metadataKey" binding:"required,len=64"`
}

Becomes something like this:

type accountCreateReq struct {
	Signature               string                        `json:"signature" binding:"required,len=130"`
	AccountCreation   accountCreateObj    `json:"accountCreation" binding:"required"`
}

type accountCreateObj struct {
	StorageLimit     int    `json:"storageLimit" binding:"required,gte=100"`
	DurationInMonths int    `json:"durationInMonths" binding:"required,gte=1"`
	MetadataKey      string `json:"metadataKey" binding:"required,len=64"`
}

  1. json.Marshal the request.AccountCreation, then create a hash of the returned []byte with utils.Hash. Call utils.Recover on the hash and signature. The resulting public key is what we will use as the AccountID when we create the account.
  2. Create the account as we normally do using the information in request.AccountCreation.

Other endpoints can be updated in the following manner:

  1. Add Signature and Address (or AccountID) at the top level, and nest the other parameters under an object under the top level.
  2. json.Marshal the object holding the nested parameters. Call utils.Hash on the returned value. Then call utils.VerifyFromStrings on the Address/AccountID, the hash you just created encoded as a hex string, and the Signature. This will return a boolean and an error. If the boolean is true, then that means the correct user sent the request and we can proceed with the rest of the logic in the handler. Otherwise call ForbiddenResponse.

For an example of the above changes, look at the recent changes in the setMetadata method (link to changes). If we can, let's add some method that captures the needed logic changes all in one place so we aren't repeating ourselves, and let's create some test utility methods so we aren't overly repeating ourselves in the tests.