def handle_crypto_setup(self, version, his_pubkey): # pick a one-time key pair for myself, and return the pubkey for that # determine what the session key will be for this connection assert version == 0x1 assert len(his_pubkey) == 64 # pick a random key pair, just for this session my_key = tcc.secp256k1.generate_secret() my_pubkey = tcc.secp256k1.publickey(my_key, False) #print('my pubkey = ' + str(b2a_hex(my_pubkey))) #print('his pubkey = ' + str(b2a_hex(his_pubkey))) pt = tcc.secp256k1.multiply(my_key, b'\x04' + his_pubkey) assert pt[0] == 4 self.session_key = tcc.sha256(pt[1:]).digest() #print("session = " + str(b2a_hex(self.session_key))) # Would be nice to have nonce in addition to the counter, but # library not ready for that, and also harder on the desktop side. self.encrypt = tcc.AES(tcc.AES.CTR | tcc.AES.Encrypt, self.session_key) self.decrypt = tcc.AES(tcc.AES.CTR | tcc.AES.Decrypt, self.session_key) from main import settings xfp = settings.get('xfp', 0) xpub = settings.get('xpub', '') assert my_pubkey[0] == 0x04 return b'mypb' + my_pubkey[1:] + pack('<II', xfp, len(xpub)) + xpub
def read_file(self, fd, password, max_size=2000, progress_fcn=None): # read a file we wrote; unlikely to work on anything else. # assuming single file contained inside fhdr = FileHeader.read(fd) assert fhdr.has_good_magic() for shdr, meta, body in SectionHeader.read_iter(fd): # read out salt data, fname, sizes fname, body_size, unpacked_size, expect_crc = self.parse_section_hdr(meta) assert len(body) == body_size assert unpacked_size <= max_size, 'too big' assert len(body) <= unpacked_size+16, 'too big, encoded' assert len(body) % 16 == 0, 'not blocked' # figure out key to be used key = self.calculate_key(password, progress_fcn) out = b'' aes = tcc.AES(tcc.AES.CBC | tcc.AES.Decrypt, key, self.iv) for blk in range(0, len(body), 16): out += aes.update(body[blk:blk+16]) # trim padding, check CRC out = out[0:unpacked_size] if masked_crc(out) != expect_crc: raise ValueError("Wrong password given, or damaged file.") # done. return contents return fname, out
async def append(self, xfp, bip39pw): # encrypt and save; always appends. from ux import ux_dramatic_pause from main import dis from actions import needs_microsd while 1: dis.fullscreen('Saving...') try: with CardSlot() as card: self._calc_key(card) data = self._read(card) if self.key else [] data.append(dict(xfp=xfp, pw=bip39pw)) encrypt = tcc.AES(tcc.AES.CTR | tcc.AES.Encrypt, self.key) msg = encrypt.update(ujson.dumps(data)) with open(self.filename(card), 'wb') as fd: fd.write(msg) await ux_dramatic_pause("Saved.", 1) return except CardMissingError: ch = await needs_microsd() if ch == 'x': # undocumented, but needs escape route break
def _read(self, card): # Return a list of saved passphrases, or empty list if fail. # Fail silently in all cases. Expect to see lots of noise here. decrypt = tcc.AES(tcc.AES.CTR | tcc.AES.Decrypt, self.key) try: msg = open(self.filename(card), 'rb').read() txt = decrypt.update(msg) return ujson.loads(txt) except: return []
def add_data(self, raw): if not self.aes: # do this late, so easier to test w/ known values. #self.aes = AES.AESCipher(self.key, mode=AES.MODE_CBC, IV=self.iv) self.aes = tcc.AES(tcc.AES.CBC | tcc.AES.Encrypt, self.key, self.iv) here = len(raw) self.pt_crc = crc32(raw, self.pt_crc) padded_len = (here + 15) & ~15 if padded_len != here: if self.padding != None: raise ValueError("can't do less than a block except at end") self.padding = (padded_len - here) raw += '\x00' * self.padding self.unpacked_size += here assert len(raw) % 16 == 0, b2a_hex(raw) self.body += self.aes.update(raw)
def get_aes(self, mode, pos): # Build AES key for en/decrypt of specific block. # Include the slot number as part of the initial counter (CTR) return tcc.AES(tcc.AES.CTR | mode, self.nvram_key, ustruct.pack('<4I', 4, 3, 2, pos))