About Authentication
Ledger security is based on asymmetric cryptography. Each ledger record is secured by a public and private key pair. Public keys are registered in the ledger and each ledger operation is verified by checking the provided signatures.
There are two main types of ledger requests, mutations and reads. A mutation stands for any kind of request to store or modify a record in the ledger.
Authenticating by signing a mutation body
Mutation requests always contain a payload which is designed to convey all information required to perform the mutation.
Because of that mutations are more straightforward and secure. The primary security mechanism for mutations is contained in the proofs
array that is provided in the meta
object as part of the payload. For example, a body of a create wallet request looks like this:
Steps to sign the object like above:
- Serialize the data
- Hash the serialized data
- Sign the hash with one or more private keys
The keys used for signing are going to be used by the ledger to verify if it is allowed to execute the request in question.
Steps to verify an incoming mutation:
- Serialize the data
- Hash the serialized data
- Compare the received hash with the hash from the payload
- Verify each received signature using public keys from the signatures array and the calculated hash
If the steps above are successful, this means that the received payload is valid and that it was sent by the owners of the provided public keys. We still need to check the permissions of those public keys in order to make sure they are authorized to perform the required operation. See About Authorization for more details about authorization.
Even though mutations are authenticated via body signature, clients can also use JWT Tokens to authenticate and provide a second layer of security.
Authenticating with JWT token
The presence of a token - Authorization header - is not mandatory. It becomes required through the configuration of authorization access rules that requires a token to grant access. Once sent, the token is validated for its format, signature and expiration, regardless of the presence of access rules.
The above mechanism doesn't work for read requests, since those are usually GET
HTTP requests without any body. For those requests the URL, query parameters and headers define what is going to be returned by the API. To make the ledger API easy to use, but in order to still keep the same security model, the ledger supports JWT tokens for security context exchange between clients and the server. JWT tokens are very flexible and also allow users to transport additional information as part of their payload that can be verified by the ledger. To keep the primary security model compatible with the model for body signatures, clients which hold private keys can issue JWT tokens that can be validated by public keys which ledger has access.
In this model, JWT token replaces the body that is sent in mutation requests, but the whole security remains the same. A client issues a JWT and signs it with its own private key. The ledger can verify that JWT with a public key and enforce security constraints configured for that public key in case the verification is successful. Requests with invalid tokens are rejected regardless of authorization rules set to the ledger.
JWT should be sent to the ledger in a standard way, using the Authorization
header:
JWT required headers:
Supported algorithms at the present moment:
- EdDSA (ed25519 schema)
JWT payload claims definition:
Definition | |
---|---|
iss | issuer of the token, represents a client identifier, for example cli , studio , etc. |
sub | subject of the token, represents a user identifier, either public key or handle of the signer or an arbitrary string for external tokens. |
aud | audience of the token, represents a recipient for which a token is intended |
iat | issued at time, time at which a token was issued, seconds since epoch |
exp | expiration time, time after which a token expires, seconds since epoch |
jti | (optional) unique id of the token, can be used to prevent replay attacks |
hsh | (optional) request hash (sha256), custom ledger field that enables request content validation |
All claims listed above except for jti
and hsh
are required. Public keys or handles can be used for all entities that have them as identifiers. Handles are preferable, if a key is registered with the ledger, since they are shorter.
Tokens without hsh
are not linked to a specific request and can be used for multiple API requests. Token expiration is controlled by exp
claim. If a jti
claim is present the ledger needs to store the token id until the token expiration time expires. Clients should create short lived tokens if they provide a jti
, max exp
allowed is 5 minutes for single use tokens.
A more secure token can also be created by including a request hash. This ties a token to a specific request, so it limits the possible attacks in case a token is leaked. The hashing algorithm used is the same as for the request body described above. The steps to create a request hash:
Create an JSON object representing a request
-
Serialize the request JSON object to string by using a deterministic algorithm - two objects with same property names and values should result in the same string. For example: the resulting string should be equal for the following request objects.
-
Hash the serialized request object with
sha256
algorithm. -
Format the
hsh
field by using the following format
We still need to check the permissions of those public keys in order to make sure they are authorized to perform the required operation.
See About Authorization for more details about authorization.