summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--docs/server-server/signing.rst103
1 files changed, 103 insertions, 0 deletions
diff --git a/docs/server-server/signing.rst b/docs/server-server/signing.rst
new file mode 100644
index 0000000000..35a4224f56
--- /dev/null
+++ b/docs/server-server/signing.rst
@@ -0,0 +1,103 @@
+Signing JSON
+============
+
+JSON is signed by encoding the JSON object without ``signatures`` or ``meta``
+keys using a canonical encoding. The JSON bytes are then signed using the
+signature algorithm and the signature encoded using base64 with the padding
+striped. The resulting base64 signature is added to an object under the
+*signing key identifier* which is added to the ``signatures`` object under the
+name of the server signing it which is added back to the original JSON object
+along with the ``meta`` object.
+
+The *signing key identifier* is the concatenation of the *signing algorithm*
+and a *key version*. The *signing algorithm* identifies the algorithm used to
+sign the JSON. The currently support value for *signing algorithm* is
+``ed25519`` as implemented by NACL (http://nacl.cr.yp.to/). The *key version*
+is used to distinguish between different signing keys used by the same entity.
+
+The ``meta`` object and the ``signatures`` object are not covered by the
+signature. Therefore intermediate servers can add metadata such as time stamps
+and additional signatures.
+
+
+::
+
+  {
+     "name": "example.org",
+     "signing_keys": {
+       "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ"
+     },
+     "meta": {
+        "retrieved_ts_ms": 922834800000
+     },
+     "signatures": {
+        "example.org": {
+           "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw"
+        }
+     }
+  }
+
+::
+
+  def sign_json(value, signing_key, signing_name):
+      signatures = value.pop("signatures", {})
+      signatures_for_name = signatures.pop(signing_name, {})
+      meta = value.pop("meta", None)
+      signature = signing_key.sign(canonical_json(value))
+      key_identifier = "%s:%s" % (signing_key.algorithm, signing_key.version)
+      signatures_for_name[key_identifier] = encode_base64(signature.signature)
+      signatures[signing_name] = signatures_for_name
+      value["signatures"] = signatures
+      if meta is not None:
+          value["meta"] = meta
+      return value
+
+Canonical JSON
+--------------
+
+The canonical JSON encoding for a value is the shortest UTF-8 JSON encoding
+with dictionary keys lexicographically sorted by unicode codepoint. Numbers in
+the JSON value must be integers in the range [-(2**53)+1, (2**53)-1].
+
+::
+
+ import json
+
+ def canonical_json(value):
+     return json.dumps(
+         value,
+         ensure_ascii=False,
+         separators=(',',':'),
+         sort_keys=True,
+     ).encode("UTF-8")
+
+Grammar
++++++++
+
+Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing
+insignificant whitespace, fractions, exponents and redundant character escapes
+
+::
+
+ value     = false / null / true / object / array / number / string
+ false     = %x66.61.6c.73.65
+ null      = %x6e.75.6c.6c
+ true      = %x74.72.75.65
+ object    = %x7B [ member *( %x2C member ) ] %7D
+ member    = string %x3A value
+ array     = %x5B [ value *( %x2C value ) ] %5B
+ number    = [ %x2D ] int
+ int       = %x30 / ( %x31-39 *digit )
+ digit     = %x30-39
+ string    = %x22 *char %x22
+ char      = unescaped / %x5C escaped
+ unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
+ escaped   = %x22 ; "    quotation mark  U+0022
+           / %x5C ; \    reverse solidus U+005C
+           / %x62 ; b    backspace       U+0008
+           / %x66 ; f    form feed       U+000C
+           / %x6E ; n    line feed       U+000A
+           / %x72 ; r    carriage return U+000D
+           / %x74 ; t    tab             U+0009
+           / %x75.30.30.30 (%x30-37 / %x62 / %x65-66) ; u000X
+           / %x75.30.30.31 (%x30-39 / %x61-66)        ; u001X