Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ implementation compliant with:
- [RFC9053: CBOR Object Signing and Encryption (COSE): Initial Algorithms](https://www.rfc-editor.org/rfc/rfc9053.html)
- [RFC9338: CBOR Object Signing and Encryption (COSE): Countersignatures](https://www.rfc-editor.org/rfc/rfc9338.html) - experimental
- [RFC8392: CWT (CBOR Web Token)](https://tools.ietf.org/html/rfc8392)
- [draft-07: Use of HPKE with COSE](https://www.ietf.org/archive/id/draft-ietf-cose-hpke-07.html) - experimental
- [draft-23: Use of HPKE with COSE](https://www.ietf.org/archive/id/draft-ietf-cose-hpke-23.html) - experimental
- [draft-06: CWT Claims in COSE Headers](https://www.ietf.org/archive/id/draft-ietf-cose-cwt-claims-in-headers-06.html) - experimental
- [draft-13: Fully-Specified Algorithms for JOSE and COSE](https://www.ietf.org/archive/id/draft-ietf-jose-fully-specified-algorithms-13.html)
- and related various specifications. See [Referenced Specifications](#referenced-specifications).
Expand Down Expand Up @@ -542,7 +542,7 @@ rpk = COSEKey.from_jwk(
)
r = Recipient.new(
protected={
COSEHeaders.ALG: COSEAlgs.HPKE_BASE_P256_SHA256_AES128GCM,
COSEHeaders.ALG: COSEAlgs.HPKE_0_KE,
},
unprotected={
COSEHeaders.KID: b"01", # kid: "01"
Expand Down Expand Up @@ -690,7 +690,7 @@ encoded = sender.encode(
b"This is the content.",
rpk,
protected={
COSEHeaders.ALG: COSEAlgs.HPKE_BASE_P256_SHA256_AES128GCM,
COSEHeaders.ALG: COSEAlgs.HPKE_0,
},
unprotected={
COSEHeaders.KID: b"01", # kid: "01"
Expand All @@ -712,6 +712,51 @@ recipient = COSE.new()
assert b"This is the content." == recipient.decode(encoded, rsk)
```

COSE-HPKE (Encrypt0) with psk_id

```py
from cwt import COSE, COSEAlgs, COSEHeaders, COSEKey

# The sender side:
rpk = COSEKey.from_jwk(
{
"kty": "EC",
"kid": "01",
"crv": "P-256",
"x": "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8",
"y": "IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4",
}
)

sender = COSE.new()
encoded = sender.encode(
b"This is the content.",
rpk,
protected={COSEHeaders.ALG: COSEAlgs.HPKE_0},
unprotected={
COSEHeaders.KID: b"01",
COSEHeaders.PSK_ID: b"psk-01", # HPKE PSK identifier
},
hpke_psk=b"secret-psk",
)

# The recipient side:
rsk = COSEKey.from_jwk(
{
"kty": "EC",
"kid": "01",
"crv": "P-256",
"x": "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8",
"y": "IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4",
"d": "V8kgd2ZBRuh2dgyVINBUqpPDr7BOMGcF22CQMIUHtNM",
}
)
recipient = COSE.new()
assert b"This is the content." == recipient.decode(encoded, rsk, hpke_psk=b"secret-psk")
```

Note: `psk_id` (label `-5`) is carried in unprotected headers and validated as a `bstr`. The actual PSK provisioning is deployment-specific as described in the HPKE draft. Pass the PSK via `hpke_psk` to `encode/encode_and_encrypt` and `decode/decode_with_headers` when using the PSK-authenticated variant.

### COSE Encrypt

#### Direct Key Distribution for encryption
Expand Down Expand Up @@ -996,7 +1041,7 @@ rpk = COSEKey.from_jwk(
)
r = Recipient.new(
protected={
COSEHeaders.ALG: COSEAlgs.HPKE_BASE_P256_SHA256_AES128GCM,
COSEHeaders.ALG: COSEAlgs.HPKE_0_KE,
},
unprotected={
COSEHeaders.KID: b"01", # kid: "01"
Expand Down Expand Up @@ -1756,7 +1801,7 @@ Python CWT is (partially) compliant with following specifications:
- [RFC8230: Using RSA Algorithms with COSE Messages](https://tools.ietf.org/html/rfc8230)
- [RFC9459: CBOR Object Signing and Encryption (COSE): AES-CTR and AES-CBC](https://www.rfc-editor.org/rfc/rfc9459.html) - experimental
- [RFC8152: CBOR Object Signing and Encryption (COSE)](https://tools.ietf.org/html/rfc8152)
- [draft-07: Use of HPKE with COSE](https://www.ietf.org/archive/id/draft-ietf-cose-hpke-07.html) - experimental
- [draft-23: Use of HPKE with COSE](https://www.ietf.org/archive/id/draft-ietf-cose-hpke-23.html) - experimental
- [draft-06: CWT Claims in COSE Headers](https://www.ietf.org/archive/id/draft-ietf-cose-cwt-claims-in-headers-06.html) - experimental
- [draft-13: Fully-Specified Algorithms for JOSE and COSE](https://www.ietf.org/archive/id/draft-ietf-jose-fully-specified-algorithms-13.html)
- [Electronic Health Certificate Specification](https://github.com/ehn-dcc-development/hcert-spec/blob/main/hcert_spec.md)
Expand Down
12 changes: 6 additions & 6 deletions cwt/algs/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,13 @@ def __init__(self, params: Dict[int, Any]):
self._key_ops = [2]
if self._alg:
# Validate alg for EC2 curve.
if self._crv == 1 and self._alg not in ([-7, -9, 35, 36] + list(COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values())):
if self._crv == 1 and self._alg not in (
[-7, -9, 35, 45, 46, 53] + list(COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values())
):
raise ValueError(f"Unsupported or unknown alg used with P-256: {self._alg}.")
elif self._crv == 2 and self._alg not in ([-35, -51, 37, 38] + list(COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values())):
elif self._crv == 2 and self._alg not in ([-35, -51, 37, 47] + list(COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values())):
raise ValueError(f"Unsupported or unknown alg used with P-384: {self._alg}.")
elif self._crv == 3 and self._alg not in ([-36, -52, 39, 40] + list(COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values())):
elif self._crv == 3 and self._alg not in ([-36, -52, 39, 48] + list(COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values())):
raise ValueError(f"Unsupported or unknown alg used with P-521: {self._alg}.")
elif self._crv == 8 and self._alg != -47:
raise ValueError(f"Unsupported or unknown alg used with secp256k1: {self._alg}.")
Expand Down Expand Up @@ -129,7 +131,7 @@ def __init__(self, params: Dict[int, Any]):
# private key for key derivation.
self._key_ops = [7, 8]
elif self._alg in COSE_ALGORITHMS_HPKE.values():
if self._key_ops:
if 4 in params:
if -4 in params:
# private key for key derivation.
if len(self._key_ops) != 1 or self._key_ops[0] != 8:
Expand All @@ -139,8 +141,6 @@ def __init__(self, params: Dict[int, Any]):
if len(self._key_ops) > 0:
raise ValueError("Invalid key_ops for HPKE public key.")
else:
if -4 in params and isinstance(self._key_ops, list) and len(self._key_ops) == 0:
raise ValueError("Invalid key_ops for HPKE private key.")
self._key_ops = [8] if -4 in params else []
else:
raise ValueError(f"Unsupported or unknown alg(3) for EC2: {self._alg}.")
Expand Down
4 changes: 1 addition & 3 deletions cwt/algs/okp.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def __init__(self, params: Dict[int, Any]):
else:
self._key_ops = [1, 2] if -4 in params else [2]
elif self._alg in COSE_ALGORITHMS_HPKE.values():
if self._key_ops:
if 4 in params:
if -4 in params:
# private key for key derivation.
if len(self._key_ops) != 1 or self._key_ops[0] != 8:
Expand All @@ -118,8 +118,6 @@ def __init__(self, params: Dict[int, Any]):
if len(self._key_ops) > 0:
raise ValueError("Invalid key_ops for HPKE public key.")
else:
if -4 in params and isinstance(self._key_ops, list) and len(self._key_ops) == 0:
raise ValueError("Invalid key_ops for HPKE private key.")
self._key_ops = [8] if -4 in params else []
else:
# self._alg in COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values():
Expand Down
41 changes: 30 additions & 11 deletions cwt/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
"cty": 3,
"content type": 3,
"kid": 4,
"ek": -4,
"psk_id": -5,
"iv": 5,
"IV": 5,
"Partial IV": 6,
Expand Down Expand Up @@ -195,17 +197,33 @@
# etc.
}

COSE_ALGORITHMS_HPKE_INTEGRATED = {
# Integrated Encryption per draft-ietf-cose-hpke-23
"HPKE-0": 35, # DHKEM(P-256, HKDF-SHA256) + HKDF-SHA256 + AES-128-GCM
"HPKE-1": 37, # DHKEM(P-384, HKDF-SHA384) + HKDF-SHA384 + AES-256-GCM
"HPKE-2": 39, # DHKEM(P-521, HKDF-SHA512) + HKDF-SHA512 + AES-256-GCM
"HPKE-3": 41, # DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-128-GCM
"HPKE-4": 42, # DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + ChaCha20Poly1305
"HPKE-5": 43, # DHKEM(X448, HKDF-SHA512) + HKDF-SHA512 + AES-256-GCM
"HPKE-6": 44, # DHKEM(X448, HKDF-SHA512) + HKDF-SHA512 + ChaCha20Poly1305
"HPKE-7": 45, # DHKEM(P-256, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM
}

COSE_ALGORITHMS_HPKE_KE = {
# Key Encryption per draft-ietf-cose-hpke-23
"HPKE-0-KE": 46, # DHKEM(P-256, HKDF-SHA256) + HKDF-SHA256 + AES-128-GCM
"HPKE-1-KE": 47, # DHKEM(P-384, HKDF-SHA384) + HKDF-SHA384 + AES-256-GCM
"HPKE-2-KE": 48, # DHKEM(P-521, HKDF-SHA512) + HKDF-SHA512 + AES-256-GCM
"HPKE-3-KE": 49, # DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-128-GCM
"HPKE-4-KE": 50, # DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + ChaCha20Poly1305
"HPKE-5-KE": 51, # DHKEM(X448, HKDF-SHA512) + HKDF-SHA512 + AES-256-GCM
"HPKE-6-KE": 52, # DHKEM(X448, HKDF-SHA512) + HKDF-SHA512 + ChaCha20Poly1305
"HPKE-7-KE": 53, # DHKEM(P-256, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM
}

COSE_ALGORITHMS_HPKE = {
"HPKE-Base-P256-SHA256-AES128GCM": 35,
"HPKE-Base-P256-SHA256-ChaCha20Poly1305": 36,
"HPKE-Base-P384-SHA384-AES256GCM": 37,
"HPKE-Base-P384-SHA384-ChaCha20Poly1305": 38,
"HPKE-Base-P521-SHA512-AES256GCM": 39,
"HPKE-Base-P521-SHA512-ChaCha20Poly1305": 40,
"HPKE-Base-X448-SHA512-AES256GCM": 43,
"HPKE-Base-X448-SHA512-ChaCha20Poly1305": 44,
"HPKE-Base-X25519-SHA256-AES128GCM": 41,
"HPKE-Base-X25519-SHA256-ChaCha20Poly1305": 42,
**COSE_ALGORITHMS_HPKE_INTEGRATED,
**COSE_ALGORITHMS_HPKE_KE,
}

COSE_ALGORITHMS_CKDM_KEY_AGREEMENT_WITH_KEY_WRAP_SS = {
Expand Down Expand Up @@ -400,7 +418,7 @@
**COSE_ALGORITHMS_CKDM,
**COSE_ALGORITHMS_KEY_WRAP,
**COSE_ALGORITHMS_CKDM_KEY_AGREEMENT,
**COSE_ALGORITHMS_HPKE,
**COSE_ALGORITHMS_HPKE_KE,
}

# COSE Algorithms for Symmetric Keys.
Expand All @@ -425,6 +443,7 @@
**COSE_ALGORITHMS_SIGNATURE,
**COSE_ALGORITHMS_SYMMETRIC,
**COSE_ALGORITHMS_RECIPIENT,
**COSE_ALGORITHMS_HPKE_INTEGRATED,
}

# COSE Named Algorithms for converting from JWK-like key.
Expand Down
Loading
Loading