Skip to content

Commit eb65120

Browse files
authored
Add support for COSE-HPKE DHKEM-X25519/X448.
Add support for COSE-HPKE DHKEM-X25519/X448.
2 parents 83c3be4 + 4fd9e52 commit eb65120

3 files changed

Lines changed: 132 additions & 3 deletions

File tree

cwt/algs/okp.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from ..const import ( # COSE_KEY_LEN,
2727
COSE_ALGORITHMS_CKDM_KEY_AGREEMENT,
2828
COSE_ALGORITHMS_CKDM_KEY_AGREEMENT_ES,
29+
COSE_ALGORITHMS_HPKE,
2930
COSE_ALGORITHMS_SIG_OKP,
3031
COSE_KEY_LEN,
3132
COSE_KEY_OPERATION_VALUES,
@@ -78,6 +79,8 @@ def __init__(self, params: Dict[int, Any]):
7879
self._hash_alg = hashes.SHA256
7980
elif self._alg in [-26, -28]:
8081
self._hash_alg = hashes.SHA512
82+
elif self._alg == -1:
83+
self._hash_alg = hashes.SHA256 if self._crv == 4 else hashes.SHA512
8184
else:
8285
raise ValueError(f"Unsupported or unknown alg used with X25519/X448: {self._alg}.")
8386

@@ -113,6 +116,9 @@ def __init__(self, params: Dict[int, Any]):
113116
# public key for key derivation.
114117
if self._key_ops:
115118
raise ValueError("Public key for ECDHE should not have key_ops.")
119+
elif self._alg in COSE_ALGORITHMS_HPKE.values():
120+
if not (set(self._key_ops) & set([7, 8])):
121+
raise ValueError("Invalid key_ops for HPKE.")
116122
else:
117123
raise ValueError(f"Unsupported or unknown alg(3) for OKP: {self._alg}.")
118124
else:

tests/test_algs_okp.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,23 @@ def test_okp_key_derive_key_with_raw_context(self):
346346
},
347347
"Unsupported or unknown crv(-1) for OKP: 8.",
348348
),
349+
(
350+
{
351+
1: 1,
352+
-1: 6,
353+
-2: b"\x18Es\xe0\x9a\x83\xfd\x0e\xe9K\xa8n\xf39i\x17\xfe\n2+|\xd1q\xcc\x87\xd2\xe9\xa9\xe8 \x9b\xd9",
354+
3: -999,
355+
},
356+
"Unsupported or unknown alg(3) for OKP: -999.",
357+
),
349358
(
350359
{
351360
1: 1,
352361
-1: 6,
353362
-2: b"\x18Es\xe0\x9a\x83\xfd\x0e\xe9K\xa8n\xf39i\x17\xfe\n2+|\xd1q\xcc\x87\xd2\xe9\xa9\xe8 \x9b\xd9",
354363
3: -1,
355364
},
356-
"Unsupported or unknown alg(3) for OKP: -1.",
365+
"Invalid key_ops for HPKE.",
357366
),
358367
(
359368
{1: 1, -1: 6, -2: "xxxxxxxxxxxxxxxx"},

tests/test_cose_hpke.py

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ def test_cose_hpke_kem_0x0012(self, kdf, aead):
147147
rpk = COSEKey.from_jwk(
148148
{
149149
"kty": "EC",
150-
"use": "sig",
151150
"crv": "P-521",
152151
"kid": "01",
153152
"x": "APkZitSJMJUMB-iPCt47sWu_CrnUHg6IAR4qjmHON-2u41Rjg6DNOS0LZYJJt-AVH5NgGVi8ElIfjo71b9HXCTOc",
@@ -176,7 +175,6 @@ def test_cose_hpke_kem_0x0012(self, kdf, aead):
176175
rsk = COSEKey.from_jwk(
177176
{
178177
"kty": "EC",
179-
"use": "sig",
180178
"crv": "P-521",
181179
"kid": "01",
182180
"x": "APkZitSJMJUMB-iPCt47sWu_CrnUHg6IAR4qjmHON-2u41Rjg6DNOS0LZYJJt-AVH5NgGVi8ElIfjo71b9HXCTOc",
@@ -186,3 +184,119 @@ def test_cose_hpke_kem_0x0012(self, kdf, aead):
186184
)
187185
recipient = COSE.new()
188186
assert b"This is the content." == recipient.decode(encoded, rsk)
187+
188+
@pytest.mark.parametrize(
189+
"kdf, aead",
190+
[
191+
(0x0001, 0x0001),
192+
(0x0001, 0x0002),
193+
(0x0001, 0x0003),
194+
(0x0002, 0x0001),
195+
(0x0002, 0x0002),
196+
(0x0002, 0x0003),
197+
(0x0003, 0x0001),
198+
(0x0003, 0x0002),
199+
(0x0003, 0x0003),
200+
],
201+
)
202+
def test_cose_hpke_kem_0x0020(self, kdf, aead):
203+
rpk = COSEKey.from_jwk(
204+
{
205+
"kty": "OKP",
206+
"crv": "X25519",
207+
"kid": "01",
208+
"alg": "HPKE",
209+
"x": "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI",
210+
"key_ops": ["deriveKey", "deriveBits"],
211+
}
212+
)
213+
214+
sender = COSE.new()
215+
encoded = sender.encode_and_encrypt(
216+
b"This is the content.",
217+
rpk,
218+
protected={
219+
1: -1, # alg: "HPKE"
220+
},
221+
unprotected={
222+
4: b"01", # kid: "01"
223+
-4: { # HPKE sender information
224+
1: 0x0020,
225+
2: kdf,
226+
3: aead,
227+
},
228+
},
229+
)
230+
231+
# The recipient side:
232+
rsk = COSEKey.from_jwk(
233+
{
234+
"kty": "OKP",
235+
"crv": "X25519",
236+
"kid": "01",
237+
"alg": "HPKE",
238+
"x": "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI",
239+
"d": "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4",
240+
"key_ops": ["deriveKey", "deriveBits"],
241+
}
242+
)
243+
recipient = COSE.new()
244+
assert b"This is the content." == recipient.decode(encoded, rsk)
245+
246+
@pytest.mark.parametrize(
247+
"kdf, aead",
248+
[
249+
(0x0001, 0x0001),
250+
(0x0001, 0x0002),
251+
(0x0001, 0x0003),
252+
(0x0002, 0x0001),
253+
(0x0002, 0x0002),
254+
(0x0002, 0x0003),
255+
(0x0003, 0x0001),
256+
(0x0003, 0x0002),
257+
(0x0003, 0x0003),
258+
],
259+
)
260+
def test_cose_hpke_kem_0x0021(self, kdf, aead):
261+
rpk = COSEKey.from_jwk(
262+
{
263+
"kty": "OKP",
264+
"crv": "X448",
265+
"kid": "01",
266+
"alg": "HPKE",
267+
"x": "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI",
268+
"key_ops": ["deriveKey"],
269+
}
270+
)
271+
272+
sender = COSE.new()
273+
encoded = sender.encode_and_encrypt(
274+
b"This is the content.",
275+
rpk,
276+
protected={
277+
1: -1, # alg: "HPKE"
278+
},
279+
unprotected={
280+
4: b"01", # kid: "01"
281+
-4: { # HPKE sender information
282+
1: 0x0021,
283+
2: kdf,
284+
3: aead,
285+
},
286+
},
287+
)
288+
289+
# The recipient side:
290+
rsk = COSEKey.from_jwk(
291+
{
292+
"kty": "OKP",
293+
"crv": "X448",
294+
"kid": "01",
295+
"alg": "HPKE",
296+
"x": "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI",
297+
"d": "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c",
298+
"key_ops": ["deriveKey"],
299+
}
300+
)
301+
recipient = COSE.new()
302+
assert b"This is the content." == recipient.decode(encoded, rsk)

0 commit comments

Comments
 (0)