summaryrefslogtreecommitdiff
path: root/src/jws
diff options
context:
space:
mode:
Diffstat (limited to 'src/jws')
-rw-r--r--src/jws/Makefile.am11
-rw-r--r--src/jws/libwebidoidc-jws.c320
2 files changed, 331 insertions, 0 deletions
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 */
+}