Skip to content

Commit 3b04b46

Browse files
committed
Just a bit of trouble with the kid when not using jwcrypto, ValueError the kid should be specified, on cwt decode, it must verify the outter cose correctly to get there within the policy engine, so something is wrong with the way the CWT keys are working.
Signed-off-by: John Andersen <[email protected]>
1 parent 111fb2c commit 3b04b46

File tree

3 files changed

+64
-27
lines changed

3 files changed

+64
-27
lines changed

docs/registration_policies.md

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,16 @@ if unverified_issuer.startswith("did:web:"):
142142

143143
# Load keys from issuer
144144
jwk_keys = []
145+
cryptography_ssh_keys = []
146+
cwt_cose_keys = []
147+
pycose_cose_keys = []
145148

146149
import urllib.request
147150
import urllib.parse
148151

149152
# TODO did:web: -> URL
150153
from cryptography.hazmat.primitives import serialization
151154

152-
cryptography_ssh_keys = []
153155
if "://" in unverified_issuer and not unverified_issuer.startswith("file://"):
154156
# TODO Logging for URLErrors
155157
# Check if OIDC issuer
@@ -166,10 +168,16 @@ if "://" in unverified_issuer and not unverified_issuer.startswith("file://"):
166168
if response.status == 200:
167169
jwks = json.loads(response.read())
168170
for jwk_key_as_dict in jwks["keys"]:
171+
"""
169172
jwk_key_as_string = json.dumps(jwk_key_as_dict)
170173
jwk_keys.append(
171174
jwcrypto.jwk.JWK.from_json(jwk_key_as_string),
172175
)
176+
"""
177+
cwt_cose_key = cwt.COSEKey.from_jwk(
178+
jwk_key_as_dict
179+
)
180+
cwt_cose_keys.append(cwt_cose_key)
173181

174182
# Try loading ssh keys. Example: https://github.com/username.keys
175183
with contextlib.suppress(urllib.request.URLError):
@@ -194,17 +202,37 @@ for cryptography_ssh_key in cryptography_ssh_keys:
194202
)
195203
)
196204

197-
cwt_cose_keys = []
198-
pycose_cose_keys = []
199-
200205
for jwk_key in jwk_keys:
206+
print(jwk_key, "kid=", jwk_key.thumbprint())
201207
cwt_cose_key = cwt.COSEKey.from_pem(
202208
jwk_key.export_to_pem(),
203209
kid=jwk_key.thumbprint(),
204210
)
205211
cwt_cose_keys.append(cwt_cose_key)
212+
213+
for cwt_cose_key in cwt_cose_keys:
206214
cwt_ec2_key_as_dict = cwt_cose_key.to_dict()
215+
import pprint
216+
import inspect
217+
cose_tags = {
218+
member.identifier: member.fullname
219+
for _member_name, member in inspect.getmembers(pycose.headers)
220+
if (
221+
hasattr(member, "identifier")
222+
and hasattr(member, "fullname")
223+
)
224+
}
225+
pprint.pprint(cose_tags)
226+
cwt_ec2_key_as_dict_labeled = {
227+
cose_tags.get(key, key): value
228+
for key, value in cwt_ec2_key_as_dict.items()
229+
}
230+
print("cwt_ec2_key_as_dict_labeled['STATIC_KEY_ID']", cwt_ec2_key_as_dict_labeled['CRITICAL'])
231+
pprint.pprint(cwt_ec2_key_as_dict)
232+
pprint.pprint(cwt_ec2_key_as_dict_labeled)
207233
pycose_cose_key = pycose.keys.ec2.EC2Key.from_dict(cwt_ec2_key_as_dict)
234+
pycose_cose_key.kid = cwt_ec2_key_as_dict_labeled['CRITICAL']
235+
# cwt_cose_key.kid = cwt_ec2_key_as_dict_labeled['CRITICAL']
208236
pycose_cose_keys.append(pycose_cose_key)
209237

210238
verify_signature = False
@@ -214,6 +242,7 @@ for pycose_cose_key in pycose_cose_keys:
214242
verify_signature = msg.verify_signature()
215243
if verify_signature:
216244
break
245+
msg.kid = pycose_cose_key.kid
217246

218247
unittest.TestCase().assertTrue(
219248
verify_signature,
@@ -270,14 +299,17 @@ $ scitt-emulator server --workspace workspace/ --tree-alg CCF --use-lro
270299
```
271300

272301
The current emulator notary (create-statement) implementation will sign
273-
statements using a generated key or a key we provide via the `--private-key-pem`
274-
argument. If we provide the `--private-key-pem` argument but the key at the
275-
given path does not exist, the generated key will be written out to that path.
302+
statements using a generated ephemeral key or a key we provide via the
303+
`--private-key-pem` argument.
304+
305+
Since we need to export the key for verification by the policy engine, we will
306+
first generate it using `ssh-keygen`.
276307

277308
```console
278-
$ export ISSUER_PORT="9000" && \
279-
export ISSUER_URL="http://localhost:${ISSUER_PORT}"
280-
$ scitt-emulator client create-claim \
309+
$ export ISSUER_PORT="9000" \
310+
&& export ISSUER_URL="http://localhost:${ISSUER_PORT}" \
311+
&& ssh-keygen -q -f /dev/stdout -t ecdsa -b 384 -N '' -I $RANDOM <<<y 2>/dev/null | python -c 'import sys; from cryptography.hazmat.primitives import serialization; print(serialization.load_ssh_private_key(sys.stdin.buffer.read(), password=None).private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()).decode().rstrip())' > private-key.pem \
312+
&& scitt-emulator client create-claim \
281313
--private-key-pem private-key.pem \
282314
--issuer "${ISSUER_URL}" \
283315
--subject "solar" \

scitt_emulator/create_statement.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) SCITT Authors
22
# Licensed under the MIT License.
3+
import uuid
34
import pathlib
45
import argparse
56
from typing import Optional
@@ -10,9 +11,6 @@
1011
import pycose.messages
1112
import pycose.keys.ec2
1213

