def parse_data(self, data): """Parse data stored on the card""" # smartcard id to understand it's our data fingerprint = tagged_hash("scid", self.secret)[:4] l = len(self.MAGIC) + 4 prefix = data[:l + 1] encrypted = True if prefix == bytes([l]) + self.MAGIC + fingerprint: # smartcard encryption key key = tagged_hash("scenc", self.secret) elif prefix == bytes([l]) + self.MAGIC + b"\x00" * 4: key = b"\xcc" * 32 encrypted = False else: raise KeyStoreError( "Looks like stored data is created on a different device.") adata, plaintext = aead_decrypt(data, key=key) s = BytesIO(plaintext) o = {} while True: k = s.read(1) if len(k) == 0: break l = s.read(1)[0] v = s.read(l) assert len(v) == l if k in self.KEYS: o[self.KEYS[k]] = v return o, encrypted
def unlock(self, pin): """ Unlock the keystore, raises PinError if PIN is invalid. Raises CriticalErrorWipeImmediately if no attempts left. """ # decrease the counter self._pin_attempts_left -= 1 self.save_state() # check we have attempts if self._pin_attempts_left <= 0: self.wipe(self.path) raise CriticalErrorWipeImmediately("No more PIN attempts!\nWipe!") # calculate hmac with entered PIN key = tagged_hash("pin", self.secret) pin_hmac = hmac.new(key=key, msg=pin.encode(), digestmod="sha256").digest() # check hmac is the same if pin_hmac != self.pin: raise PinError("Invalid PIN!\n%d of %d attempts left..." % (self._pin_attempts_left, self._pin_attempts_max)) self._pin_attempts_left = self._pin_attempts_max self._is_locked = False self.save_state() # derive PIN keys for reckless storage self.pin_secret = tagged_hash("pin", self.secret + pin.encode()) self.load_enc_secret()
def serialize_data(self, obj): """Serialize secrets for storage on the card""" r = b"" for k in self.KEYS: v = self.KEYS[k] if v in obj: r += k + bytes([len(obj[v])]) + obj[v] # smartcard encryption key key = tagged_hash("scenc", self.secret) # smartcard id to understand it's our data fingerprint = tagged_hash("scid", self.secret)[:4] res = aead_encrypt(key, self.MAGIC + fingerprint, r) print(res) return res
def set_pin(self, pin): """Saves hmac of the PIN code for verification later""" # set up pin key = tagged_hash("pin", self.secret) self.pin = hmac.new(key=key, msg=pin, digestmod="sha256").digest() self.pin_secret = tagged_hash("pin", self.secret + pin.encode()) self.save_state() # update encryption secret if self.enc_secret is None: self.enc_secret = get_random_bytes(32) self.save_aead(self.path + "/enc_secret", plaintext=self.enc_secret, key=self.pin_secret) # call unlock now self.unlock(pin)
def serialize_data(self, obj, encrypt=True): """Serialize secrets for storage on the card""" r = b"" for k in self.KEYS: v = self.KEYS[k] if v in obj: r += k + bytes([len(obj[v])]) + obj[v] if encrypt: # smartcard encryption key key = tagged_hash("scenc", self.secret) # smartcard id to understand it's our data fingerprint = tagged_hash("scid", self.secret)[:4] res = aead_encrypt(key, self.MAGIC + fingerprint, r) else: # "unencrypted" data fingerprint = b"\x00" * 4 res = aead_encrypt(b"\xcc" * 32, self.MAGIC + fingerprint, r) return res
def get_auth_word(self, pin_part): """ Get anti-phishing word to check internal secret from part of the PIN so user can stop when he sees wrong words """ key = tagged_hash("auth", self.secret) h = hmac.new(key, pin_part, digestmod="sha256").digest() # wordlist is 2048 long (11 bits) so # this modulo doesn't create an offset word_number = int.from_bytes(h[:2], 'big') % len(bip39.WORDLIST) return bip39.WORDLIST[word_number]
def is_locked(self): """ Override this method!!! with your locking check function """ # hack: we don't support PIN but # we need enc_secret, so let's do it here. # DONT USE THIS IF YOU HAVE PIN SUPPORT! if self.enc_secret is None: self.enc_secret = tagged_hash("enc", self.secret) return False
def get_auth_word(self, pin_part): """ Get anti-phishing word to check integrity of the device and the card. Internal secret, verified card's pubkey and PIN part are used to generate the words. The user should stop entering the PIN if he sees wrong words. It can happen if the device or the card is different. """ # check if secure channel is already open # so the card can't lie about it's pubkey if not self.applet.is_secure_channel_open: raise KeyStoreError("Secure channel is closed.") # use both internal secret and card's key to generate # anti-phishing words key = tagged_hash("auth", self.secret + self.applet.card_pubkey) h = hmac.new(key, pin_part, digestmod="sha256").digest() # wordlist is 2048 long (11 bits) so # this modulo doesn't create an offset word_number = int.from_bytes(h[:2], "big") % len(bip39.WORDLIST) return bip39.WORDLIST[word_number]
def _unlock(self, pin): """ Implement this. Unlock the keystore, raises PinError if PIN is invalid. Raises CriticalErrorWipeImmediately if no attempts left. """ # check we have attempts if self.pin_attempts_left <= 0: # wipe is happening automatically on this error raise CriticalErrorWipeImmediately("No more PIN attempts!\nWipe!") # check PIN code somehow, raise PinError if it's incorrect # for reference - first decrease PIN counter, then check PIN # raise PIN Error if it's invalid like this: # if pin == "INVALID PIN": # raise PinError("Invalid PIN!\n%d of %d attempts left..." % ( # self._pin_attempts_left, self._pin_attempts_max) # ) # reset PIN counter here and unlock # set encryption secret somehow mb save it # don't use this approach, it's just for reference self.enc_secret = tagged_hash("enc", self.secret)
def app_secret(self, app): return tagged_hash(app, self.secret)
def settings_key(self): return tagged_hash("settings key", self.secret)
def hexid(self): return hexlify( tagged_hash("smartcard/pubkey", self.applet.card_pubkey)[:4]).decode()
def userkey(self): if self._userkey is None: # userkey is uniquie for every smart card self._userkey = tagged_hash( "userkey", self.secret + (self.applet.card_pubkey or b"")) return self._userkey
def sdpath(self): hexid = hexlify(tagged_hash("sdid", self.secret)[:4]).decode() return platform.fpath("/sd/specterdiy%s" % hexid)
def fileprefix(self, path): if path is self.flashpath: return 'reckless' hexid = hexlify(tagged_hash("sdid", self.secret)[:4]).decode() return "specterdiy%s" % hexid