|
| 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