1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-10 23:40:58 +00:00

core/webauthn: Implement support for Ed25519 signatures in FIDO2.

This commit is contained in:
Andrew Kozlik 2020-02-27 18:58:21 +01:00 committed by Andrew Kozlik
parent f359e36273
commit e378820f7f
4 changed files with 225 additions and 133 deletions

View File

@ -0,0 +1,21 @@
from micropython import const
# COSE Key Object Parameter labels
COSE_KEY_KTY = const(1) # identification of the key type
COSE_KEY_ALG = const(3) # algorithm to be used with the key
COSE_KEY_CRV = const(-1) # elliptic curve identifier
COSE_KEY_X = const(-2) # x coordinate of the public key or encoded public key
COSE_KEY_Y = const(-3) # y coordinate of the public key
# COSE Algorithm values
COSE_ALG_ES256 = const(-7) # ECDSA with SHA-256
COSE_ALG_EDDSA = const(-8) # EdDSA
COSE_ALG_ECDH_ES_HKDF_256 = const(-25) # ephemeral-static ECDH with HKDF SHA-256
# COSE Key Type values
COSE_KEYTYPE_OKP = const(1) # octet key pair
COSE_KEYTYPE_EC2 = const(2) # elliptic curve keys with x- and y-coordinate pair
# COSE Elliptic Curve values
COSE_CURVE_P256 = const(1) # NIST P-256 curve
COSE_CURVE_ED25519 = const(6) # Ed25519 curve

View File

