File: formats.py

package info (click to toggle)
python-securesystemslib 1.3.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,316 kB
  • sloc: python: 5,319; sh: 38; makefile: 5
file content (172 lines) | stat: -rwxr-xr-x 5,527 bytes parent folder | download | duplicates (2)
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