diff --git a/certs/slhdsa/entity-slhdsa-shake-128f-priv.der b/certs/slhdsa/entity-slhdsa-shake-128f-priv.der new file mode 100644 index 0000000000..33bec54677 Binary files /dev/null and b/certs/slhdsa/entity-slhdsa-shake-128f-priv.der differ diff --git a/certs/slhdsa/entity-slhdsa-shake-128f-priv.pem b/certs/slhdsa/entity-slhdsa-shake-128f-priv.pem new file mode 100644 index 0000000000..911fdcf56e --- /dev/null +++ b/certs/slhdsa/entity-slhdsa-shake-128f-priv.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFICAQAwCwYJYIZIAWUDBAMbBEBRJWNRVa9SISo3g3sk/GEnwHJR7f26z5qDX5v9 +QaTCdhp4N9VSWJzaIVXWZSs2Atxlng8nDbMnaTuaqm3GqNYE +-----END PRIVATE KEY----- diff --git a/certs/slhdsa/entity-slhdsa-shake-128f.der b/certs/slhdsa/entity-slhdsa-shake-128f.der new file mode 100644 index 0000000000..908e5a041f Binary files /dev/null and b/certs/slhdsa/entity-slhdsa-shake-128f.der differ diff --git a/certs/slhdsa/entity-slhdsa-shake-128f.pem b/certs/slhdsa/entity-slhdsa-shake-128f.pem new file mode 100644 index 0000000000..4e519c0734 --- /dev/null +++ b/certs/slhdsa/entity-slhdsa-shake-128f.pem @@ -0,0 +1,370 @@ +-----BEGIN CERTIFICATE----- +MIJE/DCCAiagAwIBAgIQb1OXdK5TJa7uqUGY3Yug5TALBglghkgBZQMEAxswgaox +CzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAwDgYDVQQHDAdCb3plbWFu +MRgwFgYDVQQKDA93b2xmU1NMX1NMSC1EU0ExITAfBgNVBAsMGEVudGl0eS1TTEhE +U0Etc2hha2UtMTI4ZjEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMSAwHgYJKoZI +hvcNAQkBFhFmYWN0c0B3b2xmc3NsLmNvbTAiGA8yMDI2MDcwNjIwMzMyMVoYDzIw +MjkwNDAyMjAzMzIxWjCBqjELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRhbmEx +EDAOBgNVBAcMB0JvemVtYW4xGDAWBgNVBAoMD3dvbGZTU0xfU0xILURTQTEhMB8G +A1UECwwYRW50aXR5LVNMSERTQS1zaGFrZS0xMjhmMRgwFgYDVQQDDA93d3cud29s +ZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMDAwCwYJ +YIZIAWUDBAMbAyEAGng31VJYnNohVdZlKzYC3GWeDycNsydpO5qqbcao1gSjUDBO +MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFNykvyqtrp1DSDnzLfvTdIv/9pmhMB8G +A1UdIwQYMBaAFNykvyqtrp1DSDnzLfvTdIv/9pmhMAsGCWCGSAFlAwQDGwOCQsEA +5vFRcO0MLuWZoOoYH1CI3Vlio8dB3Et0fayR4oVvyYOInWapI+Kpjxvc0qYcW7Rf +zshiHOiCQJfA0gnFSvnRAZMEv4pSGY/dNB9iL2MiLGVQmButsBLi1MRBy6w7yOv0 +sNuUIFJBZNhbRV528sIFuB0c1sFLw5TcbqX752EWyp22wm8r7cnOeC7rvA89kwyb +5XyA6zsg9+LanAlhJBzPl+9I0YrnW+0KfSgR5hQ1zjztBZ1qvHT6Ew+r7N7qT5XV +xwn7cdHGJPetZgpDSbjQZRa6yn2Bq3j8ev9QDcqWODdxSlEdith2jBG+c7LkKMDq +E2j53YoMf46Cl1uCg9Lnz13W/BgvCprrGYn+fmLYgwbsNsCVwvop1HKmQT5Twh3S +dYf9m6xcms8vvNDS/2W88fOGH4A/fHan2qNjtmyK89MO8xgXc4Qi3iC8lshKltqI +OphAiaSKcqR1ExR3R17RVjp9ao74r9j50uwYpsYAcUx7qGYaUsfVyQcmUjGmXG6F +E0bDtUG2Naw08N2HCZI0METQLCByyyU7FrGNAvQ1b8yaPBCEordeDt/NpfZkXBIv +YLK2yFQyohczJEo9qZm2b3Kov8bIrsAQJSRoSWtp7pvcitL7j6XmqOXMnDyr5MtB +jKPobbcfNvZFqHNc/rB1QzlV4LoPxJ6GH/cqcBebEe+tdie2Rh22HZDhWKdwhhhN +yhQb4W9jYs4xftdBkx+xAIgDtfNsUI2R0UsPKWfycPkxI7tKB1mrQndRgMitr9ig +TlyQArb7xxXYljEbAbA4Q1w0X09+PXxhRprkPL0OLK4tKaxjiD9LHlLK7wxlaF0w +6eRbrR8pLcHHNGFOkhydY8GCj/zQitTTAHNhvOfs8fJC5+Reh1FovDPlnZFX9QJS +l/NIzTbfcVQNpi1r5sn8igf+3dY8N+Gb2P2ojl/yuuxEGGiO26nkvHSjUw+gQWQ1 +0RQmXl3Piqi5uy5wVLsEdBAHfA0JvRsmQjaNPawEX73kt15zYGw01kapYC5S7CyD +AkcEdHOIPvVjCUSUImhC2P9KpYjNyMsxpJFV6qyxtK+PKBZ+g7PxJCD80iNTIpoE +Bexo6yoa07RzS8vpLmy7uZqvGdBPyugtOJAndJdjdkeAladyD8hUsi4MuQhj9UV2 +XJ/72BZIocwXW3mZrd+fLbgOs0Zb3z3601QmlRUF3JqAVB8EegMGOM/yZ4J8fMJ4 +qzL70n5oDorozMr6lDEOyVm1raYH13gOkyxMQ5HSNTwyX8sDpps/8OKmSRondHXS +ZLa4hjmg1pbUJpzmaSrHukvQmJx8VUQVSU9hEUgzSbNcSGvq+YPJAptnNJHKeALE +JydNKZ72JdH94akkSVIS+sLxjbIe9BieQABvxUtAZ56aNkSObbApqfJvfRzHKNh6 +fP32aEsfWZLzqDTBrjKYaCMmkKjAGmeFry443DlXRhh2mN+pnsJMFKMQKYUZvW0J +ecipOhmdF91Zu4oOT0RcY3b3s7UtzbsOO6hNyCowX+5nrQWYZlecU55y7ecThmss +/VdL9gundKO2d0zhfYaYsgYor4v1TBjsqgIVXwLQpMEpCaq7FbTqf/I+DTtCBwiR +v0ptKOZek0kDriEwFbY0mvv7kWAvsNJt4zEAlTRxH4ahu+imzMP7g4sUj7tTZdUp +YFQsxJ5ZQLKXdnLjj050T0UqFpt0Hob4BQ/r8VEoPZ4PuJDxVUiGoMfRM98jrOSW +AIWEDxSXSFKFbfnn30J3Az3wuSXkvudN2EBOWF0PhnEd5zHaKF5C0agIMtn/3imY +Ln4HXrrFdCJDJ+5MppD5Yb64M3IC4bSwq0WW3nWLsm/1VdtHdHJ8eNJ0quGvkRPq +JB84VKX2DURRMcKxNkxghoW+asmp5g3NMazEpW/6DiIXZTp/Fvu/BzOh/LDTUOAZ +7scx9V83IC3m2+oGrkE3oXJpHeucfRxsdx/12JwA+rdfKHJEWmceRYQPBZjouSKy +JuVYVeobvqksVAsGxvHpjA+8qpTXIB1+58zrR/RboQYiCNnLpknMtzwjlZIfCXJA +PBMI8N9f/Q/rYEBV5MDT2iB6C4/V1cnBZRQ0aoD7KqmnIDcasMgFTHAAPNGc+KpP +ikmxkfua4SkN7IMSWRSWwDEwyZFoGf+Fn6ua5gC+b+q5JWhvtkJzSMBcXNN7mTAi +xT5Z8DNm9WNPpK4bKLk4ZKYb54UKKoUpzT+UNVXJ4F7qFUjxtyZpIDYz+6xYpYwB +GnXdZqhEEMPiNiB85/NynyrrqEt2mJoAvz+hvAXYaa1zw8f+ZK71+6qwl8vGNps1 +iqCveZuWkvVcZ0BWZaTvikFq+MmHJKxtjh9LcRTzB/NsfR02nBj6zztkmi1+vvYg ++4C20SBEFpHH12z1AmHFUv7QzsfYFm3WGEpihWKY+FfAZI7SLiqdMMIIEOQoM++P +RSCTdJVz1Oa9OOCaHm1uJjJTpWUmP0qd7oAU1Z41wOTmr6Y2LQxh9dWYvaAMVwyT +3g3fJdKNNgK4CTIWfOocpUKs9NhyMI75Qh9iUc6G9gszwWFdVRy6jUHmTgWDwZfj +BWHP3MwIO8/qZHr7zYCpUif3ZodBsMTw0noGaTMT75ygU89g3nPrDcQ2u6FqymBf +e5m9HEVLuoffwiaFyoM5+jRKz7jhigeTMlvpynbfil3rLjSRf/Uq1vs9ERqd5064 +okmnXLg6YZQwy/V1Q4QJYIRnbRI2nn1Ol64LSomQ+Sda8ud9wZ6PxLX+P+NyddYY +SO7Fhzfn03LC+eHLRJ1SZyUCCtyAXH1Qw0pMkoBGKJkoPi+zcITBXmIl2RdifHfN +DnFLPoIad3xvfh7P0q7qINKmNKNnHa+3/asXYdSatnmyBDkW5/+PEhxpchJZviMZ +fPKspRaBdNcN0q3EPcqQ1fIyCGM+cKm5numfJx2cnCxSqHVhd85YSYmSwkbPKfb9 +olBTA+HuAGXx+4/wJxtfR8pxLUObX5hBTxxyUutgZc8QRA2jCvAAKG/0V7Iqd33T +y50prYOp1cTxNblatqzdU2FYXPgzCBpLuO5pbXUcII/MozU1a/o/aXfMv6noKt5m +H8245zAoBaKNY5zL3AjMRUMK9jIJ2ectmtiCAC6YClWSDINZIlB1j4zNSmHhDkfa +fQT1t7YvbpgxLacZWc5guFtwd8+AJUcSBLNpdyw9mOVaxocdsEcYzLzdPI+uwQN9 +EsRsGO5bSeDJrTklwbbzmz60/6rBk9I202Roc8V86jE6OjS1SYojuybQg1Wcax5g +qeGXimyc2xvQXsOB7cMtnk2i1C8sgemrdrD2cGBZRKXTvlW6WUujo4LiBBJ/Nof7 +UCzGbHfPX9F9A6W124gFd24Ux2uIlE6vSCp0ZFdOZygnAbpCODB/LJyr4PfaE6VB +nOSv9Dz+sNUgdQk2JshcOB1Bs0Gn9bMEWcC0SiytWZliQLOkg7a/auYaKuqA9bEA +3AyyQZQstTE690Bzk+v2o2seE/FvT93exiQjizkyz6Y0FdiP7N3kyYDVVM04wYjR +BQQHFPpBKCcuca9Q3Qk6Ll0e/M44mpMwTIDZ52K1Ga8QB3bDWQIRcNxK02I88/E+ +OAysUdIo1kCICeFLK7XxnFvNXkL1NZv3TzD7H8+dq+q+YIv5Pl1dD8gpEiYYhD5c +MUgPhPOGI56BIV7zHK7ZfsbOGIVA3uhMlMLom8OUs0jiIPKX0Zxp7wZepySb5YfJ +mnN+VKTTg2nM1X7c01OgOzHvEKZhvBjrMov4k30DGiCfDp1VtRJq2ClPnE1eY6F7 +4DpXlRcjP7KjvcAQrxLt8NyveFrnRewsTBJiL3Tq/tlFyOC7VYFqbZybTspxbsNw +ZjzibGlWLwn35edBNHMOzq52SPZwsDUd7Ya6t6VbxtbnHRxyhd1G0n+8iI14xMPL +r22s/6PKlgzufhzevgiwmNydw2IsL9V9JLaPuVZ4g6dXYTwH6+o50FNbnZ5X96jf +STFR10C5eBPGnHveU29agXS/UqeM9ia8zep+dZFdXZGcu1/s2UnJ/Zl7DPGgM+K1 +ujCDzdwEEBhR97GdBJ+1FEuKZCBIT6E0b9WfMnd5ZJ7fqY1GVOaCcVMmXuvI9L22 +oBGNgEIhhJ7rWH/RVGdzSAh3qzJKgh/2qJgnHgYhNA4w4g1iYESrC+e0NpN6l+Sn +3idhL/0fFSAi7pMRM9gjKmTiEWipATktGwhM5XR8+B+h78H747ul/+6g1uwy2t8e +BkComkaT50V6lHlgU/iwVsmAgNTloeRrLKpNxVHLnRvoYW7zU/oup4hrtWYQe0wD +rq2TNtsMw4NfCxiSM1PYZHoM/Vu/V3RzAeT9PhSgMhy+F5zRS+3MeOlSEPFy89KG +Mcq7zm6GMOIYktLiH2u57wPunndkmgMZCdamDOfM95GXpAyJrwJ77/TBJmuBrPHq +4Ni7MlGj463Qynlu0YRjBvpiuSpaz1Pn0enf4UtuuimpnKWNTPrb1hknfyq6Kws/ +cRsrFwyluaZbZgYOp82MpmWqoyOr6Yqp8cbATgOvJqnqAAhT7+0sq/aXdJqYWc7q +Bp4FLq6w3uvXPanN8SVhP1PGmMPT7n8rWW1Je6buSwsnHUpx/HnG2uy/+09yxgiF +perYFqGF2kIVtL4HSCpbsgUbPkr8ioyd97bXdoH55zXHUmZ8uDgZ9C9q+Em427sC +oJoDklcI3v0z2cup02Al5/91SNDZ2pGj7T0CNrvPwIb3wcN1jweo246WCOxf64wq +U9IHFXDri6UFPYDD3HdkpqoQe1fYAtdIxLRiFLuqFGcnYQ5dl9mYfzb/ovvplEbM +8y1tPPeoRV5FGL3Z66cWuVmA8la3kw9I3aOy0H5KyzkxLhnMFZH3MNdOSenE09uM +6tvFD/DBCtt8+yNRZb8u0cFob8rJtx5gm1aIIV6Ng0ald0zAJBOPtQRTI94+vurr +xkT0fuAg7mRyqd1xNNSThzMSPp5U9mJNL4CMyC22hRUwOdhR/4I7AwkxcwM+KM0E +NoR13tu+ce01usiA2Z97e9NzuUIEIXjk8pHIdZ9l+OrLI8S/EcP+LlnwV5afergq +pAYxHMcpqOMfDcj+LC2nvDLbJyAL+WhGDszfNKWQvxFNDgRXCQ7jUUpIb3M5Ifei +KTjsdLPfQ5DqxzQpk0nPBXJl04n1ALxW40itxCafqvU7UdG9mdV8E2kYdsplgzHk +FSNxwDdbuOVK19dpCkfWnuXsQRauCyTZqsmWje6Ko0alT5x+ugViYykZ5aGWMvXE +HS69wQFJeTd/laFR7M4unyhbiXINFOAVUQsmKoUSqOTP3M43GrZhMVuEoPXWzVGn +Y+iNTYXelLKZLSEicJr9LLxU2T8wD3RGF5VSv74Y9lWFVMOReK0K0QKTT3vpJ6Em +abMwwRYvEIz4IQTCJK7w/fhOFZg5sDkc7mK7oHosB43KzqS4W5rkmqWyyHUtOJi0 +mmPtyuzR5wS+znjQuzYJ6UyBPNnAuvTFYk9VIthNqxjP0vlO8aHci5F2/2O2QF1T +0P1uvSbEkAh1K+oSfIS7cz/K+ech0xLMEccCh7wneR1yIYDg81Hpg92Efg21RTfR +bxbdVMUixaLKu4PAFLpDWfFsS7piu90nozvbXIy617Kqo+Ba088h8vraUPoiHJBT +lv9GXvSxTWdo2g/o0wz3o39ZPx81qFw/cYl3Jj+hOhTGMqlH9Lmn+h8tuHC4JI9D +Mc1yOoVNR+xlV3DH3KeK4x/bfAIxlCwRlOVyWRTsob86/mC5qwLN+2VytZNXFy6G +1sD/oOJX5vc3APforehLfl6wV2NBYoz5EVef7tns+DoGf9zVoDt9Wxt9TMINB5S1 +8xvauhz+6msVp7K8VMO3UmyEmpJg4/39dNt9a26ZsS9sf/BzSHllE6mNvjTKVM8f +odUAANhe3r242FHR5Um+ZfdWTpmRcZ7vEWVV7uLrME/wyKP4O+az7B1czh0IXS5j +lOXBE6IGCTub7jiFcEg0BkbpWqCe3XivTeEJvQuK9gDLAujyI4ti+Z+xBPrD7lFt +qEY8u/GU2QXF6ComU9Zb8J9S7k4HFCnAyDu7K2X6V+0jcSVT9bQTx7Hlrq3Gru0n +jHprYUAcFS4CjzFc/6LcXkZAogNW0H5e9Ai1ya7fe9kIVxPaqC/4HHMZrxQBJY9C +nfdORWR0ymPYtcMBDrrW+Bpsbn6EjH4nqf14p7BEU+UEOFNbsztoQupawbn6StK/ +bppTO2iGRkVS80n2ANBYI8DOkdVuWg/CDLHOkDwqp/8QFwUPcL4tUshcfgQ0n6Nu +ZxZ2fRfnFwkWQ+JxX8pO1IlbvigkgJ8/OLFLIYukjXSyfUBZWrwcjnBrIE01uZzY +1nU70YBEFiELbhOBw9w/T1XQcmHntTQdobesjjg9wMf+osM6Tj8N/sGPni/EwQu3 +NgX7xmll+dFE80EmtIKfDLHr8Ek3QzuERGGaF3vPhnblFXFGtqmrTbADrxJZCY1U +44WiIB3z/2ru0nC244ySLRYJlF+8+HRdafZqvd6fNUv93RpRg2Ux0sGvbfzqto32 +lUO2cprELZ+Ql/TzVI23cFV97NJKAO0xtFuAk5NhQiNAuocZFcDT1z28mdIyBJ41 +ceOd8vlXNlNc1TEEQomLft6Gl68OBmSmfDOs2XZBtnFloqkkb6j0IfhWIPPfgIDR +pdSvaJRTvkGDQ/I1pBC/O34M0N293l7IN0R1ZsAsV0NLsbDTDAkBHRNVD42kq03d +VjXr+c66ckMMmg84LfYzo1RcSJmQMTscCSADDUb3Lpcbg+fW8UTvzqTSibD2rek6 +UZfvi2j1Vaqn4oL8q0bjeWKG5Xns1OgLb/lvmZ5hxFvzW4HRT95dybcj+9NKoriJ +yWj3mAWqfWHSyDTfT34gRqKXALnfVE6N9Ul1qTOE+yCxXaj9Vw9PVrjr7eZYjkPi +XD+ZFN8XHgc8eZdJwA22AbKOdGeRq34/vU/lxwraJgHjUctAIHf5CQR8uVg7aXMK +mPAmgGiL6OnW0FrnMHCRCiH1opw7yq8dhGvX1wJvMnzUwdkaJy5w1wzl/L0YizL2 +f9UfgZrBu0yAYkC5cIyoQWn+aJq6hUtB4MCtR8vuSv1bNmqgWBvKEMvXIpkBPvih +WAzQ/fYespJmVemGFW/5lWVXYJtKj503x7kvEzIyP8fPDLLJDku1Qpf4WXWxLkKu +kui5zTKR+jl5L1CxDN6HUxx96q02ieSFqGJSsfsp8zaVF5Va4d8I1SQ6Ke/X5rws +6+s8lE9pFc/X2iMP+JGKU2jvqNtqov/trZj1RsqExV7Ytnc22LbHcl5xJWfhPIwA +MGnyMzVCcOMNVfe1tHVhuXUS2FSJTX+dE9sE0JmU0AxNp+jPOSVByc2WS7VSAtng +fvb8txi9xZU6sdhHoJhLS8MbDt4p5YDbew3Rz0Qsmty5fiyc9vNlHugUua2Hu2vQ +xJR7saUNJZ9URh9xaWLCFv5J2mxXP/MDDdgIxpds+0GqrGmEP4A9zMn1RnGy8aTF +Eqix9hKmgt2Pf26Fne5CB/yiQlvxzwtfL7n6WgKdqLq5SdhL+5iediBKPLQaPlC7 +qcvqF3GQU0KOm34dHEkkXiCzNnmVFdG+Nqt0r2sT3SDMnZnzUQci6FRuYpRHBbNy +n+UbWvjWEBsTRE++EWzR5boDL+yAUeddNQABgtbtrvI99sXlfaH/lPJRW6dHAmgD +crCfl7S1hVq5eUyVnYPAC6lRSt7Kb57LZi8uNOlsyjr4oFPNSeNJvj1l3WqGbEbt +OipZWhpSxtFr+mdp7wJedsUTZPyJmxBQA0AWC3ETRnI9sMAvmUvBBrFILJs0UMko +layLC3RIzuNOYtfFIwXX3WG4BuJlDJWbxh2kguh3BtLh9qbheZdEPq35GKjsNJX6 +bFWcQZNBfJmA/rdKGTDP8XEiFf2sqiO7MBM+iRDt9ZpwIKqmTi126yDvGLkrtjZ0 +293jtqw9uJGvu1FKd/74USgLNd03ILuk6g2qEC35okLHcMIX4uLZKiZt/9qNbSKm +OG5kDGVPsMHz+m/lKD1UIW1pEGRh+NkYXoUje+/Uv29X0DHV1iUX+4gWjrJ88R29 ++GIFT2nduSVCMM6/It//gldEdILSLxoElUBvGG5K6ZH9hXvEZd8bxKVe1hO2Cl/h +WfgnErS6eWOFzovCHd/GcaaAVz4kotVA+Vswb1iYfFy0MTKBHjYRx8NwImvnJFod +tJH/7zmKGcsU4M3IuJqcKwH+HpiR8cieJ4+Iee+GyUaS/X+Lr6CjIbLFvs2TLIsL +SkHLa/rgqskhFKvxy3Dz6DMvPJT2ZGG4kubs2Dm5pWmaDje4MGk2oCp2hHTOKpuU +MKO9O8Wr2OBWOD6Gfl72Q+MiEiqZT9+BQhdm+dn3FfkhsZ8TG0jY+j/CYsbZQBrU +ttz14mlFj7n1AqXsMULKFqxVTwKp9Ll/kFlHmIIW2RqY53FU9/f3n+DEViiKnbmM +QDQYU0wrG+hfWX/w4bgeZQ7XvqstXvpzBKeZIKJGc+/UtzvQiPCAIcgQuy+Be2MC +Kn4Yxdh1r/oBxET339HJuMNV6WsPt1duZQ2SqjKtLCjr1dootzWPCWdcbysZ8V+L +1LmR6FIPKcYzSvyETb27esdI1MkcEo8bFRTZXA0MHH56UP+/7UZ6eF2dSh9StTIk +jhV+L58PLxKndmit9vwb/lcn2WxCikk1/IoS6L0C6lRhqB35dfjXj/MA/XmIWRLw +99bdXtHVN4NtUpDnt+03Xp0ZwBdCErwtzXBHg/mtBKwhvnJKiKpRMm9V5N42v3BQ +/5FIhxT9RB+tzjuSXxhQcn+/OR4Az/EQ1BiS/fqsntIS8mJWQ9sPVTNq16+riYWd +ILX864amSP+vMtufAVQm12D0z7uK4tlzPcO22mhkgsYxrXiIS9yBjIZFO0OvMaIc +rU+OClrPSXipMfHv8dRiQSLpY+9Z1yfUpowc31bq+MMpxZQQAXaa2NybV25MHjGC +8c3OyEwwgSKNxQcWOXo16t43goZwUN7xQnCHAkbsj71cbf3QaxVrgMVS0REOPhNL +Bw1PVPDccrEPDlCakD1Rxvn3rn3L72A+DnYxkZLvCMFxX1j3eutiXtZgcHIhdbji ++h6o09tgICVM2BHwrbaAei4v5dG2T8WFy60m/8f9QkAMUzGucwy3BAe7FSddOVlU +GLef49bWxE0nXIQehSc1Xsaa4mCZiSZtvChQgTvsK/v9hMaAtuxFIO1KKrFu3e7A +Q2/j2nP7mxrlPkcIgOQUGTyxwtKGBHLLDH6X9olEwO6AJu2lbzhHCsfZ1JPMI2xp +gTgC9hT/51wZl3Ok0Jlyaj7Nc5PaHNHAq+T+fwSKl+BPIonsi/mum249F7b2IVkP +2E1uLAeqrX4aBeuSAOcM6tNgHeLOU1u+gwZV4DLvc9npEkPJLzow4CQmyUse1jB+ +V8N4ocdeclYbJ1bA8QbAQxeyH02VeVemcZBT9fCyWKUKKvFBJxdN9dq7/3/Mxgoq +TKaXOgFXLrlC6l68o9ybRbi/+fd4iKgnkev+2ybef/Uk/5ID/4W3a9IcngAFR5kr +Td1yaxTjRbugiQGKQ5VB4sKKVlMK9CSODXKdxKokApFLVAfzIRMr0cr/s/cTVtvh +eEEdkdOK4ahZby6mNva3VSK4xa4PAhFTi4TOO2GiVvFwSYbiBODre2pQEGte4P7j +V682TJiXUxbqM8g89UWXh7rA8Pcr3627YpVSakrgGKodV4bPmyJfJ4onvkir+zIu +9kr2va708NA0RVCt8q7NMflwopYWt/3Kli9KB50racJ5/EDeKN6UTD+r6SkNGH9m +7MkaSEKfaAZMxSg6YoT2gEBbdhblboXpNFK5OxckrPkmfHL7hT7dm2mhxgoP5ajU +qX1pD5IHrsd6Wz2XzjZEy7gSYhTqJ6+hPm5J2Jm0BvYxhufHPUgYNr8Jet+ckyPl +PQ1Pk++3JtHW/4TNZQQFx5n5a70SENrPc6qpMBKswCsq2k9Y/tPnyBwhYc+j9aSn +augGEzQmKJp6mwds/PHI0pXYZyv+ttRlGd0APrC0iL9Ec+s8UnBDi8l0LeZv5W/t +1D7hs8KR4fAi7SdeOryAVV4PekmYJ0VT5YmwK/ubKAlSt9MdDYBMgD80n9CrOHZg +u1NVL4ybG8DvmD476+NjrfiRrm7rXzerdNsRRQh84sg68UecHyp5CAB+c6yUeLt+ +GzWAH/E7UD7AEzao6T3QHxNEpp8QiDhr16EnIslHfI6wPPwi8gOsZUkkbSKs6njF +rZ+giiAfpsLDCW5mvGZYO0pbFRtR5m5msXrjqUu78U8fwInjy6V8rz+69jOfoQEh +uYdQXfQHccxijgkssugyVnvzucX9+eB+0SN1RfAEkW3LxU1sflN4RKCGIjJZ2eJ+ +rR8IJdDmv6Yjo/Y5NGb9qXJyoemIF4arj3tXA4G3ZA0utziYS7nAz7shy0zhaI/i +OH5FHNnQO7JhNcsOMT2BmzqDGtu8La9tyapgynkWY6uNAXMQVyvWdb8av6heQW5z +KeSuX+Iyc2ChbwkfCNmmaUctOILKkq726gnGSRwd3NC+0XZWslX1lQMNaUIJ5w1A +fO/Zv9dDunUEwl+y/a8g5ezU+wwwN2GLzBFjjbEYLp/ckckoctndA0NwR6Y24F1Z +2YMhLCOddA1ZYUJtfLu1Bz6qGRenz6g1l02DgRu6i/9lChHVya5wH3HwvM8V7Svo +yVzp/l8vuFEDVk9YwJYSazdUC+erF98cBJWGG/LeifsAbXmPFABY5F/lULJ3EX9a +JVDRIPYp6Qw4fgndG2atJzb0O9kL8XbijrFEsS1goE3uxIHgi75mGZvglFZ1nF5C +YsEn29tYyjeiLlmqblD9YPjL0+2N/32darNUBKaWvg2cEKwndIHaSIC0uqE8fuNo +0zcY6uD5rAyMM1JkfQ8YfFsU7dKFg6jrpx8oiv+7cFrJ/VOfGXFOJOZdMkoo8V6H +Li5TkutK9K/IRjGnFwXhFCfuNZAed5BFnD9luMmWwu+9vwd0cSoLcI+gGZ1OsDnk +MKJu6nu0lyVUqxZSh6oBW8WkQ227f8hxWTuwUESR/ixRMgdYiQw4QjBDNJkHOmkU +mTcPtgeYgwlTaXHUWlTRszknxi8lj+u1Uqe9LelqeJB7IYaiG4r4AB0r6poo8Jrx +3Q8eFSogmXiKOeaJrto6+PXdCuYW0yPBUgvrEbvcvkkZPP7PuNK5mgZGhTnBNvr8 +C14l0Zl32vBtqkbKHNFyicx9AgCmLzSmXOlKbkDCgexPfNe6ItcEhKUtQmtVDdS1 +aNIKFVfi0e1aUyWVJLRx7sdlx/PcefCI7LM1fp5SiJuZHazvoyvApBu+azvNBrCz +nliEYKB/Sf5ceOj4dizBChGzAIlD0zk02ZA7JSBLm+rLo5FQF2wVDco2SBKOlzRx +Ymvk59BrxPg+6brsgX2EPs2q/izC9vD4S6e63NBowggf/SWASZ3POUAfNXASNkDo +Pd1p8JDyDKWzcQdneRpuJFl26VPtsoDAq2gzPeEeY7VYT114WCPtbtzdFus8opuG +UUgWA61wH3gKvN05hMVsyBEBmkh6C5I/ymaWQYgsCLuYLSadrgZeu18F5XI/jbWJ +iPzf5yI3z1ElVoF1SwHh5+9z62BZHRajDVcmQolQvPXpZbDKgrudNXeR672GG5oz +bKxuNwi3X3YDjfc+mxjp/iE+BBrrQHnwB+VPZmxOXvN0GQJm3859+Buyym0ZGImy +zQSqNBnAGgK5L/TQsfkuzRyv/EnPr4ALq0TZZFw2tb7DPjYhlrUKcOW7BnxMAJVa +yMAhGcMFue0dhsEvHQ/dnM+3mRRkPilMV55U2veb7NMxtZqT0HNQBUeJtu/uAKIw +9X3MphFIWAFvBfUh03QRV0YzR3+hopBLZ+Zs1L6xtslxnolOe2ygOl0bh0pSXq8E +qbDFKSeP0WXfWB33XdO+AHsaLU5A0vsDDYGr45MyqYA82MkunpR+fhZJwYtihDFu +zhPU3XV0GpOQL4avYJmD7tMs69TkVNS3BjxA/aK2JmbUV0DZKGmkIWaWJQLQHrA6 +9MicGsLRbdAF2M0vZeJEu10uog5Vyx9riBU5KgBIS0AIlQzyQA9CR87elEA7gwCO +Vns+9quxKXRlZs3uiXQuAThJponWVsJ8Jx+6iQBrfXMSRfawmjSA9u5AKBkgBG8I +kSFiJ5UrzLWt5vrA5F3D6iaa4pD6G8ic/AiC+rcJm3EfzdejlvBv/gqFra/BNa0I +eDjrAK3DJ7n2ofPjjrrpwNu/cZ/0c1byg40ZOf/hiZHlAPHrsjl4OlUchZ4WHXDo +bQjCUHWj5QXhl1+cexlnmBmawNjH/eyQD7FWCATV3dSYPIkqbsttiwm2CFhu0yja +J+hMdxxq+WpKuB++MWSeEMqQDPwXTUfsod5wBsGtFOUVLscXXBbUWnKwGeWaHKON +ISIyNisdTWgAQFOOgbkrlUoxCT2W5ESROtsHVA2opNj/bWPpVmnzweldLcrb5yDI +Lva0KZTx7EEgFAULHtS2wrec8JtDBO7DNceCh7vALiDCwP78xR+Ju1Vyls1ab34j +smxtDTc9/p/KOL9vVHJvIwbCCk+2AjSRbhN/jDDZqx0lzvk7uReeuHsJIXHN0m3r +JjnwPCEo+Bk2Pa2ebmMp45Z2kX8R14IjEfL9YPTP9FVdQy9A3E3OMlD+xrhNYXTv +Xr3nfpsUXvm3gF4orzJhXcb/vB9DM1RA0NXjzpeZ+84u+L9Az0duzG603lMCIMG+ +r9kUqYaxBCcLj9Qyqel8G+SOz91II55piB/pphh3RYomgIraYvYokscM82UXLM6b +WAn3FoORN8nDaghXpiz5CV0WAaaeQ3/TB4GQXJWFQCH4/53IB8ssmcrSx4Tu8tH7 +NAD0LkKeK/JN4fSrZf5UmHsJfVFi7aVM0DLseDCbvgcJ3kmEwl6+Z8EpqOjFnjST +HDAcPP7Xgf8tWkuS8qizIiwmAz2e9v+oyGj7GaZAqn76UeJU+BFkXLQbnMw1Z9XO +u5pa3AA3SVAn5rmUqpMytS+GtSPHC2qch6nv9uQatgcl3lhvT48dVqBV7NjCc1zT +vbcayhspZ+FTtvWdKleYfbDxhubQK7PzGYCDxvabzeknHSVBeuNpRMnzC98SgYSX +4fKjwr/3KT0PMBVr6H1jQbF5WIjCTqJFbcCzzSpHhlnSCKowkF8w0UViLwZ7Dt8y +vU5eZnSTzGw4KlifLGCNLS2S5MkQVM4WT6+mf/jlexrV+kdUYbt2UdRgjtPibvDr +oOIj7E0OPfNPF1zL4d484KdiADlKscf75NqJdR1x3tlNj+cHTYN6Gi3vvTu8QsYy +gYnVvrEYXV+K6DhLZpoufnOZOjMJhRGECXsAEY55n9tRmOPsYpAevCRQ/FI7Vpf7 +tG/4eBT5iVN5q6gSN938LsC1HX0b6sFKm/N6PrcbX3Pp08nYCQlkd57Sq+xw3iGh +UFD1IHZqYcoHaZ9QzBZVCYpBVS8jo0+L6ume26Q3EIAL/UsXsDNYh2xsh39r4cAq +kBjHIeZrj6glK1gID0OHs5/IAzgNhizfTWu5GQ7tElB6vY4RUqe3V7Kb3FejpIs/ +bRYvyS8cH5/+nHXcHx7TaFpbxdW3hI1MmfLUBfwvkZRRzNWO9Q1/wTBVJ4dtAvuv +6phIDIenVHFH5abnmlUkKuJOZs9YpuMxYcJhZKAHeSuG0/9+T3/7VvK9kZ4fc7sB +uhMBJ/h5p6cqgOB8t1pnpb6uUsBDHLBq+xnB6Ophvc6S4qVYN/nlTHL9ZzPEVBBO ++HIXB62+o96nKP1XO3icyOxR8oq0vmHLT3ED4txn0/PAJLdNg5kSpVPSCnuECEKM +9jPd4S69JhE6kEddpTJma18lZ1wGRv/Ye4iHMoy7bJ8GO9NTYgwnqcoBteCYuFld +jOjSacvy02D5W16ioDlL35m1aEiKCxxpvcnjNyDmrxyy5LBa1vuhWQmYE4eqw7Kz +p9SzaLMk2jcgkuVaeWkcsqKQ5pqr2AzSkRi+izaR6lRok+q2W60k6fN1oiZ6EIMW +tAmMsz4KzfP8iEiKDP8sfkY5MI12AC7npQwyMWGk+a4g37OLu9PC001bePr9XWqt +yYofQYBeIFwMSoJpSZ7/K9JSCXcByYIejL/JR4ggzW9s+9SCnSid4Q1i52E8z2aZ +TzjEHjqZeIWRsWBR6FvV4mW7SKbGz5mYfUSuTnZxSNk6DKAwPbDKTJEp/Ym+uplF +iZhLhCuPVfThTx8ozv1TvXa8HpQ+HCDshRTrr1iqcU1sJ2iShX05yAkzYVs184b5 +tv42z5omcCcOEAmUJWalIAkmwdhxRYJm1bVEmlZgjeiUa/VvRSyAuZa15SFbmenZ +Wbv+25x+chyhSUd1mlBD50K4b4DL3+SKAga5KO5Wuva8OGfGW9e9bx9brN7b5wPg +Hanm41384f95HIvoCkvNOnu3bNd5d6E2V+b3oiIkg/8U7bGt/zfbpePvRKoeOF5b +9xmyoAZnrDNePGptZZ2hTeZ0vsMimt4DS0LvX/z6WEEmBUOMOxUcPEGfJYtfa1AD +DoadMYcP5Ywfw7Qg90Uy8c3BZTN24hVFAmLSUwY69E2nOXQr7ks+KrbByJOPJvqY +DqEfXz8E2B3vRSrXN+H/9SxHEYV1HB2DFQKnCLjb8tF7MjSoDCBABsx2V94MQ2C3 +YmZLNj5mmN9lVV37AT/39AdSsNv7oTRKLMbuIzIMh1O2nv8yuWOvEO1E5XGvNjhE +D/uLZCIulEORav1cfnrlU/oN523pcd7rk4GmtdVufVWSWEA7M8v4+6uZ5pW6niSi +2GAJkCUKiuiUiLXiA1Uqe6JrFF1Bc+n1vQlK/ovM1y4UxYeRJh3ShLEQwFf++wYf +R4g8Sk6tPlvM8e67iuISY3/tImMkISMcVXf1hXydYwcAWPxlQdP0NJCgwgWlfqU/ +SzkS/L5EbMAM5GzggzGU0mMmexxTaMl2pofQN0+a21EmeBrKLCAIxfvCVWgA/56+ +i+kKnsNskug4fw/w6Se7Pqmvft5zrikhisUBtIQ582EJ85GK7zM/DPDa4maV/EwB +dvuOnWMZTFtEohYRLIeLNBy0/KO0i9omluPHyHAgC1EajnEeXVlA3qO0sxaw95z3 +LZZKujTS09tWTaWHtEwWEV6WGcp0jE9BlTv0T4F4C8rhTGfURX2fKViRLNG2PgT+ +rs8He827pk26tPuQ0BrQMXJVR6P6fRcU6r1bj4YZzt3erMmgXy8IyMN0fftHN79T +3PynQGu1fV5V/KAmUzLbUP0AExAz7JZLMOqbwr1B41wNzUwy5EWoB4s+bnJvAFB7 +LUbLYv0tBM4OuzNqwp/9JhXTyEtvlXXfql/yyge/0dqrJ/w6wPuRxAmm/opCnPKv +9UHV5wknZb4D2eiB/54obag4DEwYflg+G9qQOmXJGZbuKjCNhnKqEehRNrQA0G/a +vCrrZs/ckCffLk431cDN9sQYKeImZ9LJHsh3JlVv489dW21xHwPDD9fMF92TYjTM +uj9nnSyADoz9w/TXmCK0gTwwRsCe0kv/oRSEjYxbMs8rxhiNa+kK1iu7aEJcMnUD +XPqYlvypYt9DL2dH137ssna87aUf/ttKC+qMb8c4vDViaVLxUyct6j2Sio9iOAhi +WdsTGKykZQvT85vaZS+vt/w52Cn3u7o3p1jHcHFf+ZB54gyBkBa43Q5jV2v4Yrhc ++dswW4UjuB3kBaed4oet8YQmQPtGwGAEsuRJZnwUcr9IdewjDRymWV9lilS4aaXt +dvV9jANO8TWVFBAoxHnBsBeZ78kk+UHZOINPbDFc7p37vidHgKD4PkbBBH4Xv3yZ +zqLakzBTxwI5LwwFZsniNY9klWddtc16GFHiA+IAcInIV5lK7Ciok3/HfVX36T2y +/0dDbNaD+KklsLXkgmYJtBGL0XsFGH4enWJcfaJiCVZVDl4JCov7fzTiZ9C1NzWT +mHzR/7rdG6uMAVMKti/OniJEmxg10mTQyHrFd9nj/9ejltj27rTtIcF7yWv0dagS +PJRGXyVQS0yf1YutcKSSj+aGjTJyPFKqbDifjHaQEGlPP+BLaknXyuHwJ8hKGIdD +gMSYvhyvZW7rISt7ClmYbhwAm2aLg0HeMhemFZHm6mZvDyvQZYVZuIhKDm9v51w6 +6HpcBn5j+pYfOIabwK9Y62xXKoxZuD9Pcu9cobuCRDHzubrv23sxkvwJ10ltRjax +vuRGNyollLme7a4wZqKgxLGJz3phQrD6nW6/ECtYvjJAw1uYedeLCYmbIUsiCaCc +izX4l6TXWTasKLbzcHFTcbjsRnI+pUpN3yTJvZMZKK9ittI8g6u21WBDdpcob+dB +zlOq/ZQfdg2u6Tq1hoKBAmXRBjoL+zy0a5thLdUy+vEA9nJLBQRwrIVyHAnWs2qj +3bAa210ezJuCMQYdwh25TvFdzzRllgqOvSa5HaUyHaZm7BpFYcDUTbcl2MydILju +04xKlxaCtBkz+9LpXrlM897DVVHtWlXkJWKV7/KUe/PsmHNAat1J3z+zwg3UN69g +8J3U1z6EGukBkiIVNSpQnUCaeypxYFfoE3VwoWU6KtY1pAM8rKiTZqnEIK/Ix6wb +Ic/ZGBQ3vLAOl764OZLSQXIGdPL+cQuLOLjbZTOP4QfWN28M7tBvGHM2LVdGitYq +fBl8Ji5SApPe0EgAqG9jvsgzHN5TfulTTRaUxhszmRZGgH3oSw+9g8H2FoeUxbuA +mxTGvrDakm0rXD6F42aZcDzBpbAMJMdE7YCPPhApgSEtNzrMS/XYsfZIHuQeeGI0 +gNAP4XikCNC7TEYc72LXffAp/40MgRS31iHO60BIJ5qG1XXWNgJBMEUrs/Dfgm6Z +Zy/fDZCcSEZB6/ILB3x+xcfjUIXWChLE+UmR2xSHY1Q+p2mKL8i60mRZaQeAjEGb +VSFA0ZlZBy5WIyPQBM+m/9FUcRlDNqTNVT/N3XQFTDCWrXDND3CMdM16GR36gbD0 +RBHLYQreVWfCzIeT/8mu8ZNJkM4ja3s7/wKI93zzg8Ay9IKTGN9huCSD3caZYF5X +3puJd4NiM4XzBGv/kpeaOWSX6fjC5QIt6ttiVaZkKAjkikiwsHVxgcgmKNVCizIX +beB98hxB3Q54cUoA1JBf8trCOKVSV6StqaVlQy5TWuQHsrvPyZixx0ZBryCiw/Jx +nlF2NDMFVZcSGNef3hpySqEtKv3dLnz8G/ROmo/Lor7U4VbjBGtAvrwKYtpl5UZp +hq5DI7Slz9JFM4yYqhGbAZAX/9qhrBiY0JGIm4nFHzAO2LOkREmuw8RuS4TmGxoL +GZ+zpe02OscU1smw9OtlwuRULM9EEQF/462AsM4po33EfEolFhieWH3QqhbExMy0 +BdK6tveUndfAK+TYTK6YcvhX0jGtvJudt1FinccuryXKnm5eUYUEEUYHD896/tig +8S0qJ2teE/AJRV95lgYyhN0wduzwkYfCaa2sMC15bjh0O4ILnWNvfXDmrzCrZv7W +q+cp+MwTI17H/dldTYfAszpeTUG1lozOFDgUxVb+YgscACJhTjREJs4tYbNnjuBx +13WCdHlLGII6sAahPgcBLBUHxYZRSTb1uD0Y7PsYCXvaA5pCLcs42XUQ7ReJSxc0 +6D77g8fjyT58zV7d7205Bi6X6PNudvwSd4OZVod3SHIHH1rh0fztFE1sJaNFhSki +ylQbr9rEe/Pyz/Kn2OlqH9ew03j3LcrWmEw6ar/9YDSxZ9hKAEWYtnoYA/LZFYrn +KGeo0FPfMTFWceLhhhaVcMW1KtcRdf5W9+nxXzBXC4oC5NiD9PnCvjUh8wIYcgMX +hobHrASqb4Ys2iQTY8XjWkk6wqxsFFV1/QR3wHjl5aBjzNkgZdOjMGnSTL9H7TAj +1jomRpGiOT9ZWoqT9cb+F21CT6bwJMSwoKl7FY2ta6hAqGrE1hkp6NWjNB89I5gh +y46dtBrEmY9Vhrg4QCPMvOZzs7DAwSTGGQfc++KowFwluRI6mR/y684zuTz0w1bK +zH7Tuidhcz+SDI/qIKrxM2Wg6Bh25pvA69D/wXZD6P2PCPiXAJewixG4TuKoKKwC +EcTzC0Ycc8zObIpoAN3tlIDLG16B68XAaBuNTPkwzYK4NyXenLd0pVel+vIzwxn1 +7yir31WJXlT1tHq9PmbyjmfweVzNdB1iMzXjwkhWSZS/gQD1PsgQtzuujbqJ/PRp +HbReJbcYpvgNFoXxxcH2jWCMSsRNkbbEG2aGnb9+TtrGGSplNtj+MRxc5APCxMp8 +9we2/Brr/NsLUrI/OBiJ6G0uR3IvErLM4OIo2/e8ngySBeCB0gbBA4LkRqj+cRjo +aJLg66DXn9TqO9oN8yCwlShVSMWmoBSN9rY16hMH84JfTTX+jnaoevO49fyRcW5u +75beJ1xiThuhS6KAe1V701RDYoFqnTLjD1my9MlevnLrluqFaKYPqz9H/TqByEbv +ZavZ78OwLIvzkXj40/5QjBsE48fU/W1rIKHsFgvt6kgqsMDSh/b8WW1JSGBX9nmV +GlnZVyrPR1ezvvBnfKdoKdZdjkJtPM2l4RWpVnqWZwaYh7QQ9OpjZgXSgCSY3jaT +JWe8SodLswGPKH4rz/hSKfhpB//ncB7bB1JpjatpkQwS6vYTDyJc9/mfZB09sXjx +Da5d3F3meZApfKq5J1sMLkrVPcf674ov7GDjkaGy42zFXYU4+yVNgTIcLu0TkMS4 +OwwWHEs8dzSniegOhQjPzmpzpiRIO/nYqguLjDDDo3Tk39klrQMhMzzK/Hqq64Up +b35zCsKsRmUWG6xARNrSkPA3kA3UgelKdnNJmjbqaZo52nL4fqEdl63A5KUbhDcm +JvBbj954kMaMj6R60KS3wI1GF1Iys1b3Y1dSzRTpP1bGLgsXuF1yc0iBVzwVbRWK +O/jzW5NuAAd6UlbSiVUYlgcqT2kbapb3RU2oLSXrsoQ5fjQjsTammFs1P1Ex2q0X +XVWj5nfzNG4jyqWSyiUcy1c89V5+9HJ0TJLQ0Ac9Ec1PTB8oI7ZB99DbeYFIkPgu +t0DrbsloSKXeWAX3KwU5CNHoLZy8s8I8JkkXm3j+2sCDOnmGpFmeFdvqm+nLr9zh +CF/iVaG5HsYt+i2ltdHon9O1SQtPn2ox0GTq81ag/gTwPq3Im3Al3p+gXtg8SiHO +1hBArsuRKSFB1ixpu2b91nb7sHvnT7oo4EhmKizbqnzTCVEzq61uPGeoApv3ZOeR +ouly61mEhEjoKQx1GkOBOU0DKWr2wwJxLC+1ttjsLOXtLK0dwDgPZOpMtNW1HSgp +nvb4FWax7i7IAbNJJQ4tpdcCHLBXv+0TLdAzOAFqNodsZoXimubSS5j7iZu1mBa3 +8TvQNo87/4hoDMXdRpuPSpuwQcJUvgOvXuCg2JencsHVTfcV6V5BgrfKJHvLewB/ +cv/+2ivlRzX9aFbtqI+BxfGnHn9xKifkOWWZrMs9O2vKQvlMFeVIlqzck1Ws6NJw +BMaXaJCWQv2lvPSx8YzEjbInfKFKl+36aRUCIQ0Z5L5hNxBZrxQ/5b9qw5QPTBE6 +E/Ih4TJsKlx2s16XrRL3IsOo+KLuktG0QX/mS9/l0Ti165jsuIegIhRz93IqW9ko +lfy+76ELAaZIML/gMEJnQuU4Yb8bOXfy6vs8EtVxrzU7OSFT/fPY/oDczO75llc3 +3Zn8F9hxojoXFwYnXLd3I/ylZvfNm77WegBJ39c6aUT4c2EIKolbsCd5BjosuT+Z +FpLDgMhELjGlWmWJeKAXOQFHQSq6W3LD4jrerARgUPicxny+ElMR+qVLOSCJYHUv +TuWyJRUNmmPOC4ODx0zUZu10yffZJP5nTnCf7hsxleeDniDXpp25Vn87HBNlHFsp +heUnN6t5qE9+Dr1Jt1Ljq6VdnwHBqllS0iFyWwj54Jzf4QrN/Cg7A4hziRa81bsM +9Vz5Be7JSxNXHTdEYRU1i8wrQ5adYu7L0F2eWrHh2PJ6M2TUye12QUH884j9u7fc +VrUpFr3DGBWoUbWx71Wq73hDKs5wJX3l0uSnsGlnLYwnC0EODCobL9iv/q3FHUcC +Z2pOLBlA7OtUCFVecpqJsp1zLHxLk33e2y/i2+80+0EuvM7nWi/jaJpOWFFwSadf +3GoZAnIPcgCxbWHgGqf3bsG21ypceYR5IENHbBrpP4NkehgpPeMroL5XKBB/Tdj5 +V194IDNtBrEXZFkEAxoFgZ+7146kO1sC2NRlpNUa9tQFuHCHFUq0GTLnUELlJXme +WNqgZHsi5TxxjmVmQjNTwy3o0apdyK0NaKlfrpkrPCuz92i4mTq1GQLkGE7G4N71 +8gFFASJeiKB4iV5yUYhoVtHW5M9VPlK2tqZ144abVh4Is/yLxu7V6jMBF6oVQOe1 +XoRvKxr4+oTvyJu4dNgpPzSy3cVu3lfgCeNIzmtN6xY0/jkyAIun0pHpYHbatF2l +rOWiE/g4nuwHyXBAWtbOP0ULZSfVQ0wUH/4OzLtAuZFPZJUe4tuIT7/gH5RLolhx +yT7FzevbyOBcqTnPF2XzPL1qNnAwHW0bDEl65EtYhNSVApeGe3B2bs4txBxcNgcx +HxgTxyoirzz8a9xjgAOakM+7INl881s7CWyL0eVR+I22lS5FF4QXRnQynVBMafGz +cD7HLzbp6eDVPRFXj6W8scGiHpPLeieIkhjhaF6NyqCksJLyzbTHHx3pcnfEGl7U +oErek6lxp88Pld6saoRmUuDYdjemNgAb1DzgmWJaDE2da2xrEUqy1MuHQxge0Zjc +ouavUhhLNk4cZzDoSyOe74tcHZpLv0cCporTIIWQyr74l0UqvFC4T0Vu0DvSjp+m +EfDH7XldMWdqldUxPtrNjQnoBv1s1lDCvcjRAB1ZziHHw5IPlyVQ1DDrJ04sYzwy +KJDx2XUizHKopeenDPYT+hWysbKLICOkF+m6BLcVWQLpoBr3et08kw1YKjyx7VKW +h9XrQwQOEUt0G+sX3v46PLpTs8Bpni9MTHSS3sKFyYecuhpiUWMr8NET0mkO+yIi +MisClpilxYPNB2avbbhb+nWlI7UWw7DCHUfcMkqx3UiTdjwlhElQ6criIwu7gkHF +dVRT2Swtrd4n3ZxllVWFPrBu1em90nx0YYPoi9FV3d/C5VWgj27DgmN8QAelqtNP +jpD5bf3pkmDxizrKbzqpJ+rADWRGvvm5JFiIkWqe+2QlIQOQalIV+psk0+UeWfLb +oFbD3XoHgOT8azRQ513NK0j9+qQ+Kfo/pH0ctm09IJEh4YEmzfYOiKuciV8UTwnk +dAN06il8qkW3ANTAcvfW5TMJEzBSYoJQtM0opSpEcGIAG+5770iHMmIQm+otY5SL +dNV8Ojum8IK8lfGOWuuX8ausl3kz5vK1VTqMcT1vBD6dhcesy0Bctd2q0xkohBAn +u3CwjzwfFZF1anqt98zql249+cok9Y3upcadgxNfgwtMx4xg+Wzl2OJN5r6dSMR8 +6C73WiCGpa6KRYUm/P/EqelPO5PywH0DG9pSj788SCR8JtChRXupRCwEFs4t4L7u +3bqmgHnctX9eHgEg20G+Gr/c6HeSAPNbjKMbk9oxfNsCM+smQLaeYfV7qszjcrDB +ahFVe5GaenWB3CQiOyc0bOUUarPrDNKycukxdGK/tjk7n0ReH9fil1lAlmQohhhQ +oV5KducmVtUHuGNHjRjLoDLPG+KvrKig0fI7UuISg17vdk/z5aA6k/wFOxRYGkEM +HLpihgrDFyWuetPhlLGtzRuZUTlGL+HJO42q57sXRKP+2aSBFFgnUMZik04gdWZR +md15y/1IAIeY6jB83ullf+AuP/HwtdEFmR7dBFAHar1OPeqxkUa9WRKN84AzRIGX +PO/kosT0B6soNq/7agICLLW/U3PrQYkrkydlkJdva9R1troJtCLM9jt84F+5zVRb +aVU/uGCK5+6VmSfUiKuZS/embaxT13UCZ5LTemVs/pz+kvT559Wlb9kOZZ1BIs/O +Mp/5bmal86XTXwIFb6YQeVux33GAxRw6Y6g3rRm38ZFaE6XertXlPes/h8pvbI+7 +oLQeC812EcEQia+KT3WnzWhv8muxqhIRNJgeR56sD3mjwlSsPSMuF5rt/F9fBLnZ +57x0aJ68krXmu1TwDwP0HH9v0j6zRpXvCVEmTp4+FpUWH4xkH+lfQEF6gGN3SpHG +Ry+f+nXyQuaCy3dLrORKRmi/NJQZKGMwbUqEyITIQGEamupWt3fsfNEYacgEPeDZ +7wrzISxX3eGGF/GM213WUYkgY1pT8siXP9bpXtKTagEqFRrod3PGBP+rDY5NJBGr +mYvshCYuhhciV7MqxYnjfAiSKJQwiioCJVu+jJ3pcipVAZXqSJEe5h1PG5zfn5jo +9VwuPDAa/8+JnXjjEHJYiskMnFHATK+uubU5/2ZvdCpmTdElh3M7d7Gj0OVQ38Af +a7ESRwzixmAf/XWfEqP5oO/Sd4ZE0L43AZpybZbHu44UbANfjeipJhAR/vyrXx3T +aMRWkMdz9b1nPtgHhOFJlyfCqBCXefvrNQnJirw4aUWPd8N7ocec1M1DO5bC2C/w +se2g6dpuehl7Wo3wxJ12HoukbsXbB/Q8sgwrV/noT+F9y2SoWDdTgmijAiaqsf/A +4jGkQXv/60fL10H11B2A4UXS2jJg7yHxTCLVkKSoIykYu5oLz2qGieUVRXJRerXf +inoU4m3mM2qc6DO+a6REVUmrs95bVwzjIMw54gxu+8044dQwI7evz2bZ/A1v4Cl7 +DF1HEyPW/kA/vtsS9C1G2R7/dAMO8/YCl442spzlJsp4AXEJo+D1pOQfbFNbYUxs +-----END CERTIFICATE----- diff --git a/certs/slhdsa/gen-slhdsa-entity-cert.c b/certs/slhdsa/gen-slhdsa-entity-cert.c new file mode 100644 index 0000000000..dc26396384 --- /dev/null +++ b/certs/slhdsa/gen-slhdsa-entity-cert.c @@ -0,0 +1,144 @@ +/* gen-slhdsa-entity-cert.c + * + * Regenerate the self-signed SLH-DSA-SHAKE-128f entity certificate and key + * used by tests/test-tls13-slhdsa-entity.conf. + * + * OpenSSL < 3.5 cannot emit SLH-DSA certificates, so wolfSSL's own certificate + * generation is used here instead. SLH-DSA-SHAKE-128f is chosen because its + * ~17KB signature makes the TLS 1.3 CertificateVerify message exceed a single + * record, exercising fragmented CertificateVerify send + reassembly. + * + * Build (from the wolfSSL source root, after configuring with SLH-DSA + cert + * generation, e.g. ./configure --enable-slhdsa --enable-certgen --enable-keygen + * --enable-certext): + * + * gcc certs/slhdsa/gen-slhdsa-entity-cert.c -I. \ + * -L./src/.libs -lwolfssl -lm -o gen-slhdsa-entity-cert + * LD_LIBRARY_PATH=./src/.libs ./gen-slhdsa-entity-cert + * + * The committed PEM/DER fixtures under this directory are authoritative; this + * program is for renewal only. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define GEN_BUF_SZ 70000 + +static int save(const char* path, const byte* buf, int sz) +{ + FILE* f = fopen(path, "wb"); + if (f == NULL) { + printf("Unable to open %s for writing\n", path); + return -1; + } + fwrite(buf, 1, (size_t)sz, f); + fclose(f); + return 0; +} + +int main(void) +{ + WC_RNG rng; + SlhDsaKey key; + Cert cert; + byte* der = NULL; + byte* keyder = NULL; + byte* pem = NULL; + int ret, certSz, keySz, pemSz; + + wolfCrypt_Init(); + + if (wc_InitRng(&rng) != 0) { + printf("RNG init failed\n"); + return 1; + } + if ((ret = wc_SlhDsaKey_Init(&key, SLHDSA_SHAKE128F, NULL, + INVALID_DEVID)) != 0) { + printf("wc_SlhDsaKey_Init failed: %d\n", ret); + return 1; + } + if ((ret = wc_SlhDsaKey_MakeKey(&key, &rng)) != 0) { + printf("wc_SlhDsaKey_MakeKey failed: %d\n", ret); + return 1; + } + + der = (byte*)malloc(GEN_BUF_SZ); + keyder = (byte*)malloc(GEN_BUF_SZ); + pem = (byte*)malloc(GEN_BUF_SZ); + if (der == NULL || keyder == NULL || pem == NULL) { + printf("Out of memory\n"); + return 1; + } + + /* PKCS#8 private key (DER + PEM). */ + keySz = wc_SlhDsaKey_PrivateKeyToDer(&key, keyder, GEN_BUF_SZ); + if (keySz < 0) { + printf("wc_SlhDsaKey_PrivateKeyToDer failed: %d\n", keySz); + return 1; + } + save("certs/slhdsa/entity-slhdsa-shake-128f-priv.der", keyder, keySz); + + /* Self-signed certificate (DER + PEM). */ + wc_InitCert(&cert); + strncpy(cert.subject.country, "US", CTC_NAME_SIZE); + strncpy(cert.subject.state, "Montana", CTC_NAME_SIZE); + strncpy(cert.subject.locality, "Bozeman", CTC_NAME_SIZE); + strncpy(cert.subject.org, "wolfSSL_SLH-DSA", CTC_NAME_SIZE); + strncpy(cert.subject.unit, "Entity-SLHDSA-shake-128f", CTC_NAME_SIZE); + strncpy(cert.subject.commonName, "www.wolfssl.com", CTC_NAME_SIZE); + strncpy(cert.subject.email, "facts@wolfssl.com", CTC_NAME_SIZE); + cert.daysValid = 1000; + cert.selfSigned = 1; + cert.isCA = 1; /* self-signed, also usable as its own trust anchor */ + cert.sigType = CTC_SLH_DSA_SHAKE_128F; + + if ((ret = wc_SetSubjectKeyIdFromPublicKey_ex(&cert, + SLH_DSA_SHAKE_128F_TYPE, &key)) < 0) { + printf("wc_SetSubjectKeyIdFromPublicKey_ex failed: %d\n", ret); + return 1; + } + if ((ret = wc_SetAuthKeyIdFromPublicKey_ex(&cert, + SLH_DSA_SHAKE_128F_TYPE, &key)) < 0) { + printf("wc_SetAuthKeyIdFromPublicKey_ex failed: %d\n", ret); + return 1; + } + + ret = wc_MakeCert_ex(&cert, der, GEN_BUF_SZ, SLH_DSA_SHAKE_128F_TYPE, + &key, &rng); + if (ret < 0) { + printf("wc_MakeCert_ex failed: %d\n", ret); + return 1; + } + ret = wc_SignCert_ex(cert.bodySz, cert.sigType, der, GEN_BUF_SZ, + SLH_DSA_SHAKE_128F_TYPE, &key, &rng); + if (ret < 0) { + printf("wc_SignCert_ex failed: %d\n", ret); + return 1; + } + certSz = ret; + save("certs/slhdsa/entity-slhdsa-shake-128f.der", der, certSz); + + pemSz = wc_DerToPem(der, certSz, pem, GEN_BUF_SZ, CERT_TYPE); + if (pemSz > 0) + save("certs/slhdsa/entity-slhdsa-shake-128f.pem", pem, pemSz); + pemSz = wc_DerToPem(keyder, keySz, pem, GEN_BUF_SZ, PKCS8_PRIVATEKEY_TYPE); + if (pemSz > 0) + save("certs/slhdsa/entity-slhdsa-shake-128f-priv.pem", pem, pemSz); + + printf("Generated SLH-DSA-SHAKE-128f entity cert (certSz=%d keySz=%d)\n", + certSz, keySz); + + free(der); + free(keyder); + free(pem); + wc_SlhDsaKey_Free(&key); + wc_FreeRng(&rng); + wolfCrypt_Cleanup(); + return 0; +} diff --git a/certs/slhdsa/include.am b/certs/slhdsa/include.am index 2d9a1bdd37..cafc997805 100644 --- a/certs/slhdsa/include.am +++ b/certs/slhdsa/include.am @@ -33,4 +33,9 @@ EXTRA_DIST += \ certs/slhdsa/server-mldsa44-sha2.pem \ certs/slhdsa/server-mldsa44-sha2.der \ certs/slhdsa/client-mldsa44-sha2.pem \ - certs/slhdsa/client-mldsa44-sha2.der + certs/slhdsa/client-mldsa44-sha2.der \ + certs/slhdsa/gen-slhdsa-entity-cert.c \ + certs/slhdsa/entity-slhdsa-shake-128f-priv.pem \ + certs/slhdsa/entity-slhdsa-shake-128f-priv.der \ + certs/slhdsa/entity-slhdsa-shake-128f.pem \ + certs/slhdsa/entity-slhdsa-shake-128f.der diff --git a/scripts/include.am b/scripts/include.am index 38dc071466..540d06d616 100644 --- a/scripts/include.am +++ b/scripts/include.am @@ -98,6 +98,7 @@ if !BUILD_IPV6 dist_noinst_SCRIPTS+= scripts/external.test dist_noinst_SCRIPTS+= scripts/google.test dist_noinst_SCRIPTS+= scripts/openssl.test +EXTRA_DIST+= scripts/slhdsa-oqs-interop.sh if BUILD_OCSP dist_noinst_SCRIPTS+= scripts/ocsp.test diff --git a/scripts/slhdsa-oqs-interop.sh b/scripts/slhdsa-oqs-interop.sh new file mode 100755 index 0000000000..8808debc1d --- /dev/null +++ b/scripts/slhdsa-oqs-interop.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# +# SLH-DSA TLS 1.3 interoperability test: wolfSSL <-> OpenSSL + oqs-provider. +# +# Exercises the SLH-DSA handshake signature (CertificateVerify) in both +# directions against an independent implementation, per draft-reddy-tls-slhdsa +# (TLS SignatureScheme code points 0x0911-0x091C). The larger 'f' parameter +# sets produce >16KB signatures, so this also exercises fragmented +# CertificateVerify send + reassembly across the two stacks. +# +# OpenSSL < 3.5 has no native SLH-DSA and OpenSSL >= 3.5 does not expose +# SLH-DSA as a TLS signature scheme, while oqs-provider drops SLH-DSA when the +# underlying OpenSSL provides it natively. OpenSSL 3.4 (provider TLS-sigalg +# support, no native SLH-DSA) is therefore the reference peer here, with +# liboqs + oqs-provider supplying the SLH-DSA algorithms and the 0x0911 code +# points that wolfSSL matches. +# +# Prerequisites (built from source, see build steps below): +# - liboqs (main) built with OQS_USE_OPENSSL=OFF +# - oqs-provider (main) built against OpenSSL 3.4 +# - OpenSSL 3.4 (shared) +# - wolfSSL configured with: --enable-tls13 --enable-slhdsa [CFLAGS=-DWOLFSSL_SLHDSA_SHA2] +# +# Build steps (run once; OQS_ROOT defaults to $HOME/oqs-interop): +# git clone https://github.com/open-quantum-safe/liboqs +# cmake -S liboqs -B liboqs/build -GNinja -DCMAKE_INSTALL_PREFIX=$OQS_ROOT/local \ +# -DBUILD_SHARED_LIBS=ON -DOQS_BUILD_ONLY_LIB=ON -DOQS_USE_OPENSSL=OFF +# ninja -C liboqs/build install +# git clone -b openssl-3.4 https://github.com/openssl/openssl $OQS_ROOT/openssl34 +# (cd $OQS_ROOT/openssl34 && ./Configure --prefix=$OQS_ROOT/ossl34 shared no-docs \ +# -Wl,-rpath,$OQS_ROOT/ossl34/lib64 && make -j"$(nproc)" && make install_sw) +# git clone https://github.com/open-quantum-safe/oqs-provider +# cmake -S oqs-provider -B oqs-provider/build -GNinja \ +# -DOPENSSL_ROOT_DIR=$OQS_ROOT/ossl34 \ +# -Dliboqs_DIR=$OQS_ROOT/local/lib/cmake/liboqs +# ninja -C oqs-provider/build oqsprovider +# +# Usage: run from the wolfSSL source root after `./configure ... && make`. +# OQS_ROOT=/path/to/oqs-interop ./scripts/slhdsa-oqs-interop.sh + +set -u + +OQS_ROOT="${OQS_ROOT:-$HOME/oqs-interop}" +OSSL_DIR="${OSSL_DIR:-$OQS_ROOT/ossl34}" +OQS_MODULES="${OQS_MODULES:-$OQS_ROOT/oqs-provider/build/lib}" +OQS_LIBS="${OQS_LIBS:-$OQS_ROOT/local/lib}" +OSSL="$OSSL_DIR/bin/openssl" +PORT="${PORT:-11801}" +WOLF_SRV=./examples/server/server +WOLF_CLI=./examples/client/client +CERTDIR=certs/slhdsa +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"; pkill -f "s_server -tls1_3 -accept $PORT" 2>/dev/null' EXIT + +# Environment for driving the OpenSSL side with oqs-provider. +osslenv() { + env LD_LIBRARY_PATH="$OSSL_DIR/lib64:$OQS_LIBS" \ + OPENSSL_MODULES="$OQS_MODULES" \ + OPENSSL_CONF="${OPENSSL_CONF:-/usr/lib/ssl/openssl.cnf}" "$@" +} +# Environment for driving the wolfSSL examples. +wolfenv() { env LD_LIBRARY_PATH=./src/.libs "$@"; } + +fail=0 +pass() { echo "PASS: $1"; } +fatal() { echo "FAIL: $1"; fail=1; } + +[ -x "$OSSL" ] || { echo "OpenSSL 3.4 not found at $OSSL (see build steps)"; exit 1; } +[ -f "$OQS_MODULES/oqsprovider.so" ] || { echo "oqsprovider.so not found (see build steps)"; exit 1; } + +echo "wolfSSL: $(cat wolfssl/version.h 2>/dev/null | grep LIBWOLFSSL_VERSION_STRING | awk '{print $3}' | tr -d '\"')" +echo "OpenSSL: $(osslenv "$OSSL" version)" +echo "oqs SLH-DSA sig algs: $(osslenv "$OSSL" list -signature-algorithms -provider oqsprovider 2>/dev/null | grep -c slhdsa)" +echo + +# ---- Direction A: wolfSSL server signs, OpenSSL+oqs client verifies ---- +# param=SLH-DSA scheme name (openssl), cert/key = wolfSSL-generated SLH-DSA leaf +dirA() { + local name="$1" cert="$2" key="$3" sigalg="$4" + wolfenv timeout 30 "$WOLF_SRV" -v 4 -c "$cert" -k "$key" -d -p "$PORT" \ + > "$TMP/a_srv.log" 2>&1 & + local sp=$!; sleep 1 + echo | osslenv timeout 25 "$OSSL" s_client -tls1_3 -connect 127.0.0.1:"$PORT" \ + -CAfile "$cert" -provider oqsprovider -provider default \ + -sigalgs "$sigalg" -verify_return_error > "$TMP/a_cli.log" 2>&1 + wait $sp 2>/dev/null + if grep -q "Peer signature type: $sigalg" "$TMP/a_cli.log" \ + && grep -q "Verify return code: 0 (ok)" "$TMP/a_cli.log"; then + pass "A [$name] wolfSSL signs -> OpenSSL+oqs verifies" + else + fatal "A [$name] wolfSSL signs -> OpenSSL+oqs verifies" + tail -3 "$TMP/a_cli.log"; tail -2 "$TMP/a_srv.log" + fi +} + +# ---- Direction B: OpenSSL+oqs server signs, wolfSSL client verifies ---- +dirB() { + local name="$1" sigalg="$2" + # OpenSSL generates its own SLH-DSA leaf so it can load the private key. + osslenv "$OSSL" req -x509 -new -newkey "$sigalg" \ + -provider oqsprovider -provider default -nodes \ + -keyout "$TMP/o_key.pem" -out "$TMP/o_cert.pem" \ + -subj "/CN=openssl-$sigalg" -days 365 > "$TMP/b_gen.log" 2>&1 + osslenv timeout 30 "$OSSL" s_server -tls1_3 -accept "$PORT" \ + -provider oqsprovider -provider default \ + -cert "$TMP/o_cert.pem" -key "$TMP/o_key.pem" -naccept 1 -www \ + > "$TMP/b_srv.log" 2>&1 & + local sp=$!; sleep 1 + wolfenv timeout 25 "$WOLF_CLI" -v 4 -A "$TMP/o_cert.pem" -h 127.0.0.1 -p "$PORT" -g \ + > "$TMP/b_cli.log" 2>&1 + wait $sp 2>/dev/null + if grep -q "Doing SLH-DSA peer cert verify" "$TMP/b_cli.log" \ + && grep -q "DoTls13CertificateVerify, return 0" "$TMP/b_cli.log"; then + pass "B [$name] OpenSSL+oqs signs -> wolfSSL verifies" + else + fatal "B [$name] OpenSSL+oqs signs -> wolfSSL verifies" + tail -4 "$TMP/b_cli.log" + fi +} + +# SHAKE-128f: ~17KB signature -> fragmented CertificateVerify (both directions). +if [ -f "$CERTDIR/entity-slhdsa-shake-128f.pem" ]; then + dirA "SHAKE-128f (fragmented)" \ + "$CERTDIR/entity-slhdsa-shake-128f.pem" \ + "$CERTDIR/entity-slhdsa-shake-128f-priv.pem" slhdsashake128f +fi +dirB "SHAKE-128f (fragmented)" slhdsashake128f + +# SHA2-128s: single-record signature; exercises the SHA2 parameter family. +if [ -f "$CERTDIR/root-slhdsa-sha2-128s.pem" ]; then + dirA "SHA2-128s" \ + "$CERTDIR/root-slhdsa-sha2-128s.pem" \ + "$CERTDIR/root-slhdsa-sha2-128s-priv.pem" slhdsasha2128s +fi + +echo +[ $fail -eq 0 ] && echo "All SLH-DSA interop scenarios passed." || echo "Some scenarios FAILED." +exit $fail diff --git a/src/internal.c b/src/internal.c index 8c73463766..3a4ee6e5fa 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2356,6 +2356,11 @@ int InitSSL_Side(WOLFSSL* ssl, word16 side) ssl->options.haveMlDsaSig = 1; /* always on client side */ } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + if (ssl->options.side == WOLFSSL_CLIENT_END) { + ssl->options.haveSlhDsaSig = 1; /* always on client side */ + } +#endif /* WOLFSSL_HAVE_SLHDSA */ #if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) if (ssl->options.side == WOLFSSL_CLIENT_END) { @@ -2774,6 +2779,11 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) ctx->haveMlDsaSig = 1; /* always on client side */ /* server can turn on by loading key */ #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + if (method->side == WOLFSSL_CLIENT_END) + ctx->haveSlhDsaSig = 1; /* always on client side */ + /* server can turn on by loading key */ +#endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_ECC if (method->side == WOLFSSL_CLIENT_END) { ctx->haveECDSAsig = 1; /* always on client side */ @@ -3475,6 +3485,70 @@ static WC_INLINE void AddSuiteHashSigAlgo(byte* hashSigAlgo, byte macAlgo, } else #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + #ifdef WOLFSSL_SLHDSA_SHA2 + if (sigAlgo == slhdsa_sha2_128s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_128S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_128f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_128F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_192s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_192S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_192f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_192F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_256s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_256S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_256f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_256F_SA_MINOR); + } + else + #endif /* WOLFSSL_SLHDSA_SHA2 */ + if (sigAlgo == slhdsa_shake_128s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_128S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_128f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_128F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_192s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_192S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_192f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_192F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_256s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_256S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_256f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_256F_SA_MINOR); + } + else + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef WC_RSA_PSS if (sigAlgo == rsa_pss_sa_algo) { /* RSA PSS is sig then mac */ @@ -3589,6 +3663,60 @@ void InitSuitesHashSigAlgo(byte* hashSigAlgo, int haveSig, int tls1_2, keySz, &idx); } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + /* Only advertise the parameter sets that are actually compiled in, so we + * never offer a scheme we cannot sign or verify. */ + if (haveSig & SIG_SLHDSA) { + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_128s_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128F) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_128f_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192S) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_192s_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192F) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_192f_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256S) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_256s_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256F) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_256f_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_128S + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_128s_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_128F + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_128f_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_192S + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_192s_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_192F + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_192f_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_256S + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_256s_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_256F + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_256f_sa_algo, + keySz, &idx); + #endif + } +#endif /* WOLFSSL_HAVE_SLHDSA */ if (haveSig & SIG_RSA) { #ifdef WC_RSA_PSS if (tls1_2) { @@ -4750,6 +4878,84 @@ void InitSuites(Suites* suites, ProtocolVersion pv, int keySz, word16 haveRSA, * hashalgo The hash algorithm. * hsType The signature type. */ +#ifdef WOLFSSL_HAVE_SLHDSA +/* Map a TLS SLH-DSA signature-scheme minor byte (draft-reddy-tls-slhdsa) to + * the internal sa_algo. Returns invalid_sa_algo if unrecognized or the + * parameter family is not compiled in. */ +byte SlhDsaSigMinorToType(byte minor) +{ + switch (minor) { + #ifdef WOLFSSL_SLHDSA_SHA2 + case SLHDSA_SHA2_128S_SA_MINOR: return slhdsa_sha2_128s_sa_algo; + case SLHDSA_SHA2_128F_SA_MINOR: return slhdsa_sha2_128f_sa_algo; + case SLHDSA_SHA2_192S_SA_MINOR: return slhdsa_sha2_192s_sa_algo; + case SLHDSA_SHA2_192F_SA_MINOR: return slhdsa_sha2_192f_sa_algo; + case SLHDSA_SHA2_256S_SA_MINOR: return slhdsa_sha2_256s_sa_algo; + case SLHDSA_SHA2_256F_SA_MINOR: return slhdsa_sha2_256f_sa_algo; + #endif + case SLHDSA_SHAKE_128S_SA_MINOR: return slhdsa_shake_128s_sa_algo; + case SLHDSA_SHAKE_128F_SA_MINOR: return slhdsa_shake_128f_sa_algo; + case SLHDSA_SHAKE_192S_SA_MINOR: return slhdsa_shake_192s_sa_algo; + case SLHDSA_SHAKE_192F_SA_MINOR: return slhdsa_shake_192f_sa_algo; + case SLHDSA_SHAKE_256S_SA_MINOR: return slhdsa_shake_256s_sa_algo; + case SLHDSA_SHAKE_256F_SA_MINOR: return slhdsa_shake_256f_sa_algo; + default: return (byte)invalid_sa_algo; + } +} + +/* Map an internal SLH-DSA sa_algo to its enum SlhDsaParam value. Returns -1 + * if hsType is not an SLH-DSA scheme. */ +int SlhDsaTypeToParam(byte hsType) +{ + switch (hsType) { + #ifdef WOLFSSL_SLHDSA_SHA2 + case slhdsa_sha2_128s_sa_algo: return SLHDSA_SHA2_128S; + case slhdsa_sha2_128f_sa_algo: return SLHDSA_SHA2_128F; + case slhdsa_sha2_192s_sa_algo: return SLHDSA_SHA2_192S; + case slhdsa_sha2_192f_sa_algo: return SLHDSA_SHA2_192F; + case slhdsa_sha2_256s_sa_algo: return SLHDSA_SHA2_256S; + case slhdsa_sha2_256f_sa_algo: return SLHDSA_SHA2_256F; + #endif + case slhdsa_shake_128s_sa_algo: return SLHDSA_SHAKE128S; + case slhdsa_shake_128f_sa_algo: return SLHDSA_SHAKE128F; + case slhdsa_shake_192s_sa_algo: return SLHDSA_SHAKE192S; + case slhdsa_shake_192f_sa_algo: return SLHDSA_SHAKE192F; + case slhdsa_shake_256s_sa_algo: return SLHDSA_SHAKE256S; + case slhdsa_shake_256f_sa_algo: return SLHDSA_SHAKE256F; + default: return -1; + } +} + +/* Is hsType any of the SLH-DSA signature schemes? */ +int IsSlhDsaSigAlgo(byte hsType) +{ + return SlhDsaTypeToParam(hsType) != -1; +} + +/* Map an enum SlhDsaParam value to its internal SLH-DSA sa_algo. Returns + * invalid_sa_algo if the parameter set is not one of the TLS schemes. */ +byte SlhDsaParamToType(int param) +{ + switch (param) { + #ifdef WOLFSSL_SLHDSA_SHA2 + case SLHDSA_SHA2_128S: return slhdsa_sha2_128s_sa_algo; + case SLHDSA_SHA2_128F: return slhdsa_sha2_128f_sa_algo; + case SLHDSA_SHA2_192S: return slhdsa_sha2_192s_sa_algo; + case SLHDSA_SHA2_192F: return slhdsa_sha2_192f_sa_algo; + case SLHDSA_SHA2_256S: return slhdsa_sha2_256s_sa_algo; + case SLHDSA_SHA2_256F: return slhdsa_sha2_256f_sa_algo; + #endif + case SLHDSA_SHAKE128S: return slhdsa_shake_128s_sa_algo; + case SLHDSA_SHAKE128F: return slhdsa_shake_128f_sa_algo; + case SLHDSA_SHAKE192S: return slhdsa_shake_192s_sa_algo; + case SLHDSA_SHAKE192F: return slhdsa_shake_192f_sa_algo; + case SLHDSA_SHAKE256S: return slhdsa_shake_256s_sa_algo; + case SLHDSA_SHAKE256F: return slhdsa_shake_256f_sa_algo; + default: return (byte)invalid_sa_algo; + } +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + void DecodeSigAlg(const byte* input, byte* hashAlgo, byte* hsType) { *hsType = invalid_sa_algo; @@ -4827,22 +5033,39 @@ void DecodeSigAlg(const byte* input, byte* hashAlgo, byte* hsType) } break; #endif /* HAVE_FALCON */ - #ifdef WOLFSSL_HAVE_MLDSA + #if defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA) + /* ML-DSA and SLH-DSA share the same major byte (0x09); their minor + * bytes are disjoint (ML-DSA 0x04-0x06, SLH-DSA 0x11-0x1C). */ case MLDSA_SA_MAJOR: + #ifdef WOLFSSL_HAVE_MLDSA if (input[1] == MLDSA_44_SA_MINOR) { *hsType = mldsa_44_sa_algo; *hashAlgo = sha256_mac; + break; } else if (input[1] == MLDSA_65_SA_MINOR) { *hsType = mldsa_65_sa_algo; *hashAlgo = sha384_mac; + break; } else if (input[1] == MLDSA_87_SA_MINOR) { *hsType = mldsa_87_sa_algo; *hashAlgo = sha512_mac; + break; + } + #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + { + byte slhType = SlhDsaSigMinorToType(input[1]); + if (slhType != (byte)invalid_sa_algo) { + *hsType = slhType; + /* Hash performed as part of sign/verify operation. */ + *hashAlgo = sha512_mac; + } } + #endif /* WOLFSSL_HAVE_SLHDSA */ break; - #endif /* WOLFSSL_HAVE_MLDSA */ + #endif /* WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ default: *hashAlgo = input[0]; *hsType = input[1]; @@ -7225,6 +7448,7 @@ int SetSSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) ssl->options.haveStaticECC = ctx->haveStaticECC; ssl->options.haveFalconSig = ctx->haveFalconSig; ssl->options.haveMlDsaSig = ctx->haveMlDsaSig; + ssl->options.haveSlhDsaSig = ctx->haveSlhDsaSig; #ifndef NO_PSK ssl->options.havePSK = (word16)(ctx->havePSK); @@ -8414,6 +8638,11 @@ void FreeKey(WOLFSSL* ssl, int type, void** pKey) wc_MlDsaKey_Free((wc_MlDsaKey*)*pKey); break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + wc_SlhDsaKey_Free((SlhDsaKey*)*pKey); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifndef NO_DH case DYNAMIC_TYPE_DH: #if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \ @@ -8522,6 +8751,11 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey) sz = sizeof(wc_MlDsaKey); break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + sz = sizeof(SlhDsaKey); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifndef NO_DH case DYNAMIC_TYPE_DH: sz = sizeof(DhKey); @@ -8640,6 +8874,14 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey) ret = 0; break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + /* SLH-DSA requires the parameter set at init; use an always-present + * placeholder here and re-init with the real one once known. */ + ret = wc_SlhDsaKey_Init((SlhDsaKey*)*pKey, WC_SLHDSA_DEFAULT_PARAM, + ssl->heap, ssl->devId); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_CURVE448 case DYNAMIC_TYPE_CURVE448: wc_curve448_init((curve448_key*)*pKey); @@ -8686,7 +8928,8 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey) #if (!defined(NO_CERTS) || !defined(WOLFSSL_NO_TLS12)) && \ (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ defined(HAVE_CURVE25519) || defined(HAVE_ED448) || \ - defined(HAVE_CURVE448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA)) + defined(HAVE_CURVE448) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) static int ReuseKey(WOLFSSL* ssl, int type, void* pKey) { int ret = 0; @@ -8744,6 +8987,14 @@ static int ReuseKey(WOLFSSL* ssl, int type, void* pKey) ret = wc_MlDsaKey_Init((wc_MlDsaKey*)pKey, NULL, INVALID_DEVID); break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + wc_SlhDsaKey_Free((SlhDsaKey*)pKey); + /* Re-init with placeholder param; caller re-inits with real one. */ + ret = wc_SlhDsaKey_Init((SlhDsaKey*)pKey, WC_SLHDSA_DEFAULT_PARAM, NULL, + INVALID_DEVID); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifndef NO_DH case DYNAMIC_TYPE_DH: wc_FreeDhKey((DhKey*)pKey); @@ -9096,6 +9347,10 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) FreeKey(ssl, DYNAMIC_TYPE_MLDSA, (void**)&ssl->peerMlDsaKey); ssl->peerMlDsaKeyPresent = 0; #endif +#if defined(WOLFSSL_HAVE_SLHDSA) + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; +#endif #if defined(HAVE_FALCON) FreeKey(ssl, DYNAMIC_TYPE_FALCON, (void**)&ssl->peerFalconKey); ssl->peerFalconKeyPresent = 0; @@ -9382,6 +9637,10 @@ void FreeHandshakeResources(WOLFSSL* ssl) FreeKey(ssl, DYNAMIC_TYPE_MLDSA, (void**)&ssl->peerMlDsaKey); ssl->peerMlDsaKeyPresent = 0; #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; +#endif /* WOLFSSL_HAVE_SLHDSA */ } #ifdef HAVE_ECC @@ -17716,6 +17975,59 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, break; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + int keyRet = 0; + int slhParam = + wc_SlhDsaOidToParam(args->dCert->keyOID); + if (slhParam < 0) { + ret = PEER_KEY_ERROR; + break; + } + + if (ssl->peerSlhDsaKey == NULL) { + /* alloc/init on demand */ + keyRet = AllocKey(ssl, DYNAMIC_TYPE_SLHDSA, + (void**)&ssl->peerSlhDsaKey); + } else if (ssl->peerSlhDsaKeyPresent) { + keyRet = ReuseKey(ssl, DYNAMIC_TYPE_SLHDSA, + ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; + } + + if (keyRet == 0) { + /* AllocKey/ReuseKey used a placeholder parameter + * set; re-init with the certificate's. */ + wc_SlhDsaKey_Free(ssl->peerSlhDsaKey); + keyRet = wc_SlhDsaKey_Init(ssl->peerSlhDsaKey, + slhParam, ssl->heap, ssl->devId); + } + + if (keyRet != 0 || + wc_SlhDsaKey_ImportPublic(ssl->peerSlhDsaKey, + args->dCert->publicKey, + args->dCert->pubKeySize) + != 0) { + ret = PEER_KEY_ERROR; + } + else { + ssl->peerSlhDsaKeyPresent = 1; + } + break; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ default: break; } @@ -29958,6 +30270,9 @@ static int ParseCipherList(Suites* suites, #ifdef WOLFSSL_HAVE_MLDSA haveSig |= SIG_MLDSA; #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + haveSig |= SIG_SLHDSA; + #endif /* WOLFSSL_HAVE_SLHDSA */ } else #ifdef BUILD_TLS_SM4_GCM_SM3 @@ -30156,6 +30471,7 @@ int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, int haveECDSAsig = 0; int haveFalconSig = 0; int haveMlDsaSig = 0; + int haveSlhDsaSig = 0; int haveAnon = 0; int tls1_3 = 0; @@ -30230,6 +30546,9 @@ int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, #ifdef WOLFSSL_HAVE_MLDSA haveMlDsaSig = 1; #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + haveSlhDsaSig = 1; + #endif /* WOLFSSL_HAVE_SLHDSA */ } else #endif /* WOLFSSL_TLS13 */ @@ -30268,6 +30587,7 @@ int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, haveSig |= haveRSAsig ? SIG_RSA : 0; haveSig |= haveFalconSig ? SIG_FALCON : 0; haveSig |= haveMlDsaSig ? SIG_MLDSA : 0; + haveSig |= haveSlhDsaSig ? SIG_SLHDSA : 0; haveSig |= haveAnon ? SIG_ANON : 0; InitSuitesHashSigAlgo(suites->hashSigAlgo, haveSig, 1, tls1_3, keySz, &suites->hashSigAlgoSz); @@ -31689,6 +32009,52 @@ static int DecodePrivateKey_ex(WOLFSSL *ssl, byte keyType, const DerBuffer* key, } } #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + #if !defined(NO_RSA) || defined(HAVE_ECC) + FreeKey(ssl, (int)*hsType, hsKey); + #endif + + if (IsSlhDsaSigAlgo(keyType)) { + int slhParam = SlhDsaTypeToParam(keyType); + + *hsType = DYNAMIC_TYPE_SLHDSA; + ret = AllocKey(ssl, (int)*hsType, hsKey); + if (ret != 0) { + goto exit_dpk; + } + + /* AllocKey initialised the key with a placeholder parameter set; + * re-init with the parameter set matching the loaded key. */ + wc_SlhDsaKey_Free((SlhDsaKey*)*hsKey); + ret = wc_SlhDsaKey_Init((SlhDsaKey*)*hsKey, slhParam, ssl->heap, + ssl->devId); + if (ret != 0) { + goto exit_dpk; + } + + WOLFSSL_MSG("Trying SLH-DSA private key"); + + /* Set start of data to beginning of buffer. */ + idx = 0; + PRIVATE_KEY_UNLOCK(); + ret = wc_SlhDsaKey_PrivateKeyDecode(key->buffer, &idx, + (SlhDsaKey*)*hsKey, key->length); + PRIVATE_KEY_LOCK(); + if (ret == 0) { + int slhSigSz; + WOLFSSL_MSG("Using SLH-DSA private key"); + + /* Return the maximum signature length. */ + slhSigSz = wc_SlhDsaKey_SigSize((SlhDsaKey*)*hsKey); + if (slhSigSz <= 0) { + ERROR_OUT(ALGO_ID_E, exit_dpk); + } + *sigLen = (word32)slhSigSz; + + goto exit_dpk; + } + } +#endif /* WOLFSSL_HAVE_SLHDSA */ (void)idx; (void)keySzDecoded; diff --git a/src/ssl_load.c b/src/ssl_load.c index b63a3b8430..6928e8ac05 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -1045,6 +1045,82 @@ static int ProcessBufferTryDecodeMlDsa(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +/* Try to decode the DER encoding as an SLH-DSA private key. + * + * @param [in] ctx SSL context object. + * @param [in] ssl SSL object. + * @param [in] der DER encoding. + * @param [in, out] keyFormat On in, expected format. 0 means unknown. + * @param [in] heap Dynamic memory allocation hint. + * @param [out] keyType Type of key (an slhdsa_*_sa_algo). + * @param [out] keySize Size of the private key. + * @return 0 on success or when not an SLH-DSA key (with keyFormat 0). + */ +static int ProcessBufferTryDecodeSlhDsa(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + DerBuffer* der, int* keyFormat, void* heap, byte* keyType, int* keySize) +{ + int ret; + word32 idx; + SlhDsaKey* key; + int keyTypeTemp = 0; + int keySizeTemp = 0; + + (void)ctx; + (void)ssl; + + /* Allocate an SLH-DSA key to parse into. */ + key = (SlhDsaKey*)XMALLOC(sizeof(SlhDsaKey), heap, DYNAMIC_TYPE_SLHDSA); + if (key == NULL) { + return MEMORY_E; + } + + /* Initialise with an always-present placeholder parameter set; + * wc_SlhDsaKey_PrivateKeyDecode selects the real one from the key's + * algorithm OID. */ + ret = wc_SlhDsaKey_Init(key, WC_SLHDSA_DEFAULT_PARAM, heap, INVALID_DEVID); + if (ret == 0) { + idx = 0; + PRIVATE_KEY_UNLOCK(); + ret = wc_SlhDsaKey_PrivateKeyDecode(der->buffer, &idx, key, + der->length); + PRIVATE_KEY_LOCK(); + if (ret == 0) { + int param = (int)key->params->param; + + keyTypeTemp = SlhDsaParamToType(param); + if (keyTypeTemp == (byte)invalid_sa_algo) { + ret = ALGO_ID_E; + } + else { + keySizeTemp = wc_SlhDsaKey_PrivateSize(key); + if (keySizeTemp <= 0) { + ret = ALGO_ID_E; + } + } + + if (ret == 0) { + *keyFormat = wc_SlhDsaParamToOid((enum SlhDsaParam)param); + *keyType = (byte)keyTypeTemp; + *keySize = keySizeTemp; + } + } + else if (*keyFormat == 0) { + WOLFSSL_MSG("Not an SLH-DSA key"); + /* Unknown format wasn't SLH-DSA, so keep trying other formats. */ + ret = 0; + } + + /* Free dynamically allocated data in key. */ + wc_SlhDsaKey_Free(key); + } + + /* Dispose of allocated key. */ + XFREE(key, heap, DYNAMIC_TYPE_SLHDSA); + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + /* Try to decode DER data is a known private key. * * Checks size meets minimum for key type. @@ -1188,6 +1264,27 @@ static int ProcessBufferTryDecode(WOLFSSL_CTX* ctx, WOLFSSL* ssl, matchAnyKey = 1; } #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + /* Try SLH-DSA if key format is an SLH-DSA key OID or yet unknown. */ + if ((ret == 0) && + ((*keyFormat == 0) || + (*keyFormat == SLH_DSA_SHA2_128Sk) || + (*keyFormat == SLH_DSA_SHA2_128Fk) || + (*keyFormat == SLH_DSA_SHA2_192Sk) || + (*keyFormat == SLH_DSA_SHA2_192Fk) || + (*keyFormat == SLH_DSA_SHA2_256Sk) || + (*keyFormat == SLH_DSA_SHA2_256Fk) || + (*keyFormat == SLH_DSA_SHAKE_128Sk) || + (*keyFormat == SLH_DSA_SHAKE_128Fk) || + (*keyFormat == SLH_DSA_SHAKE_192Sk) || + (*keyFormat == SLH_DSA_SHAKE_192Fk) || + (*keyFormat == SLH_DSA_SHAKE_256Sk) || + (*keyFormat == SLH_DSA_SHAKE_256Fk))) { + ret = ProcessBufferTryDecodeSlhDsa(ctx, ssl, der, keyFormat, heap, + keyType, keySz); + matchAnyKey = 1; + } +#endif /* WOLFSSL_HAVE_SLHDSA */ /* Check we know the format. */ if ((ret == 0) && @@ -1513,6 +1610,27 @@ static void wolfssl_set_have_from_key_oid(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } break; #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + if (ssl != NULL) { + ssl->options.haveSlhDsaSig = 1; + } + else { + ctx->haveSlhDsaSig = 1; + } + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ default: WOLFSSL_MSG("Cert key not supported"); break; @@ -1535,6 +1653,9 @@ static void ProcessBufferCertSetHave(WOLFSSL_CTX* ctx, WOLFSSL* ssl, ssl->options.haveECDSAsig = 0; ssl->options.haveFalconSig = 0; ssl->options.haveMlDsaSig = 0; + #ifdef WOLFSSL_HAVE_SLHDSA + ssl->options.haveSlhDsaSig = 0; + #endif } /* Set which signature we have based on the type in the cert. */ @@ -1589,6 +1710,28 @@ static void ProcessBufferCertSetHave(WOLFSSL_CTX* ctx, WOLFSSL* ssl, ctx->haveMlDsaSig = 1; } break; + #endif + #ifdef WOLFSSL_HAVE_SLHDSA + case CTC_SLH_DSA_SHA2_128S: + case CTC_SLH_DSA_SHA2_128F: + case CTC_SLH_DSA_SHA2_192S: + case CTC_SLH_DSA_SHA2_192F: + case CTC_SLH_DSA_SHA2_256S: + case CTC_SLH_DSA_SHA2_256F: + case CTC_SLH_DSA_SHAKE_128S: + case CTC_SLH_DSA_SHAKE_128F: + case CTC_SLH_DSA_SHAKE_192S: + case CTC_SLH_DSA_SHAKE_192F: + case CTC_SLH_DSA_SHAKE_256S: + case CTC_SLH_DSA_SHAKE_256F: + WOLFSSL_MSG("SLH-DSA cert signature"); + if (ssl) { + ssl->options.haveSlhDsaSig = 1; + } + else if (ctx) { + ctx->haveSlhDsaSig = 1; + } + break; #endif default: WOLFSSL_MSG("Cert signature not supported"); @@ -1596,9 +1739,11 @@ static void ProcessBufferCertSetHave(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } #if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) || \ - defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || !defined(NO_RSA) + defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) || !defined(NO_RSA) #if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) || \ - defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) /* Set the private key curve OID. */ if (ssl != NULL) { ssl->pkCurveOID = cert->pkCurveOID; @@ -1826,6 +1971,33 @@ static int ProcessBufferCertPublicKey(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + int slhParam = wc_SlhDsaOidToParam((int)cert->keyOID); + if (slhParam < 0) { + ret = ALGO_ID_E; + break; + } + keyType = SlhDsaParamToType(slhParam); + /* SLH-DSA has fixed, small public keys and no minimum-size + * option; store the public key size for reference. */ + keySz = wc_SlhDsaKey_PublicSizeFromParam( + (enum SlhDsaParam)slhParam); + break; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ default: WOLFSSL_MSG("No key size check done on public key in certificate"); @@ -2036,6 +2208,31 @@ static int ProcessBufferCertAltPublicKey(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + int slhParam = wc_SlhDsaOidToParam((int)cert->keyOID); + if (slhParam < 0) { + ret = ALGO_ID_E; + break; + } + keyType = SlhDsaParamToType(slhParam); + keySz = wc_SlhDsaKey_PublicSizeFromParam( + (enum SlhDsaParam)slhParam); + break; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ default: /* In this case, there was an OID that we didn't recognize. diff --git a/src/tls13.c b/src/tls13.c index a20b6ec9e2..8f3c5df7c9 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -182,7 +182,8 @@ static const byte #ifndef NO_CERTS #if !defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ - defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) static WC_INLINE int GetMsgHash(WOLFSSL* ssl, byte* hash); @@ -8685,7 +8686,8 @@ static int SendTls13CertificateRequest(WOLFSSL* ssl, byte* reqCtx, #ifndef NO_CERTS #if (!defined(NO_WOLFSSL_SERVER) || !defined(WOLFSSL_NO_CLIENT_AUTH)) && \ (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ - defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA)) + defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA)) /* Encode the signature algorithm into buffer. * * hashalgo The hash algorithm. @@ -8784,6 +8786,58 @@ static WC_INLINE void EncodeSigAlg(const WOLFSSL * ssl, byte hashAlgo, output[1] = MLDSA_87_SA_MINOR; break; #endif +#ifdef WOLFSSL_HAVE_SLHDSA + #ifdef WOLFSSL_SLHDSA_SHA2 + case slhdsa_sha2_128s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_128S_SA_MINOR; + break; + case slhdsa_sha2_128f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_128F_SA_MINOR; + break; + case slhdsa_sha2_192s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_192S_SA_MINOR; + break; + case slhdsa_sha2_192f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_192F_SA_MINOR; + break; + case slhdsa_sha2_256s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_256S_SA_MINOR; + break; + case slhdsa_sha2_256f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_256F_SA_MINOR; + break; + #endif /* WOLFSSL_SLHDSA_SHA2 */ + case slhdsa_shake_128s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_128S_SA_MINOR; + break; + case slhdsa_shake_128f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_128F_SA_MINOR; + break; + case slhdsa_shake_192s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_192S_SA_MINOR; + break; + case slhdsa_shake_192f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_192F_SA_MINOR; + break; + case slhdsa_shake_256s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_256S_SA_MINOR; + break; + case slhdsa_shake_256f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_256F_SA_MINOR; + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ default: break; } @@ -8791,7 +8845,8 @@ static WC_INLINE void EncodeSigAlg(const WOLFSSL * ssl, byte hashAlgo, #endif #if !defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ - defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) #ifdef WOLFSSL_DUAL_ALG_CERTS /* These match up with what the OQS team has defined. */ #define HYBRID_SA_MAJOR 0xFE @@ -8984,27 +9039,40 @@ static WC_INLINE int DecodeTls13SigAlg(byte* input, byte* hashAlgo, ret = INVALID_PARAMETER; break; #endif /* HAVE_FALCON */ -#if defined(WOLFSSL_HAVE_MLDSA) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA) + /* ML-DSA and SLH-DSA share the same major byte (0x09); their minor + * bytes are disjoint (ML-DSA 0x04-0x06, SLH-DSA 0x11-0x1C). */ case MLDSA_SA_MAJOR: + ret = INVALID_PARAMETER; + #if defined(WOLFSSL_HAVE_MLDSA) if (input[1] == MLDSA_44_SA_MINOR) { *hsType = mldsa_44_sa_algo; /* Hash performed as part of sign/verify operation. */ *hashAlgo = sha512_mac; + ret = 0; } else if (input[1] == MLDSA_65_SA_MINOR) { *hsType = mldsa_65_sa_algo; - /* Hash performed as part of sign/verify operation. */ *hashAlgo = sha512_mac; + ret = 0; } else if (input[1] == MLDSA_87_SA_MINOR) { *hsType = mldsa_87_sa_algo; - /* Hash performed as part of sign/verify operation. */ *hashAlgo = sha512_mac; + ret = 0; } - else - { - ret = INVALID_PARAMETER; + #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + if (ret != 0) { + byte slhType = SlhDsaSigMinorToType(input[1]); + if (slhType != invalid_sa_algo) { + *hsType = slhType; + /* Hash performed as part of sign/verify operation. */ + *hashAlgo = sha512_mac; + ret = 0; + } } + #endif /* WOLFSSL_HAVE_SLHDSA */ break; -#endif /* WOLFSSL_HAVE_MLDSA */ +#endif /* WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ default: *hashAlgo = input[0]; *hsType = input[1]; @@ -10208,6 +10276,11 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) args->sigAlgo = ssl->buffers.keyType; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + else if (ssl->hsType == DYNAMIC_TYPE_SLHDSA) { + args->sigAlgo = ssl->buffers.keyType; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ else { ERROR_OUT(ALGO_ID_E, exit_scv); } @@ -10374,6 +10447,15 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) args->sigLen = MLDSA_MAX_SIG_SIZE; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + if (ssl->hsType == DYNAMIC_TYPE_SLHDSA) { + int slhSigSz = wc_SlhDsaKey_SigSize((SlhDsaKey*)ssl->hsKey); + if (slhSigSz <= 0) { + ERROR_OUT(ALGO_ID_E, exit_scv); + } + args->sigLen = (word16)slhSigSz; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef WOLFSSL_DUAL_ALG_CERTS if (ssl->sigSpec != NULL && @@ -10497,6 +10579,15 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) args->length = (word16)args->sigLen; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + if (ssl->hsType == DYNAMIC_TYPE_SLHDSA) { + /* Pure SLH-DSA with empty context, per draft-reddy-tls-slhdsa. */ + ret = wc_SlhDsaKey_Sign((SlhDsaKey*)ssl->hsKey, NULL, 0, + args->sigData, args->sigDataSz, + sigOut, &args->sigLen, ssl->rng); + args->length = (word16)args->sigLen; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ #if !defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) if (ssl->hsType == DYNAMIC_TYPE_RSA) { @@ -10741,39 +10832,143 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) } #endif /* WOLFSSL_DTLS13 */ - /* This message is always encrypted. */ - ret = BuildTls13Message(ssl, args->output, - WC_MAX_CERT_VERIFY_SZ + MAX_MSG_EXTRA, - args->output + RECORD_HEADER_SZ, - args->sendSz - RECORD_HEADER_SZ, handshake, - 1, 0, 0); + { + /* Body of the handshake message: [sigAlg(2) | sigLen(2) | sig]. + * This currently sits at args->verify in the output buffer. */ + word32 msgSz = (word32)args->length + HASH_SIG_SIZE + VERIFY_HEADER; + word32 maxFrag = (word32)wolfssl_local_GetMaxPlaintextSize(ssl); + + if (HANDSHAKE_HEADER_SZ + msgSz <= maxFrag) { + /* Fits in a single record: the common path used by RSA, ECC, + * EdDSA and ML-DSA is left byte-for-byte unchanged. */ + + /* This message is always encrypted. */ + ret = BuildTls13Message(ssl, args->output, + WC_MAX_CERT_VERIFY_SZ + MAX_MSG_EXTRA, + args->output + RECORD_HEADER_SZ, + args->sendSz - RECORD_HEADER_SZ, + handshake, 1, 0, 0); - if (ret < 0) { - goto exit_scv; + if (ret < 0) { + goto exit_scv; + } + else { + args->sendSz = ret; + ret = 0; + } + + #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) + if (ssl->hsInfoOn) + AddPacketName(ssl, "CertificateVerify"); + if (ssl->toInfoOn) { + ret = AddPacketInfo(ssl, "CertificateVerify", handshake, + args->output, args->sendSz, WRITE_PROTO, 0, + ssl->heap); + if (ret != 0) + goto exit_scv; + } + #endif + + ssl->buffers.outputBuffer.length += (word32)args->sendSz; } else { - args->sendSz = ret; - ret = 0; - } + /* Signature does not fit in a single TLS record (e.g. SLH-DSA, + * whose signatures range up to ~50KB). Fragment the handshake + * message across multiple encrypted records. Only the first + * fragment carries the 4-byte handshake header; the transcript + * hash is maintained correctly because BuildTls13Message hashes + * each fragment's plaintext in order. */ + byte* frag; + word32 offset = 0; + + if (maxFrag <= HANDSHAKE_HEADER_SZ) { + ERROR_OUT(BUFFER_E, exit_scv); + } - #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) - if (ssl->hsInfoOn) - AddPacketName(ssl, "CertificateVerify"); - if (ssl->toInfoOn) { - ret = AddPacketInfo(ssl, "CertificateVerify", handshake, - args->output, args->sendSz, WRITE_PROTO, 0, - ssl->heap); + /* Copy the assembled body out of the output buffer before we + * begin overwriting it with per-record data. */ + frag = (byte*)XMALLOC(msgSz, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (frag == NULL) { + ERROR_OUT(MEMORY_E, exit_scv); + } + XMEMCPY(frag, args->verify, msgSz); + + while (offset < msgSz && ret == 0) { + byte* output; + word32 fragSz; + word32 i = RECORD_HEADER_SZ; + int recSz; + int thisSendSz; + + if (offset == 0) { + fragSz = maxFrag - HANDSHAKE_HEADER_SZ; + if (fragSz > msgSz) + fragSz = msgSz; + thisSendSz = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ + + (int)fragSz + MAX_MSG_EXTRA; + } + else { + fragSz = msgSz - offset; + if (fragSz > maxFrag) + fragSz = maxFrag; + thisSendSz = RECORD_HEADER_SZ + (int)fragSz + + MAX_MSG_EXTRA; + } + + if ((ret = CheckAvailableSize(ssl, thisSendSz)) != 0) { + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + goto exit_scv; + } + output = GetOutputBuffer(ssl); + + if (offset == 0) { + AddTls13FragHeaders(output, fragSz, 0, msgSz, + certificate_verify, ssl); + i += HANDSHAKE_HEADER_SZ; + } + else { + AddTls13RecordHeader(output, fragSz, handshake, ssl); + } + XMEMCPY(output + i, frag + offset, fragSz); + i += fragSz; + + /* This message is always encrypted. */ + recSz = BuildTls13Message(ssl, output, thisSendSz, + output + RECORD_HEADER_SZ, + (int)(i - RECORD_HEADER_SZ), handshake, 1, 0, 0); + if (recSz < 0) { + ret = recSz; + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + goto exit_scv; + } + + #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) + if (ssl->hsInfoOn) + AddPacketName(ssl, "CertificateVerify"); + if (ssl->toInfoOn) { + ret = AddPacketInfo(ssl, "CertificateVerify", handshake, + output, recSz, WRITE_PROTO, 0, ssl->heap); + if (ret != 0) { + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + goto exit_scv; + } + } + #endif + + ssl->buffers.outputBuffer.length += (word32)recSz; + offset += fragSz; + } + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (ret != 0) goto exit_scv; } - #endif - ssl->buffers.outputBuffer.length += (word32)args->sendSz; ssl->options.buildingMsg = 0; if (!ssl->options.groupMessages) ret = SendBuffered(ssl); break; } + } default: ret = INPUT_CASE_ERROR; } /* switch(ssl->options.asyncState) */ @@ -10992,6 +11187,41 @@ static int decodeMlDsaKey(WOLFSSL* ssl, int level) } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +/* ssl->peerCert->sapkiDer is the alternative public key. Hopefully it is an + * SLH-DSA public key. Convert it into a usable public key. */ +static int decodeSlhDsaKey(WOLFSSL* ssl, int param) +{ + int keyRet; + word32 tmpIdx = 0; + + if (ssl->peerSlhDsaKeyPresent) + return INVALID_PARAMETER; + + keyRet = AllocKey(ssl, DYNAMIC_TYPE_SLHDSA, (void**)&ssl->peerSlhDsaKey); + if (keyRet != 0) + return PEER_KEY_ERROR; + + ssl->peerSlhDsaKeyPresent = 1; + + /* AllocKey initialised the key with a placeholder parameter set; re-init + * with the parameter set negotiated in the CertificateVerify. */ + wc_SlhDsaKey_Free(ssl->peerSlhDsaKey); + keyRet = wc_SlhDsaKey_Init(ssl->peerSlhDsaKey, param, ssl->heap, + ssl->devId); + if (keyRet != 0) + return PEER_KEY_ERROR; + + keyRet = wc_SlhDsaKey_PublicKeyDecode(ssl->peerCert.sapkiDer, &tmpIdx, + ssl->peerSlhDsaKey, + ssl->peerCert.sapkiLen); + if (keyRet != 0) + return PEER_KEY_ERROR; + + return 0; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef HAVE_FALCON /* ssl->peerCert->sapkiDer is the alternative public key. Hopefully it is a * falcon public key. Convert it into a usable public key. */ @@ -11207,6 +11437,22 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, ret = decodeMlDsaKey(ssl, WC_ML_DSA_87); break; #endif + #ifdef WOLFSSL_HAVE_SLHDSA + case slhdsa_sha2_128s_sa_algo: + case slhdsa_sha2_128f_sa_algo: + case slhdsa_sha2_192s_sa_algo: + case slhdsa_sha2_192f_sa_algo: + case slhdsa_sha2_256s_sa_algo: + case slhdsa_sha2_256f_sa_algo: + case slhdsa_shake_128s_sa_algo: + case slhdsa_shake_128f_sa_algo: + case slhdsa_shake_192s_sa_algo: + case slhdsa_shake_192f_sa_algo: + case slhdsa_shake_256s_sa_algo: + case slhdsa_shake_256f_sa_algo: + ret = decodeSlhDsaKey(ssl, SlhDsaTypeToParam(sa)); + break; + #endif #ifdef HAVE_FALCON case falcon_level1_sa_algo: ret = decodeFalconKey(ssl, 1); @@ -11250,6 +11496,14 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, ssl->peerMlDsaKeyPresent = 0; } #endif + #ifdef WOLFSSL_HAVE_SLHDSA + else if (ssl->peerSlhDsaKeyPresent && + !IsSlhDsaSigAlgo(sa)) { + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, + (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; + } + #endif #ifdef HAVE_FALCON else if (ssl->peerFalconKeyPresent && sa != falcon_level1_sa_algo && @@ -11326,6 +11580,13 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, ssl->peerMlDsaKeyPresent; } #endif + #ifdef WOLFSSL_HAVE_SLHDSA + if (IsSlhDsaSigAlgo(ssl->options.peerSigAlgo)) { + WOLFSSL_MSG("Peer sent SLH-DSA sig"); + validSigAlgo = (ssl->peerSlhDsaKey != NULL) && + ssl->peerSlhDsaKeyPresent; + } + #endif #ifndef NO_RSA if (ssl->options.peerSigAlgo == rsa_sa_algo) { WOLFSSL_MSG("Peer sent PKCS#1.5 algo - not valid TLS 1.3"); @@ -11633,6 +11894,31 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, } } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + if (IsSlhDsaSigAlgo(ssl->options.peerSigAlgo) && + (ssl->peerSlhDsaKeyPresent)) { + WOLFSSL_MSG("Doing SLH-DSA peer cert verify"); + /* Pure SLH-DSA with empty context, per draft-reddy-tls-slhdsa. + * wc_SlhDsaKey_Verify returns 0 for a valid signature. */ + ret = wc_SlhDsaKey_Verify(ssl->peerSlhDsaKey, NULL, 0, + args->sigData, args->sigDataSz, + sig, args->sigSz); + + if (ret == 0) { + /* CLIENT/SERVER: data verified with public key from + * certificate. */ + ssl->options.peerAuthGood = 1; + + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, + (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; + } + else { + WOLFSSL_MSG("SLH-DSA signature verification failed"); + ret = SIG_VERIFY_E; + } + } + #endif /* WOLFSSL_HAVE_SLHDSA */ /* Check for error */ if (ret != 0) { @@ -14677,7 +14963,8 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) case FIRST_REPLY_THIRD: #if (!defined(NO_CERTS) && (!defined(NO_RSA) || defined(HAVE_ECC) || \ defined(HAVE_ED25519) || defined(HAVE_ED448) || \ - defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA))) && \ + defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA))) && \ (!defined(NO_WOLFSSL_SERVER) || !defined(WOLFSSL_NO_CLIENT_AUTH)) if (!ssl->options.resuming && ssl->options.sendVerify) { ssl->error = SendTls13CertificateVerify(ssl); diff --git a/tests/suites.c b/tests/suites.c index d6dd4fa44b..608e094e29 100644 --- a/tests/suites.c +++ b/tests/suites.c @@ -1349,6 +1349,21 @@ int SuiteTest(int argc, char** argv) goto exit; } #endif +#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WOLFSSL_SLHDSA_PARAM_128F) && \ + defined(WOLFSSL_TLS13) + /* SLH-DSA entity (leaf) certificate used for the handshake signature in + * CertificateVerify. SLH-DSA-SHAKE-128f's ~17KB signature also exercises + * fragmented CertificateVerify send + reassembly. */ + XSTRLCPY(argv0[1], "tests/test-tls13-slhdsa-entity.conf", + sizeof(argv0[1])); + printf("starting TLSv13 SLH-DSA entity-cert CertificateVerify tests\n"); + test_harness(&args); + if (args.return_code != 0) { + printf("error from script %d\n", args.return_code); + args.return_code = EXIT_FAILURE; + goto exit; + } +#endif #if defined(HAVE_ECC) && defined(WOLFSSL_SHA512) && \ (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) /* add P-521 certificate cipher suite tests */ diff --git a/tests/test-tls13-slhdsa-entity.conf b/tests/test-tls13-slhdsa-entity.conf new file mode 100644 index 0000000000..0ecd2c1626 --- /dev/null +++ b/tests/test-tls13-slhdsa-entity.conf @@ -0,0 +1,40 @@ +# SLH-DSA entity (leaf) certificate used for the TLS 1.3 handshake signature +# in the CertificateVerify message (draft-reddy-tls-slhdsa). TLS 1.3 only. +# +# Uses a self-signed SLH-DSA-SHAKE-128f certificate. Its ~17KB signature makes +# the CertificateVerify handshake message exceed a single TLS record, so this +# also exercises fragmented CertificateVerify send + reassembly. + +# Server-auth scenario. + +# server TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-k ./certs/slhdsa/entity-slhdsa-shake-128f-priv.pem +-d + +# client TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-A ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-C + +# Mutual-auth scenario (SLH-DSA CertificateVerify in both directions). + +# server TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-k ./certs/slhdsa/entity-slhdsa-shake-128f-priv.pem +-A ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-V +# Remove -V when CRL for SLH-DSA certificates available. + +# client TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-k ./certs/slhdsa/entity-slhdsa-shake-128f-priv.pem +-A ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-C diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index 4d24d5ff96..381cc87ee2 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -70,20 +70,28 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #define SLHDSA_WM1 (SLHDSA_W - 1) -#ifndef WOLFSSL_SLHDSA_PARAM_NO_256 +/* The buffer-sizing maxima below (SLHDSA_MAX_N/A/H_M/INDICES_SZ/MD) size + * stack/heap buffers that are later written using the true n/a/h_m taken from + * key->params at runtime, so they must stay large enough for every compiled-in + * parameter set across BOTH hash families. They key off the combined + * SLHDSA_ALL_NO_* guards (derived in wc_slhdsa.h), NOT the per-family SHAKE + * 'NO' guards, otherwise a SHA2-only build (SHAKE disabled) would collapse the + * maxima below the SHA2 parameters' true needs and overflow the FORS/XMSS + * 'nodes' buffers. */ +#ifndef SLHDSA_ALL_NO_256 /* Maximum size of hash output. */ #define SLHDSA_MAX_N 32 - #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST + #ifndef SLHDSA_ALL_NO_FAST /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 35 #else /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 22 #endif -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) +#elif !defined(SLHDSA_ALL_NO_192) /* Maximum size of hash output. */ #define SLHDSA_MAX_N 24 - #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST + #ifndef SLHDSA_ALL_NO_FAST /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 33 #else @@ -93,7 +101,7 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #else /* Maximum size of hash output. */ #define SLHDSA_MAX_N 16 - #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST + #ifndef SLHDSA_ALL_NO_FAST /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 33 #else @@ -102,11 +110,11 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #endif #endif -#ifndef WOLFSSL_SLHDSA_PARAM_NO_SMALL - #if !defined(WOLFSSL_SLHDSA_PARAM_NO_256) +#ifndef SLHDSA_ALL_NO_SMALL + #if !defined(SLHDSA_ALL_NO_256) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 14 - #elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) + #elif !defined(SLHDSA_ALL_NO_192) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 14 #else @@ -114,10 +122,10 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #define SLHDSA_MAX_A 12 #endif #else - #if !defined(WOLFSSL_SLHDSA_PARAM_NO_256) + #if !defined(SLHDSA_ALL_NO_256) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 9 - #elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) + #elif !defined(SLHDSA_ALL_NO_192) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 8 #else @@ -126,7 +134,7 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #endif #endif -#ifndef WOLFSSL_SLHDSA_PARAM_NO_SMALL +#ifndef SLHDSA_ALL_NO_SMALL /* Maximum height of Merkle tree. */ #define SLHDSA_MAX_H_M 9 #else @@ -147,19 +155,19 @@ wc_static_assert(SLHDSA_MAX_MSG_SZ <= 255); * declarations and the ForceZero() sizes so they cannot drift. */ #define SLHDSA_SHAKE_X4_STATE_W (25 * 4) -#ifndef WOLFSSL_SLHDSA_PARAM_NO_256F +#ifndef SLHDSA_ALL_NO_256F /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 49 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_256S) +#elif !defined(SLHDSA_ALL_NO_256S) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 47 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192F) +#elif !defined(SLHDSA_ALL_NO_192F) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 42 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192S) +#elif !defined(SLHDSA_ALL_NO_192S) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 39 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_128F) +#elif !defined(SLHDSA_ALL_NO_128F) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 34 #else diff --git a/wolfssl/internal.h b/wolfssl/internal.h index ebafb498af..58e83cd5e3 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -129,6 +129,9 @@ #ifdef WOLFSSL_HAVE_MLDSA #include #endif +#ifdef WOLFSSL_HAVE_SLHDSA + #include +#endif #ifdef HAVE_HKDF #include #endif @@ -1801,6 +1804,23 @@ enum Misc { MLDSA_87_SA_MAJOR = 0x09, MLDSA_87_SA_MINOR = 0x06, + /* These values for SLH-DSA correspond to the code points assigned in + * draft-reddy-tls-slhdsa (0x0911-0x091C) and match what oqs-provider uses. + * The major byte (0x09) is shared with ML-DSA. */ + SLHDSA_SA_MAJOR = 0x09, + SLHDSA_SHA2_128S_SA_MINOR = 0x11, + SLHDSA_SHA2_128F_SA_MINOR = 0x12, + SLHDSA_SHA2_192S_SA_MINOR = 0x13, + SLHDSA_SHA2_192F_SA_MINOR = 0x14, + SLHDSA_SHA2_256S_SA_MINOR = 0x15, + SLHDSA_SHA2_256F_SA_MINOR = 0x16, + SLHDSA_SHAKE_128S_SA_MINOR = 0x17, + SLHDSA_SHAKE_128F_SA_MINOR = 0x18, + SLHDSA_SHAKE_192S_SA_MINOR = 0x19, + SLHDSA_SHAKE_192F_SA_MINOR = 0x1A, + SLHDSA_SHAKE_256S_SA_MINOR = 0x1B, + SLHDSA_SHAKE_256F_SA_MINOR = 0x1C, + MIN_RSA_SHA512_PSS_BITS = 512 * 2 + 8 * 8, /* Min key size */ MIN_RSA_SHA384_PSS_BITS = 384 * 2 + 8 * 8, /* Min key size */ @@ -1981,7 +2001,8 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #endif #ifndef MAX_X509_SIZE - #if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + #if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) #define MAX_X509_SIZE (8*1024) /* max static x509 buffer size; ML-DSA is big */ #elif defined(WOLFSSL_HAPROXY) #define MAX_X509_SIZE 3072 /* max static x509 buffer size */ @@ -4012,6 +4033,7 @@ struct WOLFSSL_CTX { byte haveECDSAsig:1; /* server cert signed w/ ECDSA */ byte haveFalconSig:1; /* server cert signed w/ Falcon */ byte haveMlDsaSig:1; /* server cert signed w/ ML-DSA */ + byte haveSlhDsaSig:1; /* server cert signed w/ SLH-DSA */ byte haveStaticECC:1; /* static server ECC private key */ byte partialWrite:1; /* only one msg per write call */ byte autoRetry:1; /* retry read/write on a WANT_{READ|WRITE} */ @@ -4475,9 +4497,10 @@ enum KeyExchangeAlgorithm { #define SIG_FALCON 0x08 #define SIG_MLDSA 0x10 #define SIG_ANON 0x20 +#define SIG_SLHDSA 0x40 /* SIG_ANON is omitted by default */ #define SIG_ALL (SIG_ECDSA | SIG_RSA | SIG_SM2 | SIG_FALCON | \ - SIG_MLDSA) + SIG_MLDSA | SIG_SLHDSA) /* Supported Authentication Schemes */ enum SignatureAlgorithm { @@ -4497,6 +4520,18 @@ enum SignatureAlgorithm { sm2_sa_algo = 17, any_sa_algo = 18, ecc_brainpool_sa_algo = 19, + slhdsa_sha2_128s_sa_algo = 20, + slhdsa_sha2_128f_sa_algo = 21, + slhdsa_sha2_192s_sa_algo = 22, + slhdsa_sha2_192f_sa_algo = 23, + slhdsa_sha2_256s_sa_algo = 24, + slhdsa_sha2_256f_sa_algo = 25, + slhdsa_shake_128s_sa_algo = 26, + slhdsa_shake_128f_sa_algo = 27, + slhdsa_shake_192s_sa_algo = 28, + slhdsa_shake_192f_sa_algo = 29, + slhdsa_shake_256s_sa_algo = 30, + slhdsa_shake_256f_sa_algo = 31, invalid_sa_algo = 255 }; @@ -5167,6 +5202,7 @@ struct Options { word16 haveStaticECC:1; /* static server ECC private key */ word16 haveFalconSig:1; /* server Falcon signed cert */ word16 haveMlDsaSig:1; /* server ML-DSA signed cert */ + word16 haveSlhDsaSig:1; /* server SLH-DSA signed cert */ word16 havePeerCert:1; /* do we have peer's cert */ word16 havePeerVerify:1; /* and peer's cert verify */ word16 usingPSK_cipher:1; /* are using psk as cipher */ @@ -6313,6 +6349,10 @@ struct WOLFSSL { wc_MlDsaKey* peerMlDsaKey; byte peerMlDsaKeyPresent; #endif +#ifdef WOLFSSL_HAVE_SLHDSA + SlhDsaKey* peerSlhDsaKey; + byte peerSlhDsaKeyPresent; +#endif #ifdef HAVE_LIBZ z_stream c_stream; /* compression stream */ z_stream d_stream; /* decompression stream */ @@ -7195,6 +7235,12 @@ WOLFSSL_LOCAL int FindSuite(const Suites* suites, byte first, byte second); WOLFSSL_LOCAL void DecodeSigAlg(const byte* input, byte* hashAlgo, byte* hsType); +#ifdef WOLFSSL_HAVE_SLHDSA +WOLFSSL_LOCAL byte SlhDsaSigMinorToType(byte minor); +WOLFSSL_LOCAL int SlhDsaTypeToParam(byte hsType); +WOLFSSL_LOCAL int IsSlhDsaSigAlgo(byte hsType); +WOLFSSL_LOCAL byte SlhDsaParamToType(int param); +#endif WOLFSSL_LOCAL enum wc_HashType HashAlgoToType(int hashAlgo); #ifndef NO_CERTS diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 8b12a306b2..a85a498c91 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -883,7 +883,12 @@ extern const WOLFSSL_ObjectInfo wolfssl_object_info[]; #endif #endif -#if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) +#if defined(WOLFSSL_HAVE_SLHDSA) + /* SLH-DSA signatures are large (up to ~50KB for the 'f' parameter sets). + * This buffer must hold the full handshake signature before it is + * fragmented across TLS records in SendTls13CertificateVerify. */ + #define WC_MAX_CERT_VERIFY_SZ (WC_SLHDSA_MAX_SIG_LEN + 1024) +#elif defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) #define WC_MAX_CERT_VERIFY_SZ 6000 /* For ML-DSA */ #elif defined(WOLFSSL_CERT_EXT) #define WC_MAX_CERT_VERIFY_SZ 2048 /* For larger extensions */ diff --git a/wolfssl/wolfcrypt/wc_slhdsa.h b/wolfssl/wolfcrypt/wc_slhdsa.h index ecf29dfea5..0acc6bfdcc 100644 --- a/wolfssl/wolfcrypt/wc_slhdsa.h +++ b/wolfssl/wolfcrypt/wc_slhdsa.h @@ -468,18 +468,80 @@ #endif /* WOLFSSL_SLHDSA_SHA2 */ +/* ======== Combined family-absence guards ======== */ + +/* A size/speed class is only truly absent when neither its SHAKE variant nor + * its SHA2 variant is compiled in. The per-family 'NO' guards above describe + * one family each; the SHA2 'NO' macros exist only when WOLFSSL_SLHDSA_SHA2 is + * defined, otherwise WOLFSSL_SLHDSA_NO_SHA2 stands for "all SHA2 absent". + * These combined guards let the maximum-size defines below (and the internal + * buffer maxima in wc_slhdsa.c) stay large enough for every compiled-in + * parameter set across both hash families. Without them a SHA2-only build + * (SHAKE disabled) would size buffers for the 128-bit level only and overflow + * on the 192/256-bit SHA2 parameter sets. */ +#if defined(WOLFSSL_SLHDSA_PARAM_NO_256) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_256)) + #define SLHDSA_ALL_NO_256 +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_192) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_192)) + #define SLHDSA_ALL_NO_192 +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_SMALL) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_SMALL)) + #define SLHDSA_ALL_NO_SMALL +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_FAST) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_FAST)) + #define SLHDSA_ALL_NO_FAST +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_128) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128)) + #define SLHDSA_ALL_NO_128 +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_256F) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_256F)) + #define SLHDSA_ALL_NO_256F +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_256S) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_256S)) + #define SLHDSA_ALL_NO_256S +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_192F) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_192F)) + #define SLHDSA_ALL_NO_192F +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_192S) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_192S)) + #define SLHDSA_ALL_NO_192S +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_128F) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128F)) + #define SLHDSA_ALL_NO_128F +#endif + /* ======== Maximum size defines ======== */ /* Determine maximum private and public key lengths based on maximum 256-bit * output length. SHA2 variants have identical sizes to SHAKE counterparts. */ -#ifndef WOLFSSL_SLHDSA_PARAM_NO_256 +#ifndef SLHDSA_ALL_NO_256 /* Maximum private key length. */ #define WC_SLHDSA_MAX_PRIV_LEN WC_SLHDSA_SHAKE256F_PRIV_LEN /* Maximum public key length. */ #define WC_SLHDSA_MAX_PUB_LEN WC_SLHDSA_SHAKE256F_PUB_LEN /* Maximum seed length. */ #define WC_SLHDSA_MAX_SEED WC_SLHDSA_SHAKE256_SEED_LEN -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) +#elif !defined(SLHDSA_ALL_NO_192) /* Maximum private key length. */ #define WC_SLHDSA_MAX_PRIV_LEN WC_SLHDSA_SHAKE192F_PRIV_LEN /* Maximum public key length. */ @@ -567,6 +629,39 @@ enum SlhDsaParam { #endif }; +/* A parameter set that is guaranteed to be compiled in. Use as a placeholder + * for wc_SlhDsaKey_Init when the real parameter set is only known later (e.g. + * determined from a decoded key OID or a negotiated TLS signature scheme). + * The SHAKE family is disabled when only the SHA2 family is enabled, so the + * placeholder cannot be a fixed SHAKE value. */ +#if defined(WOLFSSL_SLHDSA_PARAM_128S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE128S +#elif defined(WOLFSSL_SLHDSA_PARAM_128F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE128F +#elif defined(WOLFSSL_SLHDSA_PARAM_192S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE192S +#elif defined(WOLFSSL_SLHDSA_PARAM_192F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE192F +#elif defined(WOLFSSL_SLHDSA_PARAM_256S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE256S +#elif defined(WOLFSSL_SLHDSA_PARAM_256F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE256F +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_128S +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_128F +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_192S +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_192F +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_256S +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_256F +#else + #error "WOLFSSL_HAVE_SLHDSA requires at least one parameter set" +#endif + /* Helper macro to detect SHA2 parameter sets. */ #ifdef WOLFSSL_SLHDSA_SHA2 #define SLHDSA_IS_SHA2(p) ((p) >= SLHDSA_SHA2_128S)