mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-14 12:09:35 +00:00
76 lines
2.2 KiB
Python
76 lines
2.2 KiB
Python
|
|
import hashlib
|
||
|
|
from os import urandom
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
from .. import constants
|
||
|
|
from ..card_interface import CardInterface
|
||
|
|
from ..crypto.generate_pairing_token import generate_pairing_token
|
||
|
|
from ..exceptions import InvalidResponseError
|
||
|
|
from ..preconditions import require_initialized
|
||
|
|
|
||
|
|
|
||
|
|
@require_initialized
|
||
|
|
def pair(
|
||
|
|
card: CardInterface,
|
||
|
|
shared_secret: str | bytes,
|
||
|
|
pairing_mode: Optional[constants.PairingMode] = constants.PairingMode.ANY
|
||
|
|
) -> tuple[int, bytes]:
|
||
|
|
'''
|
||
|
|
Performs an ECDH-based pairing handshake with the card.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
card: The keycard interface.
|
||
|
|
shared_secret: A 32-byte secret or a passphrase convertible to one.
|
||
|
|
pairing_mode: Mode for pairing: ANY, EPHEMERAL, PERSISTENT
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
tuple[int, bytes]: Pairing index and derived 32-byte pairing key.
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
ValueError: If the shared secret is not 32 bytes.
|
||
|
|
APDUError: If the card returns a non-success status word.
|
||
|
|
InvalidResponseError: If response lengths or values are unexpected.
|
||
|
|
'''
|
||
|
|
if isinstance(shared_secret, str):
|
||
|
|
shared_secret = generate_pairing_token(shared_secret)
|
||
|
|
|
||
|
|
if len(shared_secret) != 32:
|
||
|
|
raise ValueError('Shared secret must be 32 bytes')
|
||
|
|
|
||
|
|
client_challenge = urandom(32)
|
||
|
|
|
||
|
|
response = card.send_apdu(
|
||
|
|
ins=constants.INS_PAIR,
|
||
|
|
p2=pairing_mode,
|
||
|
|
data=client_challenge
|
||
|
|
)
|
||
|
|
|
||
|
|
if len(response) != 64:
|
||
|
|
raise InvalidResponseError('Unexpected response length')
|
||
|
|
|
||
|
|
card_cryptogram = response[:32]
|
||
|
|
card_challenge = response[32:]
|
||
|
|
|
||
|
|
expected = hashlib.sha256(shared_secret + client_challenge).digest()
|
||
|
|
|
||
|
|
if card_cryptogram != expected:
|
||
|
|
raise InvalidResponseError('Card cryptogram mismatch')
|
||
|
|
|
||
|
|
client_cryptogram = hashlib.sha256(shared_secret + card_challenge).digest()
|
||
|
|
|
||
|
|
response = card.send_apdu(
|
||
|
|
ins=constants.INS_PAIR,
|
||
|
|
p1=0x01,
|
||
|
|
data=client_cryptogram
|
||
|
|
)
|
||
|
|
|
||
|
|
if len(response) != 33:
|
||
|
|
raise InvalidResponseError('Unexpected response length')
|
||
|
|
|
||
|
|
pairing_index = response[0]
|
||
|
|
salt = response[1:]
|
||
|
|
|
||
|
|
pairing_key = hashlib.sha256(shared_secret + salt).digest()
|
||
|
|
|
||
|
|
return pairing_index, pairing_key
|