1#!/usr/bin/env python3 2 3# This file is part of the Trezor project. 4# 5# Copyright (C) 2012-2022 SatoshiLabs and contributors 6# 7# This library is free software: you can redistribute it and/or modify 8# it under the terms of the GNU Lesser General Public License version 3 9# as published by the Free Software Foundation. 10# 11# This library is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU Lesser General Public License for more details. 15# 16# You should have received a copy of the License along with this library. 17# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>. 18 19""" 20Use Trezor as a hardware key for opening EncFS filesystem! 21 22Usage: 23 24encfs --standard --extpass=./encfs_aes_getpass.py ~/.crypt ~/crypt 25""" 26 27import hashlib 28import json 29import os 30import sys 31from typing import TYPE_CHECKING, Sequence 32 33import trezorlib 34import trezorlib.misc 35from trezorlib.client import TrezorClient 36from trezorlib.tools import Address 37from trezorlib.transport import enumerate_devices 38from trezorlib.ui import ClickUI 39 40version_tuple = tuple(map(int, trezorlib.__version__.split("."))) 41if not (0, 11) <= version_tuple < (0, 14): 42 raise RuntimeError("trezorlib version mismatch (required: 0.13, 0.12, or 0.11)") 43 44 45if TYPE_CHECKING: 46 from trezorlib.transport import Transport 47 48 49def wait_for_devices() -> Sequence["Transport"]: 50 devices = enumerate_devices() 51 while not len(devices): 52 sys.stderr.write("Please connect Trezor to computer and press Enter...") 53 input() 54 devices = enumerate_devices() 55 56 return devices 57 58 59def choose_device(devices: Sequence["Transport"]) -> "Transport": 60 if not len(devices): 61 raise RuntimeError("No Trezor connected!") 62 63 if len(devices) == 1: 64 try: 65 return devices[0] 66 except IOError: 67 raise RuntimeError("Device is currently in use") 68 69 i = 0 70 sys.stderr.write("----------------------------\n") 71 sys.stderr.write("Available devices:\n") 72 for d in devices: 73 try: 74 client = TrezorClient(d, ui=ClickUI()) 75 except IOError: 76 sys.stderr.write("[-] <device is currently in use>\n") 77 continue 78 79 if client.features.label: 80 sys.stderr.write(f"[{i}] {client.features.label}\n") 81 else: 82 sys.stderr.write(f"[{i}] <no label>\n") 83 client.close() 84 i += 1 85 86 sys.stderr.write("----------------------------\n") 87 sys.stderr.write("Please choose device to use:") 88 89 try: 90 device_id = int(input()) 91 return devices[device_id] 92 except Exception: 93 raise ValueError("Invalid choice, exiting...") 94 95 96def main() -> None: 97 98 if "encfs_root" not in os.environ: 99 sys.stderr.write( 100 "\nThis is not a standalone script and is not meant to be run independently.\n" 101 ) 102 sys.stderr.write( 103 "\nUsage: encfs --standard --extpass=./encfs_aes_getpass.py ~/.crypt ~/crypt\n" 104 ) 105 sys.exit(1) 106 107 devices = wait_for_devices() 108 transport = choose_device(devices) 109 client = TrezorClient(transport, ui=ClickUI()) 110 111 rootdir = os.environ["encfs_root"] # Read "man encfs" for more 112 passw_file = os.path.join(rootdir, "password.dat") 113 114 if not os.path.exists(passw_file): 115 # New encfs drive, let's generate password 116 117 sys.stderr.write("Please provide label for new drive: ") 118 label = input() 119 120 sys.stderr.write("Computer asked Trezor for new strong password.\n") 121 122 # 32 bytes, good for AES 123 trezor_entropy = trezorlib.misc.get_entropy(client, 32) 124 urandom_entropy = os.urandom(32) 125 passw = hashlib.sha256(trezor_entropy + urandom_entropy).digest() 126 127 if len(passw) != 32: 128 raise ValueError("32 bytes password expected") 129 130 bip32_path = Address([10, 0]) 131 passw_encrypted = trezorlib.misc.encrypt_keyvalue( 132 client, bip32_path, label, passw, False, True 133 ) 134 135 data = { 136 "label": label, 137 "bip32_path": bip32_path, 138 "password_encrypted_hex": passw_encrypted.hex(), 139 } 140 141 json.dump(data, open(passw_file, "w")) 142 143 # Let's load password 144 data = json.load(open(passw_file, "r")) 145 146 passw = trezorlib.misc.decrypt_keyvalue( 147 client, 148 data["bip32_path"], 149 data["label"], 150 bytes.fromhex(data["password_encrypted_hex"]), 151 False, 152 True, 153 ) 154 155 print(passw) 156 157 158if __name__ == "__main__": 159 main() 160