summaryrefslogtreecommitdiff
path: root/src/util/age_restriction.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/age_restriction.c')
-rw-r--r--src/util/age_restriction.c531
1 files changed, 444 insertions, 87 deletions
diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c
index 189ec4e8c..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,36 +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 (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);
@@ -125,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
}
@@ -178,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;
@@ -215,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
@@ -296,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);
@@ -331,6 +381,7 @@ TALER_age_commitment_attest (
&at,
&attest->signature);
}
+#undef sign
return GNUNET_OK;
}
@@ -347,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);
@@ -380,6 +431,7 @@ TALER_age_commitment_verify (
&attest->signature,
&comm->keys[group - 1].pub);
}
+#undef verify
}
@@ -403,6 +455,9 @@ void
TALER_age_proof_free (
struct TALER_AgeProof *proof)
{
+ if (NULL == proof)
+ return;
+
if (NULL != proof->keys)
{
GNUNET_CRYPTO_zero_keys (
@@ -418,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 */