Skip to content

Commit 19cba78

Browse files
committed
Merge PR #285
2 parents 6430bf9 + 84edc05 commit 19cba78

7 files changed

Lines changed: 1676 additions & 2 deletions

File tree

examples/sign_arkg.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Copyright (c) 2024 Yubico AB
2+
# All rights reserved.
3+
#
4+
# Redistribution and use in source and binary forms, with or
5+
# without modification, are permitted provided that the following
6+
# conditions are met:
7+
#
8+
# 1. Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
# 2. Redistributions in binary form must reproduce the above
11+
# copyright notice, this list of conditions and the following
12+
# disclaimer in the documentation and/or other materials provided
13+
# with the distribution.
14+
#
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18+
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19+
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
25+
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
# POSSIBILITY OF SUCH DAMAGE.
27+
28+
"""
29+
Connects to the first FIDO device found which supports the PRF extension,
30+
creates a new credential for it with the extension enabled, and uses it to
31+
derive two separate secrets.
32+
"""
33+
34+
import sys
35+
36+
from exampleutils import get_client
37+
38+
from fido2 import cbor
39+
from fido2.cose import ESP256_SPLIT_ARKG_PLACEHOLDER, CoseKey
40+
from fido2.ctap2.extensions import PreviewSignExtension
41+
from fido2.server import Fido2Server
42+
from fido2.utils import sha256, websafe_decode, websafe_encode
43+
44+
uv = "discouraged"
45+
46+
# Locate a suitable FIDO authenticator
47+
client, info = get_client(
48+
lambda info: PreviewSignExtension.NAME in info.extensions,
49+
extensions=[PreviewSignExtension()],
50+
)
51+
52+
server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="none")
53+
user = {"id": b"user_id", "name": "A. User"}
54+
55+
# Prepare parameters for makeCredential
56+
create_options, state = server.register_begin(
57+
user,
58+
resident_key_requirement="discouraged",
59+
user_verification=uv,
60+
authenticator_attachment="cross-platform",
61+
)
62+
63+
# Create a credential
64+
result = client.make_credential(
65+
{
66+
**create_options["publicKey"],
67+
"extensions": {
68+
PreviewSignExtension.NAME: {
69+
"generateKey": {"algorithms": [ESP256_SPLIT_ARKG_PLACEHOLDER]}
70+
}
71+
},
72+
}
73+
)
74+
75+
# Complete registration
76+
auth_data = server.register_complete(state, result)
77+
credentials = [auth_data.credential_data]
78+
print("New credential created, with the sign extension.")
79+
80+
# PRF result:
81+
sign_result = result.client_extension_results.previewSign
82+
print("CREATE sign result", sign_result)
83+
sign_key = sign_result.generated_key
84+
if not sign_key:
85+
print(
86+
"Failed to create credential with sign extension",
87+
result.client_extension_results,
88+
)
89+
sys.exit(1)
90+
91+
# Extension output contains master public key
92+
pk = CoseKey.parse(
93+
cbor.decode(websafe_decode(sign_key["publicKey"]))
94+
) # COSE key in bytes
95+
print("public key", pk)
96+
97+
# Master public key contains blinding and KEM keys
98+
# ARKG derive_public_key uses these
99+
print("Blinding public key", pk.pk_bl)
100+
print("KEM public key", pk.pk_kem)
101+
102+
# Arbitrary bytestring used for ctx, ikm
103+
ctx = b"my-ctx-here"
104+
ikm = b"my-ikm-here"
105+
# Derived public key to verify with, and kh to send to Authenticator
106+
pk2, args = pk.derive_public_key(ikm, ctx)
107+
print("Derived public key", pk2)
108+
print("COSE_Sign_Args for derived key", args)
109+
110+
# Prepare a message to sign
111+
message = b"New message"
112+
ph_data = sha256(message)
113+
114+
# Prepare parameters for getAssertion
115+
request_options, state = server.authenticate_begin(credentials, user_verification=uv)
116+
117+
118+
# Authenticate the credential
119+
result = client.get_assertion(
120+
{
121+
**request_options["publicKey"],
122+
# Add extension outputs. We have only 1 credential in allowCredentials
123+
"extensions": {
124+
PreviewSignExtension.NAME: {
125+
"signByCredential": {
126+
websafe_encode(credentials[0].credential_id): {
127+
"keyHandle": sign_key.key_handle,
128+
"tbs": ph_data,
129+
"additionalArgs": cbor.encode(args),
130+
},
131+
},
132+
}
133+
},
134+
}
135+
)
136+
137+
# Only one cred in allowCredentials, only one response.
138+
result = result.get_response(0)
139+
140+
sign_result = result.client_extension_results[PreviewSignExtension.NAME]
141+
print("GET sign result", sign_result)
142+
143+
# Response contains a signature over message
144+
signature = sign_result.get("signature")
145+
146+
print("Test verify signature", signature)
147+
pk2.verify(message, websafe_decode(signature))
148+
print("Signature verified with derived public key!")

0 commit comments

Comments
 (0)