diff --git a/examples/test-vectors-playground.ipynb b/examples/test-vectors-playground.ipynb new file mode 100644 index 00000000..8188082b --- /dev/null +++ b/examples/test-vectors-playground.ipynb @@ -0,0 +1,268 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test Vectors for draft-lake-authz\n", + "\n", + "Requirements:\n", + "\n", + "```python\n", + "pip install cryptography==3.4.7 cbor2==5.3.0 rich==10.6.0 hkdf==0.0.3\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import rich, cbor2, hkdf, hashlib\n", + "from cryptography.hazmat.primitives import asymmetric, serialization\n", + "from cryptography.hazmat.primitives.ciphers import aead\n", + "from cryptography.hazmat.primitives.asymmetric import ec\n", + "from cryptography.hazmat.primitives import hashes\n", + "from cryptography.hazmat.primitives.kdf.hkdf import HKDF\n", + "from cryptography.hazmat.backends import default_backend\n", + "from binascii import hexlify, unhexlify" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "# input\n", + "LOC_W = \"7818636f61703a2f2f656e726f6c6c6d656e742e736572766572\"\n", + "ID_U = \"a104412b\"\n", + "SS = 2\n", + "\n", + "# static_keys\n", + "U = \"555C89F41EA42D458F0B4D74499E1C177BA9AD910F525BACF3D64D35E8568DEC\"\n", + "G_U = \"AC69E4299F79FAED612E37C37F99D2B3939B142A8E8E65B90FAB5001F7F2CF56\"\n", + "V = \"1DAF151B30F0F247AEB5598C1EEE8664384166BBC37F262DC6581A67486BCF3C\"\n", + "G_V = \"188B3E5A62352FFCDE66894FCDBBCB33D243A045BAA99357A72012A6AF3A33AD\"\n", + "W = \"4E5E15AB35008C15B89E91F9F329164D4AACD53D9923672CE0019F9ACD98573F\"\n", + "G_W = \"FFA4F102134029B3B156890B88C9D9619501196574174DCB68A07DB0588E4D41\"\n", + "\n", + "# ephemeral_keys\n", + "X = \"A0C71BDBA570FFD270D90BDF416C142921F214406271FCF55B8567F079B50DA0\"\n", + "G_X = \"FF14FB42677CE9D016907F571E5E1CD4E815F098AA37084063A0C34570F6F7F5\"\n", + "Y = \"A1D1A1C084AB0D912CC7A15B7F252FABCA252FAD4CAA8E5D569C94578B52A047\"\n", + "G_Y = \"9F69C52FAE8F7EA9194022C70B238FCBF4AFFFDFFC8341EEC85BA68E2F9BB744\"\n", + "Z = \"644658D815CBCA8EA863090A2D498990B5C75357A729231EC3DE7DF5A7AFE49E\"\n", + "G_Z = \"6B67C90638924C4AE8472CA6FB9A90BE5F43132753346379C672972D323F7A41\"\n", + "\n", + "# enc_id\n", + "enc_id = \"36b4ce1137cdc1e9b2c21d8bd5a0c5a311adbd3259\"\n", + "salt_fixme = \"0000000000000000000000000000000000000000000000000000000000000000\"\n", + "g_xw = \"e19be60432dfa2e033af21329d393a5d0d3150a6c998b4f4b951af67694dbe1a\"\n", + "prk = \"04da32d221db25db701667f9d3903374a45a9b04f25d1cb481b099a480cece04\"\n", + "k_1 = \"9540f7fa26ee9430f7caaa37a6ea438a\"\n", + "iv_1 = \"3d9489764d109bc4bda5bd0d13\"\n", + "plaintext = \"44a104412b\"\n", + "enc_structure = \"8368456e63727970743040424102\"\n", + "\n", + "# voucher_info\n", + "voucher_info = \"5832581a7818636f61703a2f2f656e726f6c6c6d656e742e7365727665725536b4ce1137cdc1e9b2c21d8bd5a0c5a311adbd3259\"\n", + "voucher_info_seq = \"581a7818636f61703a2f2f656e726f6c6c6d656e742e7365727665725536b4ce1137cdc1e9b2c21d8bd5a0c5a311adbd3259\"\n" + ] + } + ], + "source": [ + "def format_tv(tv, nokeys=False):\n", + " for k, v in tv.items():\n", + " if k[0] == \"_\" or (nokeys and k in [\"static_keys\", \"ephemeral_keys\"]):\n", + " continue\n", + " elif type(v) == dict:\n", + " print(f\"\\n# {k}\")\n", + " format_tv(v)\n", + " elif type(v) == int:\n", + " print(f'{k:<8} = {v}')\n", + " else:\n", + " print(f'{k:<8} = \"{v}\"')\n", + "\n", + "def add_keys(tv):\n", + " def as_hex(k):\n", + " return hex(k)[2:]\n", + " def new_keypair_dx_testvector(agent):\n", + " private_key = ec.generate_private_key(ec.SECP256R1(), backend=default_backend())\n", + " x = private_key.public_key().public_numbers().x\n", + " y = private_key.public_key().public_numbers().y\n", + " d = private_key.private_numbers().private_value\n", + " return {f\"{agent}\": as_hex(d), f\"G_{agent}\": as_hex(x), f\"_G_{agent}_y\": as_hex(y)}\n", + "\n", + " tv[\"static_keys\"] = {}\n", + " tv[\"ephemeral_keys\"] = {}\n", + " for a in [\"U\", \"V\", \"W\"]:\n", + " tv[\"static_keys\"].update(new_keypair_dx_testvector(a))\n", + " for a in [\"X\", \"Y\", \"Z\"]:\n", + " tv[\"ephemeral_keys\"].update(new_keypair_dx_testvector(a))\n", + "\n", + " return tv\n", + "\n", + "def p256_ecdh(d_hex, x_hex, y_hex):\n", + " private_key = ec.derive_private_key(int(d_hex, 16), ec.SECP256R1(), default_backend())\n", + " # NOTE: rust uses the compressed form of the public key (without the y coordinate), but the result should be the same\n", + " public_key = ec.EllipticCurvePublicNumbers(\n", + " int(x_hex, 16),\n", + " int(y_hex, 16),\n", + " ec.SECP256R1()\n", + " ).public_key(default_backend())\n", + " return private_key.exchange(ec.ECDH(), public_key).hex()\n", + "\n", + "def hkdf_extract(salt, ikm):\n", + " return hkdf.hkdf_extract(unhexlify(salt), unhexlify(ikm), hash=hashlib.sha256).hex()\n", + "\n", + "def hkdf_expand(prk, info, length):\n", + " return hkdf.hkdf_expand(unhexlify(prk), info, length, hash=hashlib.sha256).hex()\n", + "\n", + "def aes_ccm_encrypt_tag_8(key, iv, enc_structure, plaintext):\n", + " return aead.AESCCM(unhexlify(key)).encrypt(unhexlify(iv), unhexlify(plaintext), unhexlify(enc_structure)).hex()\n", + "\n", + "def add_enc_id(tv):\n", + " salt_fixme = \"00\" * 32 # FIXME rust\n", + " g_xw = p256_ecdh(tv[\"ephemeral_keys\"][\"X\"], tv[\"static_keys\"][\"G_W\"], tv[\"static_keys\"][\"_G_W_y\"])\n", + " prk = hkdf_extract(salt_fixme, g_xw)\n", + " k_1 = hkdf_expand(prk, cbor2.dumps(0)+cbor2.dumps(b'')+cbor2.dumps(32), 16) # info is (0, b'', 16) # FIXME[draft] make 'length' explicit\n", + " iv_1 = hkdf_expand(prk, cbor2.dumps(1)+cbor2.dumps(b'')+cbor2.dumps(32), 13) # info is (1, b'', 13) # FIXME[draft] make 'length' explicit\n", + " plaintext = cbor2.dumps(unhexlify(tv[\"input\"][\"ID_U\"])).hex()\n", + " _ss = tv[\"input\"][\"SS\"].to_bytes(1, byteorder='big')\n", + " enc_structure = cbor2.dumps([\"Encrypt0\", b'', cbor2.dumps(_ss)]).hex()\n", + " enc_id = aes_ccm_encrypt_tag_8(k_1, iv_1, enc_structure, plaintext)\n", + " tv.update({\n", + " \"enc_id\": {\n", + " \"enc_id\": enc_id,\n", + " \"salt_fixme\": salt_fixme,\n", + " \"g_xw\": g_xw,\n", + " \"prk\": prk,\n", + " \"k_1\": k_1,\n", + " \"iv_1\": iv_1,\n", + " \"plaintext\": plaintext,\n", + " \"enc_structure\": enc_structure,\n", + " }\n", + " })\n", + "\n", + " return tv\n", + "\n", + "def add_voucher_info(tv):\n", + " voucher_info_seq = (cbor2.dumps(unhexlify(tv[\"input\"][\"LOC_W\"])) + cbor2.dumps(unhexlify(tv[\"enc_id\"][\"enc_id\"]))).hex()\n", + " voucher_info = cbor2.dumps(unhexlify(voucher_info_seq)).hex()\n", + " tv.update({\n", + " \"voucher_info\": {\n", + " \"voucher_info\": voucher_info,\n", + " \"voucher_info_seq\": voucher_info_seq,\n", + " }\n", + " })\n", + " return tv\n", + "\n", + "tv = {\n", + " \"input\": {\n", + " \"LOC_W\": cbor2.dumps(\"coap://enrollment.server\").hex(), # already a tstr\n", + " \"ID_U\": cbor2.dumps({4: b'\\x2B'}).hex(),\n", + " \"SS\": 2,\n", + " },\n", + " 'static_keys': {\n", + " 'U': '555C89F41EA42D458F0B4D74499E1C177BA9AD910F525BACF3D64D35E8568DEC',\n", + " 'G_U': 'AC69E4299F79FAED612E37C37F99D2B3939B142A8E8E65B90FAB5001F7F2CF56',\n", + " '_G_U_y': '9572CF756D05E8B80DF519AEF4BF43E546BCB871A8BC4B676ED548F24F4EC362',\n", + " 'V': '1DAF151B30F0F247AEB5598C1EEE8664384166BBC37F262DC6581A67486BCF3C',\n", + " 'G_V': '188B3E5A62352FFCDE66894FCDBBCB33D243A045BAA99357A72012A6AF3A33AD',\n", + " '_G_V_y': 'C19B9BCB27EF514228016F94DE85C068AFE416B80752EDF256F2593FE367766A',\n", + " 'W': '4E5E15AB35008C15B89E91F9F329164D4AACD53D9923672CE0019F9ACD98573F',\n", + " 'G_W': 'FFA4F102134029B3B156890B88C9D9619501196574174DCB68A07DB0588E4D41',\n", + " '_G_W_y': 'BD08125C1A5E9C4F4AA60198A9F897EB656784DE50C0FE840FE3683FC20C295C'\n", + " },\n", + " 'ephemeral_keys': {\n", + " 'X': 'A0C71BDBA570FFD270D90BDF416C142921F214406271FCF55B8567F079B50DA0',\n", + " 'G_X': 'FF14FB42677CE9D016907F571E5E1CD4E815F098AA37084063A0C34570F6F7F5',\n", + " '_G_X_y': '353AFA30B59B6FA90843F8BECD981CEDC0A2BD3E61421EAFE171544D9994C769',\n", + " 'Y': 'A1D1A1C084AB0D912CC7A15B7F252FABCA252FAD4CAA8E5D569C94578B52A047',\n", + " 'G_Y': '9F69C52FAE8F7EA9194022C70B238FCBF4AFFFDFFC8341EEC85BA68E2F9BB744',\n", + " '_G_Y_y': '52CB7AAF4E56C610C91A6185B92AFF5B03E9F73E6010AEBBA72B9C4BDA269C9A',\n", + " 'Z': '644658D815CBCA8EA863090A2D498990B5C75357A729231EC3DE7DF5A7AFE49E',\n", + " 'G_Z': '6B67C90638924C4AE8472CA6FB9A90BE5F43132753346379C672972D323F7A41',\n", + " '_G_Z_y': 'FA1EFAD24A287B1FEF04683B5B24963A107067541B2E4766088552EE11337D87'\n", + " },\n", + "}\n", + "# tv = add_keys(tv) # uncomment to generate a new set of keys\n", + "\n", + "tv = add_enc_id(tv)\n", + "tv = add_voucher_info(tv)\n", + "\n", + "# rich.print(tv)\n", + "format_tv(tv)\n", + "# format_tv(tv, nokeys=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "..\n", + "----------------------------------------------------------------------\n", + "Ran 2 tests in 0.016s\n", + "\n", + "OK\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 125, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import unittest\n", + "\n", + "class Test(unittest.TestCase):\n", + " def test_ead_1(self):\n", + " self.assertEqual(\n", + " p256_ecdh(tv[\"ephemeral-keys\"][\"X\"], tv[\"static-keys\"][\"G_W\"], tv[\"static-keys\"][\"_G_W_y\"]), \n", + " p256_ecdh(tv[\"static-keys\"][\"W\"], tv[\"ephemeral-keys\"][\"G_X\"], tv[\"ephemeral-keys\"][\"_G_X_y\"]), \n", + " )\n", + "\n", + "unittest.main(argv=[''], exit=False)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +}