From 6202ffc3fa4ffd0ab4f62535a0526792571f76e7 Mon Sep 17 00:00:00 2001 From: Vivien Kraus Date: Wed, 1 Jan 2020 00:00:00 +0100 Subject: Add a signature and verification function for JWS --- ChangeLog | 1 + NEWS | 3 + doc/webid-oidc.texi | 4 + po/fr.po | 68 ++++----- po/webid-oidc.pot | 60 ++++---- src/ChangeLog | 4 + src/Makefile.am | 1 + src/jws/Makefile.am | 11 ++ src/jws/libwebidoidc-jws.c | 320 ++++++++++++++++++++++++++++++++++++++++++ src/libwebidoidc.c | 2 + src/scm/webid-oidc/errors.scm | 16 +++ src/scm/webid-oidc/stubs.scm | 21 ++- src/utilities.h | 18 +-- tests/Makefile.am | 4 +- tests/verification-failed.scm | 24 ++++ tests/verify.scm | 15 ++ 16 files changed, 503 insertions(+), 69 deletions(-) create mode 100644 src/jws/Makefile.am create mode 100644 src/jws/libwebidoidc-jws.c create mode 100644 tests/verification-failed.scm create mode 100644 tests/verify.scm diff --git a/ChangeLog b/ChangeLog index 10c78c8..e916ee5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,7 @@ (Strip a public key): Update NEWS (Hash some data): Update NEWS (Hash a key): Update NEWS + (Sign and verify signatures): update NEWS 2020-11-22 Vivien Kraus diff --git a/NEWS b/NEWS index 9c2eb24..8148e14 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,9 @@ The function =hash= takes a string, and hashes its UTF-8 encoding. In DPoP, the identity provider hashes the client's key in the access token so that resource servers can verify that the client uses the correct key. +** Sign and verify signatures +The function =sign= creates a signature with a known JWA, and =verify= +verifies the signature. # Local Variables: # mode: org # End: diff --git a/doc/webid-oidc.texi b/doc/webid-oidc.texi index 46ca253..a435c33 100644 --- a/doc/webid-oidc.texi +++ b/doc/webid-oidc.texi @@ -167,6 +167,10 @@ The identifier @var{crv} does not identify an elliptic curve. @var{value} does not identify a valid hash algorithm. @end deftp +@deftp {exception type} &invalid-signature @var{key} @var{payload} @var{signature} +@var{key} has not signed @var{payload} with @var{signature}. +@end deftp + @node GNU Free Documentation License @appendix GNU Free Documentation License diff --git a/po/fr.po b/po/fr.po index 096e5c6..66fe7b1 100644 --- a/po/fr.po +++ b/po/fr.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: webid-oidc 0.0.0\n" "Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n" -"POT-Creation-Date: 2021-06-05 16:11+0200\n" +"POT-Creation-Date: 2021-06-05 16:12+0200\n" "PO-Revision-Date: 2021-06-05 11:07+0200\n" "Last-Translator: Vivien Kraus \n" "Language-Team: French \n" @@ -12,7 +12,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: src/libwebidoidc.c:10 +#: src/libwebidoidc.c:11 msgid "This is the main function." msgstr "Ceci est la fonction principale." @@ -126,112 +126,122 @@ msgstr "Utilisation : generate-random [NOMBRE D'OCTETS]\n" msgid "Usage: generate-key [NUMBER OF BITS | CURVE]\n" msgstr "Utilisation : generate-key [NOMBRE DE BITS | COURBE]\n" -#: src/scm/webid-oidc/errors.scm:105 +#: src/scm/webid-oidc/errors.scm:115 msgid "that’s how it is" msgstr "c’est comme ça" -#: src/scm/webid-oidc/errors.scm:110 +#: src/scm/webid-oidc/errors.scm:120 #, scheme-format msgid "the value ~s is not a base64 string (because ~a)" msgstr "la valeur ~s n’est pas une chaîne base64 (parce que ~a)" -#: src/scm/webid-oidc/errors.scm:113 +#: src/scm/webid-oidc/errors.scm:123 #, scheme-format msgid "the value ~s is not JSON (because ~a)" msgstr "la valeur ~s n’est pas du JSON (parce que ~a)" -#: src/scm/webid-oidc/errors.scm:116 +#: src/scm/webid-oidc/errors.scm:126 #, scheme-format msgid "the value ~s does not identify an elleptic curve" msgstr "la valeur ~s n’identifie pas une courbe elliptique" -#: src/scm/webid-oidc/errors.scm:121 +#: src/scm/webid-oidc/errors.scm:131 #, scheme-format msgid "the value ~s does not identify a JWK (because ~a)" msgstr "la valeur ~s n’identifie pas une JWK (parce que ~a)" -#: src/scm/webid-oidc/errors.scm:123 +#: src/scm/webid-oidc/errors.scm:133 #, scheme-format msgid "the value ~s does not identify a JWK" msgstr "la valeur ~s n’identifie pas une JWK" -#: src/scm/webid-oidc/errors.scm:128 +#: src/scm/webid-oidc/errors.scm:138 #, scheme-format msgid "the value ~s does not identify a public JWK (because ~a)" msgstr "la valeur ~s n’identifie pas une JWK publique (parce que ~a)" -#: src/scm/webid-oidc/errors.scm:130 +#: src/scm/webid-oidc/errors.scm:140 #, scheme-format msgid "the value ~s does not identify a public JWK" msgstr "la valeur ~s n’identifie pas une JWK publique" -#: src/scm/webid-oidc/errors.scm:135 +#: src/scm/webid-oidc/errors.scm:145 #, scheme-format msgid "the value ~s does not identify a private JWK (because ~a)" msgstr "la valeur ~s n’identifie pas une JWK privée (parce que ~a)" -#: src/scm/webid-oidc/errors.scm:137 +#: src/scm/webid-oidc/errors.scm:147 #, scheme-format msgid "the value ~s does not identify a private JWK" msgstr "la valeur ~s n’identifie pas une JWK privée" -#: src/scm/webid-oidc/errors.scm:142 +#: src/scm/webid-oidc/errors.scm:152 #, scheme-format msgid "the value ~s does not identify a JWKS (because ~a)" msgstr "la valeur ~s n’identifie pas un JWKS (parce que ~a)" -#: src/scm/webid-oidc/errors.scm:144 +#: src/scm/webid-oidc/errors.scm:154 #, scheme-format msgid "the value ~s does not identify a JWKS" msgstr "la valeur ~s n’identifie pas un JWKS" -#: src/scm/webid-oidc/errors.scm:147 +#: src/scm/webid-oidc/errors.scm:157 #, scheme-format msgid "the value ~s does not identify a hash algorithm" msgstr "la valeur ~s n’identifie pas un algorithme de hachage" -#: src/scm/webid-oidc/errors.scm:152 +#: src/scm/webid-oidc/errors.scm:162 msgid "that’s it" msgstr "c’est tout" -#: src/scm/webid-oidc/errors.scm:156 +#: src/scm/webid-oidc/errors.scm:166 #, scheme-format msgid "~a and ~a" msgstr "~a et ~a" -#: src/scm/webid-oidc/errors.scm:159 +#: src/scm/webid-oidc/errors.scm:169 #, scheme-format msgid "~a, ~a" msgstr "~a, ~a" -#: src/scm/webid-oidc/errors.scm:163 +#: src/scm/webid-oidc/errors.scm:173 +#, scheme-format +msgid "the signature ~a does not match key ~s with payload ~a" +msgstr "la signature ~a ne correspond pas à la clé ~s avec le contenu ~a" + +#: src/scm/webid-oidc/errors.scm:176 msgid "there is an undefined variable" msgstr "il y a une variable non définie" -#: src/scm/webid-oidc/errors.scm:165 +#: src/scm/webid-oidc/errors.scm:178 #, scheme-format msgid "the origin is ~a" msgstr "l’origine est ~a" -#: src/scm/webid-oidc/errors.scm:168 +#: src/scm/webid-oidc/errors.scm:181 #, scheme-format msgid "a message is attached: ~a" msgstr "un message est attaché : ~a" -#: src/scm/webid-oidc/errors.scm:171 +#: src/scm/webid-oidc/errors.scm:184 #, scheme-format msgid "the values ~s are problematic" msgstr "les valeurs ~s sont problématiques" -#: src/scm/webid-oidc/errors.scm:174 +#: src/scm/webid-oidc/errors.scm:187 msgid "there is a kind and args" msgstr "il y a un type et des arguments" -#: src/scm/webid-oidc/errors.scm:176 +#: src/scm/webid-oidc/errors.scm:189 msgid "there is an assertion failure" msgstr "il y a un échec d’assertion" -#: src/scm/webid-oidc/errors.scm:178 +#: src/scm/webid-oidc/errors.scm:191 +#, scheme-format +msgid "the program quits with code ~a" +msgstr "le programme quitte avec le code ~a" + +#: src/scm/webid-oidc/errors.scm:194 #, scheme-format msgid "Unhandled exception type ~a." msgstr "Type d’exception non pris en charge ~a." @@ -678,14 +688,6 @@ msgstr "Type d’exception non pris en charge ~a." #~ msgid "the container ~s should be emptied before being deleted" #~ msgstr "le conteneur ~s doit être vidé avant d’être détruit" -#, scheme-format -#~ msgid "the signature ~a does not match key ~s with payload ~a" -#~ msgstr "la signature ~a ne correspond pas à la clé ~s avec le contenu ~a" - -#, scheme-format -#~ msgid "the program quits with code ~a" -#~ msgstr "le programme quitte avec le code ~a" - #~ msgid "the program cannot recover from this exception" #~ msgstr "le programme ne peut pas récupérer après cette exception" diff --git a/po/webid-oidc.pot b/po/webid-oidc.pot index 606b5e0..29153a4 100644 --- a/po/webid-oidc.pot +++ b/po/webid-oidc.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: webid-oidc SNAPSHOT\n" "Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n" -"POT-Creation-Date: 2021-06-05 16:11+0200\n" +"POT-Creation-Date: 2021-06-05 16:12+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/libwebidoidc.c:10 +#: src/libwebidoidc.c:11 msgid "This is the main function." msgstr "" @@ -122,112 +122,122 @@ msgstr "" msgid "Usage: generate-key [NUMBER OF BITS | CURVE]\n" msgstr "" -#: src/scm/webid-oidc/errors.scm:105 +#: src/scm/webid-oidc/errors.scm:115 msgid "that’s how it is" msgstr "" -#: src/scm/webid-oidc/errors.scm:110 +#: src/scm/webid-oidc/errors.scm:120 #, scheme-format msgid "the value ~s is not a base64 string (because ~a)" msgstr "" -#: src/scm/webid-oidc/errors.scm:113 +#: src/scm/webid-oidc/errors.scm:123 #, scheme-format msgid "the value ~s is not JSON (because ~a)" msgstr "" -#: src/scm/webid-oidc/errors.scm:116 +#: src/scm/webid-oidc/errors.scm:126 #, scheme-format msgid "the value ~s does not identify an elleptic curve" msgstr "" -#: src/scm/webid-oidc/errors.scm:121 +#: src/scm/webid-oidc/errors.scm:131 #, scheme-format msgid "the value ~s does not identify a JWK (because ~a)" msgstr "" -#: src/scm/webid-oidc/errors.scm:123 +#: src/scm/webid-oidc/errors.scm:133 #, scheme-format msgid "the value ~s does not identify a JWK" msgstr "" -#: src/scm/webid-oidc/errors.scm:128 +#: src/scm/webid-oidc/errors.scm:138 #, scheme-format msgid "the value ~s does not identify a public JWK (because ~a)" msgstr "" -#: src/scm/webid-oidc/errors.scm:130 +#: src/scm/webid-oidc/errors.scm:140 #, scheme-format msgid "the value ~s does not identify a public JWK" msgstr "" -#: src/scm/webid-oidc/errors.scm:135 +#: src/scm/webid-oidc/errors.scm:145 #, scheme-format msgid "the value ~s does not identify a private JWK (because ~a)" msgstr "" -#: src/scm/webid-oidc/errors.scm:137 +#: src/scm/webid-oidc/errors.scm:147 #, scheme-format msgid "the value ~s does not identify a private JWK" msgstr "" -#: src/scm/webid-oidc/errors.scm:142 +#: src/scm/webid-oidc/errors.scm:152 #, scheme-format msgid "the value ~s does not identify a JWKS (because ~a)" msgstr "" -#: src/scm/webid-oidc/errors.scm:144 +#: src/scm/webid-oidc/errors.scm:154 #, scheme-format msgid "the value ~s does not identify a JWKS" msgstr "" -#: src/scm/webid-oidc/errors.scm:147 +#: src/scm/webid-oidc/errors.scm:157 #, scheme-format msgid "the value ~s does not identify a hash algorithm" msgstr "" -#: src/scm/webid-oidc/errors.scm:152 +#: src/scm/webid-oidc/errors.scm:162 msgid "that’s it" msgstr "" -#: src/scm/webid-oidc/errors.scm:156 +#: src/scm/webid-oidc/errors.scm:166 #, scheme-format msgid "~a and ~a" msgstr "" -#: src/scm/webid-oidc/errors.scm:159 +#: src/scm/webid-oidc/errors.scm:169 #, scheme-format msgid "~a, ~a" msgstr "" -#: src/scm/webid-oidc/errors.scm:163 +#: src/scm/webid-oidc/errors.scm:173 +#, scheme-format +msgid "the signature ~a does not match key ~s with payload ~a" +msgstr "" + +#: src/scm/webid-oidc/errors.scm:176 msgid "there is an undefined variable" msgstr "" -#: src/scm/webid-oidc/errors.scm:165 +#: src/scm/webid-oidc/errors.scm:178 #, scheme-format msgid "the origin is ~a" msgstr "" -#: src/scm/webid-oidc/errors.scm:168 +#: src/scm/webid-oidc/errors.scm:181 #, scheme-format msgid "a message is attached: ~a" msgstr "" -#: src/scm/webid-oidc/errors.scm:171 +#: src/scm/webid-oidc/errors.scm:184 #, scheme-format msgid "the values ~s are problematic" msgstr "" -#: src/scm/webid-oidc/errors.scm:174 +#: src/scm/webid-oidc/errors.scm:187 msgid "there is a kind and args" msgstr "" -#: src/scm/webid-oidc/errors.scm:176 +#: src/scm/webid-oidc/errors.scm:189 msgid "there is an assertion failure" msgstr "" -#: src/scm/webid-oidc/errors.scm:178 +#: src/scm/webid-oidc/errors.scm:191 +#, scheme-format +msgid "the program quits with code ~a" +msgstr "" + +#: src/scm/webid-oidc/errors.scm:194 #, scheme-format msgid "Unhandled exception type ~a." msgstr "" diff --git a/src/ChangeLog b/src/ChangeLog index d4a8415..548be10 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,5 +1,9 @@ 2020-11-25 Vivien Kraus + * libwebidoidc.c (init_webidoidc): Initialize the jws submodule. + + * Makefile.am: Build the "jws" submodule. + * libwebidoidc.c (init_webidoidc): Initialize the hash submodule. * Makefile.am: Build the "hash" submodule. diff --git a/src/Makefile.am b/src/Makefile.am index ace4ef9..72181cb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,6 +27,7 @@ include %reldir%/base64/Makefile.am include %reldir%/random/Makefile.am include %reldir%/jwk/Makefile.am include %reldir%/hash/Makefile.am +include %reldir%/jws/Makefile.am include %reldir%/pre-inst/Makefile.am include %reldir%/inst/Makefile.am include %reldir%/scm/Makefile.am diff --git a/src/jws/Makefile.am b/src/jws/Makefile.am new file mode 100644 index 0000000..cf09f0e --- /dev/null +++ b/src/jws/Makefile.am @@ -0,0 +1,11 @@ +noinst_LTLIBRARIES += %reldir%/libwebidoidc-jws.la +EXTRA_DIST += %reldir%/libwebidoidc-jws.x +BUILT_SOURCES += %reldir%/libwebidoidc-jws.x + +%canon_reldir%_libwebidoidc_jws_la_LIBADD = $(GUILE_LIBS) $(NETTLE_LIBS) $(HOGWEED_LIBS) + +AM_CFLAGS += -I %reldir% -I $(srcdir)/%reldir% + +INDENTED += %reldir%/libwebidoidc-jws.c + +%reldir%/libwebidoidc-jws.o: %reldir%/libwebidoidc-jws.x diff --git a/src/jws/libwebidoidc-jws.c b/src/jws/libwebidoidc-jws.c new file mode 100644 index 0000000..17ae6b5 --- /dev/null +++ b/src/jws/libwebidoidc-jws.c @@ -0,0 +1,320 @@ +#include + +#define _(s) dgettext (PACKAGE, s) + +SCM_SYMBOL (rs256, "RS256"); +SCM_SYMBOL (rs512, "RS512"); +SCM_SYMBOL (es256, "ES256"); +SCM_SYMBOL (es384, "ES384"); +SCM_SYMBOL (es512, "ES512"); +SCM_SYMBOL (ps256, "PS256"); +SCM_SYMBOL (ps384, "PS384"); +SCM_SYMBOL (ps512, "PS512"); + +SCM_SYMBOL (sha256, "SHA-256"); +SCM_SYMBOL (sha384, "SHA-384"); +SCM_SYMBOL (sha512, "SHA-512"); + +SCM_SYMBOL (kty_ec, "EC"); +SCM_SYMBOL (kty_rsa, "RSA"); + +SCM_SYMBOL (incompatible_alg, "incompatible-alg"); + +/* Required for utilities.h */ +SCM_SYMBOL (p256, "P-256"); +SCM_SYMBOL (p384, "P-384"); +SCM_SYMBOL (p521, "P-521"); +SCM_SYMBOL (kcrv, "crv"); +SCM_SYMBOL (kx, "x"); +SCM_SYMBOL (ky, "y"); +SCM_SYMBOL (kd, "d"); +SCM_SYMBOL (kn, "n"); +SCM_SYMBOL (ke, "e"); +SCM_SYMBOL (kp, "p"); +SCM_SYMBOL (kq, "q"); +SCM_SYMBOL (kdp, "dp"); +SCM_SYMBOL (kdq, "dq"); +SCM_SYMBOL (kqi, "qi"); + +void webid_oidc_random (size_t len, uint8_t * dst); +SCM webidoidc_hash_g (SCM alg, SCM payload); +SCM webidoidc_kty_g (SCM key); + +static void +generate_random (void *unused, size_t len, uint8_t * dst) +{ + (void) unused; + webid_oidc_random (len, dst); +} + +static void +do_dsa_signature_clear (void *ptr) +{ + struct dsa_signature *sig = ptr; + dsa_signature_clear (sig); +} + +static void +dynwind_dsa_signature_clear (struct dsa_signature *sig) +{ + scm_dynwind_unwind_handler (do_dsa_signature_clear, sig, + SCM_F_WIND_EXPLICITLY); +} + +SCM_DEFINE (webidoidc_jws_sign_g, "sign", 3, 0, 0, + (SCM alg, SCM key, SCM payload), + "Sign @var{payload} with @var{key}, using the given algorithm @var{alg}. If @var{alg} is not @code{'RS256}, @code{'RS512}, @code{'ES256}, @code{'ES384}, @code{'ES512}, @code{'PS256}, @code{'PS384}, or @code{'PS512}, or is not compatible with the key type, throw an error.") +{ + SCM digest; + size_t c_digest_size; + uint8_t *c_digest; + if (scm_is_eq (alg, rs256) + || scm_is_eq (alg, es256) || scm_is_eq (alg, ps256)) + { + digest = webidoidc_hash_g (sha256, payload); + } + else if (scm_is_eq (alg, es384) || scm_is_eq (alg, ps384)) + { + digest = webidoidc_hash_g (sha384, payload); + } + else if (scm_is_eq (alg, rs512) + || scm_is_eq (alg, es512) || scm_is_eq (alg, ps512)) + { + digest = webidoidc_hash_g (sha512, payload); + } + else + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + c_digest = get_as_bytevector (digest, &c_digest_size, 1); + if (scm_is_eq (alg, es256) + || scm_is_eq (alg, es384) || scm_is_eq (alg, es512)) + { + struct ecc_scalar c_key; + SCM crv = scm_assq_ref (key, kcrv); + const struct ecc_curve *c_crv = do_ecc_curve_load (crv, 1); + struct dsa_signature sig; + SCM r, s; + uint8_t *c_r, *c_s, *c_ret; + size_t c_r_size, c_s_size; + scm_dynwind_begin (0); + ecc_scalar_init (&c_key, c_crv); + dynwind_ecc_scalar_clear (&c_key); + dsa_signature_init (&sig); + dynwind_dsa_signature_clear (&sig); + if (!do_ecc_scalar_load (&c_key, key)) + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + ecdsa_sign (&c_key, NULL, &generate_random, c_digest_size, c_digest, + &sig); + r = wrap_mpz_t (sig.r); + s = wrap_mpz_t (sig.s); + c_r = get_as_bytevector (r, &c_r_size, 1); + c_s = get_as_bytevector (s, &c_s_size, 1); + scm_dynwind_end (); + c_ret = scm_gc_malloc_pointerless (c_r_size + c_s_size, "signature"); + memcpy (c_ret, c_r, c_r_size); + memcpy (c_ret + c_r_size, c_s, c_s_size); + return wrap_bytevector (c_r_size + c_s_size, c_ret); + } + else + { + struct rsa_public_key c_pub; + struct rsa_private_key c_key; + mpz_t c_sig; + SCM ret; + scm_dynwind_begin (0); + rsa_public_key_init (&c_pub); + dynwind_rsa_public_key_clear (&c_pub); + rsa_private_key_init (&c_key); + dynwind_rsa_private_key_clear (&c_key); + if (!do_rsa_public_key_load (&c_pub, key) + || !do_rsa_private_key_load (&c_key, key)) + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + mpz_init (c_sig); + dynwind_mpz_t_clear (&c_sig); + if (scm_is_eq (alg, rs256) + && rsa_sha256_sign_digest_tr (&c_pub, &c_key, NULL, + &generate_random, c_digest, c_sig)) + { + ret = wrap_mpz_t (c_sig); + } + else if (scm_is_eq (alg, rs512) + && rsa_sha512_sign_digest_tr (&c_pub, &c_key, NULL, + &generate_random, c_digest, + c_sig)) + { + ret = wrap_mpz_t (c_sig); + } + else if (scm_is_eq (alg, ps256)) + { + uint8_t padding[SHA256_DIGEST_SIZE]; + webid_oidc_random (SHA256_DIGEST_SIZE, padding); + if (rsa_pss_sha256_sign_digest_tr + (&c_pub, &c_key, NULL, &generate_random, SHA256_DIGEST_SIZE, + padding, c_digest, c_sig)) + { + ret = wrap_mpz_t (c_sig); + } + else + { + scm_wrong_type_arg ("sign", SCM_ARG2, key); + } + } + else if (scm_is_eq (alg, ps384)) + { + uint8_t padding[SHA384_DIGEST_SIZE]; + webid_oidc_random (SHA384_DIGEST_SIZE, padding); + if (rsa_pss_sha384_sign_digest_tr + (&c_pub, &c_key, NULL, &generate_random, SHA384_DIGEST_SIZE, + padding, c_digest, c_sig)) + { + ret = wrap_mpz_t (c_sig); + } + else + { + scm_wrong_type_arg ("sign", SCM_ARG2, key); + } + } + else if (scm_is_eq (alg, ps512)) + { + uint8_t padding[SHA512_DIGEST_SIZE]; + webid_oidc_random (SHA512_DIGEST_SIZE, padding); + if (rsa_pss_sha512_sign_digest_tr + (&c_pub, &c_key, NULL, &generate_random, SHA512_DIGEST_SIZE, + padding, c_digest, c_sig)) + { + ret = wrap_mpz_t (c_sig); + } + else + { + scm_wrong_type_arg ("sign", SCM_ARG2, key); + } + } + else + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + scm_dynwind_end (); + return ret; + } + return SCM_UNDEFINED; +} + +SCM_DEFINE (webidoidc_jws_verify_g, "verify", 4, 0, 0, + (SCM alg, SCM key, SCM payload, SCM signature), + "Verify that @var{signature} is the signature of @var{payload} with @var{key}, using the given algorithm @var{alg}. If @var{alg} is not @code{'RS256}, @code{'RS512}, @code{'ES256}, @code{'ES384}, @code{'ES512}, @code{'PS256}, @code{'PS384}, or @code{'PS512}, or @var{alg} cannot be used with @var{key}, throw @code{incompatible-alg}.") +{ + SCM digest; + size_t c_digest_size; + uint8_t *c_digest; + if (scm_is_eq (alg, rs256) + || scm_is_eq (alg, es256) || scm_is_eq (alg, ps256)) + { + digest = webidoidc_hash_g (sha256, payload); + } + else if (scm_is_eq (alg, es384) || scm_is_eq (alg, ps384)) + { + digest = webidoidc_hash_g (sha384, payload); + } + else if (scm_is_eq (alg, rs512) + || scm_is_eq (alg, es512) || scm_is_eq (alg, ps512)) + { + digest = webidoidc_hash_g (sha512, payload); + } + else + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + c_digest = get_as_bytevector (digest, &c_digest_size, 1); + if (scm_is_eq (alg, es256) || scm_is_eq (alg, es384) + || scm_is_eq (alg, es512)) + { + struct ecc_point c_pub; + SCM crv = scm_assq_ref (key, kcrv); + const struct ecc_curve *c_crv = do_ecc_curve_load (crv, 1); + struct dsa_signature sig; + size_t c_sig_size; + uint8_t *c_sig; + SCM r, s; + int ok = 0; + c_sig = get_as_bytevector (signature, &c_sig_size, 1); + r = wrap_bytevector (c_sig_size / 2, c_sig); + s = + wrap_bytevector (c_sig_size - c_sig_size / 2, + c_sig + (c_sig_size / 2)); + scm_dynwind_begin (0); + ecc_point_init (&c_pub, c_crv); + dynwind_ecc_point_clear (&c_pub); + dsa_signature_init (&sig); + dynwind_dsa_signature_clear (&sig); + if (!do_ecc_point_load (&c_pub, key)) + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + do_mpz_t_load (sig.r, r, 1); + do_mpz_t_load (sig.s, s, 1); + ok = ecdsa_verify (&c_pub, c_digest_size, c_digest, &sig); + scm_dynwind_end (); + return scm_from_bool (ok); + } + else + { + struct rsa_public_key c_pub; + mpz_t c_sig; + int ok = 0; + scm_dynwind_begin (0); + rsa_public_key_init (&c_pub); + dynwind_rsa_public_key_clear (&c_pub); + if (!do_rsa_public_key_load (&c_pub, key)) + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + mpz_init (c_sig); + dynwind_mpz_t_clear (&c_sig); + do_mpz_t_load (c_sig, signature, 1); + if (scm_is_eq (alg, rs256)) + { + ok = rsa_sha256_verify_digest (&c_pub, c_digest, c_sig); + } + else if (scm_is_eq (alg, rs512)) + { + ok = rsa_sha512_verify_digest (&c_pub, c_digest, c_sig); + } + else if (scm_is_eq (alg, ps256)) + { + ok = + rsa_pss_sha256_verify_digest (&c_pub, c_digest_size, c_digest, + c_sig); + } + else if (scm_is_eq (alg, ps384)) + { + ok = + rsa_pss_sha384_verify_digest (&c_pub, c_digest_size, c_digest, + c_sig); + } + else if (scm_is_eq (alg, ps512)) + { + ok = + rsa_pss_sha512_verify_digest (&c_pub, c_digest_size, c_digest, + c_sig); + } + else + { + scm_throw (incompatible_alg, scm_list_2 (alg, key)); + } + scm_dynwind_end (); + return scm_from_bool (ok); + } +} + +void +init_webidoidc_jws (void) +{ +#ifndef SCM_MAGIC_SNARFER +#include "libwebidoidc-jws.x" +#endif /* not SCM_MAGIC_SNARFER */ +} diff --git a/src/libwebidoidc.c b/src/libwebidoidc.c index 953fea5..1c637e5 100644 --- a/src/libwebidoidc.c +++ b/src/libwebidoidc.c @@ -3,6 +3,7 @@ void init_webidoidc_base64 (void); void init_webidoidc_random (void); void init_webidoidc_jwk (void); void init_webidoidc_hash (void); +void init_webidoidc_jws (void); void init_webidoidc (void) @@ -12,4 +13,5 @@ init_webidoidc (void) init_webidoidc_random (); init_webidoidc_jwk (); init_webidoidc_hash (); + init_webidoidc_jws (); } diff --git a/src/scm/webid-oidc/errors.scm b/src/scm/webid-oidc/errors.scm index a690088..ad8fef3 100644 --- a/src/scm/webid-oidc/errors.scm +++ b/src/scm/webid-oidc/errors.scm @@ -94,6 +94,16 @@ (raise-exception ((record-constructor &unsupported-alg) value))) +(define-public &invalid-signature + (make-exception-type + '&invalid-signature + &external-error + '(key payload signature))) + +(define-public (raise-invalid-signature key payload signature) + (raise-exception + ((record-constructor &invalid-signature) key payload signature))) + (define*-public (error->str err #:key (max-depth #f)) (if (record? err) (let* ((type (record-type-descriptor err)) @@ -159,6 +169,9 @@ (format #f (G_ "~a, ~a") (recurse (car components)) (recurse (apply make-exception (cdr components))))))))) + ((&invalid-signature) + (format #f (G_ "the signature ~a does not match key ~s with payload ~a") + (get 'signature) (get 'key) (get 'payload))) ((&undefined-variable) (G_ "there is an undefined variable")) ((&origin) @@ -174,6 +187,9 @@ (format #f (G_ "there is a kind and args"))) ((&assertion-failure) (format #f (G_ "there is an assertion failure"))) + ((&quit-exception) + (format #f (G_ "the program quits with code ~a") + (get 'code))) (else (error (format #f (G_ "Unhandled exception type ~a.") (record-type-name type)))))) diff --git a/src/scm/webid-oidc/stubs.scm b/src/scm/webid-oidc/stubs.scm index 58fe356..3f16888 100644 --- a/src/scm/webid-oidc/stubs.scm +++ b/src/scm/webid-oidc/stubs.scm @@ -38,6 +38,23 @@ (lambda error (raise-unsupported-alg (cadr error))))) +(define (fix-sign alg key payload) + (catch 'unsupported-alg + (lambda () + (sign alg key payload)) + (lambda error + (raise-unsupported-alg (cadr error))))) + +(define (fix-verify alg key payload signature) + (catch 'unsupported-alg + (lambda () + (let ((ok + (verify alg key payload signature))) + (unless ok + (raise-invalid-signature key payload signature)))) + (lambda error + (raise-unsupported-alg (cadr error))))) + (export base64-encode (fix-base64-decode . base64-decode) @@ -47,7 +64,9 @@ (fix-kty . kty) strip-key (fix-hash . hash) - jkt) + jkt + (fix-sign . sign) + (fix-verify . verify)) ;; json reader from guile-json will not behave consistently with ;; SRFI-180 with objects: keys will be mapped to strings, not diff --git a/src/utilities.h b/src/utilities.h index ae5d82c..c07265e 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -71,7 +71,7 @@ static int do_rsa_public_key_load (struct rsa_public_key *x, SCM data); static int do_rsa_private_key_load (struct rsa_private_key *x, SCM data); /* Register x to be destroyed at the end of the dynamic wind. */ -static void dynwind_mpz_t_clear (mpz_t x); +static void dynwind_mpz_t_clear (mpz_t * x); static void dynwind_ecc_point_clear (struct ecc_point *x); static void dynwind_ecc_scalar_clear (struct ecc_scalar *x); static void dynwind_rsa_public_key_clear (struct rsa_public_key *x); @@ -167,9 +167,9 @@ wrap_ecc_point (const struct ecc_curve *crv, const struct ecc_point *point) SCM ret; scm_dynwind_begin (0); mpz_init (x); - dynwind_mpz_t_clear (x); + dynwind_mpz_t_clear (&x); mpz_init (y); - dynwind_mpz_t_clear (y); + dynwind_mpz_t_clear (&y); ecc_point_get (point, x, y); ret = scm_list_3 (scm_cons (kcrv, wrap_ecc_curve (crv)), @@ -185,7 +185,7 @@ wrap_ecc_scalar (const struct ecc_curve *crv, const struct ecc_scalar *scalar) SCM ret; scm_dynwind_begin (0); mpz_init (z); - dynwind_mpz_t_clear (z); + dynwind_mpz_t_clear (&z); ecc_scalar_get (scalar, z); ret = scm_list_2 (scm_cons (kcrv, wrap_ecc_curve (crv)), @@ -314,9 +314,9 @@ do_ecc_point_load (struct ecc_point *point, SCM data) int ret = 1; scm_dynwind_begin (0); mpz_init (x); - dynwind_mpz_t_clear (x); + dynwind_mpz_t_clear (&x); mpz_init (y); - dynwind_mpz_t_clear (y); + dynwind_mpz_t_clear (&y); ret = (do_mpz_t_load (x, scm_assq_ref (data, kx), 0) && do_mpz_t_load (y, scm_assq_ref (data, ky), 0) @@ -332,7 +332,7 @@ do_ecc_scalar_load (struct ecc_scalar *scalar, SCM data) int ret = 1; scm_dynwind_begin (0); mpz_init (z); - dynwind_mpz_t_clear (z); + dynwind_mpz_t_clear (&z); ret = (do_mpz_t_load (z, scm_assq_ref (data, kd), 0) && ecc_scalar_set (scalar, z)); @@ -396,9 +396,9 @@ do_ecc_scalar_clear (void *ptr) } static inline void -dynwind_mpz_t_clear (mpz_t z) +dynwind_mpz_t_clear (mpz_t * z) { - scm_dynwind_unwind_handler (do_mpz_t_clear, &z, SCM_F_WIND_EXPLICITLY); + scm_dynwind_unwind_handler (do_mpz_t_clear, z, SCM_F_WIND_EXPLICITLY); } static inline void diff --git a/tests/Makefile.am b/tests/Makefile.am index b889e0b..22718cf 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -8,7 +8,9 @@ TESTS = %reldir%/load-library.scm \ %reldir%/jwk-kty-rsa-incorrect.scm \ %reldir%/hash-ok.scm \ %reldir%/hash-unsupported.scm \ - %reldir%/jkt.scm + %reldir%/jkt.scm \ + %reldir%/verify.scm \ + %reldir%/verification-failed.scm EXTRA_DIST += $(TESTS) diff --git a/tests/verification-failed.scm b/tests/verification-failed.scm new file mode 100644 index 0000000..b580caa --- /dev/null +++ b/tests/verification-failed.scm @@ -0,0 +1,24 @@ +(use-modules (webid-oidc stubs) + (webid-oidc errors) + (webid-oidc testing)) + +(with-test-environment + "verification-failed" + (lambda () + (let* ((key (json-string->scm "{ + \"kty\":\"EC\", + \"x\":\"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs\", + \"y\":\"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA\", + \"crv\":\"P-256\" + }")) + (payload "eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCRnMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JEQSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiJlMWozVl9iS2ljOC1MQUVCIiwiaHRtIjoiR0VUIiwiaHR1IjoiaHR0cHM6Ly9yZXNvdXJjZS5leGFtcGxlLm9yZy9wcm90ZWN0ZWRyZXNvdXJjZSIsImlhdCI6MTU2MjI2MjYxOH0") + (signature "lNhmpAX_WwmpBvwhok4E74kWCiGBNdavjLAeevGy32H3dbF0Jbri69Nm2ukkwb-uyUI4AUg_JSskfWIyo4UCbQ")) ;; Replaced 1 with _ + (with-exception-handler + (lambda (error) + (unless ((record-predicate &invalid-signature) error) + (exit 1))) + (lambda () + (verify 'ES256 key payload signature) + (exit 2)) + #:unwind? #t + #:unwind-for-type &invalid-signature)))) diff --git a/tests/verify.scm b/tests/verify.scm new file mode 100644 index 0000000..e6dc2fd --- /dev/null +++ b/tests/verify.scm @@ -0,0 +1,15 @@ +(use-modules (webid-oidc stubs) + (webid-oidc testing)) + +(with-test-environment + "verify" + (lambda () + (let* ((key (json-string->scm "{ + \"kty\":\"EC\", + \"x\":\"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs\", + \"y\":\"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA\", + \"crv\":\"P-256\" + }")) + (payload "eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCRnMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JEQSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiJlMWozVl9iS2ljOC1MQUVCIiwiaHRtIjoiR0VUIiwiaHR1IjoiaHR0cHM6Ly9yZXNvdXJjZS5leGFtcGxlLm9yZy9wcm90ZWN0ZWRyZXNvdXJjZSIsImlhdCI6MTU2MjI2MjYxOH0") + (signature "lNhmpAX1WwmpBvwhok4E74kWCiGBNdavjLAeevGy32H3dbF0Jbri69Nm2ukkwb-uyUI4AUg1JSskfWIyo4UCbQ")) + (verify 'ES256 key payload signature)))) -- cgit v1.2.3