@ -4,12 +4,14 @@ from ubinascii import hexlify
import storage.device import storage.device
from trezor import log, utils from trezor import log, utils
from trezor.crypto import bip32, chacha20poly1305, hashlib, hmac, random from trezor.crypto import bip32, chacha20poly1305, der, hashlib, hmac, random
from trezor.crypto.curve import ed25519, nist256p1
from apps.common import HARDENED, cbor, seed from apps.common import HARDENED, cbor, seed
from apps.webauthn import common
if False: if False:
from typing import Optional from typing import Iterable, Optional
# Credential ID values # Credential ID values
_CRED_ID_VERSION = b"\xf1\xd0\x02\x00" _CRED_ID_VERSION = b"\xf1\xd0\x02\x00"
@ -17,14 +19,26 @@ _CRED_ID_MIN_LENGTH = const(33)
_KEY_HANDLE_LENGTH = const(64) _KEY_HANDLE_LENGTH = const(64)
# Credential ID keys # Credential ID keys
_CRED_ID_RP_ID = const(0x01) _CRED_ID_RP_ID = const(1)
_CRED_ID_RP_NAME = const(0x02) _CRED_ID_RP_NAME = const(2)
_CRED_ID_USER_ID = const(0x03) _CRED_ID_USER_ID = const(3)
_CRED_ID_USER_NAME = const(0x04) _CRED_ID_USER_NAME = const(4)
_CRED_ID_USER_DISPLAY_NAME = const(0x05) _CRED_ID_USER_DISPLAY_NAME = const(5)
_CRED_ID_CREATION_TIME = const(0x06) _CRED_ID_CREATION_TIME = const(6)
_CRED_ID_HMAC_SECRET = const(0x07) _CRED_ID_HMAC_SECRET = const(7)
_CRED_ID_USE_SIGN_COUNT = const(0x08) _CRED_ID_USE_SIGN_COUNT = const(8)
_CRED_ID_ALGORITHM = const(9)
_CRED_ID_CURVE = const(10)
# Defaults
_DEFAULT_ALGORITHM = common.COSE_ALG_ES256
_DEFAULT_CURVE = common.COSE_CURVE_P256
# Curves
_CURVE_NAME = {
common.COSE_CURVE_ED25519: "ed25519",
common.COSE_CURVE_P256: "nist256p1",
}
# Key paths # Key paths
_U2F_KEY_PATH = const(0x80553246) _U2F_KEY_PATH = const(0x80553246)
@ -39,13 +53,29 @@ class Credential:
self.user_id = None # type: Optional[bytes] self.user_id = None # type: Optional[bytes]
def app_name(self) -> str: def app_name(self) -> str:
return "" raise NotImplementedError
def account_name(self) -> Optional[str]: def account_name(self) -> Optional[str]:
return None return None
def private_key(self) -> bytes: def public_key(self) -> bytes:
return b"" raise NotImplementedError
def _private_key(self) -> bytes:
raise NotImplementedError
def sign(self, data: Iterable[bytes]) -> bytes:
raise NotImplementedError
def _u2f_sign(self, data: Iterable[bytes]) -> bytes:
dig = hashlib.sha256()
for segment in data:
dig.update(segment)
sig = nist256p1.sign(self._private_key(), dig.digest(), False)
return der.encode_seq((sig[1:33], sig[33:]))
def bogus_signature(self) -> bytes:
raise NotImplementedError
def hmac_secret_key(self) -> Optional[bytes]: def hmac_secret_key(self) -> Optional[bytes]:
return None return None
@ -71,6 +101,8 @@ class Fido2Credential(Credential):
self.creation_time = 0 # type: int self.creation_time = 0 # type: int
self.hmac_secret = False # type: bool self.hmac_secret = False # type: bool
self.use_sign_count = False # type: bool self.use_sign_count = False # type: bool
self.algorithm = _DEFAULT_ALGORITHM # type: int
self.curve = _DEFAULT_CURVE # type: int
def __lt__(self, other: Credential) -> bool: def __lt__(self, other: Credential) -> bool:
# Sort FIDO2 credentials newest first amongst each other. # Sort FIDO2 credentials newest first amongst each other.
@ -83,29 +115,35 @@ class Fido2Credential(Credential):
def generate_id(self) -> None: def generate_id(self) -> None:
self.creation_time = storage.device.next_u2f_counter() or 0 self.creation_time = storage.device.next_u2f_counter() or 0
data = cbor.encode( if not self.check_required_fields():
{ raise AssertionError
key: value
for key, value in ( data = {
(_CRED_ID_RP_ID, self.rp_id), key: value
(_CRED_ID_RP_NAME, self.rp_name), for key, value in (
(_CRED_ID_USER_ID, self.user_id), (_CRED_ID_RP_ID, self.rp_id),
(_CRED_ID_USER_NAME, self.user_name), (_CRED_ID_RP_NAME, self.rp_name),
(_CRED_ID_USER_DISPLAY_NAME, self.user_display_name), (_CRED_ID_USER_ID, self.user_id),
(_CRED_ID_CREATION_TIME, self.creation_time), (_CRED_ID_USER_NAME, self.user_name),
(_CRED_ID_HMAC_SECRET, self.hmac_secret), (_CRED_ID_USER_DISPLAY_NAME, self.user_display_name),
(_CRED_ID_USE_SIGN_COUNT, self.use_sign_count), (_CRED_ID_CREATION_TIME, self.creation_time),
) (_CRED_ID_HMAC_SECRET, self.hmac_secret),
if value (_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( key = seed.derive_slip21_node_without_passphrase(
[b"SLIP-0022", _CRED_ID_VERSION, b"Encryption key"] [b"SLIP-0022", _CRED_ID_VERSION, b"Encryption key"]
).key() ).key()
iv = random.bytes(12) iv = random.bytes(12)
ctx = chacha20poly1305(key, iv) ctx = chacha20poly1305(key, iv)
ctx.auth(self.rp_id_hash) ctx.auth(self.rp_id_hash)
ciphertext = ctx.encrypt(data) ciphertext = ctx.encrypt(cbor.encode(data))
tag = ctx.finish() tag = ctx.finish()
self.id = _CRED_ID_VERSION + iv + ciphertext + tag self.id = _CRED_ID_VERSION + iv + ciphertext + tag
@ -156,10 +194,13 @@ class Fido2Credential(Credential):
cred.creation_time = data.get(_CRED_ID_CREATION_TIME, 0) cred.creation_time = data.get(_CRED_ID_CREATION_TIME, 0)
cred.hmac_secret = data.get(_CRED_ID_HMAC_SECRET, False) cred.hmac_secret = data.get(_CRED_ID_HMAC_SECRET, False)
cred.use_sign_count = data.get(_CRED_ID_USE_SIGN_COUNT, 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 cred.id = cred_id
if ( if (
not cred.check_required_fields() (_CRED_ID_ALGORITHM in data) != (_CRED_ID_CURVE in data)
or not cred.check_required_fields()
or not cred.check_data_types() or not cred.check_data_types()
or hashlib.sha256(cred.rp_id).digest() != rp_id_hash or hashlib.sha256(cred.rp_id).digest() != rp_id_hash
): ):
@ -184,6 +225,8 @@ class Fido2Credential(Credential):
and isinstance(self.hmac_secret, bool) and isinstance(self.hmac_secret, bool)
and isinstance(self.use_sign_count, bool) and isinstance(self.use_sign_count, bool)
and isinstance(self.creation_time, (int, type(None))) and isinstance(self.creation_time, (int, type(None)))
and isinstance(self.algorithm, (int, type(None)))
and isinstance(self.curve, (int, type(None)))
and isinstance(self.id, (bytes, bytearray)) and isinstance(self.id, (bytes, bytearray))
) )
@ -200,13 +243,67 @@ class Fido2Credential(Credential):
else: else:
return None return None
def private_key(self) -> bytes: def _private_key(self) -> bytes:
path = [HARDENED | 10022, HARDENED | int.from_bytes(self.id[:4], "big")] + [ path = [HARDENED | 10022, HARDENED | int.from_bytes(self.id[:4], "big")] + [
HARDENED | i for i in ustruct.unpack(">4L", self.id[-16:]) HARDENED | i for i in ustruct.unpack(">4L", self.id[-16:])
] ]
node = seed.derive_node_without_passphrase(path, "nist256p1") node = seed.derive_node_without_passphrase(path, _CURVE_NAME[self.curve])
return node.private_key() return node.private_key()
def public_key(self) -> bytes:
if self.curve == common.COSE_CURVE_P256:
pubkey = nist256p1.publickey(self._private_key(), False)
return cbor.encode(
{
common.COSE_KEY_ALG: self.algorithm,
common.COSE_KEY_KTY: common.COSE_KEYTYPE_EC2,
common.COSE_KEY_CRV: self.curve,
common.COSE_KEY_X: pubkey[1:33],
common.COSE_KEY_Y: pubkey[33:],
}
)
elif self.curve == common.COSE_CURVE_ED25519:
pubkey = ed25519.publickey(self._private_key())
return cbor.encode(
{
common.COSE_KEY_ALG: self.algorithm,
common.COSE_KEY_KTY: common.COSE_KEYTYPE_OKP,
common.COSE_KEY_CRV: self.curve,
common.COSE_KEY_X: pubkey,
}
)
raise TypeError
def sign(self, data: Iterable[bytes]) -> bytes:
if (self.algorithm, self.curve) == (
common.COSE_ALG_ES256,
common.COSE_CURVE_P256,
):
return self._u2f_sign(data)
elif (self.algorithm, self.curve) == (
common.COSE_ALG_EDDSA,
common.COSE_CURVE_ED25519,
):
return ed25519.sign(
self._private_key(), b"".join(bytes(segment) for segment in data)
)
raise TypeError
def bogus_signature(self) -> bytes:
if (self.algorithm, self.curve) == (
common.COSE_ALG_ES256,
common.COSE_CURVE_P256,
):
return der.encode_seq((b"\x0a" * 32, b"\x0a" * 32))
elif (self.algorithm, self.curve) == (
common.COSE_ALG_EDDSA,
common.COSE_CURVE_ED25519,
):
return b"\x0a" * 64
raise TypeError
def hmac_secret_key(self) -> Optional[bytes]: def hmac_secret_key(self) -> Optional[bytes]:
# Returns the symmetric key for the hmac-secret extension also known as CredRandom. # Returns the symmetric key for the hmac-secret extension also known as CredRandom.
@ -238,11 +335,20 @@ class U2fCredential(Credential):
# Sort U2F credentials lexicographically amongst each other. # Sort U2F credentials lexicographically amongst each other.
return self.id < other.id return self.id < other.id
def private_key(self) -> bytes: def _private_key(self) -> bytes:
if self.node is None: if self.node is None:
return b"" return b""
return self.node.private_key() return self.node.private_key()
def public_key(self) -> bytes:
return nist256p1.publickey(self._private_key(), False)
def sign(self, data: Iterable[bytes]) -> bytes:
return self._u2f_sign(data)
def bogus_signature(self) -> bytes:
return der.encode_seq((b"\x0a" * 32, b"\x0a" * 32))
def generate_key_handle(self) -> None: def generate_key_handle(self) -> None:
# derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r' # derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r'
path = [HARDENED | random.uniform(0x80000000) for _ in range(0, 8)] path = [HARDENED | random.uniform(0x80000000) for _ in range(0, 8)]

View File

@ -13,6 +13,7 @@ from trezor.ui.popup import Popup
from trezor.ui.text import Text from trezor.ui.text import Text
from apps.common import cbor from apps.common import cbor
from apps.webauthn import common
from apps.webauthn.confirm import ConfirmContent, ConfirmInfo from apps.webauthn.confirm import ConfirmContent, ConfirmInfo
from apps.webauthn.credential import Credential, Fido2Credential, U2fCredential from apps.webauthn.credential import Credential, Fido2Credential, U2fCredential
from apps.webauthn.resident_credentials import ( from apps.webauthn.resident_credentials import (
@ -21,7 +22,7 @@ from apps.webauthn.resident_credentials import (
) )
if False: if False:
from typing import Any, Coroutine, List, Optional, Tuple from typing import Any, Coroutine, Iterable, List, Optional, Tuple
_CID_BROADCAST = const(0xFFFFFFFF) # broadcast channel id _CID_BROADCAST = const(0xFFFFFFFF) # broadcast channel id
@ -122,17 +123,6 @@ _U2F_CONFIRM_TIMEOUT_MS = const(
_FIDO2_CONFIRM_TIMEOUT_MS = const(60 * 1000) _FIDO2_CONFIRM_TIMEOUT_MS = const(60 * 1000)
_POPUP_TIMEOUT_MS = const(4 * 1000) _POPUP_TIMEOUT_MS = const(4 * 1000)
# CBOR object signing and encryption algorithms and keys
_COSE_ALG_KEY = const(3)
_COSE_ALG_ES256 = const(-7) # ECDSA P-256 with SHA-256
_COSE_ALG_ECDH_ES_HKDF_256 = const(-25) # Ephemeral-static ECDH with HKDF SHA-256
_COSE_KEY_TYPE_KEY = const(1)
_COSE_KEY_TYPE_EC2 = const(2) # elliptic curve keys with x- and y-coordinate pair
_COSE_CURVE_KEY = const(-1) # elliptic curve identifier
_COSE_CURVE_P256 = const(1) # P-256 curve
_COSE_X_COORD_KEY = const(-2) # x coordinate of the public key
_COSE_Y_COORD_KEY = const(-3) # y coordinate of the public key
# hid error codes # hid error codes
_ERR_NONE = const(0x00) # no error _ERR_NONE = const(0x00) # no error
_ERR_INVALID_CMD = const(0x01) # invalid command _ERR_INVALID_CMD = const(0x01) # invalid command
@ -181,7 +171,6 @@ _BOGUS_APPID_CHROME = b"A" * 32
_BOGUS_APPID_FIREFOX = b"\0" * 32 _BOGUS_APPID_FIREFOX = b"\0" * 32
_BOGUS_APPIDS = (_BOGUS_APPID_CHROME, _BOGUS_APPID_FIREFOX) _BOGUS_APPIDS = (_BOGUS_APPID_CHROME, _BOGUS_APPID_FIREFOX)
_AAGUID = b"\xd6\xd0\xbd\xc3b\xee\xc4\xdb\xde\x8dzenJD\x87" # First 16 bytes of SHA-256("TREZOR 2") _AAGUID = b"\xd6\xd0\xbd\xc3b\xee\xc4\xdb\xde\x8dzenJD\x87" # First 16 bytes of SHA-256("TREZOR 2")
_BOGUS_PRIV_KEY = b"\xAA" * 32
# authentication control byte # authentication control byte
_AUTH_ENFORCE = const(0x03) # enforce user presence and sign _AUTH_ENFORCE = const(0x03) # enforce user presence and sign
@ -1167,20 +1156,18 @@ def msg_register(req: Msg, dialog_mgr: DialogManager) -> Cmd:
return Cmd(req.cid, _CMD_MSG, buf) return Cmd(req.cid, _CMD_MSG, buf)
def msg_register_sign(challenge: bytes, cred: U2fCredential) -> bytes: def basic_attestation_sign(data: Iterable[bytes]) -> bytes:
pubkey = nist256p1.publickey(cred.private_key(), False)
# hash the request data together with keyhandle and pubkey
dig = hashlib.sha256() dig = hashlib.sha256()
dig.update(b"\x00") # uint8_t reserved; for segment in data:
dig.update(cred.rp_id_hash) # uint8_t appId[32]; dig.update(segment)
dig.update(challenge) # uint8_t chal[32];
dig.update(cred.id) # uint8_t keyHandle[64];
dig.update(pubkey) # uint8_t pubKey[65];
# sign the digest and convert to der
sig = nist256p1.sign(_U2F_ATT_PRIV_KEY, dig.digest(), False) sig = nist256p1.sign(_U2F_ATT_PRIV_KEY, dig.digest(), False)
sig = der.encode_seq((sig[1:33], sig[33:])) return der.encode_seq((sig[1:33], sig[33:]))
def msg_register_sign(challenge: bytes, cred: U2fCredential) -> bytes:
pubkey = cred.public_key()
sig = basic_attestation_sign((b"\x00", cred.rp_id_hash, challenge, cred.id, pubkey))
# pack to a response # pack to a response
buf, resp = make_struct( buf, resp = make_struct(
@ -1272,16 +1259,8 @@ def msg_authenticate_sign(
ctr = cred.next_signature_counter() ctr = cred.next_signature_counter()
ctrbuf = ustruct.pack(">L", ctr) ctrbuf = ustruct.pack(">L", ctr)
# hash input data together with counter # sign the input data together with counter
dig = hashlib.sha256() sig = cred.sign((rp_id_hash, flags, ctrbuf, challenge))
dig.update(rp_id_hash) # uint8_t appId[32];
dig.update(flags) # uint8_t flags;
dig.update(ctrbuf) # uint8_t ctr[4];
dig.update(challenge) # uint8_t chal[32];
# sign the digest and convert to der
sig = nist256p1.sign(cred.private_key(), dig.digest(), False)
sig = der.encode_seq((sig[1:33], sig[33:]))
# pack to a response # pack to a response
buf, resp = make_struct(resp_cmd_authenticate(len(sig))) buf, resp = make_struct(resp_cmd_authenticate(len(sig)))
@ -1384,11 +1363,18 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY) return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
return None return None
# Check that the relying party supports ECDSA P-256 with SHA-256. We don't support any other algorithms. # Check that the relying party supports ECDSA with SHA-256 or EdDSA. We don't support any other algorithms.
pub_key_cred_params = param[_MAKECRED_CMD_PUB_KEY_CRED_PARAMS] pub_key_cred_params = param[_MAKECRED_CMD_PUB_KEY_CRED_PARAMS]
if _COSE_ALG_ES256 not in algorithms_from_pub_key_cred_params( for alg in algorithms_from_pub_key_cred_params(pub_key_cred_params):
pub_key_cred_params if alg == common.COSE_ALG_ES256:
): cred.algorithm = alg
cred.curve = common.COSE_CURVE_P256
break
elif alg == common.COSE_ALG_EDDSA:
cred.algorithm = alg
cred.curve = common.COSE_CURVE_ED25519
break
else:
return cbor_error(req.cid, _ERR_UNSUPPORTED_ALGORITHM) return cbor_error(req.cid, _ERR_UNSUPPORTED_ALGORITHM)
# Get options. # Get options.
@ -1465,31 +1451,26 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
return None return None
def use_self_attestation(rp_id_hash: bytes) -> bool:
from apps.webauthn import knownapps
app = knownapps.by_rp_id_hash(rp_id_hash)
if app is not None and app.use_self_attestation is not None:
return app.use_self_attestation
else:
return _DEFAULT_USE_SELF_ATTESTATION
def cbor_make_credential_sign( def cbor_make_credential_sign(
client_data_hash: bytes, cred: Fido2Credential, user_verification: bool client_data_hash: bytes, cred: Fido2Credential, user_verification: bool
) -> bytes: ) -> bytes:
from apps.webauthn import knownapps
privkey = cred.private_key()
pubkey = nist256p1.publickey(privkey, False)
flags = _AUTH_FLAG_UP | _AUTH_FLAG_AT flags = _AUTH_FLAG_UP | _AUTH_FLAG_AT
if user_verification: if user_verification:
flags |= _AUTH_FLAG_UV flags |= _AUTH_FLAG_UV
# Encode the authenticator data (Credential ID, its public key and extensions). # Encode the authenticator data (Credential ID, its public key and extensions).
credential_pub_key = cbor.encode(
{
_COSE_ALG_KEY: _COSE_ALG_ES256,
_COSE_KEY_TYPE_KEY: _COSE_KEY_TYPE_EC2,
_COSE_CURVE_KEY: _COSE_CURVE_P256,
_COSE_X_COORD_KEY: pubkey[1:33],
_COSE_Y_COORD_KEY: pubkey[33:],
}
)
att_cred_data = ( att_cred_data = (
_AAGUID + len(cred.id).to_bytes(2, "big") + cred.id + credential_pub_key _AAGUID + len(cred.id).to_bytes(2, "big") + cred.id + cred.public_key()
) )
extensions = b"" extensions = b""
@ -1507,27 +1488,18 @@ def cbor_make_credential_sign(
+ extensions + extensions
) )
app = knownapps.by_rp_id_hash(cred.rp_id_hash) if use_self_attestation(cred.rp_id_hash):
if app is not None and app.use_self_attestation is not None: sig = cred.sign((authenticator_data, client_data_hash))
use_self_attestation = app.use_self_attestation attestation_statement = {"alg": cred.algorithm, "sig": sig}
else: else:
use_self_attestation = _DEFAULT_USE_SELF_ATTESTATION sig = basic_attestation_sign((authenticator_data, client_data_hash))
attestation_statement = {
# Compute the attestation signature of the authenticator data. "alg": common.COSE_ALG_ES256,
if not use_self_attestation: "sig": sig,
privkey = _U2F_ATT_PRIV_KEY "x5c": _U2F_ATT_CERT,
}
dig = hashlib.sha256()
dig.update(authenticator_data)
dig.update(client_data_hash)
sig = nist256p1.sign(privkey, dig.digest(), False)
sig = der.encode_seq((sig[1:33], sig[33:]))
# Encode the authenticatorMakeCredential response data. # Encode the authenticatorMakeCredential response data.
attestation_statement = {"alg": _COSE_ALG_ES256, "sig": sig}
if not use_self_attestation:
attestation_statement["x5c"] = [_U2F_ATT_CERT]
return cbor.encode( return cbor.encode(
{ {
_MAKECRED_RESP_FMT: "packed", _MAKECRED_RESP_FMT: "packed",
@ -1657,16 +1629,16 @@ def cbor_get_assertion_hmac_secret(
cred: Credential, hmac_secret: dict cred: Credential, hmac_secret: dict
) -> Optional[bytes]: ) -> Optional[bytes]:
key_agreement = hmac_secret[1] # The public key of platform key agreement key. key_agreement = hmac_secret[1] # The public key of platform key agreement key.
# NOTE: We should check the key_agreement[_COSE_ALG_KEY] here, but to avoid compatibility issues we don't, # NOTE: We should check the key_agreement[COSE_KEY_ALG] here, but to avoid compatibility issues we don't,
# because there is currently no valid value which describes the actual key agreement algorithm. # because there is currently no valid value which describes the actual key agreement algorithm.
if ( if (
key_agreement[_COSE_KEY_TYPE_KEY] != _COSE_KEY_TYPE_EC2 key_agreement[common.COSE_KEY_KTY] != common.COSE_KEYTYPE_EC2
or key_agreement[_COSE_CURVE_KEY] != _COSE_CURVE_P256 or key_agreement[common.COSE_KEY_CRV] != common.COSE_CURVE_P256
): ):
return None return None
x = key_agreement[_COSE_X_COORD_KEY] x = key_agreement[common.COSE_KEY_X]
y = key_agreement[_COSE_Y_COORD_KEY] y = key_agreement[common.COSE_KEY_Y]
salt_enc = hmac_secret[2] # The encrypted salt. salt_enc = hmac_secret[2] # The encrypted salt.
salt_auth = hmac_secret[3] # The HMAC of the encrypted salt. salt_auth = hmac_secret[3] # The HMAC of the encrypted salt.
if ( if (
@ -1739,16 +1711,11 @@ def cbor_get_assertion_sign(
) )
# Sign the authenticator data and the client data hash. # Sign the authenticator data and the client data hash.
dig = hashlib.sha256()
dig.update(authenticator_data)
dig.update(client_data_hash)
if user_presence: if user_presence:
privkey = cred.private_key() sig = cred.sign((authenticator_data, client_data_hash))
else: else:
# Spec deviation: Use a bogus key during silent authentication. # Spec deviation: Use a bogus signature during silent authentication.
privkey = _BOGUS_PRIV_KEY sig = cred.bogus_signature()
sig = nist256p1.sign(privkey, dig.digest(), False)
sig = der.encode_seq((sig[1:33], sig[33:]))
# Encode the authenticatorGetAssertion response data. # Encode the authenticatorGetAssertion response data.
response = { response = {
@ -1796,16 +1763,16 @@ def cbor_client_pin(req: Cmd) -> Cmd:
return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION) return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION)
# Encode the public key of the authenticator key agreement key. # Encode the public key of the authenticator key agreement key.
# NOTE: There is currently no valid value for _COSE_ALG_KEY which describes the actual # NOTE: There is currently no valid value for COSE_KEY_ALG which describes the actual
# key agreement algorithm as specified, but _COSE_ALG_ECDH_ES_HKDF_256 is allegedly # key agreement algorithm as specified, but COSE_ALG_ECDH_ES_HKDF_256 is allegedly
# recommended by the latest draft of the CTAP2 spec. # recommended by the latest draft of the CTAP2 spec.
response_data = { response_data = {
_CLIENTPIN_RESP_KEY_AGREEMENT: { _CLIENTPIN_RESP_KEY_AGREEMENT: {
_COSE_ALG_KEY: _COSE_ALG_ECDH_ES_HKDF_256, common.COSE_KEY_ALG: common.COSE_ALG_ECDH_ES_HKDF_256,
_COSE_KEY_TYPE_KEY: _COSE_KEY_TYPE_EC2, common.COSE_KEY_KTY: common.COSE_KEYTYPE_EC2,
_COSE_CURVE_KEY: _COSE_CURVE_P256, common.COSE_KEY_CRV: common.COSE_CURVE_P256,
_COSE_X_COORD_KEY: _KEY_AGREEMENT_PUBKEY[1:33], common.COSE_KEY_X: _KEY_AGREEMENT_PUBKEY[1:33],
_COSE_Y_COORD_KEY: _KEY_AGREEMENT_PUBKEY[33:], common.COSE_KEY_Y: _KEY_AGREEMENT_PUBKEY[33:],
} }
} }

View File

@ -31,9 +31,9 @@ class TestCredential(unittest.TestCase):
creation_time = 2 creation_time = 2
public_key = ( public_key = (
b"0451f0d4c307bc737c90ac605c6279f7d01e451798aa7b74df550fdb43a7760c" b"a501020326200121582051f0d4c307bc737c90ac605c6279f7d01e451798aa7b"
b"7c02b5107fef42094d00f52a9b1e90afb90e1b9decbf15a6f13d4f882de857e2" b"74df550fdb43a7760c7c22582002b5107fef42094d00f52a9b1e90afb90e1b9d"
b"f4" b"ecbf15a6f13d4f882de857e2f4"
) )
cred_random = ( cred_random = (
@ -57,9 +57,7 @@ class TestCredential(unittest.TestCase):
# Check credential keys. # Check credential keys.
self.assertEqual(hexlify(cred.hmac_secret_key()), cred_random) self.assertEqual(hexlify(cred.hmac_secret_key()), cred_random)
self.assertEqual(hexlify(cred.public_key()), public_key)
cred_public_key = nist256p1.publickey(cred.private_key(), False)
self.assertEqual(hexlify(cred_public_key), public_key)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()