/*
webid-oidc, implementation of the Solid specification
Copyright (C) 2020, 2021 Vivien Kraus
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
#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 */
}