Skip to content

Commit

Permalink
Adapt JSON serialization (#19)
Browse files Browse the repository at this point in the history
* Adapt JSON serialization

Signed-off-by: Daniel Fett <[email protected]>

* Apply suggestions from code review

Co-authored-by: Giuseppe De Marco <[email protected]>
Signed-off-by: Daniel Fett <[email protected]>

* Add workaround for old specification files

Signed-off-by: Daniel Fett <[email protected]>

---------

Signed-off-by: Daniel Fett <[email protected]>
Co-authored-by: Giuseppe De Marco <[email protected]>
  • Loading branch information
danielfett and peppelinux authored May 15, 2024
1 parent 9181a0a commit cde6139
Show file tree
Hide file tree
Showing 16 changed files with 416 additions and 172 deletions.
40 changes: 40 additions & 0 deletions examples/json_serialization_general/specification.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
user_claims:
sub: john_doe_42
!sd given_name: John
!sd family_name: Doe
!sd birthdate: "1940-01-01"

holder_disclosed_claims:
sub: null
given_name: true
family_name: true
birthdate: false

key_binding: True

serialization_format: json

settings_override:
key_settings:
key_size: 256
kty: EC
issuer_keys:
- kty: EC
d: Ur2bNKuBPOrAaxsRnbSH6hIhmNTxSGXshDSUD1a1y7g
crv: P-256
x: b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ
y: Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8
kid: issuer-key-1
- kty: EC
crv: P-256
d: WsGosxrp0XK7VEviPL9xBm3fBb7Xys2vLhPGhESNoXY
x: bN-hp3IN0GZB3OlaQnHDPhY4nZsZbQyo4wY-y1NWCvA
y: vaSsH5jt9zt3aQvTvrSaFYLyjPG9Ug-2vntoNXlCbVU
kid: issuer-key-2

holder_key:
kty: EC
d: 5K5SCos8zf9zRemGGUl6yfok-_NiiryNZsvANWMhF-I
crv: P-256
x: TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc
y: ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ
18 changes: 9 additions & 9 deletions examples/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ key_settings:

kty: EC

issuer_key:
kty: EC
d: Ur2bNKuBPOrAaxsRnbSH6hIhmNTxSGXshDSUD1a1y7g
crv: P-256
x: b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ
y: Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8
issuer_keys:
- kty: EC
d: Ur2bNKuBPOrAaxsRnbSH6hIhmNTxSGXshDSUD1a1y7g
crv: P-256
x: b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ
y: Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8

holder_key:
kty: EC
Expand All @@ -23,9 +23,9 @@ key_settings:

key_binding_nonce: "1234567890"

expiry_seconds: 86400000 # 1000 days
expiry_seconds: 86400000 # 1000 days

random_seed: 0

iat: 1683000000 # Tue May 02 2023 04:00:00 GMT+0000
exp: 1883000000 # Sat Sep 01 2029 23:33:20 GMT+0000
iat: 1683000000 # Tue May 02 2023 04:00:00 GMT+0000
exp: 1883000000 # Sat Sep 01 2029 23:33:20 GMT+0000
104 changes: 63 additions & 41 deletions src/sd_jwt/bin/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
textwrap_json,
textwrap_text,
multiline_code,
markdown_disclosures,
EXAMPLE_SHORT_WIDTH,
)
from sd_jwt.utils.yaml_specification import load_yaml_specification
from sd_jwt.utils.yaml_specification import (
load_yaml_specification,
remove_sdobj_wrappers,
)

logger = logging.getLogger("sd_jwt")

Expand All @@ -35,6 +39,7 @@ def generate_nonce():

DEFAULT_EXP_MINS = 15


