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