summaryrefslogtreecommitdiff
path: root/src
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 /src
parent98e768d50ccfb301ee237fe8aed36ea61e048e59 (diff)
Add a signature and verification function for JWS
Diffstat (limited to 'src')
-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
8 files changed, 383 insertions, 10 deletions
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