def run():
parser = argparse.ArgumentParser(
description=f"{__file__} demo.",
Expand Down Expand Up @@ -119,6 +124,12 @@ def run():

### Load settings
settings = load_yaml_settings(_args.settings_path)
### Load example file
example_identifer = _args.example.stem
example = load_yaml_specification(_args.example)
### "settings_override" key in example can override settings
settings.update(example.get("settings_override", {}))
print(f"Settings: {settings}")

# If "no randomness" is requested, we hash the file name of the example
# file to use it as the random seed. This ensures that the same example
Expand All @@ -134,18 +145,15 @@ def run():
seed = None

demo_keys = get_jwk(settings["key_settings"], _args.no_randomness, seed)

### Load example file

example_identifer = _args.example.stem
example = load_yaml_specification(_args.example)
print(f"Using keys: {demo_keys}")
use_decoys = example.get("add_decoy_claims", False)
serialization_format = example.get("serialization_format", "compact")

### Add default claims if necessary
iat = _args.iat or int(datetime.datetime.utcnow().timestamp())
exp = _args.exp or iat + (DEFAULT_EXP_MINS * 60)
claims = {
"iss": settings.ISSUER,
"iss": settings["identifiers"]["issuer"],
"iat": iat,
"exp": exp,
}
Expand All @@ -156,82 +164,92 @@ def run():
SDJWTIssuer.unsafe_randomness = _args.no_randomness
sdjwt_at_issuer = SDJWTIssuer(
claims,
demo_keys["issuer_key"],
demo_keys["issuer_keys"],
demo_keys["holder_key"] if example.get("key_binding", False) else None,
add_decoy_claims=use_decoys,
serialization_format=serialization_format,
)

### Produce SD-JWT-R for selected example

# Note: The only input from the issuer is the combined SD-JWT and SVC!

sdjwt_at_holder = SDJWTHolder(sdjwt_at_issuer.sd_jwt_issuance)
sdjwt_at_holder = SDJWTHolder(
sdjwt_at_issuer.sd_jwt_issuance,
serialization_format=serialization_format,
)
sdjwt_at_holder.create_presentation(
example["holder_disclosed_claims"],
_args.nonce if example.get("key_binding", False) else None,
settings.VERIFIER if example.get("key_binding", False) else None,
(
settings["identifiers"]["issuer"]
if example.get("key_binding", False)
else None
),
demo_keys["holder_key"] if example.get("key_binding", False) else None,
)

### Verify the SD-JWT using the SD-JWT-R


# Define a function to check the issuer and retrieve the
# matching public key
def cb_get_issuer_key(issuer):
def cb_get_issuer_key(issuer, header_parameters):
# Do not use in production - this allows to use any issuer name for demo purposes
if issuer == claims["iss"]:
return demo_keys["issuer_public_key"]
return demo_keys["issuer_public_keys"]
else:
raise Exception(f"Unknown issuer: {issuer}")


# Note: The only input from the holder is the combined presentation!
sdjwt_at_verifier = SDJWTVerifier(
sdjwt_at_holder.sd_jwt_presentation,
cb_get_issuer_key,
settings.VERIFIER if example.get("key_binding", False) else None,
(
settings["identifiers"]["issuer"]
if example.get("key_binding", False)
else None
),
_args.nonce if example.get("key_binding", False) else None,
serialization_format=serialization_format,
)
verified = sdjwt_at_verifier.get_verified_payload()

### Done - now output everything to CLI (unless --replace-examples-in was used)

iid_payload = ""
for hash in sdjwt_at_holder._hash_to_decoded_disclosure:
salt, claim_name, claim_value = sdjwt_at_holder._hash_to_decoded_disclosure[hash]
b64 = sdjwt_at_holder._hash_to_disclosure[hash]
encoded_json = sdjwt_at_holder._base64url_decode(b64).decode("utf-8")

iid_payload += (
f"__Claim `{claim_name}`:__\n\n"
f" * SHA-256 Hash: `{hash}`\n"
f" * Disclosure:\\\n"
f"{multiline_code(textwrap_text(b64, EXAMPLE_SHORT_WIDTH))}\n"
f" * Contents:\n"
f"{multiline_code(textwrap_text(encoded_json, EXAMPLE_SHORT_WIDTH))}\n\n\n"
)

iid_payload = iid_payload.strip()

_artifacts = {
"user_claims": (example["user_claims"], "User Claims", "json"),
"sd_jwt_payload": (sdjwt_at_issuer.sd_jwt_payload, "Payload of the SD-JWT", "json"),
"user_claims": (
remove_sdobj_wrappers(example["user_claims"]),
"User Claims",
"json",
),
"sd_jwt_payload": (
sdjwt_at_issuer.sd_jwt_payload,
"Payload of the SD-JWT",
"json",
),
"sd_jwt_jws_part": (
sdjwt_at_issuer.serialized_sd_jwt,
"Serialized SD-JWT",
"txt",
),
"disclosures": (iid_payload, "Payloads of the II-Disclosures", "md"),
"disclosures": (
markdown_disclosures(
sdjwt_at_issuer.ii_disclosures,
),
"Payloads of the II-Disclosures",
"md",
),
"sd_jwt_issuance": (
sdjwt_at_issuer.sd_jwt_issuance,
"Combined SD-JWT and Disclosures",
"txt",
),
"kb_jwt_payload": (
sdjwt_at_holder.key_binding_jwt_payload
if example.get("key_binding")
else None,
(
sdjwt_at_holder.key_binding_jwt_payload
if example.get("key_binding")
else None
),
"Payload of the Holder Binding JWT",
"json",
),
Expand All @@ -245,7 +263,11 @@ def cb_get_issuer_key(issuer):
"Combined representation of SD-JWT and HS-Disclosures",
"txt",
),
"verified_contents": (verified, "Verified released contents of the SD-JWT", "json"),
"verified_contents": (
verified,
"Verified released contents of the SD-JWT",
"json",
),
}

