def generate_id(self) -> None: self.creation_time = storage.device.next_u2f_counter() or 0 if not self.check_required_fields(): raise AssertionError data = { key: value for key, value in ( (_CRED_ID_RP_ID, self.rp_id), (_CRED_ID_RP_NAME, self.rp_name), (_CRED_ID_USER_ID, self.user_id), (_CRED_ID_USER_NAME, self.user_name), (_CRED_ID_USER_DISPLAY_NAME, self.user_display_name), (_CRED_ID_CREATION_TIME, self.creation_time), (_CRED_ID_HMAC_SECRET, self.hmac_secret), (_CRED_ID_USE_SIGN_COUNT, self.use_sign_count), ) if value } if self.algorithm != _DEFAULT_ALGORITHM or self.curve != _DEFAULT_CURVE: data[_CRED_ID_ALGORITHM] = self.algorithm data[_CRED_ID_CURVE] = self.curve key = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", _CRED_ID_VERSION, b"Encryption key"]).key() iv = random.bytes(12) ctx = chacha20poly1305(key, iv) ctx.auth(self.rp_id_hash) ciphertext = ctx.encrypt(cbor.encode(data)) tag = ctx.finish() self.id = _CRED_ID_VERSION + iv + ciphertext + tag if len(self.id) > CRED_ID_MAX_LENGTH: raise AssertionError
def generate_id(self) -> None: self.creation_time = storage.device.next_u2f_counter() or 0 data = cbor.encode( { key: value for key, value in ( (_CRED_ID_RP_ID, self.rp_id), (_CRED_ID_RP_NAME, self.rp_name), (_CRED_ID_USER_ID, self.user_id), (_CRED_ID_USER_NAME, self.user_name), (_CRED_ID_USER_DISPLAY_NAME, self.user_display_name), (_CRED_ID_CREATION_TIME, self.creation_time), (_CRED_ID_HMAC_SECRET, self.hmac_secret), (_CRED_ID_USE_SIGN_COUNT, self.use_sign_count), ) if value } ) key = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", _CRED_ID_VERSION, b"Encryption key"] ).key() iv = random.bytes(12) ctx = chacha20poly1305(key, iv) ctx.auth(self.rp_id_hash) ciphertext = ctx.encrypt(data) tag = ctx.finish() self.id = _CRED_ID_VERSION + iv + ciphertext + tag
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 hmac_secret_key(self) -> Optional[bytes]: # Returns the symmetric key for the hmac-secret extension also known as CredRandom. if not self.hmac_secret: return None node = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", self.id[0:4], b"hmac-secret", self.id]) return node.key()
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