diff options
Diffstat (limited to 'regress/unittests/sshsig')
26 files changed, 1141 insertions, 0 deletions
diff --git a/regress/unittests/sshsig/Makefile b/regress/unittests/sshsig/Makefile new file mode 100644 index 0000000..bc3c6c7 --- /dev/null +++ b/regress/unittests/sshsig/Makefile @@ -0,0 +1,25 @@ +# $OpenBSD: Makefile,v 1.3 2023/01/15 23:35:10 djm Exp $ + +PROG=test_sshsig +SRCS=tests.c + +# From usr.bin/ssh +SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c +SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c +SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c +SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c +SRCS+=addr.c addrmatch.c bitmap.c sshsig.c +SRCS+=ed25519.c hash.c +SRCS+=cipher-chachapoly.c chacha.c poly1305.c ssh-ecdsa-sk.c ssh-sk.c +SRCS+=ssh-ed25519-sk.c sk-usbhid.c + +SRCS+=digest-openssl.c +#SRCS+=digest-libc.c +SRCS+=utf8.c + +REGRESS_TARGETS=run-regress-${PROG} + +run-regress-${PROG}: ${PROG} + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} -d ${.CURDIR}/testdata + +.include <bsd.regress.mk> diff --git a/regress/unittests/sshsig/mktestdata.sh b/regress/unittests/sshsig/mktestdata.sh new file mode 100755 index 0000000..d2300f9 --- /dev/null +++ b/regress/unittests/sshsig/mktestdata.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# $OpenBSD: mktestdata.sh,v 1.1 2020/06/19 04:32:09 djm Exp $ + +NAMESPACE=unittest + +set -ex + +cd testdata + +if [ -f ../../../misc/sk-dummy/sk-dummy.so ] ; then + SK_DUMMY=../../../misc/sk-dummy/sk-dummy.so +elif [ -f ../../../misc/sk-dummy/obj/sk-dummy.so ] ; then + SK_DUMMY=../../../misc/sk-dummy/obj/sk-dummy.so +else + echo "Can't find sk-dummy.so" 1>&2 + exit 1 +fi + +rm -f signed-data namespace +rm -f rsa dsa ecdsa ed25519 ecdsa_sk ed25519_sk +rm -f rsa.sig dsa.sig ecdsa.sig ed25519.sig ecdsa_sk.sig ed25519_sk.sig + +printf "This is a test, this is only a test" > signed-data +printf "$NAMESPACE" > namespace + +ssh-keygen -t rsa -C "RSA test" -N "" -f rsa -m PEM +ssh-keygen -t dsa -C "DSA test" -N "" -f dsa -m PEM +ssh-keygen -t ecdsa -C "ECDSA test" -N "" -f ecdsa -m PEM +ssh-keygen -t ed25519 -C "ED25519 test key" -N "" -f ed25519 +ssh-keygen -w "$SK_DUMMY" -t ecdsa-sk -C "ECDSA-SK test key" \ + -N "" -f ecdsa_sk +ssh-keygen -w "$SK_DUMMY" -t ed25519-sk -C "ED25519-SK test key" \ + -N "" -f ed25519_sk + +ssh-keygen -Y sign -f rsa -n $NAMESPACE - < signed-data > rsa.sig +ssh-keygen -Y sign -f dsa -n $NAMESPACE - < signed-data > dsa.sig +ssh-keygen -Y sign -f ecdsa -n $NAMESPACE - < signed-data > ecdsa.sig +ssh-keygen -Y sign -f ed25519 -n $NAMESPACE - < signed-data > ed25519.sig +ssh-keygen -w "$SK_DUMMY" \ + -Y sign -f ecdsa_sk -n $NAMESPACE - < signed-data > ecdsa_sk.sig +ssh-keygen -w "$SK_DUMMY" \ + -Y sign -f ed25519_sk -n $NAMESPACE - < signed-data > ed25519_sk.sig diff --git a/regress/unittests/sshsig/testdata/dsa b/regress/unittests/sshsig/testdata/dsa new file mode 100644 index 0000000..7c0063e --- /dev/null +++ b/regress/unittests/sshsig/testdata/dsa @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCXpndQdz2mQVnk+lYOF3nxDT+h6SiJmUvBFhnFWBv8tG4pTOkb +EwGufLEzGpzjTj+3bjVau7LFt37AFrqs4Num272BWNsYNIjOlGPgq7Xjv32FN00x +JYh1DoRs1cGGnvohlsWEamGGhTHD1a9ipctPEBV+NrxtZMrl+pO/ZZg8vQIVAKJB +P3iNYSpSuW74+q4WxLCuK8O3AoGAQldE+BIuxlvoG1IFiWesx0CU+H2KO0SEZc9A +SX/qjOabh0Fb78ofTlEf9gWHFfat8SvSJQIOPMVlb76Lio8AAMT8Eaa/qQKKYmQL +dNq4MLhhjxx5KLGt6J2JyFPExCv+qnHYHD59ngtLwKyqGjpSC8LPLktdXn8W/Aad +Ly1K7+MCgYBsMHBczhSeUh8w7i20CVg4OlNTmfJRVU2tO6OpMxZ/quitRm3hLKSN +u4xRkvHJwi4LhQtv1SXvLI5gs5P3gCG8tsIAiyCqLinHha63iBdJpqhnV/x/j7dB +yJr3xJbnmLdWLkkCtNk1Ir1/CuEz+ufAyLGdKWksEAu1UUlb501BkwIVAILIa3Rg +0h7J9lQpHJphvF3K0M1T +-----END DSA PRIVATE KEY----- diff --git a/regress/unittests/sshsig/testdata/dsa.pub b/regress/unittests/sshsig/testdata/dsa.pub new file mode 100644 index 0000000..e77aa7e --- /dev/null +++ b/regress/unittests/sshsig/testdata/dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAJemd1B3PaZBWeT6Vg4XefENP6HpKImZS8EWGcVYG/y0bilM6RsTAa58sTManONOP7duNVq7ssW3fsAWuqzg26bbvYFY2xg0iM6UY+CrteO/fYU3TTEliHUOhGzVwYae+iGWxYRqYYaFMcPVr2Kly08QFX42vG1kyuX6k79lmDy9AAAAFQCiQT94jWEqUrlu+PquFsSwrivDtwAAAIBCV0T4Ei7GW+gbUgWJZ6zHQJT4fYo7RIRlz0BJf+qM5puHQVvvyh9OUR/2BYcV9q3xK9IlAg48xWVvvouKjwAAxPwRpr+pAopiZAt02rgwuGGPHHkosa3onYnIU8TEK/6qcdgcPn2eC0vArKoaOlILws8uS11efxb8Bp0vLUrv4wAAAIBsMHBczhSeUh8w7i20CVg4OlNTmfJRVU2tO6OpMxZ/quitRm3hLKSNu4xRkvHJwi4LhQtv1SXvLI5gs5P3gCG8tsIAiyCqLinHha63iBdJpqhnV/x/j7dByJr3xJbnmLdWLkkCtNk1Ir1/CuEz+ufAyLGdKWksEAu1UUlb501Bkw== DSA test diff --git a/regress/unittests/sshsig/testdata/dsa.sig b/regress/unittests/sshsig/testdata/dsa.sig new file mode 100644 index 0000000..0b14ad6 --- /dev/null +++ b/regress/unittests/sshsig/testdata/dsa.sig @@ -0,0 +1,13 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAbEAAAAHc3NoLWRzcwAAAIEAl6Z3UHc9pkFZ5PpWDhd58Q0/oekoiZ +lLwRYZxVgb/LRuKUzpGxMBrnyxMxqc404/t241Wruyxbd+wBa6rODbptu9gVjbGDSIzpRj +4Ku14799hTdNMSWIdQ6EbNXBhp76IZbFhGphhoUxw9WvYqXLTxAVfja8bWTK5fqTv2WYPL +0AAAAVAKJBP3iNYSpSuW74+q4WxLCuK8O3AAAAgEJXRPgSLsZb6BtSBYlnrMdAlPh9ijtE +hGXPQEl/6ozmm4dBW+/KH05RH/YFhxX2rfEr0iUCDjzFZW++i4qPAADE/BGmv6kCimJkC3 +TauDC4YY8ceSixreidichTxMQr/qpx2Bw+fZ4LS8Csqho6UgvCzy5LXV5/FvwGnS8tSu/j +AAAAgGwwcFzOFJ5SHzDuLbQJWDg6U1OZ8lFVTa07o6kzFn+q6K1GbeEspI27jFGS8cnCLg +uFC2/VJe8sjmCzk/eAIby2wgCLIKouKceFrreIF0mmqGdX/H+Pt0HImvfElueYt1YuSQK0 +2TUivX8K4TP658DIsZ0paSwQC7VRSVvnTUGTAAAACHVuaXR0ZXN0AAAAAAAAAAZzaGE1MT +IAAAA3AAAAB3NzaC1kc3MAAAAodi5lr0pqBpO76OY4N1CtfR85BCgZ95qfVjP/e9lToj0q +lwjSJJXUjw== +-----END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/testdata/ecdsa b/regress/unittests/sshsig/testdata/ecdsa new file mode 100644 index 0000000..55fb440 --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFg0ZCSEB5LNeLsXYL25g3kqEWsqh52DR+yNOjyQJqyZoAoGCCqGSM49 +AwEHoUQDQgAE3sud88FV0N8FPspZSV7LWqj6uPPLRZiSsenNuEYAteWPyDgrZsWb +LzXBuUJucepaCNuW/QWgHBRbrjWj3ERm3A== +-----END EC PRIVATE KEY----- diff --git a/regress/unittests/sshsig/testdata/ecdsa.pub b/regress/unittests/sshsig/testdata/ecdsa.pub new file mode 100644 index 0000000..14ec6cf --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN7LnfPBVdDfBT7KWUley1qo+rjzy0WYkrHpzbhGALXlj8g4K2bFmy81wblCbnHqWgjblv0FoBwUW641o9xEZtw= ECDSA test diff --git a/regress/unittests/sshsig/testdata/ecdsa.sig b/regress/unittests/sshsig/testdata/ecdsa.sig new file mode 100644 index 0000000..7978157 --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa.sig @@ -0,0 +1,7 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE +EE3sud88FV0N8FPspZSV7LWqj6uPPLRZiSsenNuEYAteWPyDgrZsWbLzXBuUJucepaCNuW +/QWgHBRbrjWj3ERm3AAAAAh1bml0dGVzdAAAAAAAAAAGc2hhNTEyAAAAZQAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAASgAAACEAycVNsTlE+XEZYyYiDxWZlliruf/pPMhEEMR/XLdQ +a4MAAAAhALQt+5gES7L3uKGptHB6UZQMuZ2WyI0C6FJs4v6AtMIU +-----END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk b/regress/unittests/sshsig/testdata/ecdsa_sk new file mode 100644 index 0000000..62ae44c --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa_sk @@ -0,0 +1,13 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2 +RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQSg1WuY0XE+ +VexOsrJsFYuxyVoe6eQ/oXmyz2pEHKZw9moyWehv+Fs7oZWFp3JVmOtybKQ6dvfUZYauQE +/Ov4PAAAAABHNzaDoAAAGI6iV41+oleNcAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv +cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEoNVrmNFxPlXsTrKybBWLsclaHunkP6F5ss +9qRBymcPZqMlnob/hbO6GVhadyVZjrcmykOnb31GWGrkBPzr+DwAAAAARzc2g6AQAAAOMt +LS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KTUhjQ0FRRUVJQm9oeW54M2tpTFVEeS +t5UjU3WXBXSU5KektnU1p6WnV2VTljYXFla3JGcW9Bb0dDQ3FHU000OQpBd0VIb1VRRFFn +QUVvTlZybU5GeFBsWHNUckt5YkJXTHNjbGFIdW5rUDZGNXNzOXFSQnltY1BacU1sbm9iL2 +hiCk82R1ZoYWR5VlpqcmNteWtPbmIzMUdXR3JrQlB6citEd0E9PQotLS0tLUVORCBFQyBQ +UklWQVRFIEtFWS0tLS0tCgAAAAAAAAARRUNEU0EtU0sgdGVzdCBrZXk= +-----END OPENSSH PRIVATE KEY----- diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk.pub b/regress/unittests/sshsig/testdata/ecdsa_sk.pub new file mode 100644 index 0000000..385ebf1 --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa_sk.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBKDVa5jRcT5V7E6ysmwVi7HJWh7p5D+hebLPakQcpnD2ajJZ6G/4WzuhlYWnclWY63JspDp299Rlhq5AT86/g8AAAAAEc3NoOg== ECDSA-SK test key diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk.sig b/regress/unittests/sshsig/testdata/ecdsa_sk.sig new file mode 100644 index 0000000..86de360 --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa_sk.sig @@ -0,0 +1,8 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAH8AAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVuc3NoLmNvbQ +AAAAhuaXN0cDI1NgAAAEEEoNVrmNFxPlXsTrKybBWLsclaHunkP6F5ss9qRBymcPZqMlno +b/hbO6GVhadyVZjrcmykOnb31GWGrkBPzr+DwAAAAARzc2g6AAAACHVuaXR0ZXN0AAAAAA +AAAAZzaGE1MTIAAAB3AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20A +AABIAAAAIHohGwyy8iKT3zwd1TYA9V/Ioo7h/3zCJUtyq/Qigt/HAAAAIGzidTwq7D/kFa +7Xjcp/KkdbIs4MfQpfAW/0OciajlpzARI0Vng= +-----END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub new file mode 100644 index 0000000..1597302 --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBBRGwDjs4HhJFcn4tJ5Gr72KcmRmCS1OirETxaXvnsNApgoOLF1a/7rxldfSMHm73eT1nhHe97W8qicPPEAKDJQAAAALbWluZHJvdC5vcmc= diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig new file mode 100644 index 0000000..4bdd8ed --- /dev/null +++ b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig @@ -0,0 +1,13 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAIYAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVuc3NoLmNvbQ +AAAAhuaXN0cDI1NgAAAEEEFEbAOOzgeEkVyfi0nkavvYpyZGYJLU6KsRPFpe+ew0CmCg4s +XVr/uvGV19Iwebvd5PWeEd73tbyqJw88QAoMlAAAAAttaW5kcm90Lm9yZwAAAAh1bml0dG +VzdAAAAAAAAAAGc2hhNTEyAAABhwAAACt3ZWJhdXRobi1zay1lY2RzYS1zaGEyLW5pc3Rw +MjU2QG9wZW5zc2guY29tAAAASQAAACBj2oMT9tb5wRXe6mdmf4/lgAO8wrgr95ouozwNg4 +itnQAAACEAtU9g5wz3HchUiLfLD6plr9T4TiJ32lVCrATSjpiy0SMBAAADHwAAABdodHRw +czovL3d3dy5taW5kcm90Lm9yZwAAAON7InR5cGUiOiJ3ZWJhdXRobi5nZXQiLCJjaGFsbG +VuZ2UiOiJVMU5JVTBsSEFBQUFDSFZ1YVhSMFpYTjBBQUFBQUFBQUFBWnphR0UxTVRJQUFB +QkFMTHU4WmdjU3h0Nk1zRlV6dWlaZ0c2R3dNZEo5ZDd4ZUU3WW9SSXcwZzlpSEpfd3NGRD +cxbzRXbHllenZGV0VqYnFRMHFDN0Z3R3Bqa2pVUVAtTmQ2dyIsIm9yaWdpbiI6Imh0dHBz +Oi8vd3d3Lm1pbmRyb3Qub3JnIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQAAAAA= +-----END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/testdata/ed25519 b/regress/unittests/sshsig/testdata/ed25519 new file mode 100644 index 0000000..b44a63d --- /dev/null +++ b/regress/unittests/sshsig/testdata/ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCJYs0iDdw0Fe/FTzY1b78I4H/j+R6mz2AmLtwTjHYwBAAAAJjpGas/6Rmr +PwAAAAtzc2gtZWQyNTUxOQAAACCJYs0iDdw0Fe/FTzY1b78I4H/j+R6mz2AmLtwTjHYwBA +AAAEDpSKRA1QKW6kYiQftGRWh+H0fNekzYLG6c3bzseoCpEolizSIN3DQV78VPNjVvvwjg +f+P5HqbPYCYu3BOMdjAEAAAAEEVEMjU1MTkgdGVzdCBrZXkBAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/regress/unittests/sshsig/testdata/ed25519.pub b/regress/unittests/sshsig/testdata/ed25519.pub new file mode 100644 index 0000000..b078e45 --- /dev/null +++ b/regress/unittests/sshsig/testdata/ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIlizSIN3DQV78VPNjVvvwjgf+P5HqbPYCYu3BOMdjAE ED25519 test key diff --git a/regress/unittests/sshsig/testdata/ed25519.sig b/regress/unittests/sshsig/testdata/ed25519.sig new file mode 100644 index 0000000..8e8ff2a --- /dev/null +++ b/regress/unittests/sshsig/testdata/ed25519.sig @@ -0,0 +1,6 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgiWLNIg3cNBXvxU82NW+/COB/4/ +keps9gJi7cE4x2MAQAAAAIdW5pdHRlc3QAAAAAAAAABnNoYTUxMgAAAFMAAAALc3NoLWVk +MjU1MTkAAABAihQsbUzuNEFflk5Tw1+H9aLS7tZQk0RG8KW1DtOmDYYnWe3D3UKiG3fcJa +DNg4vBWp1j1gLRiBMOF+gwYNegDg== +-----END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/testdata/ed25519_sk b/regress/unittests/sshsig/testdata/ed25519_sk new file mode 100644 index 0000000..3a434ec --- /dev/null +++ b/regress/unittests/sshsig/testdata/ed25519_sk @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCbGg2F0GK7nOm4pQmAyCuGEjnhvs5q0TtjPbdN +//+yxwAAAARzc2g6AAAAuBw56jAcOeowAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2 +9tAAAAIJsaDYXQYruc6bilCYDIK4YSOeG+zmrRO2M9t03//7LHAAAABHNzaDoBAAAAQFXc +6dCwWewIk1EBofAouGZApW8+s0XekXenxtb78+x0mxoNhdBiu5zpuKUJgMgrhhI54b7Oat +E7Yz23Tf//sscAAAAAAAAAE0VEMjU1MTktU0sgdGVzdCBrZXkBAgMEBQY= +-----END OPENSSH PRIVATE KEY----- diff --git a/regress/unittests/sshsig/testdata/ed25519_sk.pub b/regress/unittests/sshsig/testdata/ed25519_sk.pub new file mode 100644 index 0000000..71051ec --- /dev/null +++ b/regress/unittests/sshsig/testdata/ed25519_sk.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIJsaDYXQYruc6bilCYDIK4YSOeG+zmrRO2M9t03//7LHAAAABHNzaDo= ED25519-SK test key diff --git a/regress/unittests/sshsig/testdata/ed25519_sk.sig b/regress/unittests/sshsig/testdata/ed25519_sk.sig new file mode 100644 index 0000000..49b6818 --- /dev/null +++ b/regress/unittests/sshsig/testdata/ed25519_sk.sig @@ -0,0 +1,7 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAEoAAAAac2stc3NoLWVkMjU1MTlAb3BlbnNzaC5jb20AAAAgmxoNhd +Biu5zpuKUJgMgrhhI54b7OatE7Yz23Tf//sscAAAAEc3NoOgAAAAh1bml0dGVzdAAAAAAA +AAAGc2hhNTEyAAAAZwAAABpzay1zc2gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAAEAi+7eTjW +/+LQ2M+sCD+KFtH1n7VFFJon/SZFsxODyV8cWTlFKj617Ys1Ur5TV6uaEXQhck8rBA2oQI +HTPANLIPARI0Vng= +-----END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/testdata/namespace b/regress/unittests/sshsig/testdata/namespace new file mode 100644 index 0000000..1570cd5 --- /dev/null +++ b/regress/unittests/sshsig/testdata/namespace @@ -0,0 +1 @@ +unittest
\ No newline at end of file diff --git a/regress/unittests/sshsig/testdata/rsa b/regress/unittests/sshsig/testdata/rsa new file mode 100644 index 0000000..228fad7 --- /dev/null +++ b/regress/unittests/sshsig/testdata/rsa @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/regress/unittests/sshsig/testdata/rsa.pub b/regress/unittests/sshsig/testdata/rsa.pub new file mode 100644 index 0000000..30142ac --- /dev/null +++ b/regress/unittests/sshsig/testdata/rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDfzqWaNEe0mnKPzsGtL6ywya29z/VI83SBdM+z2NnWFgCr3w9xrf9FhUzgdpK7oI7O1AnZmdkPdN79d4GchTaZ+69ASX4OLCKwZ42CVXJEKLvvzt88UV9zHNl8NrsQHapjxLehpIZkrxCrO4bCYYAiaOVBfPon4cJ394P/nwagJJKKsgQIVYIC3642s4L8SaADOxt9x9ohttXMDjS2WBTeE7YnjaR13PwKKwNO0MpAAEVw5pBnJxJ+fscM/5aBctdcFQj3BUzVOJ/V/70xfTzEiy8K8bOjvIuBttMUtIavlKQD606ti6gDtXx+B7SJ3/emdW0bVSLcju96N4DB/Qdmq0V4ESc7CQgkWZl4y0V45V/7iSLiHWsOoC9Kxw28y0uno5elsABMBMC4RYSQhAGXWueCcR7SIL2JT2K0oxbqongW0hvgAIfUShTXHy+qsV5yDaG+p+TkJkEkhtXbwkW4aNpzc7EFnXsWpvyWOiZSKvUasRPbPro0yBvlOMMPsFU= RSA test diff --git a/regress/unittests/sshsig/testdata/rsa.sig b/regress/unittests/sshsig/testdata/rsa.sig new file mode 100644 index 0000000..15a032e --- /dev/null +++ b/regress/unittests/sshsig/testdata/rsa.sig @@ -0,0 +1,19 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAN/OpZo0R7Saco/Owa0vrL +DJrb3P9UjzdIF0z7PY2dYWAKvfD3Gt/0WFTOB2krugjs7UCdmZ2Q903v13gZyFNpn7r0BJ +fg4sIrBnjYJVckQou+/O3zxRX3Mc2Xw2uxAdqmPEt6GkhmSvEKs7hsJhgCJo5UF8+ifhwn +f3g/+fBqAkkoqyBAhVggLfrjazgvxJoAM7G33H2iG21cwONLZYFN4TtieNpHXc/AorA07Q +ykAARXDmkGcnEn5+xwz/loFy11wVCPcFTNU4n9X/vTF9PMSLLwrxs6O8i4G20xS0hq+UpA +PrTq2LqAO1fH4HtInf96Z1bRtVItyO73o3gMH9B2arRXgRJzsJCCRZmXjLRXjlX/uJIuId +aw6gL0rHDbzLS6ejl6WwAEwEwLhFhJCEAZda54JxHtIgvYlPYrSjFuqieBbSG+AAh9RKFN +cfL6qxXnINob6n5OQmQSSG1dvCRbho2nNzsQWdexam/JY6JlIq9RqxE9s+ujTIG+U4ww+w +VQAAAAh1bml0dGVzdAAAAAAAAAAGc2hhNTEyAAABlAAAAAxyc2Etc2hhMi01MTIAAAGACi +nEpBrQxZi0yOrrT6h98JFfZh0XXioih4fzmvtoV0yOReWClS+otGgXoJyZHcbaKNOjDwSM +rIkUoX6OUJmtHYP0HRELnKw35m33LdBPXpFGS4tRS7NeSpvc04KtjT6jYXY9FjWy5hcn17 +Sxc/3DnJqLgJBur8acY7FeIzpWmKixPd/dGkEjdWoD9gO6szLczGuQgrOdYmSRL4yKadTJ +lVjz5OSeKSYYGQy33US2XQassRRNYf4e9byTA3DKvHa/OcTt7lFerea0kZdDpAboqffz7T +Yaw/hFskAYLIEdTW3aoXBGHSOvu8AkDOtb7qwuxGSQ27pjkDLDNsp1ceCFaCaQ6X83RZuK +ACv9JUBI5KaSf81e0bs0KezJKkhB9czeZ6dk96qISbgayEBnvhYgXvUDKtHn7HzNlCJKfK +5ABhNxfGG2CD+NKqcrndwFgS1sQO3hbA84zPQb26ShBovT8ytHBmW1F8ZK4O9Bz61Q6EZK +vs/u6xP6LUean/so5daa +-----END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/testdata/signed-data b/regress/unittests/sshsig/testdata/signed-data new file mode 100644 index 0000000..7df4bed --- /dev/null +++ b/regress/unittests/sshsig/testdata/signed-data @@ -0,0 +1 @@ +This is a test, this is only a test
\ No newline at end of file diff --git a/regress/unittests/sshsig/tests.c b/regress/unittests/sshsig/tests.c new file mode 100644 index 0000000..13cfcfd --- /dev/null +++ b/regress/unittests/sshsig/tests.c @@ -0,0 +1,142 @@ +/* $OpenBSD: tests.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */ +/* + * Regress test for sshbuf.h buffer API + * + * Placed in the public domain + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef WITH_OPENSSL +#include <openssl/evp.h> +#include <openssl/crypto.h> +#endif + +#include "ssherr.h" +#include "authfile.h" +#include "sshkey.h" +#include "sshbuf.h" +#include "sshsig.h" +#include "log.h" + +#include "../test_helper/test_helper.h" + +static struct sshbuf * +load_file(const char *name) +{ + struct sshbuf *ret = NULL; + + ASSERT_INT_EQ(sshbuf_load_file(test_data_file(name), &ret), 0); + ASSERT_PTR_NE(ret, NULL); + return ret; +} + +static struct sshkey * +load_key(const char *name) +{ + struct sshkey *ret = NULL; + ASSERT_INT_EQ(sshkey_load_public(test_data_file(name), &ret, NULL), 0); + ASSERT_PTR_NE(ret, NULL); + return ret; +} + +static void +check_sig(const char *keyname, const char *signame, const struct sshbuf *msg, + const char *namespace) +{ + struct sshkey *k, *sign_key; + struct sshbuf *sig, *rawsig; + struct sshkey_sig_details *sig_details; + + k = load_key(keyname); + sig = load_file(signame); + sign_key = NULL; + sig_details = NULL; + rawsig = NULL; + ASSERT_INT_EQ(sshsig_dearmor(sig, &rawsig), 0); + ASSERT_INT_EQ(sshsig_verifyb(rawsig, msg, namespace, + &sign_key, &sig_details), 0); + ASSERT_INT_EQ(sshkey_equal(k, sign_key), 1); + sshkey_free(k); + sshkey_free(sign_key); + sshkey_sig_details_free(sig_details); + sshbuf_free(sig); + sshbuf_free(rawsig); +} + +void +tests(void) +{ + struct sshbuf *msg; + char *namespace; + +#if 0 + log_init("test_sshsig", SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_AUTH, 1); +#endif + +#ifdef WITH_OPENSSL + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); +#endif + + TEST_START("load data"); + msg = load_file("namespace"); + namespace = sshbuf_dup_string(msg); + ASSERT_PTR_NE(namespace, NULL); + sshbuf_free(msg); + msg = load_file("signed-data"); + TEST_DONE(); + +#ifdef WITH_OPENSSL + TEST_START("check RSA signature"); + check_sig("rsa.pub", "rsa.sig", msg, namespace); + TEST_DONE(); + + TEST_START("check DSA signature"); + check_sig("dsa.pub", "dsa.sig", msg, namespace); + TEST_DONE(); + +#ifdef OPENSSL_HAS_ECC + TEST_START("check ECDSA signature"); + check_sig("ecdsa.pub", "ecdsa.sig", msg, namespace); + TEST_DONE(); +#endif +#endif + + TEST_START("check ED25519 signature"); + check_sig("ed25519.pub", "ed25519.sig", msg, namespace); + TEST_DONE(); + +#ifdef ENABLE_SK +#if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) + TEST_START("check ECDSA-SK signature"); + check_sig("ecdsa_sk.pub", "ecdsa_sk.sig", msg, namespace); + TEST_DONE(); +#endif + + TEST_START("check ED25519-SK signature"); + check_sig("ed25519_sk.pub", "ed25519_sk.sig", msg, namespace); + TEST_DONE(); + +#if defined(WITH_OPENSSL) && defined(OPENSSL_HAS_ECC) + TEST_START("check ECDSA-SK webauthn signature"); + check_sig("ecdsa_sk_webauthn.pub", "ecdsa_sk_webauthn.sig", + msg, namespace); + TEST_DONE(); +#endif +#endif /* ENABLE_SK */ + + sshbuf_free(msg); + free(namespace); +} diff --git a/regress/unittests/sshsig/webauthn.html b/regress/unittests/sshsig/webauthn.html new file mode 100644 index 0000000..5c9a32e --- /dev/null +++ b/regress/unittests/sshsig/webauthn.html @@ -0,0 +1,766 @@ +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> +<title>webauthn test</title> +</head> +<body onload="init()"> +<h1>webauthn test</h1> +<p> +This is a demo/test page for generating FIDO keys and signatures in SSH +formats. The page initially displays a form to generate a FIDO key and +convert it to a SSH public key. +</p> +<p> +Once a key has been generated, an additional form will be displayed to +allow signing of data using the just-generated key. The data may be signed +as either a raw SSH signature or wrapped in a sshsig message (the latter is +easier to test using command-line tools. +</p> +<p> +Lots of debugging is printed along the way. +</p> +<h2>Enroll</h2> +<span id="error" style="color: #800; font-weight: bold; font-size: 150%;"></span> +<form id="enrollform"> +<table> +<tr> +<td><b>Username:</b></td> +<td><input id="username" type="text" size="20" name="user" value="test" /></td> +</tr> +<tr><td></td><td><input id="assertsubmit" type="submit" value="submit" /></td></tr> +</table> +</form> +<span id="enrollresult" style="visibility: hidden;"> +<h2>clientData</h2> +<pre id="enrollresultjson" style="color: #008; font-family: monospace;"></pre> +<h2>attestationObject raw</h2> +<pre id="enrollresultraw" style="color: #008; font-family: monospace;"></pre> +<h2>attestationObject</h2> +<pre id="enrollresultattestobj" style="color: #008; font-family: monospace;"></pre> +<h2>key handle</h2> +<pre id="keyhandle" style="color: #008; font-family: monospace;"></pre> +<h2>authData raw</h2> +<pre id="enrollresultauthdataraw" style="color: #008; font-family: monospace;"></pre> +<h2>authData</h2> +<pre id="enrollresultauthdata" style="color: #008; font-family: monospace;"></pre> +<h2>SSH pubkey blob</h2> +<pre id="enrollresultpkblob" style="color: #008; font-family: monospace;"></pre> +<h2>SSH pubkey string</h2> +<pre id="enrollresultpk" style="color: #008; font-family: monospace;"></pre> +<h2>SSH private key string</h2> +<pre id="enrollresultprivkey" style="color: #008; font-family: monospace;"></pre> +</span> +<span id="assertsection" style="visibility: hidden;"> +<h2>Assert</h2> +<form id="assertform"> +<span id="asserterror" style="color: #800; font-weight: bold;"></span> +<table> +<tr> +<td><b>Data to sign:</b></td> +<td><input id="message" type="text" size="20" name="message" value="test" /></td> +</tr> +<tr> +<td><input id="message_sshsig" type="checkbox" checked /> use sshsig format</td> +</tr> +<tr> +<td><b>Signature namespace:</b></td> +<td><input id="message_namespace" type="text" size="20" name="namespace" value="test" /></td> +</tr> +<tr><td></td><td><input type="submit" value="submit" /></td></tr> +</table> +</form> +</span> +<span id="assertresult" style="visibility: hidden;"> +<h2>clientData</h2> +<pre id="assertresultjson" style="color: #008; font-family: monospace;"></pre> +<h2>signature raw</h2> +<pre id="assertresultsigraw" style="color: #008; font-family: monospace;"></pre> +<h2>authenticatorData raw</h2> +<pre id="assertresultauthdataraw" style="color: #008; font-family: monospace;"></pre> +<h2>authenticatorData</h2> +<pre id="assertresultauthdata" style="color: #008; font-family: monospace;"></pre> +<h2>signature in SSH format</h2> +<pre id="assertresultsshsigraw" style="color: #008; font-family: monospace;"></pre> +<h2>signature in SSH format (base64 encoded)</h2> +<pre id="assertresultsshsigb64" style="color: #008; font-family: monospace;"></pre> +</span> +</body> +<script> +// ------------------------------------------------------------------ +// a crappy CBOR decoder - 20200401 djm@openbsd.org + +var CBORDecode = function(buffer) { + this.buf = buffer + this.v = new DataView(buffer) + this.offset = 0 +} + +CBORDecode.prototype.empty = function() { + return this.offset >= this.buf.byteLength +} + +CBORDecode.prototype.getU8 = function() { + let r = this.v.getUint8(this.offset) + this.offset += 1 + return r +} + +CBORDecode.prototype.getU16 = function() { + let r = this.v.getUint16(this.offset) + this.offset += 2 + return r +} + +CBORDecode.prototype.getU32 = function() { + let r = this.v.getUint32(this.offset) + this.offset += 4 + return r +} + +CBORDecode.prototype.getU64 = function() { + let r = this.v.getUint64(this.offset) + this.offset += 8 + return r +} + +CBORDecode.prototype.getCBORTypeLen = function() { + let tl, t, l + tl = this.getU8() + t = (tl & 0xe0) >> 5 + l = tl & 0x1f + return [t, this.decodeInteger(l)] +} + +CBORDecode.prototype.decodeInteger = function(len) { + switch (len) { + case 0x18: return this.getU8() + case 0x19: return this.getU16() + case 0x20: return this.getU32() + case 0x21: return this.getU64() + default: + if (len <= 23) { + return len + } + throw new Error("Unsupported int type 0x" + len.toString(16)) + } +} + +CBORDecode.prototype.decodeNegint = function(len) { + let r = -(this.decodeInteger(len) + 1) + return r +} + +CBORDecode.prototype.decodeByteString = function(len) { + let r = this.buf.slice(this.offset, this.offset + len) + this.offset += len + return r +} + +CBORDecode.prototype.decodeTextString = function(len) { + let u8dec = new TextDecoder('utf-8') + r = u8dec.decode(this.decodeByteString(len)) + return r +} + +CBORDecode.prototype.decodeArray = function(len, level) { + let r = [] + for (let i = 0; i < len; i++) { + let v = this.decodeInternal(level) + r.push(v) + // console.log("decodeArray level " + level.toString() + " index " + i.toString() + " value " + JSON.stringify(v)) + } + return r +} + +CBORDecode.prototype.decodeMap = function(len, level) { + let r = {} + for (let i = 0; i < len; i++) { + let k = this.decodeInternal(level) + let v = this.decodeInternal(level) + r[k] = v + // console.log("decodeMap level " + level.toString() + " key " + k.toString() + " value " + JSON.stringify(v)) + // XXX check string keys, duplicates + } + return r +} + +CBORDecode.prototype.decodePrimitive = function(t) { + switch (t) { + case 20: return false + case 21: return true + case 22: return null + case 23: return undefined + default: + throw new Error("Unsupported primitive 0x" + t.toString(2)) + } +} + +CBORDecode.prototype.decodeInternal = function(level) { + if (level > 256) { + throw new Error("CBOR nesting too deep") + } + let t, l, r + [t, l] = this.getCBORTypeLen() + // console.log("decode level " + level.toString() + " type " + t.toString() + " len " + l.toString()) + switch (t) { + case 0: + r = this.decodeInteger(l) + break + case 1: + r = this.decodeNegint(l) + break + case 2: + r = this.decodeByteString(l) + break + case 3: + r = this.decodeTextString(l) + break + case 4: + r = this.decodeArray(l, level + 1) + break + case 5: + r = this.decodeMap(l, level + 1) + break + case 6: + console.log("XXX ignored semantic tag " + this.decodeInteger(l).toString()) + break; + case 7: + r = this.decodePrimitive(l) + break + default: + throw new Error("Unsupported type 0x" + t.toString(2) + " len " + l.toString()) + } + // console.log("decode level " + level.toString() + " value " + JSON.stringify(r)) + return r +} + +CBORDecode.prototype.decode = function() { + return this.decodeInternal(0) +} + +// ------------------------------------------------------------------ +// a crappy SSH message packer - 20200401 djm@openbsd.org + +var SSHMSG = function() { + this.r = [] +} + +SSHMSG.prototype.length = function() { + let len = 0 + for (buf of this.r) { + len += buf.length + } + return len +} + +SSHMSG.prototype.serialise = function() { + let r = new ArrayBuffer(this.length()) + let v = new Uint8Array(r) + let offset = 0 + for (buf of this.r) { + v.set(buf, offset) + offset += buf.length + } + if (offset != r.byteLength) { + throw new Error("djm can't count") + } + return r +} + +SSHMSG.prototype.serialiseBase64 = function(v) { + let b = this.serialise() + return btoa(String.fromCharCode(...new Uint8Array(b))); +} + +SSHMSG.prototype.putU8 = function(v) { + this.r.push(new Uint8Array([v])) +} + +SSHMSG.prototype.putU32 = function(v) { + this.r.push(new Uint8Array([ + (v >> 24) & 0xff, + (v >> 16) & 0xff, + (v >> 8) & 0xff, + (v & 0xff) + ])) +} + +SSHMSG.prototype.put = function(v) { + this.r.push(new Uint8Array(v)) +} + +SSHMSG.prototype.putStringRaw = function(v) { + let enc = new TextEncoder(); + let venc = enc.encode(v) + this.put(venc) +} + +SSHMSG.prototype.putString = function(v) { + let enc = new TextEncoder(); + let venc = enc.encode(v) + this.putU32(venc.length) + this.put(venc) +} + +SSHMSG.prototype.putSSHMSG = function(v) { + let msg = v.serialise() + this.putU32(msg.byteLength) + this.put(msg) +} + +SSHMSG.prototype.putBytes = function(v) { + this.putU32(v.byteLength) + this.put(v) +} + +SSHMSG.prototype.putECPoint = function(x, y) { + let x8 = new Uint8Array(x) + let y8 = new Uint8Array(y) + this.putU32(1 + x8.length + y8.length) + this.putU8(0x04) // Uncompressed point format. + this.put(x8) + this.put(y8) +} + +// ------------------------------------------------------------------ +// webauthn to SSH glue - djm@openbsd.org 20200408 + +function error(msg, ...args) { + document.getElementById("error").innerText = msg + console.log(msg) + for (const arg of args) { + console.dir(arg) + } +} +function hexdump(buf) { + const hex = Array.from(new Uint8Array(buf)).map( + b => b.toString(16).padStart(2, "0")) + const fmt = new Array() + for (let i = 0; i < hex.length; i++) { + if ((i % 16) == 0) { + // Prepend length every 16 bytes. + fmt.push(i.toString(16).padStart(4, "0")) + fmt.push(" ") + } + fmt.push(hex[i]) + fmt.push(" ") + if ((i % 16) == 15) { + fmt.push("\n") + } + } + return fmt.join("") +} +function enrollform_submit(event) { + event.preventDefault(); + console.log("submitted") + username = event.target.elements.username.value + if (username === "") { + error("no username specified") + return false + } + enrollStart(username) +} +function enrollStart(username) { + let challenge = new Uint8Array(32) + window.crypto.getRandomValues(challenge) + let userid = new Uint8Array(8) + window.crypto.getRandomValues(userid) + + console.log("challenge:" + btoa(challenge)) + console.log("userid:" + btoa(userid)) + + let pkopts = { + challenge: challenge, + rp: { + name: window.location.host, + id: window.location.host, + }, + user: { + id: userid, + name: username, + displayName: username, + }, + authenticatorSelection: { + authenticatorAttachment: "cross-platform", + userVerification: "discouraged", + }, + pubKeyCredParams: [{alg: -7, type: "public-key"}], // ES256 + timeout: 30 * 1000, + }; + console.dir(pkopts) + window.enrollOpts = pkopts + let credpromise = navigator.credentials.create({ publicKey: pkopts }); + credpromise.then(enrollSuccess, enrollFailure) +} +function enrollFailure(result) { + error("Enroll failed", result) +} +function enrollSuccess(result) { + console.log("Enroll succeeded") + console.dir(result) + window.enrollResult = result + document.getElementById("enrollresult").style.visibility = "visible" + + // Show the clientData + let u8dec = new TextDecoder('utf-8') + clientData = u8dec.decode(result.response.clientDataJSON) + document.getElementById("enrollresultjson").innerText = clientData + + // Show the raw key handle. + document.getElementById("keyhandle").innerText = hexdump(result.rawId) + + // Decode and show the attestationObject + document.getElementById("enrollresultraw").innerText = hexdump(result.response.attestationObject) + let aod = new CBORDecode(result.response.attestationObject) + let attestationObject = aod.decode() + console.log("attestationObject") + console.dir(attestationObject) + document.getElementById("enrollresultattestobj").innerText = JSON.stringify(attestationObject) + + // Decode and show the authData + document.getElementById("enrollresultauthdataraw").innerText = hexdump(attestationObject.authData) + let authData = decodeAuthenticatorData(attestationObject.authData, true) + console.log("authData") + console.dir(authData) + window.enrollAuthData = authData + document.getElementById("enrollresultauthdata").innerText = JSON.stringify(authData) + + // Reformat the pubkey as a SSH key for easy verification + window.rawKey = reformatPubkey(authData.attestedCredentialData.credentialPublicKey, window.enrollOpts.rp.id) + console.log("SSH pubkey blob") + console.dir(window.rawKey) + document.getElementById("enrollresultpkblob").innerText = hexdump(window.rawKey) + let pk64 = btoa(String.fromCharCode(...new Uint8Array(window.rawKey))); + let pk = "sk-ecdsa-sha2-nistp256@openssh.com " + pk64 + document.getElementById("enrollresultpk").innerText = pk + + // Format a private key too. + flags = 0x01 // SSH_SK_USER_PRESENCE_REQD + window.rawPrivkey = reformatPrivkey(authData.attestedCredentialData.credentialPublicKey, window.enrollOpts.rp.id, result.rawId, flags) + let privkeyFileBlob = privkeyFile(window.rawKey, window.rawPrivkey, window.enrollOpts.user.name, window.enrollOpts.rp.id) + let privk64 = btoa(String.fromCharCode(...new Uint8Array(privkeyFileBlob))); + let privkey = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + wrapString(privk64, 70) + "-----END OPENSSH PRIVATE KEY-----\n" + document.getElementById("enrollresultprivkey").innerText = privkey + + // Success: show the assertion form. + document.getElementById("assertsection").style.visibility = "visible" +} + +function decodeAuthenticatorData(authData, expectCred) { + let r = new Object() + let v = new DataView(authData) + + r.rpIdHash = authData.slice(0, 32) + r.flags = v.getUint8(32) + r.signCount = v.getUint32(33) + + // Decode attestedCredentialData if present. + let offset = 37 + let acd = new Object() + if (expectCred) { + acd.aaguid = authData.slice(offset, offset+16) + offset += 16 + let credentialIdLength = v.getUint16(offset) + offset += 2 + acd.credentialIdLength = credentialIdLength + acd.credentialId = authData.slice(offset, offset+credentialIdLength) + offset += credentialIdLength + r.attestedCredentialData = acd + } + console.log("XXXXX " + offset.toString()) + let pubkeyrest = authData.slice(offset, authData.byteLength) + let pkdecode = new CBORDecode(pubkeyrest) + if (expectCred) { + // XXX unsafe: doesn't mandate COSE canonical format. + acd.credentialPublicKey = pkdecode.decode() + } + if (!pkdecode.empty()) { + // Decode extensions if present. + r.extensions = pkdecode.decode() + } + return r +} + +function wrapString(s, l) { + ret = "" + for (i = 0; i < s.length; i += l) { + ret += s.slice(i, i + l) + "\n" + } + return ret +} + +function checkPubkey(pk) { + // pk is in COSE format. We only care about a tiny subset. + if (pk[1] != 2) { + console.dir(pk) + throw new Error("pubkey is not EC") + } + if (pk[-1] != 1) { + throw new Error("pubkey is not in P256") + } + if (pk[3] != -7) { + throw new Error("pubkey is not ES256") + } + if (pk[-2].byteLength != 32 || pk[-3].byteLength != 32) { + throw new Error("pubkey EC coords have bad length") + } +} + +function reformatPubkey(pk, rpid) { + checkPubkey(pk) + let msg = new SSHMSG() + msg.putString("sk-ecdsa-sha2-nistp256@openssh.com") // Key type + msg.putString("nistp256") // Key curve + msg.putECPoint(pk[-2], pk[-3]) // EC key + msg.putString(rpid) // RP ID + return msg.serialise() +} + +function reformatPrivkey(pk, rpid, kh, flags) { + checkPubkey(pk) + let msg = new SSHMSG() + msg.putString("sk-ecdsa-sha2-nistp256@openssh.com") // Key type + msg.putString("nistp256") // Key curve + msg.putECPoint(pk[-2], pk[-3]) // EC key + msg.putString(rpid) // RP ID + msg.putU8(flags) // flags + msg.putBytes(kh) // handle + msg.putString("") // reserved + return msg.serialise() +} + +function privkeyFile(pub, priv, user, rp) { + let innerMsg = new SSHMSG() + innerMsg.putU32(0xdeadbeef) // check byte + innerMsg.putU32(0xdeadbeef) // check byte + innerMsg.put(priv) // privkey + innerMsg.putString("webauthn.html " + user + "@" + rp) // comment + // Pad to cipher blocksize (8). + p = 1 + while (innerMsg.length() % 8 != 0) { + innerMsg.putU8(p++) + } + let msg = new SSHMSG() + msg.putStringRaw("openssh-key-v1") // Magic + msg.putU8(0) // \0 terminate + msg.putString("none") // cipher + msg.putString("none") // KDF + msg.putString("") // KDF options + msg.putU32(1) // nkeys + msg.putBytes(pub) // pubkey + msg.putSSHMSG(innerMsg) // inner + //msg.put(innerMsg.serialise()) // inner + return msg.serialise() +} + +async function assertform_submit(event) { + event.preventDefault(); + console.log("submitted") + message = event.target.elements.message.value + if (message === "") { + error("no message specified") + return false + } + let enc = new TextEncoder() + let encmsg = enc.encode(message) + window.assertSignRaw = !event.target.elements.message_sshsig.checked + console.log("using sshsig ", !window.assertSignRaw) + if (window.assertSignRaw) { + assertStart(encmsg) + return + } + // Format a sshsig-style message. + window.sigHashAlg = "sha512" + let msghash = await crypto.subtle.digest("SHA-512", encmsg); + console.log("raw message hash") + console.dir(msghash) + window.sigNamespace = event.target.elements.message_namespace.value + let sigbuf = new SSHMSG() + sigbuf.put(enc.encode("SSHSIG")) + sigbuf.putString(window.sigNamespace) + sigbuf.putU32(0) // Reserved string + sigbuf.putString(window.sigHashAlg) + sigbuf.putBytes(msghash) + let msg = sigbuf.serialise() + console.log("sigbuf") + console.dir(msg) + assertStart(msg) +} + +function assertStart(message) { + let assertReqOpts = { + challenge: message, + rpId: window.location.host, + allowCredentials: [{ + type: 'public-key', + id: window.enrollResult.rawId, + }], + userVerification: "discouraged", + timeout: (30 * 1000), + } + console.log("assertReqOpts") + console.dir(assertReqOpts) + window.assertReqOpts = assertReqOpts + let assertpromise = navigator.credentials.get({ + publicKey: assertReqOpts + }); + assertpromise.then(assertSuccess, assertFailure) +} +function assertFailure(result) { + error("Assertion failed", result) +} +function linewrap(s) { + const linelen = 70 + let ret = "" + for (let i = 0; i < s.length; i += linelen) { + end = i + linelen + if (end > s.length) { + end = s.length + } + if (i > 0) { + ret += "\n" + } + ret += s.slice(i, end) + } + return ret + "\n" +} +function assertSuccess(result) { + console.log("Assertion succeeded") + console.dir(result) + window.assertResult = result + document.getElementById("assertresult").style.visibility = "visible" + + // show the clientData. + let u8dec = new TextDecoder('utf-8') + clientData = u8dec.decode(result.response.clientDataJSON) + document.getElementById("assertresultjson").innerText = clientData + + // show the signature. + document.getElementById("assertresultsigraw").innerText = hexdump(result.response.signature) + + // decode and show the authData. + document.getElementById("assertresultauthdataraw").innerText = hexdump(result.response.authenticatorData) + authData = decodeAuthenticatorData(result.response.authenticatorData, false) + document.getElementById("assertresultauthdata").innerText = JSON.stringify(authData) + + // Parse and reformat the signature to an SSH style signature. + let sshsig = reformatSignature(result.response.signature, clientData, authData) + document.getElementById("assertresultsshsigraw").innerText = hexdump(sshsig) + let sig64 = btoa(String.fromCharCode(...new Uint8Array(sshsig))); + if (window.assertSignRaw) { + document.getElementById("assertresultsshsigb64").innerText = sig64 + } else { + document.getElementById("assertresultsshsigb64").innerText = + "-----BEGIN SSH SIGNATURE-----\n" + linewrap(sig64) + + "-----END SSH SIGNATURE-----\n"; + } +} + +function reformatSignature(sig, clientData, authData) { + if (sig.byteLength < 2) { + throw new Error("signature is too short") + } + let offset = 0 + let v = new DataView(sig) + // Expect an ASN.1 SEQUENCE that exactly spans the signature. + if (v.getUint8(offset) != 0x30) { + throw new Error("signature not an ASN.1 sequence") + } + offset++ + let seqlen = v.getUint8(offset) + offset++ + if ((seqlen & 0x80) != 0 || seqlen != sig.byteLength - offset) { + throw new Error("signature has unexpected length " + seqlen.toString() + " vs expected " + (sig.byteLength - offset).toString()) + } + + // Parse 'r' INTEGER value. + if (v.getUint8(offset) != 0x02) { + throw new Error("signature r not an ASN.1 integer") + } + offset++ + let rlen = v.getUint8(offset) + offset++ + if ((rlen & 0x80) != 0 || rlen > sig.byteLength - offset) { + throw new Error("signature r has unexpected length " + rlen.toString() + " vs buffer " + (sig.byteLength - offset).toString()) + } + let r = sig.slice(offset, offset + rlen) + offset += rlen + console.log("sig_r") + console.dir(r) + + // Parse 's' INTEGER value. + if (v.getUint8(offset) != 0x02) { + throw new Error("signature r not an ASN.1 integer") + } + offset++ + let slen = v.getUint8(offset) + offset++ + if ((slen & 0x80) != 0 || slen > sig.byteLength - offset) { + throw new Error("signature s has unexpected length " + slen.toString() + " vs buffer " + (sig.byteLength - offset).toString()) + } + let s = sig.slice(offset, offset + slen) + console.log("sig_s") + console.dir(s) + offset += slen + + if (offset != sig.byteLength) { + throw new Error("unexpected final offset during signature parsing " + offset.toString() + " expected " + sig.byteLength.toString()) + } + + // Reformat as an SSH signature. + let clientDataParsed = JSON.parse(clientData) + let innersig = new SSHMSG() + innersig.putBytes(r) + innersig.putBytes(s) + + let rawsshsig = new SSHMSG() + rawsshsig.putString("webauthn-sk-ecdsa-sha2-nistp256@openssh.com") + rawsshsig.putSSHMSG(innersig) + rawsshsig.putU8(authData.flags) + rawsshsig.putU32(authData.signCount) + rawsshsig.putString(clientDataParsed.origin) + rawsshsig.putString(clientData) + if (authData.extensions == undefined) { + rawsshsig.putU32(0) + } else { + rawsshsig.putBytes(authData.extensions) + } + + if (window.assertSignRaw) { + return rawsshsig.serialise() + } + // Format as SSHSIG. + let enc = new TextEncoder() + let sshsig = new SSHMSG() + sshsig.put(enc.encode("SSHSIG")) + sshsig.putU32(0x01) // Signature version. + sshsig.putBytes(window.rawKey) + sshsig.putString(window.sigNamespace) + sshsig.putU32(0) // Reserved string + sshsig.putString(window.sigHashAlg) + sshsig.putBytes(rawsshsig.serialise()) + return sshsig.serialise() +} + +function toggleNamespaceVisibility() { + const assertsigtype = document.getElementById('message_sshsig'); + const assertsignamespace = document.getElementById('message_namespace'); + assertsignamespace.disabled = !assertsigtype.checked; +} + +function init() { + if (document.location.protocol != "https:") { + error("This page must be loaded via https") + const assertsubmit = document.getElementById('assertsubmit') + assertsubmit.disabled = true + } + const enrollform = document.getElementById('enrollform'); + enrollform.addEventListener('submit', enrollform_submit); + const assertform = document.getElementById('assertform'); + assertform.addEventListener('submit', assertform_submit); + const assertsigtype = document.getElementById('message_sshsig'); + assertsigtype.onclick = toggleNamespaceVisibility; +} +</script> + +</html> |