1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
"""
<Program Name>
formats.py
<Author>
Geremy Condra
Vladimir Diaz <vladimir.v.diaz@gmail.com>
<Started>
Refactored April 30, 2012. -vladimir.v.diaz
<Copyright>
2008-2011 The Tor Project, Inc
2012-2016 New York University and the TUF contributors
2016-2021 Securesystemslib contributors
See LICENSE for licensing information.
<Purpose>
Implements canonical json (OLPC) encoder.
"""
from typing import Callable, Optional, Union
from securesystemslib import exceptions
def _canonical_string_encoder(string: str) -> str:
"""
<Purpose>
Encode 'string' to canonical string format. By using the escape sequence ('\')
which is mandatory to use for quote and backslash.
backslash: \\ translates to \\\\
quote: \" translates to \\".
<Arguments>
string:
The string to encode.
<Exceptions>
None.
<Side Effects>
None.
<Returns>
A string with the canonical-encoded 'string' embedded.
"""
string = '"{}"'.format(string.replace("\\", "\\\\").replace('"', '\\"'))
return string
def _encode_canonical(
object: Union[bool, None, str, int, tuple, list, dict], output_function: Callable
) -> None:
# Helper for encode_canonical. Older versions of json.encoder don't
# even let us replace the separators.
if isinstance(object, str):
output_function(_canonical_string_encoder(object))
elif object is True:
output_function("true")
elif object is False:
output_function("false")
elif object is None:
output_function("null")
elif isinstance(object, int):
output_function(str(object))
elif isinstance(object, (tuple, list)):
output_function("[")
if len(object):
for item in object[:-1]:
_encode_canonical(item, output_function)
output_function(",")
_encode_canonical(object[-1], output_function)
output_function("]")
elif isinstance(object, dict):
output_function("{")
if len(object):
items = sorted(object.items())
for key, value in items[:-1]:
output_function(_canonical_string_encoder(key))
output_function(":")
_encode_canonical(value, output_function)
output_function(",")
key, value = items[-1]
output_function(_canonical_string_encoder(key))
output_function(":")
_encode_canonical(value, output_function)
output_function("}")
else:
raise exceptions.FormatError("I cannot encode " + repr(object))
def encode_canonical(
object: Union[bool, None, str, int, tuple, list, dict],
output_function: Optional[Callable] = None,
) -> Union[str, None]:
"""
<Purpose>
Encoding an object so that it is always has the same string format
independent of the original format. This allows to compute always the same hash
or signature for that object.
Encode 'object' in canonical JSON form, as specified at
http://wiki.laptop.org/go/Canonical_JSON . It's a restricted
dialect of JSON in which keys are always lexically sorted,
there is no whitespace, floats aren't allowed, and only quote
and backslash get escaped. The result is encoded in UTF-8,
and the resulting bits are passed to output_function (if provided),
or joined into a string and returned.
Note: This function should be called prior to computing the hash or
signature of a JSON object in securesystemslib. For example, generating a
signature of a signing role object such as 'ROOT_SCHEMA' is required to
ensure repeatable hashes are generated across different json module
versions and platforms. Code elsewhere is free to dump JSON objects in any
format they wish (e.g., utilizing indentation and single quotes around
object keys). These objects are only required to be in "canonical JSON"
format when their hashes or signatures are needed.
>>> encode_canonical("")
'""'
>>> encode_canonical([1, 2, 3])
'[1,2,3]'
>>> encode_canonical([])
'[]'
>>> encode_canonical({"A": [99]})
'{"A":[99]}'
>>> encode_canonical({"x" : 3, "y" : 2})
'{"x":3,"y":2}'
<Arguments>
object:
The object to be encoded.
output_function:
The result will be passed as arguments to 'output_function'
(e.g., output_function('result')).
<Exceptions>
securesystemslib.exceptions.FormatError, if 'object' cannot be encoded or
'output_function' is not callable.
<Side Effects>
The results are fed to 'output_function()' if 'output_function' is set.
<Returns>
A string representing the 'object' encoded in canonical JSON form.
"""
result: Union[None, list] = None
# If 'output_function' is unset, treat it as
# appending to a list.
if output_function is None:
result = []
output_function = result.append
try:
_encode_canonical(object, output_function)
except (TypeError, exceptions.FormatError) as e:
message: str = "Could not encode " + repr(object) + ": " + str(e)
raise exceptions.FormatError(message)
# Return the encoded 'object' as a string.
# Note: Implies 'output_function' is None,
# otherwise results are sent to 'output_function'.
if result is not None:
return "".join(result)
return None
|