summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVivien Kraus <vivien@planete-kraus.eu>2020-01-01 00:00:00 +0100
committerVivien Kraus <vivien@planete-kraus.eu>2021-06-05 16:12:15 +0200
commit0aafddd76e758200947be243acfde9cd6ce9f5f7 (patch)
treeae863b2183879b7c5dd020fd342ec49e6566624e
parent98e768d50ccfb301ee237fe8aed36ea61e048e59 (diff)
Add a signature and verification function for JWS
-rw-r--r--ChangeLog1
-rw-r--r--NEWS3
-rw-r--r--doc/webid-oidc.texi4
-rw-r--r--po/fr.po68
-rw-r--r--po/webid-oidc.pot60
-rw-r--r--src/ChangeLog4
-rw-r--r--src/Makefile.am1
-rw-r--r--src/jws/Makefile.am11
-rw-r--r--src/jws/libwebidoidc-jws.c320
-rw-r--r--src/libwebidoidc.c2
-rw-r--r--src/scm/webid-oidc/errors.scm16
-rw-r--r--src/scm/webid-oidc/stubs.scm21
-rw-r--r--src/utilities.h18
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/verification-failed.scm24
-rw-r--r--tests/verify.scm15
16 files changed, 503 insertions, 69 deletions
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 <vivien@planete-kraus.eu>
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 <vivien@planete-kraus.eu>\n"
"Language-Team: French <vivien@planete-kraus.eu>\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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <vivien@planete-kraus.eu>
+ * 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 <utilities.h>
+
+#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 e279463..e72f44d 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))))