diff options
Diffstat (limited to 'src/util/age_restriction.c')
-rw-r--r-- | src/util/age_restriction.c | 532 |
1 files changed, 444 insertions, 88 deletions
diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c index 0ee020ebd..c2a7fc07c 100644 --- a/src/util/age_restriction.c +++ b/src/util/age_restriction.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -21,7 +21,22 @@ #include "platform.h" #include "taler_util.h" #include "taler_signatures.h" +#include <gnunet/gnunet_json_lib.h> #include <gcrypt.h> +#include <stdint.h> + +struct +#ifndef AGE_RESTRICTION_WITH_ECDSA +GNUNET_CRYPTO_Edx25519PublicKey +#else +GNUNET_CRYPTO_EcdsaPublicKey +#endif +TALER_age_commitment_base_public_key = { + .q_y = { 0x64, 0x41, 0xb9, 0xbd, 0xbf, 0x14, 0x39, 0x8e, + 0x46, 0xeb, 0x5c, 0x1d, 0x34, 0xd3, 0x9b, 0x2f, + 0x9b, 0x7d, 0xc8, 0x18, 0xeb, 0x9c, 0x09, 0xfb, + 0x43, 0xad, 0x16, 0x64, 0xbc, 0x18, 0x49, 0xb5}, +}; void TALER_age_commitment_hash ( @@ -39,7 +54,7 @@ TALER_age_commitment_hash ( } GNUNET_assert (__builtin_popcount (commitment->mask.bits) - 1 == - commitment->num); + (int) commitment->num); hash_context = GNUNET_CRYPTO_hash_context_start (); @@ -62,7 +77,7 @@ TALER_age_commitment_hash ( * defined by the given mask. */ uint8_t -get_age_group ( +TALER_get_age_group ( const struct TALER_AgeMask *mask, uint8_t age) { @@ -81,37 +96,97 @@ get_age_group ( } -enum GNUNET_GenericReturnValue +uint8_t +TALER_get_lowest_age ( + const struct TALER_AgeMask *mask, + uint8_t age) +{ + uint32_t m = mask->bits; + uint8_t group = TALER_get_age_group (mask, age); + uint8_t lowest = 0; + + while (group > 0) + { + m = m >> 1; + if (m & 1) + group--; + lowest++; + } + + return lowest; +} + + +#ifdef AGE_RESTRICTION_WITH_ECDSA +/** + * @brief Helper function to generate a ECDSA private key + * + * @param seed Input seed + * @param size Size of the seed in bytes + * @param[out] pkey ECDSA private key + */ +static void +ecdsa_create_from_seed ( + const void *seed, + size_t seed_size, + struct GNUNET_CRYPTO_EcdsaPrivateKey *key) +{ + enum GNUNET_GenericReturnValue ret; + + GNUNET_assert ( + GNUNET_OK == + GNUNET_CRYPTO_kdf (key, + sizeof (*key), + &seed, + seed_size, + "age commitment", + sizeof ("age commitment") - 1, + NULL, 0)); + /* See GNUNET_CRYPTO_ecdsa_key_create */ + key->d[0] &= 248; + key->d[31] &= 127; + key->d[31] |= 64; +} + + +#endif + + +void TALER_age_restriction_commit ( const struct TALER_AgeMask *mask, - const uint8_t age, + uint8_t age, const struct GNUNET_HashCode *seed, - struct TALER_AgeCommitmentProof *new) + struct TALER_AgeCommitmentProof *ncp) { struct GNUNET_HashCode seed_i; - uint8_t num_pub = __builtin_popcount (mask->bits) - 1; - uint8_t num_priv = get_age_group (mask, age); + uint8_t num_pub; + uint8_t num_priv; size_t i; + GNUNET_assert (NULL != mask); GNUNET_assert (NULL != seed); - GNUNET_assert (NULL != new); - GNUNET_assert (mask->bits & 1); /* fist bit must have been set */ - GNUNET_assert (0 <= num_priv); + GNUNET_assert (NULL != ncp); + GNUNET_assert (mask->bits & 1); /* first bit must have been set */ + + num_pub = __builtin_popcount (mask->bits) - 1; + num_priv = TALER_get_age_group (mask, age); + GNUNET_assert (31 > num_priv); GNUNET_assert (num_priv <= num_pub); seed_i = *seed; - new->commitment.mask.bits = mask->bits; - new->commitment.num = num_pub; - new->proof.num = num_priv; - new->proof.keys = NULL; + ncp->commitment.mask.bits = mask->bits; + ncp->commitment.num = num_pub; + ncp->proof.num = num_priv; + ncp->proof.keys = NULL; - new->commitment.keys = GNUNET_new_array ( + ncp->commitment.keys = GNUNET_new_array ( num_pub, struct TALER_AgeCommitmentPublicKeyP); if (0 < num_priv) - new->proof.keys = GNUNET_new_array ( + ncp->proof.keys = GNUNET_new_array ( num_priv, struct TALER_AgeCommitmentPrivateKeyP); @@ -126,47 +201,24 @@ TALER_age_restriction_commit ( /* Only save the private keys for age groups less than num_priv */ if (i < num_priv) - pkey = &new->proof.keys[i]; + pkey = &ncp->proof.keys[i]; #ifndef AGE_RESTRICTION_WITH_ECDSA GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i, sizeof(seed_i), &pkey->priv); GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv, - &new->commitment.keys[i].pub); - seed_i.bits[0] += 1; - } - - return GNUNET_OK; + &ncp->commitment.keys[i].pub); #else - if (GNUNET_OK != - GNUNET_CRYPTO_kdf (pkey, - sizeof (*pkey), - &salti, - sizeof (salti), - "age commitment", - strlen ("age commitment"), - NULL, 0)) - goto FAIL; - - /* See GNUNET_CRYPTO_ecdsa_key_create */ - pkey->priv.d[0] &= 248; - pkey->priv.d[31] &= 127; - pkey->priv.d[31] |= 64; - + ecdsa_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv); GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv, - &new->commitment.keys[i].pub); + &ncp->commitment.keys[i].pub); +#endif + seed_i.bits[0] += 1; } - - return GNUNET_OK; - -FAIL: - GNUNET_free (new->commitment.keys); - if (NULL != new->proof.keys) - GNUNET_free (new->proof.keys); - return GNUNET_SYSERR; -#endif } @@ -179,7 +231,7 @@ TALER_age_commitment_derive ( GNUNET_assert (NULL != newacp); GNUNET_assert (orig->proof.num <= orig->commitment.num); - GNUNET_assert (orig->commitment.num == + GNUNET_assert (((int) orig->commitment.num) == __builtin_popcount (orig->commitment.mask.bits) - 1); newacp->commitment.mask = orig->commitment.mask; @@ -216,33 +268,30 @@ TALER_age_commitment_derive ( &newacp->proof.keys[i].priv); } #else - char label[sizeof(uint64_t) + 1] = {0}; - - /* Because GNUNET_CRYPTO_ecdsa_public_key_derive expects char * (and calls - * strlen on it), we must avoid 0's in the label. */ - uint64_t nz_salt = salt | 0x8040201008040201; - memcpy (label, &nz_salt, sizeof(nz_salt)); - - /* 1. Derive the public keys */ - for (size_t i = 0; i < orig->commitment.num; i++) - { - GNUNET_CRYPTO_ecdsa_public_key_derive ( - &orig->commitment.keys[i].pub, - label, - "age commitment derive", - &newacp->commitment.keys[i].pub); - } - - /* 2. Derive the private keys */ - for (size_t i = 0; i < orig->proof.num; i++) { - struct GNUNET_CRYPTO_EcdsaPrivateKey *priv; - priv = GNUNET_CRYPTO_ecdsa_private_key_derive ( - &orig->proof.keys[i].priv, - label, - "age commitment derive"); - newacp->proof.keys[i].priv = *priv; - GNUNET_free (priv); + const char *label = GNUNET_h2s (salt); + + /* 1. Derive the public keys */ + for (size_t i = 0; i < orig->commitment.num; i++) + { + GNUNET_CRYPTO_ecdsa_public_key_derive ( + &orig->commitment.keys[i].pub, + label, + "age commitment derive", + &newacp->commitment.keys[i].pub); + } + + /* 2. Derive the private keys */ + for (size_t i = 0; i < orig->proof.num; i++) + { + struct GNUNET_CRYPTO_EcdsaPrivateKey *priv; + priv = GNUNET_CRYPTO_ecdsa_private_key_derive ( + &orig->proof.keys[i].priv, + label, + "age commitment derive"); + newacp->proof.keys[i].priv = *priv; + GNUNET_free (priv); + } } #endif @@ -297,8 +346,8 @@ TALER_age_commitment_attest ( GNUNET_assert (NULL != attest); GNUNET_assert (NULL != cp); - group = get_age_group (&cp->commitment.mask, - age); + group = TALER_get_age_group (&cp->commitment.mask, + age); GNUNET_assert (group < 32); @@ -332,6 +381,7 @@ TALER_age_commitment_attest ( &at, &attest->signature); } +#undef sign return GNUNET_OK; } @@ -348,8 +398,8 @@ TALER_age_commitment_verify ( GNUNET_assert (NULL != attest); GNUNET_assert (NULL != comm); - group = get_age_group (&comm->mask, - age); + group = TALER_get_age_group (&comm->mask, + age); GNUNET_assert (group < 32); @@ -381,6 +431,7 @@ TALER_age_commitment_verify ( &attest->signature, &comm->keys[group - 1].pub); } +#undef verify } @@ -404,6 +455,9 @@ void TALER_age_proof_free ( struct TALER_AgeProof *proof) { + if (NULL == proof) + return; + if (NULL != proof->keys) { GNUNET_CRYPTO_zero_keys ( @@ -419,21 +473,323 @@ TALER_age_proof_free ( void TALER_age_commitment_proof_free ( - struct TALER_AgeCommitmentProof *cp) + struct TALER_AgeCommitmentProof *acp) { - if (NULL != cp->proof.keys) + if (NULL == acp) + return; + + if (NULL != acp->proof.keys) { GNUNET_CRYPTO_zero_keys ( - cp->proof.keys, - sizeof(*cp->proof.keys) * cp->proof.num); + acp->proof.keys, + sizeof(*acp->proof.keys) * acp->proof.num); - GNUNET_free (cp->proof.keys); - cp->proof.keys = NULL; + GNUNET_free (acp->proof.keys); + acp->proof.keys = NULL; } - if (NULL != cp->commitment.keys) + if (NULL != acp->commitment.keys) { - GNUNET_free (cp->commitment.keys); - cp->commitment.keys = NULL; + GNUNET_free (acp->commitment.keys); + acp->commitment.keys = NULL; } } + + +struct TALER_AgeCommitmentProof * +TALER_age_commitment_proof_duplicate ( + const struct TALER_AgeCommitmentProof *acp) +{ + struct TALER_AgeCommitmentProof *nacp; + + GNUNET_assert (NULL != acp); + GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 == + (int) acp->commitment.num); + + nacp = GNUNET_new (struct TALER_AgeCommitmentProof); + + TALER_age_commitment_proof_deep_copy (acp,nacp); + return nacp; +} + + +void +TALER_age_commitment_proof_deep_copy ( + const struct TALER_AgeCommitmentProof *acp, + struct TALER_AgeCommitmentProof *nacp) +{ + GNUNET_assert (NULL != acp); + GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 == + (int) acp->commitment.num); + + *nacp = *acp; + nacp->commitment.keys = + GNUNET_new_array (acp->commitment.num, + struct TALER_AgeCommitmentPublicKeyP); + nacp->proof.keys = + GNUNET_new_array (acp->proof.num, + struct TALER_AgeCommitmentPrivateKeyP); + + for (size_t i = 0; i < acp->commitment.num; i++) + nacp->commitment.keys[i] = acp->commitment.keys[i]; + + for (size_t i = 0; i < acp->proof.num; i++) + nacp->proof.keys[i] = acp->proof.keys[i]; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_parse_age_groups (const json_t *root, + struct TALER_AgeMask *mask) +{ + enum GNUNET_GenericReturnValue ret; + const char *str; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("age_groups", + &str), + GNUNET_JSON_spec_end () + }; + + ret = GNUNET_JSON_parse (root, + spec, + NULL, + NULL); + if (GNUNET_OK == ret) + TALER_parse_age_group_string (str, mask); + + GNUNET_JSON_parse_free (spec); + + return ret; +} + + +enum GNUNET_GenericReturnValue +TALER_parse_age_group_string ( + const char *groups, + struct TALER_AgeMask *mask) +{ + + const char *pos = groups; + unsigned int prev = 0; + unsigned int val = 0; + char c; + + /* reset mask */ + mask->bits = 0; + + while (*pos) + { + c = *pos++; + if (':' == c) + { + if (prev >= val) + return GNUNET_SYSERR; + + mask->bits |= 1 << val; + prev = val; + val = 0; + continue; + } + + if ('0'>c || '9'<c) + return GNUNET_SYSERR; + + val = 10 * val + c - '0'; + + if (0>=val || 32<=val) + return GNUNET_SYSERR; + } + + if (32<=val || prev>=val) + return GNUNET_SYSERR; + + mask->bits |= (1 << val); + mask->bits |= 1; // mark zeroth group, too + + return GNUNET_OK; +} + + +const char * +TALER_age_mask_to_string ( + const struct TALER_AgeMask *mask) +{ + static char buf[256] = {0}; + uint32_t bits = mask->bits; + unsigned int n = 0; + char *pos = buf; + + memset (buf, 0, sizeof(buf)); + + while (bits != 0) + { + bits >>= 1; + n++; + if (0 == (bits & 1)) + { + continue; + } + + if (n > 9) + { + *(pos++) = '0' + n / 10; + } + *(pos++) = '0' + n % 10; + + if (0 != (bits >> 1)) + { + *(pos++) = ':'; + } + } + return buf; +} + + +void +TALER_age_restriction_from_secret ( + const struct TALER_PlanchetMasterSecretP *secret, + const struct TALER_AgeMask *mask, + const uint8_t max_age, + struct TALER_AgeCommitmentProof *ncp) +{ + struct GNUNET_HashCode seed_i = {0}; + uint8_t num_pub; + uint8_t num_priv; + + GNUNET_assert (NULL != mask); + GNUNET_assert (NULL != secret); + GNUNET_assert (NULL != ncp); + GNUNET_assert (mask->bits & 1); /* fist bit must have been set */ + + num_pub = __builtin_popcount (mask->bits) - 1; + num_priv = TALER_get_age_group (mask, max_age); + + GNUNET_assert (31 > num_priv); + GNUNET_assert (num_priv <= num_pub); + + ncp->commitment.mask.bits = mask->bits; + ncp->commitment.num = num_pub; + ncp->proof.num = num_priv; + ncp->proof.keys = NULL; + ncp->commitment.keys = GNUNET_new_array ( + num_pub, + struct TALER_AgeCommitmentPublicKeyP); + if (0 < num_priv) + ncp->proof.keys = GNUNET_new_array ( + num_priv, + struct TALER_AgeCommitmentPrivateKeyP); + + /* Create as many private keys as allow with max_age and derive the + * corresponding public keys. The rest of the needed public keys are created + * by scalar multiplication with the TALER_age_commitment_base_public_key. */ + for (size_t i = 0; i < num_pub; i++) + { + enum GNUNET_GenericReturnValue ret; + const char *label = i < num_priv ? "age-commitment" : "age-factor"; + + ret = GNUNET_CRYPTO_kdf (&seed_i, sizeof(seed_i), + secret, sizeof(*secret), + label, strlen (label), + &i, sizeof(i), + NULL, 0); + GNUNET_assert (GNUNET_OK == ret); + + /* Only generate and save the private keys and public keys for age groups + * less than num_priv */ + if (i < num_priv) + { + struct TALER_AgeCommitmentPrivateKeyP *pkey = &ncp->proof.keys[i]; + +#ifndef AGE_RESTRICTION_WITH_ECDSA + GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv); + GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv, + &ncp->commitment.keys[i].pub); +#else + ecdsa_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv); + GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv, + &ncp->commitment.keys[i].pub); +#endif + } + else + { + /* For all indices larger than num_priv, derive a public key from + * TALER_age_commitment_base_public_key by scalar multiplication */ +#ifndef AGE_RESTRICTION_WITH_ECDSA + GNUNET_CRYPTO_edx25519_public_key_derive ( + &TALER_age_commitment_base_public_key, + &seed_i, + sizeof(seed_i), + &ncp->commitment.keys[i].pub); +#else + + GNUNET_CRYPTO_ecdsa_public_key_derive ( + &TALER_age_commitment_base_public_key, + GNUNET_h2s (&seed_i), + "age withdraw", + &ncp->commitment.keys[i].pub); +#endif + } + } +} + + +enum GNUNET_GenericReturnValue +TALER_parse_coarse_date ( + const char *in, + const struct TALER_AgeMask *mask, + uint32_t *out) +{ + struct tm date = {0}; + struct tm limit = {0}; + time_t seconds; + + if (NULL == in) + { + /* FIXME[oec]: correct behaviour? */ + *out = 0; + return GNUNET_OK; + } + + GNUNET_assert (NULL !=mask); + GNUNET_assert (NULL !=out); + + if (NULL == strptime (in, "%Y-%m-%d", &date)) + { + if (NULL == strptime (in, "%Y-%m-00", &date)) + if (NULL == strptime (in, "%Y-00-00", &date)) + return GNUNET_SYSERR; + /* turns out that the day is off by one in the last two cases */ + date.tm_mday += 1; + } + + seconds = timegm (&date); + if (-1 == seconds) + return GNUNET_SYSERR; + + /* calculate the limit date for the largest age group */ + { + time_t l = time (NULL); + localtime_r (&l, &limit); + } + limit.tm_year -= TALER_adult_age (mask); + GNUNET_assert (-1 != timegm (&limit)); + + if ((limit.tm_year < date.tm_year) + || ((limit.tm_year == date.tm_year) + && (limit.tm_mon < date.tm_mon)) + || ((limit.tm_year == date.tm_year) + && (limit.tm_mon == date.tm_mon) + && (limit.tm_mday < date.tm_mday))) + *out = seconds / 60 / 60 / 24; + else + *out = 0; + + return GNUNET_OK; +} + + +/* end util/age_restriction.c */ |