1# Copyright (c) 2018 Yubico AB
2# All rights reserved.
3#
4#   Redistribution and use in source and binary forms, with or
5#   without modification, are permitted provided that the following
6#   conditions are met:
7#
8#    1. Redistributions of source code must retain the above copyright
9#       notice, this list of conditions and the following disclaimer.
10#    2. Redistributions in binary form must reproduce the above
11#       copyright notice, this list of conditions and the following
12#       disclaimer in the documentation and/or other materials provided
13#       with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
25# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27
28
29"""
30Minimal CBOR implementation supporting a subset of functionality and types
31required for FIDO 2 CTAP.
32"""
33
34import struct
35import six
36
37
38def dump_int(data, mt=0):
39    if data < 0:
40        mt = 1
41        data = -1 - data
42
43    mt = mt << 5
44    if data <= 23:
45        args = (">B", mt | data)
46    elif data <= 0xFF:
47        args = (">BB", mt | 24, data)
48    elif data <= 0xFFFF:
49        args = (">BH", mt | 25, data)
50    elif data <= 0xFFFFFFFF:
51        args = (">BI", mt | 26, data)
52    else:
53        args = (">BQ", mt | 27, data)
54    return struct.pack(*args)
55
56
57def dump_bool(data):
58    return b"\xf5" if data else b"\xf4"
59
60
61def dump_list(data):
62    return dump_int(len(data), mt=4) + b"".join([encode(x) for x in data])
63
64
65def _sort_keys(entry):
66    key = entry[0]
67    return six.indexbytes(key, 0), len(key), key
68
69
70def dump_dict(data):
71    items = [(encode(k), encode(v)) for k, v in data.items()]
72    items.sort(key=_sort_keys)
73    return dump_int(len(items), mt=5) + b"".join([k + v for (k, v) in items])
74
75
76def dump_bytes(data):
77    return dump_int(len(data), mt=2) + data
78
79
80def dump_text(data):
81    data_bytes = data.encode("utf8")
82    return dump_int(len(data_bytes), mt=3) + data_bytes
83
84
85_SERIALIZERS = [
86    (bool, dump_bool),
87    (six.integer_types, dump_int),
88    (dict, dump_dict),
89    (list, dump_list),
90    (six.text_type, dump_text),
91    (six.binary_type, dump_bytes),
92]
93
94
95def encode(data):
96    for k, v in _SERIALIZERS:
97        if isinstance(data, k):
98            return v(data)
99    raise ValueError("Unsupported value: {}".format(data))
100
101
102def load_int(ai, data):
103    if ai < 24:
104        return ai, data
105    elif ai == 24:
106        return six.indexbytes(data, 0), data[1:]
107    elif ai == 25:
108        return struct.unpack_from(">H", data)[0], data[2:]
109    elif ai == 26:
110        return struct.unpack_from(">I", data)[0], data[4:]
111    elif ai == 27:
112        return struct.unpack_from(">Q", data)[0], data[8:]
113    raise ValueError("Invalid additional information")
114
115
116def load_nint(ai, data):
117    val, rest = load_int(ai, data)
118    return -1 - val, rest
119
120
121def load_bool(ai, data):
122    return ai == 21, data
123
124
125def load_bytes(ai, data):
126    l, data = load_int(ai, data)
127    return data[:l], data[l:]
128
129
130def load_text(ai, data):
131    enc, rest = load_bytes(ai, data)
132    return enc.decode("utf8"), rest
133
134
135def load_array(ai, data):
136    l, data = load_int(ai, data)
137    values = []
138    for i in range(l):
139        val, data = decode_from(data)
140        values.append(val)
141    return values, data
142
143
144def load_map(ai, data):
145    l, data = load_int(ai, data)
146    values = {}
147    for i in range(l):
148        k, data = decode_from(data)
149        v, data = decode_from(data)
150        values[k] = v
151    return values, data
152
153
154_DESERIALIZERS = {
155    0: load_int,
156    1: load_nint,
157    2: load_bytes,
158    3: load_text,
159    4: load_array,
160    5: load_map,
161    7: load_bool,
162}
163
164
165def decode_from(data):
166    fb = six.indexbytes(data, 0)
167    return _DESERIALIZERS[fb >> 5](fb & 0b11111, data[1:])
168
169
170def decode(data):
171    value, rest = decode_from(data)
172    if rest != b"":
173        raise ValueError("Extraneous data")
174    return value
175