Skip to content

Commit

Permalink
Just a bit of trouble with the kid when not using jwcrypto, ValueErro…
Browse files Browse the repository at this point in the history
…r 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 <johnandersenpdx@gmail.com>
  • Loading branch information
pdxjohnny committed Nov 12, 2023
1 parent 111fb2c commit 3b04b46
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 27 deletions.
52 changes: 42 additions & 10 deletions docs/registration_policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,16 @@ if unverified_issuer.startswith("did:web:"):

# Load keys from issuer
jwk_keys = []
cryptography_ssh_keys = []
cwt_cose_keys = []
pycose_cose_keys = []

import urllib.request
import urllib.parse

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

cryptography_ssh_keys = []
if "://" in unverified_issuer and not unverified_issuer.startswith("file://"):
# TODO Logging for URLErrors
# Check if OIDC issuer
Expand All @@ -166,10 +168,16 @@ if "://" in unverified_issuer and not unverified_issuer.startswith("file://"):
if response.status == 200:
jwks = json.loads(response.read())
for jwk_key_as_dict in jwks["keys"]:
"""
jwk_key_as_string = json.dumps(jwk_key_as_dict)
jwk_keys.append(
jwcrypto.jwk.JWK.from_json(jwk_key_as_string),
)
"""
cwt_cose_key = cwt.COSEKey.from_jwk(
jwk_key_as_dict
)
cwt_cose_keys.append(cwt_cose_key)

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

cwt_cose_keys = []
pycose_cose_keys = []

for jwk_key in jwk_keys:
print(jwk_key, "kid=", jwk_key.thumbprint())
cwt_cose_key = cwt.COSEKey.from_pem(
jwk_key.export_to_pem(),
kid=jwk_key.thumbprint(),
)
cwt_cose_keys.append(cwt_cose_key)

for cwt_cose_key in cwt_cose_keys:
cwt_ec2_key_as_dict = cwt_cose_key.to_dict()
import pprint
import inspect
cose_tags = {
member.identifier: member.fullname
for _member_name, member in inspect.getmembers(pycose.headers)
if (
hasattr(member, "identifier")
and hasattr(member, "fullname")
)
}
pprint.pprint(cose_tags)
cwt_ec2_key_as_dict_labeled = {
cose_tags.get(key, key): value
for key, value in cwt_ec2_key_as_dict.items()
}
print("cwt_ec2_key_as_dict_labeled['STATIC_KEY_ID']", cwt_ec2_key_as_dict_labeled['CRITICAL'])
pprint.pprint(cwt_ec2_key_as_dict)
pprint.pprint(cwt_ec2_key_as_dict_labeled)
pycose_cose_key = pycose.keys.ec2.EC2Key.from_dict(cwt_ec2_key_as_dict)
pycose_cose_key.kid = cwt_ec2_key_as_dict_labeled['CRITICAL']
# cwt_cose_key.kid = cwt_ec2_key_as_dict_labeled['CRITICAL']
pycose_cose_keys.append(pycose_cose_key)

verify_signature = False
Expand All @@ -214,6 +242,7 @@ for pycose_cose_key in pycose_cose_keys:
verify_signature = msg.verify_signature()
if verify_signature:
break
msg.kid = pycose_cose_key.kid

unittest.TestCase().assertTrue(
verify_signature,
Expand Down Expand Up @@ -270,14 +299,17 @@ $ scitt-emulator server --workspace workspace/ --tree-alg CCF --use-lro
```

The current emulator notary (create-statement) implementation will sign
statements using a generated key or a key we provide via the `--private-key-pem`
argument. If we provide the `--private-key-pem` argument but the key at the
given path does not exist, the generated key will be written out to that path.
statements using a generated ephemeral key or a key we provide via the
`--private-key-pem` argument.

Since we need to export the key for verification by the policy engine, we will
first generate it using `ssh-keygen`.

```console
$ export ISSUER_PORT="9000" && \
export ISSUER_URL="http://localhost:${ISSUER_PORT}"
$ scitt-emulator client create-claim \
$ export ISSUER_PORT="9000" \
&& export ISSUER_URL="http://localhost:${ISSUER_PORT}" \
&& 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 \
&& scitt-emulator client create-claim \
--private-key-pem private-key.pem \
--issuer "${ISSUER_URL}" \
--subject "solar" \
Expand Down
34 changes: 17 additions & 17 deletions scitt_emulator/create_statement.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright (c) SCITT Authors
# Licensed under the MIT License.
import uuid
import pathlib
import argparse
from typing import Optional
Expand All @@ -10,9 +11,6 @@
import pycose.messages
import pycose.keys.ec2

# TODO jwcrypto is LGPLv3, is there another option with a permissive licence?
import jwcrypto.jwk


@pycose.headers.CoseHeaderAttribute.register_attribute()
class CWTClaims(pycose.headers.CoseHeaderAttribute):
Expand Down Expand Up @@ -78,16 +76,22 @@ def create_claim(
# RSA: public_exponent(int), size(int)
# EC: crv(str) (one of P-256, P-384, P-521, secp256k1)
# OKP: crv(str) (one of Ed25519, Ed448, X25519, X448)
key = jwcrypto.jwk.JWK()
import hashlib
kid_hash = hashlib.sha256()
kid_hash.update(str(uuid.uuid4()).encode())
kid = kid_hash.hexdigest()
if private_key_pem_path and private_key_pem_path.exists():
key.import_from_pem(private_key_pem_path.read_bytes())
cwt_cose_key = cwt.COSEKey.from_pem(private_key_pem_path.read_bytes())
else:
key = key.generate(kty="EC", crv="P-384")
kid = key.thumbprint()
key_as_pem_bytes = key.export_to_pem(private_key=True, password=None)
# cwt_cose_key = cwt.COSEKey.generate_symmetric_key(alg=alg, kid=kid)
cwt_cose_key = cwt.COSEKey.from_pem(key_as_pem_bytes, kid=kid)
# cwt_cose_key_to_cose_key = cwt.algs.ec2.EC2Key.to_cose_key(cwt_cose_key)
# cwt_cose_key = cwt.COSEKey.generate_symmetric_key(alg=alg, kid=kid)
subprocess.check_call(
[
"bash"
"-c",
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}",
]
)
cwt_cose_key = cwt.COSEKey.from_pem(private_key_pem_path.read_bytes())
cwt_cose_key_to_cose_key = cwt_cose_key.to_dict()
sign1_message_key = pycose.keys.ec2.EC2Key.from_dict(cwt_cose_key_to_cose_key)

Expand All @@ -105,7 +109,7 @@ def create_claim(
# chosen by the Issuer
# Example: github.com/opensbom-generator/spdx-sbom-generator/releases/tag/v0.0.13
# 2 => tstr; sub, the subject of the statements,
2: subject,
2: "asdflkajsdflkjsadflkj" + subject,
# * tstr => any
}
# }
Expand All @@ -123,7 +127,7 @@ def create_claim(
pycose.headers.Algorithm: getattr(cwt.enums.COSEAlgs, alg),
# Key ID (label: 4): Key ID, as a bytestring
# 4 => bstr ; Key ID,
pycose.headers.KID: kid.encode("ascii"),
pycose.headers.KID: kid.encode('ascii'),
# 14 => CWT_Claims ; CBOR Web Token Claims,
CWTClaims: cwt_token,
# 393 => Reg_Info ; Registration Policy info,
Expand Down Expand Up @@ -155,10 +159,6 @@ def create_claim(
claim = msg.encode(tag=True)
claim_path.write_bytes(claim)

# Write out private key in PEM format if argument given and not exists
if private_key_pem_path and not private_key_pem_path.exists():
private_key_pem_path.write_bytes(key_as_pem_bytes)


def cli(fn):
p = fn("create-claim", description="Create a fake SCITT claim")
Expand Down
5 changes: 5 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ def create_flask_app_oidc_server(config):
app.config.update(dict(DEBUG=True))
app.config.update(config)

if not isinstance(app.config["key"], jwcrypto.jwk.JWK):
key_pem = app.config["key"]
app.config["key"] = jwcrypto.jwk.JWK()
app.config["key"].import_from_pem(key_pem)

# TODO For testing ssh key style issuers, not OIDC related needs to be moved
@app.route("/", methods=["GET"])
def ssh_public_keys():
Expand Down

0 comments on commit 3b04b46

Please sign in to comment.