mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
PR-URL: https://github.com/nodejs/node/pull/59569 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: James M Snell <jasnell@gmail.com>
512 lines
22 KiB
JavaScript
512 lines
22 KiB
JavaScript
'use strict';
|
|
|
|
const common = require('../common');
|
|
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
|
|
const { hasOpenSSL } = require('../common/crypto');
|
|
|
|
if (!hasOpenSSL(3, 5))
|
|
common.skip('requires OpenSSL >= 3.5');
|
|
|
|
const assert = require('assert');
|
|
const { subtle } = globalThis.crypto;
|
|
|
|
const fixtures = require('../common/fixtures');
|
|
|
|
function getKeyFileName(type, suffix) {
|
|
return `${type.replaceAll('-', '_')}_${suffix}.pem`;
|
|
}
|
|
|
|
function toDer(pem) {
|
|
const der = pem.replace(/(?:-----(?:BEGIN|END) (?:PRIVATE|PUBLIC) KEY-----|\s)/g, '');
|
|
return Buffer.alloc(Buffer.byteLength(der, 'base64'), der, 'base64');
|
|
}
|
|
|
|
/* eslint-disable @stylistic/js/max-len */
|
|
const keyData = {
|
|
'ML-DSA-44': {
|
|
pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_seed_only'), 'ascii')),
|
|
pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private'), 'ascii')),
|
|
pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'private_priv_only'), 'ascii')),
|
|
spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-44', 'public'), 'ascii')),
|
|
jwk: {
|
|
kty: 'AKP',
|
|
alg: 'ML-DSA-44',
|
|
pub: 'fYmD1Rx_jkoW9KG7Bs_5zyYEiWEZs15tYBxNdKq9NircZnvZBwwwaGbj0UsxJNc4Dyfp2IFAZZPO3rFCSUdpXHPrGRHwIVMzwiwfu2V7V02xoheW4mrkPThA3JRJSmNdsx6YGu37MaeJkIk6AlUexo46JfGrkRXZp_IyZxiL_L2dPrfwx-32j7WFI5sBadp7cDWfNkJjdQwW4puTe5Rw7h16GHb-DMOAKpfeMHujh7IYHuLCU6lVi90j1m8Ru0dxdmeQ1eY1vDnO7fNQKfzOLhpUNnj7BBZ24GTqFc-SN5HDCSCsSGKScTYYBwiSVTdSGG1GNqIiN2FgE4z1Jj6JFVB_OIUnl4sKbb3m8kB0BwtUPbkC0FVokGRUEGt6ba1Pc_IMpB5Gs3g9PFREI_C9o1yVW3NS2PzH_Vk4Tpf0N1K1kzIK_3IqekLfyqXmVDNsOovsS7Sw9TdmdWUNGRmhXFKRkex5VjpMIx7OwBGsYJCc4FhauWdrVtbkvHGggSpsla73ZcA4Vzh7aq47LMv0KS2YLp-DMn7SEohPHGg74118eLLn88yptxwtwt1dBFj8BKUfPrytuN1EIRQy34hwbkBLN9wDqhgn3Z3fvksRvmgN_4ZQ8YjeD-H3OFh5WJ_Rd66wHSl-YFat-_JF4UPcdlkNUbxPvDi5VL909Pe3VlwEZhT5otdtXQX4U3dUfqWKEh2kN0Q2lo8wbf3OMmBOFTfyX0eYa_5088ZnJvvliefn-TCDyc6WlcZrNqwBOF8N8-IN3b_8RPq-RuV8-mK-M83Hi4ElQB7Z44eZMmfUwFrozEG4Wq2K6MwQ_edG4dWeUVMCloTpGDFOtlLQlDoAN4m_sS2Lbwm_3ra29noUcK8_j10yy-hENE2Yluh1pIL-GoWZj3uYO-rEKVbszaagdE0DJ_uQcHUdNnBHKn64-cQ6xihXzxaeHx9OxkWWMKbzLtKpuYDK_X7EVvm8YTjl_oTsr2SWT2usjNJko32DhRV-OXLKKHo5FJpCy2bGFLXGG26CglUvgZQ2dyXiWeGVNKffOv1cQ5R_RlU2MpLiZ1bigy9hh4lu_XAHLfjQfhf71jeMuF4nEBWV-YOAjDTaDB2hcGqv_XcGXcmLWHqOWgc5Mb6lkb2zYs_oyOskmyFx6C0P7UrV8kCiN4zbuTqZNdNjlWL_QJUmU3vk6CpNa0XN1M3sLjZpOEsaqgRVPLcIDH-juVhyWiymuxe-8yNCOFSKxhscew08EQ9DEckP_iIA8qU2gcreHtvAS5VA5Emz1K2ypYe6oS3ogP-CX4nOAEfvjsb1HHJoclgiwjL1BtCLFgOE-0vn1M-nVOE6WbHGHoNKMJMHP2a3HQC7DmDfSOw5P6Cj5X7QVqhCY6tAGZWEPu3hUssp7K5UJePEdBn_LrErt4ucyXW6y1PAA2Fn8EuHaRyf2ggibDGnzq8E15m_R4LMvZAuGR0bN9jBTlm_x4ZQMqFwKkIdllkN1QTErazOyNsgU6fhA_20h5EIYT6-LqXr_Otj3Kp8MkJB9c3XNGoo5sbHTQCt0VNOHoxCFP_swiAJLtm743eOsI1M6naWLIqPagSCioosAvJYowypJQGvM-N3hBu8KUr0f911KRN7WqTAXTOHZ_vvTqcWKet0dFdh1EHuP3TrU8hSMciaphGvuK93T3gaWuJ6lcCkQndWvEo9S6FQB7eLU_ALKOQ3ROybUUkXgfyTkWDPxbHdeJCgMRv6Ig1PShPyxYb4ig',
|
|
priv: '273AhMPiZWLlSQCY41yi1fMj6xavGH0btB23zMhI1uY',
|
|
},
|
|
},
|
|
'ML-DSA-65': {
|
|
pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_seed_only'), 'ascii')),
|
|
pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private'), 'ascii')),
|
|
pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'private_priv_only'), 'ascii')),
|
|
spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-65', 'public'), 'ascii')),
|
|
jwk: {
|
|
kty: 'AKP',
|
|
alg: 'ML-DSA-65',
|
|
pub: 'hxPP5LvG83t2fJyfA1TUssJK_ydrzryrCHGZuKFxmnl5Y3sxHRCPW_JpHEoiIgR6kgELnwibZnueax1zFerTOTA7o0NwXHFiaEB-8AmqJI93DkvtbUOSTCixa3admQBKW_PtgMCVtaEVuuvCuOEFhOyuZkyfvnpBwUKOkz3t-O1wpgrSmf-rdPXOEv8YcsSn-xfLYPSLzPCnt7gnIX_fwtkgnXjref-QqjFKlKZE2e7MkmHeViJ4iGy78r3UzVhBHsmFGC0ZNc8-iT3muH5Sn0SXmNq-F2EoerWLIAsPxL2KE6UrqPAwTbHn1B5sAGWvhsVVLlFPI1s1JLVLBNRJ5vhif525xNIpMAMuAZrteD827pve3zQo9_GHjWgykj9VzM9PEcVmVqxZ5u41kUXsM4PWZF29Oh2sYsmJ2LdiJ9RcA91vRLG2DqEYm-V5JwIz8uxL17DUsEC7zYthvtqGASq05CbfPTBev33rQUv4H0Etz99U89WooTk0FisHDz1uEUilU_VY1tN5byIDitXNf0jnz3SIHDUUZARn7ll0YwO0jtksT68sQW3Liy6Exhlp1td0so2qZUrbVZasjyCOVuibwbwvrdpP3QRsoG5UqkAqk8Rm2iCpdQSg87pswOscgA8AC8TczGHNfXc9PqzAmbsEPKvmZuE60HLGzqpRqFULf3nyYUQUqbdmKJsKQ29LXeDVbyy3-fkTUDuYqNC2tBY7PkzHJSA9Z4hDC_BHEFxcelibScSNyf7y4lDVWnuJMXpQ0WRh3UkUPa007IerhixwxvBvFXQR-ytYinixvjirlcEF1wQI1DzE8KjOXYYuFPS4Yl8HeZHQ-64Q0RuxlKIRP3YvjZWh4IDVvEVs5ZLzZPbE3Twe0N5a7iCu0BzZWTeHNbcMoViFyJTpec2w3vVHeI4PJB-5HeI8xuh-9y8ytTau8QtMe4thoROoajizDQLrkw3e6ryJJ3R84i0oni4vmZWyLDilwcLqPOkQJCIDMjq7exdmVX5t3DtAW4F6Coz0z3sf7tGlSMVxA7izCoVbG0y2_l1P2h7fWBuEPT7PWlMdPqu9Pj5jqXY6jJ0nkaR_pp7dDhO1HKae5edcBYunHZqVQQjRZ_DvKzbPrDk5t6Xq9fdSkiAeP3B4qn5uU-nx7OaX7DRoVEnbbiEDynIRPSEY-Ts3alPJtBv8zuzaGNyX05Z9MyZ0w-VlC-WxOBdVEsIAp_4uJ3kQ3UsfE9DLJH8WPuDI4t4i2VnNNyFlI0XSUocc_0rWgqp2I1UzSzkVbklwkuFywPI645u4G2XAlfdd_wpjFGC-IUPXgpeSfspPwW15sBP-ITS-gwtvfzQVLpRS0euzN97xo_GMhNPZ4bW-YyZt8z_R8bsQ8ktfoP-5RUV-yzYDt0tA01QJsZdBLf5J_H7qP8l4c8V4hPe_CFL032obbxmAnVPAP69u2SaMBlL8azjk4wGVFQpQp1JqMJCao2W8ZImCVegkPZxhGbx0nkgVfyFx4ihMeDNM288JbGC4CGON8C02Q84rQzhwzZE83Y9rSe1Bb0fUMHMu6ihD5jLdeltuBL4ZdJlKgL24KZK5o5pq4_l9SyzGAjB1KAQnClNOB1SxV89CtILu-65wb17s0z3qw2-NF0B6UVlGQFebjbSyLQv2ARaETh_8cBiPugVMgBIV3K1KBwNyWejyI1ZDCssvIZHJCF2SRW3HmJerTiB23eGHFYKSLdxW7LEzoHIc2xZEc3pwR43gavjeoL0pNc-HNFV_c19wiH7Tnw3IHld_FfTqAIPnqKMNIY7D_D0DmFNTOdnzcipqKxUB0Avc-wr8Fz0gjeRpLH2iDSCJWtvWjoeYvHTktGsblDAM5j9xznwEvZfQvj8fTUnFxl3clkD6e9V1jrDQDkXfOtl-bDIv9PtMwamfJFu-z2ubF-gKytUewPNo10uhwr2TDNdUayCZDR2T3HoRLN8goIw2bFoPJ98LoPcSukEvKABjH0DiHNeqFELNZPx_uCx5N-YFkUZxHWA1QUoGhqQ3REtcT3c-SZf_TDFOPvws6bmwt4lcWpLmubOAJLFt6J8m8HCkVUshRdFzvHQm_0JEvA3JtyXZzvsPUv5njdk0nxTZktvsnqX054RQk5x8U-lBY-bK3uMOoFnHju45LMoHCUgGJi22eUm7nLGZEh84ZAbNlPLXpfavXvPJh21OW5EOAeuQ-yWNHY2xbmAiHNnb-J2VpZc1Vy82sxn8umFtKduuQuIQMOsf4qHqj5MzDY_1NjrM4Wm7XAiLC4MpQ22w9PWNQXSZWvo2fj8WUnfEibpgyRkoD2P25GRQqsRJ3-Ykl5bm_2Vfe6i3oXHOwQwZwKGXfAqXyo4iU1UI7e-qC4sj5U64oB_A_NSBaJJrZoQ2fVeGTnFxA4QMMoWCT0VlwBXK0B3jht8Xal3WcI-i9ctQB1-GrmwwgG2ttePHt1IKy69bSZE3FLkFicaHg6VxypG6ef8rVsmMrfpTATOnF5_iEaLNY9428HHGW0iz4vXwaE-MkYy7NK2KMPFiCB0ec9OjIROwayK4LREv4qknWHnVQRSm25Rr9DcVFXKj16Au7X1hv7TuVH7h25U',
|
|
priv: '1X9VEr_iXMRwBvnSytEmHrtA-DpD6FWAUqMrDNlJVBg',
|
|
},
|
|
},
|
|
'ML-DSA-87': {
|
|
pkcs8_seed_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_seed_only'), 'ascii')),
|
|
pkcs8: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private'), 'ascii')),
|
|
pkcs8_priv_only: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'private_priv_only'), 'ascii')),
|
|
spki: toDer(fixtures.readKey(getKeyFileName('ml-dsa-87', 'public'), 'ascii')),
|
|
jwk: {
|
|
kty: 'AKP',
|
|
alg: 'ML-DSA-87',
|
|
pub: 'DZXqaBATRN0GRtigxzkLxp7C9fFYxI7Gl-tdfXqJHbzVCTTvRfRwZcu3YmpsUYXBdX2pVsQ51QlqxMslKBRmfNCanBLcfd57qoEIb0K6GIKZGHxlsr9aXNjEGcKMo0ICon0LYTvTWrl72Oz-2yEA_abPK3_dBUFGAYQ6kOQhAHcT1CMmTTck23PnEd5WUpYfZOA9giFX9dNVrrdFWczj_vDOty81ObNKsxfVWT1nG7c60UCJxb2c2tMrBx3rp7Hfc_aOg54W5KHocJi0Eai0ok4buySTe0UCSCUTkeoCcdiABgOFBiXRYzpm3Lz4uot6hSgFpuh67fE9Zpgtn64vfI-O1mgcPrPPpd3yA92Jrq-dvXuM55w1RmA_hha3U5Sh2vm0tD1U57q945UppFReIv_8NAKBkxQ_vHil7ySm-m7IAM-sTUY86_IqMZqisxoz7Ff7ZR2vIiUm3-L0ow4B8uPsCv2ZlUoVXvMF6XQiOHsgqgP1rfH8DmfmPFudwiXrAW6wEmi10skPmkN92aC3TPG6nmaNryQ7f8J82yVmGxW9U7zMbg21qNkRGBi_1YwEt6D8V2pUWv5U1a4p4-Ma0f4uQG4g0odM-WGomlh7pZWZf3sffiPXk9wBrGisxtCJuaB5vtkheWxpEfWqnhc3QdOWfrsRg6P1h7M95SNVW0U8A38wwrqPOpzEnckCVdCrZz2b2KVln6a4twfINg1-3lEZR4rkEmTaTYlLFlXzbRFWBBPGATxeRxhQ_9N5VhHi7STWPFD5HIJyVqz436bbVvM6Py_oldT_xt_tWlPc0w4Pesy2CgaCPlJCnx6cjEg_sRBUcRkBoHqa7aZj4JFFm9bzEaiJ2MKfkHVT4xdbEimMHsD0HkIQpg5-zoB2Jsqgc6Qi3L57hZi-Q1V0G2lmdZ5WZkQ2m5hxle4hHtAmghgynK2p0qzDWHScxHcdd2sInYqQgMYbnvs04YYKWpIfndCUBs9q_EONN5tn8gfSwHEKlQ-KpplEL5kc-99h2uaZsxRlJOF6_z8EZ-aKaKY8jvoAV0g4kZJH5UKy_MkBiva6r-zXUmo88qJjXQatOOSdfvJUTiZiSfcpBQqF9SSDD9WWsgInaOCKO_fAFf9fuacXDMEj0esUx1YrVEe_77S5uObg-UrK405U1JhKJJvd7o8xQKxenv5BJdsbbyYQDbSSe9BrCqeHEgmfRHTXdSvl_3QOP0Ej-dT8YJJHZ1lrujU7Zg5f5Kg99tU5GdLMbHX2kt4F2a0NX09HikEemvUg4NLPhjOihfVkChr-zdF69nfsnTiaQrMgpIcl9jttN99_8Gju-LU8OWbb92m9RLxAUFP115v22f77YPoILm92IjMZMkxEhGneoclWhnudkyR7YoTBjCnT5b7AC9_05uls637FmVf7Ck8-MF4gLil3dstXi4g24bitYhxxqWwiqF4vsDouSGUnuKCMwx3TLsII_xk77TjpQP4vpLdYM3tn94AVlTMMhnI-OZkVJk-_mIbywCwRHlQb5nzVCc0BWlM1kb9PJys2IfciS8LWEoxeq9moDX5w72yJKoLN3CWpD3VdJAiW79zUaySw-IeW0XaHnlze5fYnOozG8lIeyQ9sMZasMiFovGnR3b7jyMtA38U33v16fouWuBILOu0m_QOpRDI9i3rjRM6hdC48zCtNSzc1_1VPYkWDSFK1oVAjdd8-2rjyqdPeUwnqD26VA9_d3R7x8ThrazdbRC8U1hr9jpqNHuZ4LGYu3Ui8wB-lSt9QMaHz517MY_zBEoNGyvbQWtlM7mvLu12KoMM7nvGrPJnvD-HmxTqsVQolD8_lIV5ao72yiKDpArVr6RuV4PpI0j_Wy4-yDCuwBW0gjnB9GvCwOTeByYXJT6Ul7dgHck4BbF3IyFgvmY--ceWr5mBrbAC9LJP_4Wf5O6ul3hFrhiG6zSV4zzBYLnEwfW6LNLEZjZKgmBYiC5s1xlxYWDdcQ5FGmLQ9uEDkr4VItXQWvIIdeBQPyujxmd965Mig9-Sa7SCyV_3wH8fQnGlvU-jJMGL0zvzB2gcu7hMLMagUBj1AKXj-UxpbX1i95f2TOiZDwMeCCszgvCjQ21XKg07TBXrrOiFcgcADgdo-HJr7O1T3ozOIulq1PDM7QZH6i3wDD0j3b0NCdqKCWqhfLXz2-FszyUHmA_GCzOLVzrLT2DcGWIcQbkvF0yZPgTyqKArKa8qytOerdH6oCJ0bRl96855sMVjuUdyVLX7XW_rVTwsOwV0gVAx8SrzovtDFeHRNl7BQKMsyQ1BjWu25jqKJ598vAi3LCZv0kMdiC24qPdgZU4e2aUkco11EnD6nJgdqsVFxufCl4BD_D9g5Wy42fJt4ZgNPAcbUf341KERyReeBEQj-qlPB3IUTIXcJw68GScebhxb0W_tGKMBC6-ip4QfNW7UTxUxVxmCV7h0yRnBBlkuUR1eYQwWRmEPjKd3dLHvgHtr266NQmE1tnKtJlsdPKb0ztrI9vogsENsgGNFQ2tHoeX8vqxcagGznlPVPfc3DlqjBSeTFQaPWvmCQHVKgxkbvffKzFQyFvXEqt6bGGtkwBoRJ_IIwtSeWQ2nFPBe3rlyKrtSnQFIMibJbbYvPVE03Cld9R61r-GGDSQz4aXekLzePEVwnxpe4mWJGco7ctQyE73PekL1uo2g0bRK-KgaE878OiLRBo5T7c633xEf2hMy9532M2GVdTZuoE0LL-wpAh9GmNdvJZc7g2sINvwZi778v2WHcYEKqXvdmrX-Shyh3QkzgIGZrDzM4UlxxUWaXfZ0Z6PNguk7Jafqf3xuUe9Z8zfAJl5c_VA3k8dn7IRg99hRsh-TGBCzqzgjJq4p4XMWP2QuxFTGSgHRe2GSCFzd5-lPjrj76ZyT3MPUxQe_bV2VE-Oys3MT-VkCCM8jFCANXdrfltG6jSiUZ2uJUWNqdNnxPglmmyrgff_m-5CyIRWYXQsIdZGspqdjzb4F6RbBKfL2PQlM6zUfo9JNmE8YQq815Nxkex8vDOrImnew312fZA6rRjr9_uE4lEbw3U7PFlCKBUPvPnsdgedjKYhiS0xU6iS1NDKOvYhcrkCkiU67EmFD4U0-OCv9Kpbb5bIxTJuv405NxJBElAMVI-ya0ns7D4-xUPn05E7PhtGZT0eHwItjT6omThTsTHwB_bQqYfNrjrObO1l1go2hQ-cUadZYsG5l47CB5RlFhANtaC8tiq4KJi48TmEEApB_0VwOI4EmI7SR0oaqx3HRXZfeGevCx2yC9aCYM4HqcyqP2g_1HwsOYzwq4XDEbK5Yl1dtYABxPoo7t8FBq2sSmfrBJWFv_nvreb_DPwbfoSeCy9knqvOktSQRrPMmo-nNGpandBvjmrjSk3EdeziAP7XNre5I-bn_2voDxkzGFtUM-wzlL379ASRGej8FkNWaOyqGP6Anq5PSJ',
|
|
priv: 'LZSOlEPbU9S5_mSsMULffTyxZu6qKEOQ1nfEi2NCscg',
|
|
}
|
|
},
|
|
};
|
|
/* eslint-enable @stylistic/js/max-len */
|
|
|
|
const testVectors = [
|
|
{
|
|
name: 'ML-DSA-44',
|
|
privateUsages: ['sign'],
|
|
publicUsages: ['verify']
|
|
},
|
|
{
|
|
name: 'ML-DSA-65',
|
|
privateUsages: ['sign'],
|
|
publicUsages: ['verify']
|
|
},
|
|
{
|
|
name: 'ML-DSA-87',
|
|
privateUsages: ['sign'],
|
|
publicUsages: ['verify']
|
|
},
|
|
];
|
|
|
|
async function testImportSpki({ name, publicUsages }, extractable) {
|
|
const key = await subtle.importKey(
|
|
'spki',
|
|
keyData[name].spki,
|
|
{ name },
|
|
extractable,
|
|
publicUsages);
|
|
assert.strictEqual(key.type, 'public');
|
|
assert.strictEqual(key.extractable, extractable);
|
|
assert.deepStrictEqual(key.usages, publicUsages);
|
|
assert.deepStrictEqual(key.algorithm.name, name);
|
|
assert.strictEqual(key.algorithm, key.algorithm);
|
|
assert.strictEqual(key.usages, key.usages);
|
|
|
|
if (extractable) {
|
|
// Test the roundtrip
|
|
const spki = await subtle.exportKey('spki', key);
|
|
assert.strictEqual(
|
|
Buffer.from(spki).toString('hex'),
|
|
keyData[name].spki.toString('hex'));
|
|
} else {
|
|
await assert.rejects(
|
|
subtle.exportKey('spki', key), {
|
|
message: /key is not extractable/
|
|
});
|
|
}
|
|
|
|
// Bad usage
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'spki',
|
|
keyData[name].spki,
|
|
{ name },
|
|
extractable,
|
|
['wrapKey']),
|
|
{ message: /Unsupported key usage/ });
|
|
}
|
|
|
|
async function testImportPkcs8({ name, privateUsages }, extractable) {
|
|
const key = await subtle.importKey(
|
|
'pkcs8',
|
|
keyData[name].pkcs8,
|
|
{ name },
|
|
extractable,
|
|
privateUsages);
|
|
assert.strictEqual(key.type, 'private');
|
|
assert.strictEqual(key.extractable, extractable);
|
|
assert.deepStrictEqual(key.usages, privateUsages);
|
|
assert.deepStrictEqual(key.algorithm.name, name);
|
|
assert.strictEqual(key.algorithm, key.algorithm);
|
|
assert.strictEqual(key.usages, key.usages);
|
|
|
|
if (extractable) {
|
|
// Test the roundtrip
|
|
const pkcs8 = await subtle.exportKey('pkcs8', key);
|
|
assert.strictEqual(
|
|
Buffer.from(pkcs8).toString('hex'),
|
|
keyData[name].pkcs8_seed_only.toString('hex'));
|
|
} else {
|
|
await assert.rejects(
|
|
subtle.exportKey('pkcs8', key), {
|
|
message: /key is not extractable/
|
|
});
|
|
}
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'pkcs8',
|
|
keyData[name].pkcs8,
|
|
{ name },
|
|
extractable,
|
|
[/* empty usages */]),
|
|
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
|
|
}
|
|
|
|
async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) {
|
|
const key = await subtle.importKey(
|
|
'pkcs8',
|
|
keyData[name].pkcs8_seed_only,
|
|
{ name },
|
|
extractable,
|
|
privateUsages);
|
|
assert.strictEqual(key.type, 'private');
|
|
assert.strictEqual(key.extractable, extractable);
|
|
assert.deepStrictEqual(key.usages, privateUsages);
|
|
assert.deepStrictEqual(key.algorithm.name, name);
|
|
assert.strictEqual(key.algorithm, key.algorithm);
|
|
assert.strictEqual(key.usages, key.usages);
|
|
|
|
if (extractable) {
|
|
// Test the roundtrip
|
|
const pkcs8 = await subtle.exportKey('pkcs8', key);
|
|
assert.strictEqual(
|
|
Buffer.from(pkcs8).toString('hex'),
|
|
keyData[name].pkcs8_seed_only.toString('hex'));
|
|
} else {
|
|
await assert.rejects(
|
|
subtle.exportKey('pkcs8', key), {
|
|
message: /key is not extractable/
|
|
});
|
|
}
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'pkcs8',
|
|
keyData[name].pkcs8_seed_only,
|
|
{ name },
|
|
extractable,
|
|
[/* empty usages */]),
|
|
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
|
|
}
|
|
|
|
async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) {
|
|
const key = await subtle.importKey(
|
|
'pkcs8',
|
|
keyData[name].pkcs8_priv_only,
|
|
{ name },
|
|
extractable,
|
|
privateUsages);
|
|
assert.strictEqual(key.type, 'private');
|
|
assert.strictEqual(key.extractable, extractable);
|
|
assert.deepStrictEqual(key.usages, privateUsages);
|
|
assert.deepStrictEqual(key.algorithm.name, name);
|
|
assert.strictEqual(key.algorithm, key.algorithm);
|
|
assert.strictEqual(key.usages, key.usages);
|
|
|
|
if (extractable) {
|
|
await assert.rejects(subtle.exportKey('pkcs8', key), (err) => {
|
|
assert.strictEqual(err.name, 'OperationError');
|
|
assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED');
|
|
assert.strictEqual(err.cause.message, 'Failed to get raw seed');
|
|
return true;
|
|
});
|
|
} else {
|
|
await assert.rejects(
|
|
subtle.exportKey('pkcs8', key), {
|
|
message: /key is not extractable/
|
|
});
|
|
}
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'pkcs8',
|
|
keyData[name].pkcs8_seed_only,
|
|
{ name },
|
|
extractable,
|
|
[/* empty usages */]),
|
|
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
|
|
}
|
|
|
|
async function testImportJwk({ name, publicUsages, privateUsages }, extractable) {
|
|
|
|
const jwk = keyData[name].jwk;
|
|
|
|
const tests = [
|
|
subtle.importKey(
|
|
'jwk',
|
|
{
|
|
kty: jwk.kty,
|
|
alg: jwk.alg,
|
|
pub: jwk.pub,
|
|
},
|
|
{ name },
|
|
extractable, publicUsages),
|
|
subtle.importKey(
|
|
'jwk',
|
|
jwk,
|
|
{ name },
|
|
extractable,
|
|
privateUsages),
|
|
];
|
|
|
|
const [
|
|
publicKey,
|
|
privateKey,
|
|
] = await Promise.all(tests);
|
|
|
|
assert.strictEqual(publicKey.type, 'public');
|
|
assert.strictEqual(privateKey.type, 'private');
|
|
assert.strictEqual(publicKey.extractable, extractable);
|
|
assert.strictEqual(privateKey.extractable, extractable);
|
|
assert.deepStrictEqual(publicKey.usages, publicUsages);
|
|
assert.deepStrictEqual(privateKey.usages, privateUsages);
|
|
assert.strictEqual(publicKey.algorithm.name, name);
|
|
assert.strictEqual(privateKey.algorithm.name, name);
|
|
assert.strictEqual(privateKey.algorithm, privateKey.algorithm);
|
|
assert.strictEqual(privateKey.usages, privateKey.usages);
|
|
assert.strictEqual(publicKey.algorithm, publicKey.algorithm);
|
|
assert.strictEqual(publicKey.usages, publicKey.usages);
|
|
|
|
if (extractable) {
|
|
// Test the round trip
|
|
const [
|
|
pubJwk,
|
|
pvtJwk,
|
|
] = await Promise.all([
|
|
subtle.exportKey('jwk', publicKey),
|
|
subtle.exportKey('jwk', privateKey),
|
|
]);
|
|
|
|
assert.deepStrictEqual(pubJwk.key_ops, publicUsages);
|
|
assert.strictEqual(pubJwk.ext, true);
|
|
assert.strictEqual(pubJwk.kty, 'AKP');
|
|
assert.strictEqual(pubJwk.pub, jwk.pub);
|
|
|
|
assert.deepStrictEqual(pvtJwk.key_ops, privateUsages);
|
|
assert.strictEqual(pvtJwk.ext, true);
|
|
assert.strictEqual(pvtJwk.kty, 'AKP');
|
|
assert.strictEqual(pvtJwk.pub, jwk.pub);
|
|
assert.strictEqual(pvtJwk.priv, jwk.priv);
|
|
|
|
assert.strictEqual(pubJwk.alg, jwk.alg);
|
|
assert.strictEqual(pvtJwk.alg, jwk.alg);
|
|
} else {
|
|
await assert.rejects(
|
|
subtle.exportKey('jwk', publicKey), {
|
|
message: /key is not extractable/
|
|
});
|
|
await assert.rejects(
|
|
subtle.exportKey('jwk', privateKey), {
|
|
message: /key is not extractable/
|
|
});
|
|
}
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk, use: 'enc' },
|
|
{ name },
|
|
extractable,
|
|
privateUsages),
|
|
{ message: 'Invalid JWK "use" Parameter' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk, pub: undefined },
|
|
{ name },
|
|
extractable,
|
|
privateUsages),
|
|
{ message: 'Invalid JWK' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk, priv: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, // Public vs private mismatch
|
|
{ name },
|
|
extractable,
|
|
privateUsages),
|
|
{ message: 'Invalid keyData' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk, kty: 'OKP' },
|
|
{ name },
|
|
extractable,
|
|
privateUsages),
|
|
{ message: 'Invalid JWK "kty" Parameter' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk },
|
|
{ name },
|
|
extractable,
|
|
publicUsages), // Invalid for a private key
|
|
{ message: /Unsupported key usage/ });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk, ext: false },
|
|
{ name },
|
|
true,
|
|
privateUsages),
|
|
{ message: 'JWK "ext" Parameter and extractable mismatch' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk, priv: undefined },
|
|
{ name },
|
|
extractable,
|
|
privateUsages), // Invalid for a public key
|
|
{ message: /Unsupported key usage/ });
|
|
|
|
for (const alg of [undefined, name === 'ML-DSA-44' ? 'ML-DSA-87' : 'ML-DSA-44']) {
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ kty: jwk.kty, pub: jwk.pub, alg },
|
|
{ name },
|
|
extractable,
|
|
publicUsages),
|
|
{ message: 'JWK "alg" Parameter and algorithm name mismatch' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk, alg },
|
|
{ name },
|
|
extractable,
|
|
privateUsages),
|
|
{ message: 'JWK "alg" Parameter and algorithm name mismatch' });
|
|
}
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ ...jwk },
|
|
{ name },
|
|
extractable,
|
|
[/* empty usages */]),
|
|
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'jwk',
|
|
{ kty: jwk.kty, /* missing pub */ alg: jwk.alg },
|
|
{ name },
|
|
extractable,
|
|
publicUsages),
|
|
{ name: 'DataError', message: 'Invalid keyData' });
|
|
}
|
|
|
|
async function testImportRawPublic({ name, publicUsages }, extractable) {
|
|
const jwk = keyData[name].jwk;
|
|
const pub = Buffer.from(jwk.pub, 'base64url');
|
|
|
|
const publicKey = await subtle.importKey(
|
|
'raw-public',
|
|
pub,
|
|
{ name },
|
|
extractable, publicUsages);
|
|
|
|
assert.strictEqual(publicKey.type, 'public');
|
|
assert.deepStrictEqual(publicKey.usages, publicUsages);
|
|
assert.strictEqual(publicKey.algorithm.name, name);
|
|
assert.strictEqual(publicKey.algorithm, publicKey.algorithm);
|
|
assert.strictEqual(publicKey.usages, publicKey.usages);
|
|
assert.strictEqual(publicKey.extractable, extractable);
|
|
|
|
if (extractable) {
|
|
const value = await subtle.exportKey('raw-public', publicKey);
|
|
assert.deepStrictEqual(Buffer.from(value), pub);
|
|
|
|
await assert.rejects(subtle.exportKey('raw', publicKey), {
|
|
name: 'NotSupportedError',
|
|
message: `Unable to export ${publicKey.algorithm.name} public key using raw format`,
|
|
});
|
|
}
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'raw-public',
|
|
pub.subarray(0, pub.byteLength - 1),
|
|
{ name },
|
|
extractable, publicUsages),
|
|
{ message: 'Invalid keyData' });
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'raw-public',
|
|
pub,
|
|
{ name: name === 'ML-DSA-44' ? 'ML-DSA-65' : 'ML-DSA-44' },
|
|
extractable, publicUsages),
|
|
{ message: 'Invalid keyData' });
|
|
}
|
|
|
|
async function testImportRawSeed({ name, privateUsages }, extractable) {
|
|
const jwk = keyData[name].jwk;
|
|
const seed = Buffer.from(jwk.priv, 'base64url');
|
|
|
|
const privateKey = await subtle.importKey(
|
|
'raw-seed',
|
|
seed,
|
|
{ name },
|
|
extractable, privateUsages);
|
|
|
|
assert.strictEqual(privateKey.type, 'private');
|
|
assert.deepStrictEqual(privateKey.usages, privateUsages);
|
|
assert.strictEqual(privateKey.algorithm.name, name);
|
|
assert.strictEqual(privateKey.algorithm, privateKey.algorithm);
|
|
assert.strictEqual(privateKey.usages, privateKey.usages);
|
|
assert.strictEqual(privateKey.extractable, extractable);
|
|
|
|
if (extractable) {
|
|
const value = await subtle.exportKey('raw-seed', privateKey);
|
|
assert.deepStrictEqual(Buffer.from(value), seed);
|
|
}
|
|
|
|
await assert.rejects(
|
|
subtle.importKey(
|
|
'raw-seed',
|
|
seed.subarray(0, 30),
|
|
{ name },
|
|
extractable,
|
|
privateUsages),
|
|
{ message: 'Invalid keyData' });
|
|
}
|
|
|
|
(async function() {
|
|
const tests = [];
|
|
for (const vector of testVectors) {
|
|
for (const extractable of [true, false]) {
|
|
tests.push(testImportSpki(vector, extractable));
|
|
tests.push(testImportPkcs8(vector, extractable));
|
|
tests.push(testImportPkcs8SeedOnly(vector, extractable));
|
|
tests.push(testImportPkcs8PrivOnly(vector, extractable));
|
|
tests.push(testImportJwk(vector, extractable));
|
|
tests.push(testImportRawSeed(vector, extractable));
|
|
tests.push(testImportRawPublic(vector, extractable));
|
|
}
|
|
}
|
|
await Promise.all(tests);
|
|
})().then(common.mustCall());
|
|
|
|
(async function() {
|
|
const alg = 'ML-DSA-44';
|
|
const pub = Buffer.from(keyData[alg].jwk.pub, 'base64url');
|
|
await assert.rejects(subtle.importKey('raw', pub, alg, false, []), {
|
|
name: 'NotSupportedError',
|
|
message: 'Unable to import ML-DSA-44 using raw format',
|
|
});
|
|
})().then(common.mustCall());
|