# When decoys were used, list those as well
Expand All @@ -262,7 +284,6 @@ def cb_get_issuer_key(issuer):
"md",
)


if _args.output_dir:
logger.info(
f"Writing all the examples into separate files in '{_args.output_dir}'."
Expand Down Expand Up @@ -306,5 +327,6 @@ def cb_get_issuer_key(issuer):

sys.exit(0)


if __name__ == "__main__":
run()
run()
61 changes: 36 additions & 25 deletions src/sd_jwt/bin/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,22 @@


def generate_test_case_data(settings: Dict, testcase_path: Path, type: str):
seed = settings["random_seed"]
demo_keys = get_jwk(settings["key_settings"], True, seed)

### Load test case data
testcase = load_yaml_specification(testcase_path)
settings = {
**settings,
**testcase.get("settings_override", {}),
} # override settings

seed = settings["random_seed"]

demo_keys = get_jwk(settings["key_settings"], True, seed)
use_decoys = testcase.get("add_decoy_claims", False)
serialization_format = testcase.get("serialization_format", "compact")
include_default_claims = testcase.get("include_default_claims", True)
extra_header_parameters = testcase.get("extra_header_parameters", {})
issuer_keys = demo_keys["issuer_keys"]
holder_key = demo_keys["holder_key"] if testcase.get("key_binding", False) else None

claims = {}
if include_default_claims:
Expand All @@ -55,8 +62,8 @@ def generate_test_case_data(settings: Dict, testcase_path: Path, type: str):
SDJWTIssuer.unsafe_randomness = True
sdjwt_at_issuer = SDJWTIssuer(
claims,
demo_keys["issuer_key"],
demo_keys["holder_key"] if testcase.get("key_binding", False) else None,
issuer_keys,
holder_key,
add_decoy_claims=use_decoys,
serialization_format=serialization_format,
extra_header_parameters=extra_header_parameters,
Expand All @@ -70,13 +77,13 @@ def generate_test_case_data(settings: Dict, testcase_path: Path, type: str):
)
sdjwt_at_holder.create_presentation(
testcase["holder_disclosed_claims"],
settings["key_binding_nonce"]
if testcase.get("key_binding", False)
else None,
settings["identifiers"]["verifier"]
if testcase.get("key_binding", False)
else None,
demo_keys["holder_key"] if testcase.get("key_binding", False) else None,
settings["key_binding_nonce"] if testcase.get("key_binding", False) else None,
(
settings["identifiers"]["verifier"]
if testcase.get("key_binding", False)
else None
),
holder_key,
)

### Verify the SD-JWT using the SD-JWT-R
Expand All @@ -86,19 +93,19 @@ def generate_test_case_data(settings: Dict, testcase_path: Path, type: str):
def cb_get_issuer_key(issuer, header_parameters):
# Do not use in production - this allows to use any issuer name for demo purposes
if issuer == claims.get("iss", None):
return demo_keys["issuer_public_key"]
return demo_keys["issuer_public_keys"]
else:
raise Exception(f"Unknown issuer: {issuer}")

sdjwt_at_verifier = SDJWTVerifier(
sdjwt_at_holder.sd_jwt_presentation,
cb_get_issuer_key,
settings["identifiers"]["verifier"]
if testcase.get("key_binding", False)
else None,
settings["key_binding_nonce"]
if testcase.get("key_binding", False)
else None,
(
settings["identifiers"]["verifier"]
if testcase.get("key_binding", False)
else None
),
settings["key_binding_nonce"] if testcase.get("key_binding", False) else None,
serialization_format=serialization_format,
)
verified = sdjwt_at_verifier.get_verified_payload()
Expand Down Expand Up @@ -142,16 +149,20 @@ def cb_get_issuer_key(issuer, header_parameters):
_artifacts.update(
{
"kb_jwt_header": (
sdjwt_at_holder.key_binding_jwt_header
if testcase.get("key_binding")
else None,
(
sdjwt_at_holder.key_binding_jwt_header
if testcase.get("key_binding")
else None
),
"Header of the Holder Binding JWT",
"json",
),
"kb_jwt_payload": (
sdjwt_at_holder.key_binding_jwt_payload
if testcase.get("key_binding")
else None,
(
sdjwt_at_holder.key_binding_jwt_payload
if testcase.get("key_binding")
else None
),
"Payload of the Holder Binding JWT",
"json",
),
Expand Down
Loading

0 comments on commit cde6139

Please sign in to comment.