def _validate_address_data_protocol_magic(address_data_encoded: bytes, protocol_magic: int) -> None: """ Determines whether the correct protocol magic (or none) is included in the address. Addresses on mainnet don't contain protocol magic, but addresses on the testnet do. """ address_data = cbor.decode(address_data_encoded) if not isinstance(address_data, list) or len(address_data) < 2: raise INVALID_ADDRESS attributes = address_data[1] if protocol_magics.is_mainnet(protocol_magic): if PROTOCOL_MAGIC_KEY in attributes: raise NETWORK_MISMATCH else: # testnet if len(attributes) == 0 or PROTOCOL_MAGIC_KEY not in attributes: raise NETWORK_MISMATCH protocol_magic_cbor = attributes[PROTOCOL_MAGIC_KEY] address_protocol_magic = cbor.decode(protocol_magic_cbor) if not isinstance(address_protocol_magic, int): raise INVALID_ADDRESS if address_protocol_magic != protocol_magic: raise NETWORK_MISMATCH
def _validate_auxiliary_data_blob(auxiliary_data_blob: bytes) -> None: try: # validation to prevent CBOR injection and invalid CBOR # we don't validate data format, just that it's a valid CBOR cbor.decode(auxiliary_data_blob) except Exception: raise INVALID_AUXILIARY_DATA
def _validate_protocol_magic(address_data_encoded: bytes, protocol_magic: int) -> None: """ Determines whether the correct protocol magic (or none) is included in the address. Addresses on mainnet don't contain protocol magic, but addresses on the testnet do. """ address_data = cbor.decode(address_data_encoded) if not isinstance(address_data, list) or len(address_data) < 2: raise wire.ProcessError("Invalid address") attributes = address_data[1] if protocol_magics.is_mainnet(protocol_magic): if PROTOCOL_MAGIC_KEY in attributes: raise wire.ProcessError("Output address network mismatch") else: # testnet if len(attributes) == 0 or PROTOCOL_MAGIC_KEY not in attributes: raise wire.ProcessError("Output address network mismatch") protocol_magic_cbor = attributes[PROTOCOL_MAGIC_KEY] address_protocol_magic = cbor.decode(protocol_magic_cbor) if not isinstance(address_protocol_magic, int): raise wire.ProcessError("Invalid address") if address_protocol_magic != protocol_magic: raise wire.ProcessError("Output address network mismatch")
def from_cred_id( cls, cred_id: bytes, rp_id_hash: Optional[bytes] ) -> "Fido2Credential": if len(cred_id) < CRED_ID_MIN_LENGTH or cred_id[0:4] != _CRED_ID_VERSION: raise ValueError # invalid length or version key = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", cred_id[0:4], b"Encryption key"] ).key() iv = cred_id[4:16] ciphertext = cred_id[16:-16] tag = cred_id[-16:] if rp_id_hash is None: ctx = chacha20poly1305(key, iv) data = ctx.decrypt(ciphertext) try: rp_id = cbor.decode(data)[_CRED_ID_RP_ID] except Exception as e: raise ValueError from e # CBOR decoding failed rp_id_hash = hashlib.sha256(rp_id).digest() ctx = chacha20poly1305(key, iv) ctx.auth(rp_id_hash) data = ctx.decrypt(ciphertext) if not utils.consteq(ctx.finish(), tag): raise ValueError # inauthentic ciphertext try: data = cbor.decode(data) except Exception as e: raise ValueError from e # CBOR decoding failed if not isinstance(data, dict): raise ValueError # invalid CBOR data cred = cls() cred.rp_id = data.get(_CRED_ID_RP_ID, None) cred.rp_id_hash = rp_id_hash cred.rp_name = data.get(_CRED_ID_RP_NAME, None) cred.user_id = data.get(_CRED_ID_USER_ID, None) cred.user_name = data.get(_CRED_ID_USER_NAME, None) cred.user_display_name = data.get(_CRED_ID_USER_DISPLAY_NAME, None) cred.creation_time = data.get(_CRED_ID_CREATION_TIME, 0) cred.hmac_secret = data.get(_CRED_ID_HMAC_SECRET, False) cred.use_sign_count = data.get(_CRED_ID_USE_SIGN_COUNT, False) cred.algorithm = data.get(_CRED_ID_ALGORITHM, _DEFAULT_ALGORITHM) cred.curve = data.get(_CRED_ID_CURVE, _DEFAULT_CURVE) cred.id = cred_id if ( (_CRED_ID_ALGORITHM in data) != (_CRED_ID_CURVE in data) or not cred.check_required_fields() or not cred.check_data_types() or hashlib.sha256(cred.rp_id).digest() != rp_id_hash ): raise ValueError # data consistency check failed return cred
def from_cred_id( cred_id: bytes, rp_id_hash: Optional[bytes]) -> Optional["Fido2Credential"]: if len(cred_id ) < _CRED_ID_MIN_LENGTH or cred_id[0:4] != _CRED_ID_VERSION: return None key = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", cred_id[0:4], b"Encryption key"]).key() iv = cred_id[4:16] ciphertext = cred_id[16:-16] tag = cred_id[-16:] if rp_id_hash is None: ctx = chacha20poly1305(key, iv) data = ctx.decrypt(ciphertext) try: rp_id = cbor.decode(data)[_CRED_ID_RP_ID] except Exception: return None rp_id_hash = hashlib.sha256(rp_id).digest() ctx = chacha20poly1305(key, iv) ctx.auth(rp_id_hash) data = ctx.decrypt(ciphertext) if not utils.consteq(ctx.finish(), tag): return None try: data = cbor.decode(data) except Exception: return None if not isinstance(data, dict): return None cred = Fido2Credential() cred.rp_id = data.get(_CRED_ID_RP_ID, None) cred.rp_id_hash = rp_id_hash cred.rp_name = data.get(_CRED_ID_RP_NAME, None) cred.user_id = data.get(_CRED_ID_USER_ID, None) cred.user_name = data.get(_CRED_ID_USER_NAME, None) cred.user_display_name = data.get(_CRED_ID_USER_DISPLAY_NAME, None) cred.creation_time = data.get(_CRED_ID_CREATION_TIME, 0) cred.hmac_secret = data.get(_CRED_ID_HMAC_SECRET, False) cred.use_sign_count = data.get(_CRED_ID_USE_SIGN_COUNT, False) cred.id = cred_id if (not cred.check_required_fields() or not cred.check_data_types() or hashlib.sha256(cred.rp_id).digest() != rp_id_hash): return None return cred
async def sign_tx(ctx, msg): keychain = await seed.get_keychain(ctx) progress.init(msg.transactions_count, "Loading data") try: attested = len(msg.inputs) * [False] input_coins_sum = 0 # request transactions tx_req = CardanoTxRequest() for index in range(msg.transactions_count): progress.advance() tx_ack = await request_transaction(ctx, tx_req, index) tx_hash = hashlib.blake2b(data=bytes(tx_ack.transaction), outlen=32).digest() tx_decoded = cbor.decode(tx_ack.transaction) for i, input in enumerate(msg.inputs): if not attested[i] and input.prev_hash == tx_hash: attested[i] = True outputs = tx_decoded[1] amount = outputs[input.prev_index][1] input_coins_sum += amount if not all(attested): raise wire.ProcessError("No tx data sent for input " + str(attested.index(False))) transaction = Transaction(msg.inputs, msg.outputs, keychain, msg.protocol_magic, input_coins_sum) # clear progress bar display_homescreen() for i in msg.inputs: await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE) # sign the transaction bundle and prepare the result tx_body, tx_hash = transaction.serialise_tx() tx = CardanoSignedTx(tx_body=tx_body, tx_hash=tx_hash) except ValueError as e: if __debug__: log.exception(__name__, e) raise wire.ProcessError("Signing failed") # display the transaction in UI if not await show_tx( ctx, transaction.output_addresses, transaction.outgoing_coins, transaction.fee, transaction.network_name, transaction.inputs, transaction.outputs, ): raise wire.ActionCancelled("Signing cancelled") return tx
def test_cbor_tuples(self): """ Tuples should be encoded as arrays and decoded back as lists. """ test_vectors = [ ([], '80'), ([1, 2, 3], '83010203'), ([1, [2, 3], [4, 5]], '8301820203820405'), (list(range(1, 26)), '98190102030405060708090a0b0c0d0e0f101112131415161718181819'), ] for val, encoded in test_vectors: value_tuple = tuple(val) self.assertEqual(unhexlify(encoded), encode(value_tuple)) self.assertEqual(val, decode(unhexlify(encoded)))
def _validate_metadata(metadata: bytes) -> None: if not metadata: return if len(metadata) > MAX_METADATA_LENGTH: raise INVALID_METADATA try: # this also raises an error if there's some data remaining decoded = cbor.decode(metadata) except Exception: raise INVALID_METADATA if not isinstance(decoded, dict): raise INVALID_METADATA
def test_cbor_ordered_map(self): """ OrderedMaps should be encoded as maps without any ordering and decoded back as dicts. """ test_vectors = [ ({}, 'a0'), ([[1, 2], [3, 4]], 'a201020304'), ([[3, 4], [1, 2]], 'a203040102'), ] for val, encoded_hex in test_vectors: ordered_map = OrderedMap() for key, value in val: ordered_map[key] = value encoded = unhexlify(encoded_hex) self.assertEqual(encode(ordered_map), encoded) self.assertEqual(decode(encoded), {k: v for k, v in val})
def is_safe_output_address(address) -> bool: """ Determines whether it is safe to include the address as-is as a tx output, preventing unintended side effects (e.g. CBOR injection) """ try: address_hex = base58.decode(address) address_unpacked = cbor.decode(address_hex) except ValueError as e: if __debug__: log.exception(__name__, e) return False if not isinstance(address_unpacked, list) or len(address_unpacked) != 2: return False address_data_encoded = address_unpacked[0] if not isinstance(address_data_encoded, bytes): return False return _encode_address_raw(address_data_encoded) == address
def _decode_address_raw(address: bytes) -> bytes: try: address_unpacked = cbor.decode(address) except ValueError as e: if __debug__: log.exception(__name__, e) raise INVALID_ADDRESS if not isinstance(address_unpacked, list) or len(address_unpacked) != 2: raise INVALID_ADDRESS address_data_encoded = address_unpacked[0] if not isinstance(address_data_encoded, bytes): raise INVALID_ADDRESS address_crc = address_unpacked[1] if not isinstance(address_crc, int): raise INVALID_ADDRESS if address_crc != crc.crc32(address_data_encoded): raise INVALID_ADDRESS return address_data_encoded
def _decode_raw(address: bytes) -> bytes: try: address_unpacked = cbor.decode(address) except ValueError as e: if __debug__: log.exception(__name__, e) raise wire.ProcessError("Invalid address") if not isinstance(address_unpacked, list) or len(address_unpacked) != 2: raise wire.ProcessError("Invalid address") address_data_encoded = address_unpacked[0] if not isinstance(address_data_encoded, bytes): raise wire.ProcessError("Invalid address") address_crc = address_unpacked[1] if not isinstance(address_crc, int): raise wire.ProcessError("Invalid address") if address_crc != crc.crc32(address_data_encoded): raise wire.ProcessError("Invalid address") return address_data_encoded
def test_cbor_encoding(self): test_vectors = [ # unsigned integers (0, '00'), (1, '01'), (10, '0a'), (23, '17'), (24, '1818'), (25, '1819'), (100, '1864'), (1000, '1903e8'), (1000000, '1a000f4240'), (1000000000000, '1b000000e8d4a51000'), # negative integers (-1, '20'), (-10, '29'), (-24, '37'), (-25, '3818'), (-26, '3819'), (-100, '3863'), (-1000, '3903E7'), (-1000000, '3A000F423F'), (-1000000000000, '3B000000E8D4A50FFF'), # binary strings (b'', '40'), (unhexlify('01020304'), '4401020304'), # text strings ('', '60'), ('Fun', '6346756e'), (u'P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f', '786550c599c3adc5a165726ec49b20c5be6c75c5a56f75c48d6bc3bd206bc5afc58820c3ba70c49b6c20c48fc3a162656c736bc3a920c3b36479207ac3a16b65c5996ec3bd2075c48d65c5882062c49bc5bec3ad20706f64c3a96c207ac3b36e7920c3ba6cc5af' ), # tags (Tagged(1, 1363896240), 'c11a514b67b0'), (Tagged(23, unhexlify('01020304')), 'd74401020304'), # arrays ([], '80'), ([1, 2, 3], '83010203'), ([1, [2, 3], [4, 5]], '8301820203820405'), (list(range(1, 26)), '98190102030405060708090a0b0c0d0e0f101112131415161718181819'), # maps ({}, 'a0'), ({ 1: 2, 3: 4 }, 'a201020304'), # indefinite (IndefiniteLengthArray([]), '9fff'), (IndefiniteLengthArray([1, [2, 3], [4, 5]]), '9f01820203820405ff'), (IndefiniteLengthArray([1, [2, 3], IndefiniteLengthArray([4, 5])]), '9f018202039f0405ffff'), # boolean (True, 'f5'), (False, 'f4'), # null (None, 'f6'), ] for val, encoded_hex in test_vectors: encoded = unhexlify(encoded_hex) self.assertEqual(encode(val), encoded) self.assertEqual(decode(encoded), val)