13-
# TODO jwcrypto is LGPLv3, is there another option with a permissive licence?
14-
import jwcrypto.jwk
15-
1614

1715
@pycose.headers.CoseHeaderAttribute.register_attribute()
1816
class CWTClaims(pycose.headers.CoseHeaderAttribute):
@@ -78,16 +76,22 @@ def create_claim(
7876
# RSA: public_exponent(int), size(int)
7977
# EC: crv(str) (one of P-256, P-384, P-521, secp256k1)
8078
# OKP: crv(str) (one of Ed25519, Ed448, X25519, X448)
81-
key = jwcrypto.jwk.JWK()
79+
import hashlib
80+
kid_hash = hashlib.sha256()
81+
kid_hash.update(str(uuid.uuid4()).encode())
82+
kid = kid_hash.hexdigest()
8283
if private_key_pem_path and private_key_pem_path.exists():
83-
key.import_from_pem(private_key_pem_path.read_bytes())
84+
cwt_cose_key = cwt.COSEKey.from_pem(private_key_pem_path.read_bytes())
8485
else:
85-
key = key.generate(kty="EC", crv="P-384")
86-
kid = key.thumbprint()
87-
key_as_pem_bytes = key.export_to_pem(private_key=True, password=None)
88-
# cwt_cose_key = cwt.COSEKey.generate_symmetric_key(alg=alg, kid=kid)
89-
cwt_cose_key = cwt.COSEKey.from_pem(key_as_pem_bytes, kid=kid)
90-
# cwt_cose_key_to_cose_key = cwt.algs.ec2.EC2Key.to_cose_key(cwt_cose_key)
86+
# cwt_cose_key = cwt.COSEKey.generate_symmetric_key(alg=alg, kid=kid)
87+
subprocess.check_call(
88+
[
89+
"bash"
90+
"-c",
91+
f"ssh-keygen -q -f /dev/stdout -t ecdsa -b 384 -N '' -I {kid} <<<y 2>/dev/null | python -c 'import sys; from cryptography.hazmat.primitives import serialization; print(serialization.load_ssh_private_key(sys.stdin.buffer.read(), password=None).private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()).decode().rstrip())' > {private_key_pem_path}",
92+
]
93+
)
94+
cwt_cose_key = cwt.COSEKey.from_pem(private_key_pem_path.read_bytes())
9195
cwt_cose_key_to_cose_key = cwt_cose_key.to_dict()
9296
sign1_message_key = pycose.keys.ec2.EC2Key.from_dict(cwt_cose_key_to_cose_key)
9397

@@ -105,7 +109,7 @@ def create_claim(
105109
# chosen by the Issuer
106110
# Example: github.com/opensbom-generator/spdx-sbom-generator/releases/tag/v0.0.13
107111
# 2 => tstr; sub, the subject of the statements,
108-
2: subject,
112+
2: "asdflkajsdflkjsadflkj" + subject,
109113
# * tstr => any
110114
}
111115
# }
@@ -123,7 +127,7 @@ def create_claim(
123127
pycose.headers.Algorithm: getattr(cwt.enums.COSEAlgs, alg),
124128
# Key ID (label: 4): Key ID, as a bytestring
125129
# 4 => bstr ; Key ID,
126-
pycose.headers.KID: kid.encode("ascii"),
130+
pycose.headers.KID: kid.encode('ascii'),
127131
# 14 => CWT_Claims ; CBOR Web Token Claims,
128132
CWTClaims: cwt_token,
129133
# 393 => Reg_Info ; Registration Policy info,
@@ -155,10 +159,6 @@ def create_claim(
155159
claim = msg.encode(tag=True)
156160
claim_path.write_bytes(claim)
157161

158-
# Write out private key in PEM format if argument given and not exists
159-
if private_key_pem_path and not private_key_pem_path.exists():
160-
private_key_pem_path.write_bytes(key_as_pem_bytes)
161-
162162

163163
def cli(fn):
164164
p = fn("create-claim", description="Create a fake SCITT claim")

tests/test_cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ def create_flask_app_oidc_server(config):
165165
app.config.update(dict(DEBUG=True))
166166
app.config.update(config)
167167

168+
if not isinstance(app.config["key"], jwcrypto.jwk.JWK):
169+
key_pem = app.config["key"]
170+
app.config["key"] = jwcrypto.jwk.JWK()
171+
app.config["key"].import_from_pem(key_pem)
172+
168173
# TODO For testing ssh key style issuers, not OIDC related needs to be moved
169174
@app.route("/", methods=["GET"])
170175
def ssh_public_keys():

0 commit comments

Comments
 (0)