예제 #1
0
    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
예제 #2
0
    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
예제 #4
0
    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()
예제 #5
0
    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