Skip to content

Commit 3a9c5fc

Browse files
committed
chore(hpke): add script to generate testvectors.txt with PSK vectors
1 parent 5cc2776 commit 3a9c5fc

2 files changed

Lines changed: 442 additions & 192 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
#!/usr/bin/env python3
2+
"""Generate tests/vectors/testvectors.txt including PSK vectors with psk_id in protected header."""
3+
4+
import os
5+
import sys
6+
7+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
8+
9+
import cbor2
10+
11+
from cwt import COSE, COSEKey, Recipient
12+
13+
# PSK parameters from the RFC test vector section
14+
PSK = bytes.fromhex(
15+
"0247fd33b913760fa1fa51e1892d9f307fbe65eb171e8132c2af18555a738b82"
16+
)
17+
PSK_ID = bytes.fromhex("456e6e796e20447572696e206172616e204d6f726961")
18+
19+
PLAINTEXT = b"hpke test payload"
20+
21+
# External parameter values from the RFC
22+
EXT_AAD = b"external-aad"
23+
EXT_INFO = b"external-info"
24+
EXT_HPKE_AAD = b"external-hpke-aad"
25+
26+
# KE private keys (COSE_Key CBOR hex)
27+
HPKE_0_KE_KEY = "a70102024d626f622d68706b655f305f6b6503182e200121582064ea61f745f7deed186d697a4c89715932755017766348b0443a60aac450b5a622582088f53a4cbbcfcc1bf0b33d5dc60f789a7f495244f57c158a8ceed5179639152b235820e8de39325f3c0be02442076c470a46bca742de9bc2be453ec1dc049dda1f6ca3" # noqa: E501
28+
HPKE_1_KE_KEY = "a70102024d626f622d68706b655f315f6b6503182f200221583003fcd256d1fd79ce8d6d29e3cb72a823380e1c655aa2ce211721245873bacb76eacd6e28f4557fed255246a76fdd61b82258304dd4aa71088792b44e00970c2f269c1eb546e848a6df2946e4409777deb6d7b77803a383c9e87757cef9f18910a1f76423583035172a2ccec0f1d1af547b811754e01de5406257ca808f2fabcbca5cbf7a4d22b951fc1d4da0e89e8608fde30d2f6706" # noqa: E501
29+
HPKE_2_KE_KEY = "a70102024d626f622d68706b655f325f6b6503183020032158420033db899e500ac6f1fb7a9e23f16a363e41b6d1f6dd5562c4faaa0491f1a74cbdbd039ff2b5824842d4da26c36173bc31ba2d1672699d871fdca27b9af0020bb580225842012ecb4d569869085618ce0a4e0f82fe9b618dae8b678e26e7a1ed8d8b9bdf7ffcd32dfdee1bd85ee52097866c4f493a3174e6abb6b365057d212ce3d84a5010a6df235842019f28872f689d9c3a8018712e453a23beac37cb86c87e2c5a99d7e3901f2e4f4995fae274ca07748a7076d0ecae6466a7c3cdbc55d233544a59d22d3e4dde1d4b5f" # noqa: E501
30+
HPKE_3_KE_KEY = "a60101024d626f622d68706b655f335f6b6503183120042158202d925acfd0ee359a68565b619165985a7108f7b1771131e26f11d24177dc9a3c23582060cb9ff63744acdac02a48527dfc2810fc49bc1223a240d870fa2d668c891155" # noqa: E501
31+
HPKE_4_KE_KEY = "a60101024d626f622d68706b655f345f6b650318322004215820a5922a701eebdf665a7877e32b0651db5d3ad8eb4be792f2dfd9d9ac5d04956123582000f28ee18a4ddcdd4f318dd88ba71efe0bb68002015e9c4879e99edf4e9c4b60" # noqa: E501
32+
HPKE_5_KE_KEY = "a60101024d626f622d68706b655f355f6b6503183320052158384489c1479ccd35343a90b3e1cb4922f73d9d611f12bf4abe9f76fcac6a6a974c0941fa602dfc29fb5c52b3191ea896162718d2ddbc97097e235838785cb877d73f034edaaa14d66dc3e10bc28d3ee5a290310c89eab7e347a82218874963600cf36850a389325fcbb6e4477dcc0f1b65e860d9" # noqa: E501
33+
HPKE_6_KE_KEY = "a60101024d626f622d68706b655f365f6b650318342005215838253b435291775cff909b2227b8bd6f539f521368b33871022f95713b4433df21becfffeaba9d63e839e43413e92689ead254feae3d7aa8e72358382c6894f63ec5d05047370d9415d4c0cd53ee2633926596788a41b5ff5368733b7d9499c391b08ed7c1c3d750c4c5af2ff03a44278c7c40b6" # noqa: E501
34+
HPKE_7_KE_KEY = "a70102024d626f622d68706b655f375f6b65031835200121582055137ef3179b4bba4326a5e73ae0966d92d2ccc7e1714a66fba562a1c597a08d2258201daa17ff95d717128dc944069f4060af5981575734f1f847e6bd6bc30603cd6123582073294f0f394f08becf7358ea89c0cda596cbd9705a6b7c6f0ae8d70a9a85a913" # noqa: E501
35+
36+
# Encrypt0 private keys (COSE_Key CBOR hex)
37+
HPKE_0_KEY = "a70102024e626f622d68706b655f302d696e7403182320012158206699b067898b7d2d37db0da3aecad4bdac1558870b47d67d080d6049fb81752f225820b01b6da1f210f46e20e2b552a80f4f6b9a3adad34a6701f73fbbeffb174cf7412358206716e93d6594fbfd27016daada9ccc8e6ba2eea0e103e3d7ae22278f6dfe124a" # noqa: E501
38+
HPKE_1_KEY = "a70102024e626f622d68706b655f312d696e7403182520022158308309a370b333f956c1cff9d94e1ef8aacc2808ca898fec0476d9c132893704a2a4ecc88bd002e2c71383b97bb3ab65822258304b2a3e1b2fc832c136aee1632f967b31f5afd0a32c8c9766d0e9d0e4e2560a905278b0d9965898b3fe4d2165cfa1b1c0235830bde0361bbbf278ff3286a36897b2e674286870981ef471c2c81b55a3b82827800d32b34da68993cd590ff06e0788aeaf" # noqa: E501
39+
HPKE_2_KEY = "a70102024e626f622d68706b655f322d696e740318272003215842003c20a6d2990dac871dec57d8f31283ca99b9958a00e92ba43b1ff9186813f750b01333ef1f3119601875065599aa48884425480a4d20e8e39bc84e98f745d91ed72258420058edb9dbccddc1594dc9003ab39886babd7ef7d0046aa72eae0f9c67b794c251c8a2309ae05f6f1cf4ac06045ecd45bc335d5c316936e3968e6ed42211bfdaa859235842010c50be4e0322d8bcb1424750f6ed3b22bcbe25ae9745a868688dcbbab97f522f5a95d0712b8d9ff48a5be6650179fd4e59913c76b1b28af9605ddb294756c2effd" # noqa: E501
40+
HPKE_3_KEY = "a60101024e626f622d68706b655f332d696e74031829200421582085eb6351a4e93a49953e1e23ade9504af68a73196a823c9a0654bf98c7536a7f235820f0b8ece6e3938430f36798eeea8206d0ac5e0577349ad63843cbbb63bc90b849" # noqa: E501
41+
HPKE_4_KEY = "a60101024e626f622d68706b655f342d696e7403182a20042158200191a45e7240233a4bda72ac8b38283aea336c863c7d5856b7df263038bc69072358200838e90c3407649faf0bd7eeb3e5a9fd7c643e4cb72b91997fc81d26d2f1de49" # noqa: E501
42+
HPKE_5_KEY = "a60101024e626f622d68706b655f352d696e7403182b2005215838fa09d4a5d1fa3a7b2b6de43b08c715283d7425b80bf8b628b07d0d077283aa9c1507354e98c087688e8cfe7220be5e2d44509b2fd53b24e9235838b07f1d8cb1d2f3d5ba62c0ad5a1791e0fe79f6fdb9f49910274aa184855b67850ab2a53b39b131d07bc3d4e80a4f83b1c9f8f5f97f1fa598" # noqa: E501
43+
HPKE_6_KEY = "a60101024e626f622d68706b655f362d696e7403182c20052158380aff5f4a86fc468a25b7715d066628125dad13e4243f242cd6585f89f7371a55cfc3cf42cd3405a78dd380b4e9f4d47880c684deaa3f8aa923583898b6c98f0d48162ecc4c0f5e09c97246b03564a2672e12496f0f7a0d0576fbbdfb287b5a868e5b569a55b7d3765e5685feb7270471b13392" # noqa: E501
44+
HPKE_7_KEY = "a70102024e626f622d68706b655f372d696e7403182d2001215820df717fb8deae1b58b754487c5432c8ec9a140dd11bcc7cd65cbe4b728e9263d6225820a8528d6143673203144a9636ea065c60761390916f2218c8db958a64e263d3e02358202343a73ed3dc2b5e110d734c8d5e7a8b7fea63849e78a8db3da48a65ecdb720e" # noqa: E501
45+
46+
# KE keys and their L0 content algorithm names
47+
KE_KEYS = [
48+
(HPKE_0_KE_KEY, "A128GCM"),
49+
(HPKE_1_KE_KEY, "A256GCM"),
50+
(HPKE_2_KE_KEY, "A256GCM"),
51+
(HPKE_3_KE_KEY, "A128GCM"),
52+
(HPKE_4_KE_KEY, "ChaCha20/Poly1305"),
53+
(HPKE_5_KE_KEY, "A256GCM"),
54+
(HPKE_6_KE_KEY, "A256GCM"),
55+
(HPKE_7_KE_KEY, "A256GCM"),
56+
]
57+
58+
E0_KEYS = [
59+
HPKE_0_KEY,
60+
HPKE_1_KEY,
61+
HPKE_2_KEY,
62+
HPKE_3_KEY,
63+
HPKE_4_KEY,
64+
HPKE_5_KEY,
65+
HPKE_6_KEY,
66+
HPKE_7_KEY,
67+
]
68+
69+
# KE parameter combinations: (ext_aad, ext_info, hpke_aad)
70+
KE_COMBOS = [
71+
(b"", b"", b"", "default aad, default info, default hpke aad"),
72+
(b"", b"", EXT_HPKE_AAD, "default aad, default info, external hpke aad"),
73+
(EXT_AAD, b"", b"", "external aad, default info, default hpke aad"),
74+
(EXT_AAD, b"", EXT_HPKE_AAD, "external aad, default info, external hpke aad"),
75+
(b"", EXT_INFO, b"", "default aad, external info, default hpke aad"),
76+
(b"", EXT_INFO, EXT_HPKE_AAD, "default aad, external info, external hpke aad"),
77+
(EXT_AAD, EXT_INFO, b"", "external aad, external info, default hpke aad"),
78+
(EXT_AAD, EXT_INFO, EXT_HPKE_AAD, "external aad, external info, external hpke aad"),
79+
]
80+
81+
# Encrypt0 parameter combinations: (ext_aad, hpke_info)
82+
E0_COMBOS = [
83+
(b"", b"", "default aad and default info"),
84+
(EXT_AAD, b"", "external aad and default info"),
85+
(b"", EXT_INFO, "default aad and external info"),
86+
(EXT_AAD, EXT_INFO, "external aad and external info"),
87+
]
88+
89+
90+
def extract_public_key(key_hex):
91+
"""Extract public key from COSE_Key hex (remove private key -4/d)."""
92+
key_data = cbor2.loads(bytes.fromhex(key_hex))
93+
rpk_data = {k: v for k, v in key_data.items() if k != -4}
94+
return rpk_data, key_data
95+
96+
97+
def generate_ke_vector(key_hex, content_alg_name, ext_aad, extra_info, hpke_aad, psk=None):
98+
"""Generate a KE (COSE_Encrypt) vector."""
99+
rpk_data, full_key_data = extract_public_key(key_hex)
100+
rpk = COSEKey.new(rpk_data)
101+
kid = full_key_data[2]
102+
ke_alg = full_key_data[3]
103+
104+
rec_protected = {1: ke_alg}
105+
if psk is not None:
106+
rec_protected[-5] = PSK_ID
107+
108+
r = Recipient.new(
109+
protected=rec_protected,
110+
unprotected={4: kid},
111+
recipient_key=rpk,
112+
hpke_psk=psk,
113+
extra_info=extra_info,
114+
hpke_aad=hpke_aad,
115+
)
116+
117+
enc_key = COSEKey.from_symmetric_key(alg=content_alg_name)
118+
sender = COSE.new()
119+
encoded = sender.encode_and_encrypt(
120+
PLAINTEXT,
121+
enc_key,
122+
protected={1: enc_key.alg},
123+
recipients=[r],
124+
external_aad=ext_aad,
125+
)
126+
127+
# Verify
128+
full_key = COSEKey.new(full_key_data)
129+
result = COSE.new().decode(
130+
encoded, full_key,
131+
external_aad=ext_aad, extra_info=extra_info, hpke_aad=hpke_aad,
132+
hpke_psk=psk,
133+
)
134+
assert result == PLAINTEXT, f"Decryption verification failed!"
135+
136+
return encoded.hex()
137+
138+
139+
def generate_e0_vector(key_hex, ext_aad, hpke_info, psk=None):
140+
"""Generate an Encrypt0 (COSE_Encrypt0) vector."""
141+
rpk_data, full_key_data = extract_public_key(key_hex)
142+
rpk = COSEKey.new(rpk_data)
143+
kid = full_key_data[2]
144+
alg = full_key_data[3]
145+
146+
protected = {1: alg}
147+
if psk is not None:
148+
protected[-5] = PSK_ID
149+
150+
sender = COSE.new()
151+
encoded = sender.encode_and_encrypt(
152+
PLAINTEXT,
153+
rpk,
154+
protected=protected,
155+
unprotected={4: kid},
156+
hpke_psk=psk,
157+
external_aad=ext_aad,
158+
hpke_info=hpke_info,
159+
)
160+
161+
# Verify
162+
full_key = COSEKey.new(full_key_data)
163+
result = COSE.new().decode(
164+
encoded, full_key,
165+
external_aad=ext_aad, hpke_info=hpke_info,
166+
hpke_psk=psk,
167+
)
168+
assert result == PLAINTEXT, f"Decryption verification failed!"
169+
170+
return encoded.hex()
171+
172+
173+
def main():
174+
out_path = os.path.join(os.path.dirname(__file__), "..", "tests", "vectors", "testvectors.txt")
175+
os.makedirs(os.path.dirname(out_path), exist_ok=True)
176+
177+
lines = []
178+
179+
# --- KE base vectors ---
180+
for i, (key_hex, content_alg_name) in enumerate(KE_KEYS):
181+
lines.append(f"HPKE-{i}-KE COSE_Key:: {key_hex}")
182+
lines.append("")
183+
for ext_aad, extra_info, hpke_aad, desc in KE_COMBOS:
184+
lines.append("")
185+
lines.append(f"HPKE-{i}-KE with {desc}")
186+
lines.append("")
187+
ct = generate_ke_vector(key_hex, content_alg_name, ext_aad, extra_info, hpke_aad)
188+
lines.append(f"Ciphertext: {ct}")
189+
lines.append("")
190+
lines.append("")
191+
192+
# --- Encrypt0 base vectors ---
193+
for i, key_hex in enumerate(E0_KEYS):
194+
lines.append(f"HPKE-{i} COSE_Key: {key_hex}")
195+
lines.append("")
196+
for ext_aad, hpke_info, desc in E0_COMBOS:
197+
lines.append("")
198+
lines.append(f"HPKE-{i} Encrypt0 with {desc}")
199+
lines.append("")
200+
ct = generate_e0_vector(key_hex, ext_aad, hpke_info)
201+
lines.append(f"Ciphertext: {ct}")
202+
lines.append("")
203+
lines.append("")
204+
205+
# --- KE PSK vectors ---
206+
for i, (key_hex, content_alg_name) in enumerate(KE_KEYS):
207+
lines.append(f"HPKE-{i}-KE COSE_Key: {key_hex}")
208+
lines.append("")
209+
for ext_aad, extra_info, hpke_aad, desc in KE_COMBOS:
210+
lines.append("")
211+
lines.append(f"HPKE-{i}-KE KE+PSK with {desc}")
212+
lines.append("")
213+
ct = generate_ke_vector(key_hex, content_alg_name, ext_aad, extra_info, hpke_aad, psk=PSK)
214+
lines.append(f"Ciphertext: {ct}")
215+
lines.append("")
216+
lines.append("")
217+
218+
# --- Encrypt0 PSK vectors ---
219+
for i, key_hex in enumerate(E0_KEYS):
220+
lines.append(f"HPKE-{i} COSE_Key:: {key_hex}")
221+
lines.append("")
222+
for ext_aad, hpke_info, desc in E0_COMBOS:
223+
lines.append("")
224+
lines.append(f"HPKE-{i} Encrypt0+PSK with {desc}")
225+
lines.append("")
226+
ct = generate_e0_vector(key_hex, ext_aad, hpke_info, psk=PSK)
227+
lines.append(f"Ciphertext: {ct}")
228+
lines.append("")
229+
lines.append("")
230+
231+
# Remove trailing blank lines
232+
while lines and lines[-1] == "":
233+
lines.pop()
234+
235+
with open(out_path, "w") as f:
236+
f.write("\n".join(lines) + "\n")
237+
238+
print(f"Generated {out_path}")
239+
240+
# Count vectors
241+
ct_count = sum(1 for l in lines if l.startswith("Ciphertext:"))
242+
print(f"Total vectors: {ct_count}")
243+
print(f" KE base: {len(KE_KEYS) * len(KE_COMBOS)}")
244+
print(f" Encrypt0 base: {len(E0_KEYS) * len(E0_COMBOS)}")
245+
print(f" KE PSK: {len(KE_KEYS) * len(KE_COMBOS)}")
246+
print(f" Encrypt0 PSK: {len(E0_KEYS) * len(E0_COMBOS)}")
247+
248+
249+
if __name__ == "__main__":
250+
main()

0 commit comments

Comments
 (0)