@knowledgr/rpc-auth
JSONRPC 2.0 authentication with knowledgr authorities
Specification
Overview
Request signing for JSON-RPC 2.0 implemented using knowledgr authorities.
Design Goals
- Do not require request header modification.
- Result: Signature/auth must be in message body
- Signed requests do not violate json-rpc spec.
- Result: Extensions must go into
params
.
- Result: Extensions must go into
- Method name is not obscured so that it may be routed properly to the correct handler/backend.
- Result:
method
remains unchanged by signing.
- Result:
Signed request
Requests are signed with knowledgr keys belonging to the sender.
Example JSON-RPC request:
{
"jsonrpc": "2.0",
"id": 123,
"method": "foo.bar",
"params": {
"hello": "there"
}
}
Above request signed with the posting key belonging to foo
:
{
"jsonrpc": "2.0",
"method": "foo.bar",
"id": 123,
"params": {
"__signed": {
"account": "foo",
"nonce": "1773e363793b44c3",
"params": "eyJoZWxsbyI6InRoZXJlIn0=",
"signatures": [
"1f02df499f15c8757754c11251a6e5238296f56b17f7229202fce6ccd7289e224c49c32eaf77d5905e2b4d8a8a5ddcc215c51ce45c207ef0f038328200578d1bee"
],
"timestamp": "2017-11-26T16:57:40.633Z"
}
}
}
Signature creation pseudocode:
# JSON+Base64 request params
params = base64(json_encode(request['params']))
# 8 byte nonce
nonce = random_bytes(8)
# ISO 8601 formatted timestamp
timestamp = date_now() # "2017-11-26T16:57:40.633Z"
# Signer account name
account = 'foo'
# Private posting key belonging to foo
signing_key = PrivateKey('...')
# Signing constant K (sha256('knowledgr_jsonrpc_auth'))
K = bytes_from_hex('3b3b081e46ea808d5a96b08c4bc5003f5e15767090f344faab531ec57565136b')
# first round of sha256
first = sha256(timestamp + account + method + params)
# message to be signed
message = sha256(K + first + nonce)
signature = ecdsa_sign(message, signing_key)
Signature validation
- Entire request must be <64k for sanity/anti-DoS
- Request must be valid json and json-rpc
-
request['params']['__signed']
must exist -
request['params']['__signed']
must be the only item inrequest['params']
-
request['params']['__signed']['params']
must be valid base64 -
request['params']['__signed']['params']
when base64 decoded must be valid json -
request['params']['__signed']['nonce']
must exist and be a hex string of length 16 (8 bytes decoded) -
request['params']['__signed']['timestamp']
must exist and be a valid iso8601 datetime ending in Z -
request['params']['__signed']['timestamp']
must be within the last 60 seconds -
request['params']['__signed']['account']
must be a valid knowledgr blockchain account -
request['params']['__signed']['signature']
must be a hex string >= 64 chars (32+ bytes decoded) - construct
first = sha256( request['params']['__signed']['timestamp'] + request['params']['__signed']['account'] + request['method'] + request['params']['__signed']['params'] ).bytes()
- construct
signedstring = sha256( K + first + unhexlify(nonce)).bytes()
- check signature, signedstring against posting authorities for
request['params']['__signed']['account']