Skip to content

Commit 9d6041c

Browse files
emlundainnilsson
authored andcommitted
Update sign extension to spec version 2
1 parent fc8bdc5 commit 9d6041c

4 files changed

Lines changed: 52 additions & 20 deletions

File tree

examples/sign.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@
3333
from fido2 import cbor
3434
from fido2.server import Fido2Server
3535
from fido2.utils import sha256, websafe_encode
36-
from fido2.cose import CoseKey, ES256
36+
from fido2.cose import CoseKey, ES256, ESP256, EdDSA
3737
from exampleutils import get_client
3838
import sys
3939

40+
ESP256_2P = -70009 # Placeholder value
41+
4042
uv = "discouraged"
4143

4244
# Locate a suitable FIDO authenticator
@@ -53,17 +55,34 @@
5355
authenticator_attachment="cross-platform",
5456
)
5557

58+
algorithms = [
59+
ESP256_2P,
60+
]
61+
5662
message = b"I am a message"
57-
ph_data = sha256(message)
63+
64+
has_prehash_alg = any(alg in [ESP256_2P] for alg in algorithms)
65+
has_raw_alg = any(
66+
alg
67+
in [
68+
EdDSA.ALGORITHM,
69+
ES256.ALGORITHM,
70+
ESP256.ALGORITHM,
71+
]
72+
for alg in algorithms
73+
)
74+
75+
if has_prehash_alg and has_raw_alg:
76+
raise ValueError("Cannot mix algorithms with pre-hashed and raw message")
77+
78+
data = message if has_raw_alg else sha256(message)
5879

5980
# Create a credential
6081
result = client.make_credential(
6182
{
6283
**create_options["publicKey"],
6384
"extensions": {
64-
"sign": {
65-
"generateKey": {"algorithms": [ES256.ALGORITHM], "phData": ph_data}
66-
}
85+
"sign": {"generateKey": {"algorithms": algorithms, "tbs": data}}
6786
},
6887
}
6988
)
@@ -83,18 +102,25 @@
83102
)
84103
sys.exit(1)
85104
print("New credential created, with the sign extension.")
105+
if sign_key.algorithm not in algorithms:
106+
print("Got unexpected algorithm in response:", sign_key.algorithm)
107+
sys.exit(1)
86108

87109
pk = CoseKey.parse(cbor.decode(sign_key.public_key)) # COSE key in bytes
88-
kh = sign_key.key_handle # key handle in bytes
110+
kh = pk.get_ref()
111+
kh[3] = sign_key.algorithm
112+
if pk[1] == 2: # EC2
113+
kh[-1] = pk[-1] # crv
114+
kh_bin = cbor.encode(kh) # key handle in bytes
89115
print("public key", pk)
90-
print("keyHandle", sign_key["keyHandle"])
116+
print("keyHandle", kh)
91117

92118
print("Test verify signature", sign_result["signature"])
93119
pk.verify(message, sign_result.signature)
94120
print("Signature verified!")
95121

96122
message = b"New message"
97-
ph_data = sha256(message)
123+
data = message if has_raw_alg else sha256(message)
98124

99125
# Prepare parameters for getAssertion
100126
request_options, state = server.authenticate_begin(credentials, user_verification=uv)
@@ -106,9 +132,9 @@
106132
"extensions": {
107133
"sign": {
108134
"sign": {
109-
"phData": ph_data,
135+
"tbs": data,
110136
"keyHandleByCredential": {
111-
websafe_encode(credentials[0].credential_id): kh,
137+
websafe_encode(credentials[0].credential_id): kh_bin,
112138
},
113139
},
114140
}

examples/sign_arkg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"extensions": {
119119
"sign": {
120120
"sign": {
121-
"phData": ph_data,
121+
"tbs": ph_data,
122122
"keyHandleByCredential": {
123123
websafe_encode(credentials[0].credential_id): kh,
124124
},

fido2/ctap2/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class AttestationResponse(_CborDataObject):
124124
att_stmt: dict[str, Any]
125125
ep_att: bool | None = None
126126
large_blob_key: bytes | None = None
127+
unsigned_extension_outputs: dict[str, Any] | None = None
127128

128129

129130
@dataclass(eq=False, frozen=True)

fido2/ctap2/extensions.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -543,13 +543,13 @@ def make_credential(self, ctap, options, pin_protocol):
543543
@dataclass(eq=False, frozen=True)
544544
class _SignGenerateKeyInputs(_JsonDataObject):
545545
algorithms: Sequence[int]
546-
ph_data: bytes | None = None
546+
tbs: bytes | None = None
547547

548548

549549
@dataclass(eq=False, frozen=True)
550550
class _SignSignInputs(_JsonDataObject):
551-
ph_data: bytes
552551
key_handle_by_credential: Mapping[str, bytes]
552+
tbs: bytes
553553

554554

555555
@dataclass(eq=False, frozen=True)
@@ -561,7 +561,8 @@ class _SignInputs(_JsonDataObject):
561561
@dataclass(eq=False, frozen=True)
562562
class _SignGeneratedKey(_JsonDataObject):
563563
public_key: bytes
564-
key_handle: bytes
564+
algorithm: int
565+
attestation_object: bytes
565566

566567

567568
@dataclass(eq=False, frozen=True)
@@ -604,8 +605,8 @@ def prepare_inputs(self, pin_token):
604605
)
605606
outputs = {3: gk.algorithms, 4: flags}
606607

607-
if gk.ph_data:
608-
outputs[0] = gk.ph_data
608+
if gk.tbs:
609+
outputs[6] = gk.tbs
609610

610611
return {SignExtension.NAME: outputs}
611612

@@ -614,8 +615,11 @@ def prepare_outputs(self, response, pin_token):
614615
data = extensions.get(SignExtension.NAME)
615616
if not data:
616617
return None
618+
att_obj_bytes = response.unsigned_extension_outputs[SignExtension.NAME][
619+
7
620+
]
617621
att_obj = AttestationResponse.from_dict(
618-
cbor.decode(data[7]) # type: ignore
622+
cbor.decode(att_obj_bytes) # type: ignore
619623
)
620624
cred_data = att_obj.auth_data.credential_data
621625
assert cred_data is not None # nosec
@@ -625,7 +629,8 @@ def prepare_outputs(self, response, pin_token):
625629
SignExtension.NAME: _SignOutputs(
626630
generated_key=_SignGeneratedKey(
627631
public_key=cbor.encode(pk),
628-
key_handle=cbor.encode(pk.get_ref()),
632+
algorithm=data.get(3),
633+
attestation_object=att_obj_bytes,
629634
),
630635
signature=data.get(6),
631636
)
@@ -658,8 +663,8 @@ def prepare_inputs(self, selected, pin_token):
658663

659664
return {
660665
SignExtension.NAME: {
661-
0: sign.ph_data,
662-
5: [kh],
666+
5: kh,
667+
6: sign.tbs,
663668
}
664669
}
665670

0 commit comments

Comments
 (0)