/* This file is part of TALER 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 Foundation; either version 3, or (at your option) any later version. TALER 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 General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file util/age_restriction.c * @brief Functions that are used for age restriction * @author Özgür Kesim */ #include "platform.h" #include "taler_util.h" #include "taler_signatures.h" #include #include #include 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 ( const struct TALER_AgeCommitment *commitment, struct TALER_AgeCommitmentHash *ahash) { struct GNUNET_HashContext *hash_context; struct GNUNET_HashCode hash; GNUNET_assert (NULL != ahash); if (NULL == commitment) { memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash)); return; } GNUNET_assert (__builtin_popcount (commitment->mask.bits) - 1 == (int) commitment->num); hash_context = GNUNET_CRYPTO_hash_context_start (); for (size_t i = 0; i < commitment->num; i++) { GNUNET_CRYPTO_hash_context_read (hash_context, &commitment->keys[i], sizeof(commitment->keys[i])); } GNUNET_CRYPTO_hash_context_finish (hash_context, &hash); GNUNET_memcpy (&ahash->shash.bits, &hash.bits, sizeof(ahash->shash.bits)); } /* To a given age value between 0 and 31, returns the index of the age group * defined by the given mask. */ uint8_t TALER_get_age_group ( const struct TALER_AgeMask *mask, uint8_t age) { uint32_t m = mask->bits; uint8_t i = 0; while (m > 0) { if (0 >= age) break; m = m >> 1; i += m & 1; age--; } return i; } 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, uint8_t age, const struct GNUNET_HashCode *seed, struct TALER_AgeCommitmentProof *ncp) { struct GNUNET_HashCode seed_i; uint8_t num_pub; uint8_t num_priv; size_t i; GNUNET_assert (NULL != mask); GNUNET_assert (NULL != seed); 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; 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 we need and fill the rest of the * public keys with valid curve points. * We need to make sure that the public keys are proper points on the * elliptic curve, so we can't simply fill the struct with random values. */ for (i = 0; i < num_pub; i++) { struct TALER_AgeCommitmentPrivateKeyP key = {0}; struct TALER_AgeCommitmentPrivateKeyP *pkey = &key; /* Only save the private keys for age groups less than num_priv */ if (i < num_priv) 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 seed_i.bits[0] += 1; } } enum GNUNET_GenericReturnValue TALER_age_commitment_derive ( const struct TALER_AgeCommitmentProof *orig, const struct GNUNET_HashCode *salt, struct TALER_AgeCommitmentProof *newacp) { GNUNET_assert (NULL != newacp); GNUNET_assert (orig->proof.num <= orig->commitment.num); GNUNET_assert (((int) orig->commitment.num) == __builtin_popcount (orig->commitment.mask.bits) - 1); newacp->commitment.mask = orig->commitment.mask; newacp->commitment.num = orig->commitment.num; newacp->commitment.keys = GNUNET_new_array ( newacp->commitment.num, struct TALER_AgeCommitmentPublicKeyP); newacp->proof.num = orig->proof.num; newacp->proof.keys = NULL; if (0 != newacp->proof.num) newacp->proof.keys = GNUNET_new_array ( newacp->proof.num, struct TALER_AgeCommitmentPrivateKeyP); #ifndef AGE_RESTRICTION_WITH_ECDSA /* 1. Derive the public keys */ for (size_t i = 0; i < orig->commitment.num; i++) { GNUNET_CRYPTO_edx25519_public_key_derive ( &orig->commitment.keys[i].pub, salt, sizeof(*salt), &newacp->commitment.keys[i].pub); } /* 2. Derive the private keys */ for (size_t i = 0; i < orig->proof.num; i++) { GNUNET_CRYPTO_edx25519_private_key_derive ( &orig->proof.keys[i].priv, salt, sizeof(*salt), &newacp->proof.keys[i].priv); } #else { 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 return GNUNET_OK; } GNUNET_NETWORK_STRUCT_BEGIN /** * Age group mask in network byte order. */ struct TALER_AgeMaskNBO { uint32_t bits_nbo; }; /** * Used for attestation of a particular age */ struct TALER_AgeAttestationPS { /** * Purpose must be #TALER_SIGNATURE_WALLET_AGE_ATTESTATION. * (no GNUNET_PACKED here because the struct is already packed) */ struct GNUNET_CRYPTO_EccSignaturePurpose purpose; /** * Age mask that defines the underlying age groups */ struct TALER_AgeMaskNBO mask GNUNET_PACKED; /** * The particular age that this attestation is for. * We use uint32_t here for alignment. */ uint32_t age GNUNET_PACKED; }; GNUNET_NETWORK_STRUCT_END enum GNUNET_GenericReturnValue TALER_age_commitment_attest ( const struct TALER_AgeCommitmentProof *cp, uint8_t age, struct TALER_AgeAttestation *attest) { uint8_t group; GNUNET_assert (NULL != attest); GNUNET_assert (NULL != cp); group = TALER_get_age_group (&cp->commitment.mask, age); GNUNET_assert (group < 32); if (0 == group) { /* Age group 0 means: no attestation necessary. * We set the signature to zero and communicate success. */ memset (attest, 0, sizeof(struct TALER_AgeAttestation)); return GNUNET_OK; } if (group > cp->proof.num) return GNUNET_NO; { struct TALER_AgeAttestationPS at = { .purpose.size = htonl (sizeof(at)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION), .mask.bits_nbo = htonl (cp->commitment.mask.bits), .age = htonl (age), }; #ifndef AGE_RESTRICTION_WITH_ECDSA #define sign(a,b,c) GNUNET_CRYPTO_edx25519_sign (a,b,c) #else #define sign(a,b,c) GNUNET_CRYPTO_ecdsa_sign (a,b,c) #endif sign (&cp->proof.keys[group - 1].priv, &at, &attest->signature); } #undef sign return GNUNET_OK; } enum GNUNET_GenericReturnValue TALER_age_commitment_verify ( const struct TALER_AgeCommitment *comm, uint8_t age, const struct TALER_AgeAttestation *attest) { uint8_t group; GNUNET_assert (NULL != attest); GNUNET_assert (NULL != comm); group = TALER_get_age_group (&comm->mask, age); GNUNET_assert (group < 32); /* Age group 0 means: no attestation necessary. */ if (0 == group) return GNUNET_OK; if (group > comm->num) { GNUNET_break_op (0); return GNUNET_NO; } { struct TALER_AgeAttestationPS at = { .purpose.size = htonl (sizeof(at)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION), .mask.bits_nbo = htonl (comm->mask.bits), .age = htonl (age), }; #ifndef AGE_RESTRICTION_WITH_ECDSA #define verify(a,b,c,d) GNUNET_CRYPTO_edx25519_verify ((a),(b),(c),(d)) #else #define verify(a,b,c,d) GNUNET_CRYPTO_ecdsa_verify ((a),(b),(c),(d)) #endif return verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION, &at, &attest->signature, &comm->keys[group - 1].pub); } #undef verify } void TALER_age_commitment_free ( struct TALER_AgeCommitment *commitment) { if (NULL == commitment) return; if (NULL != commitment->keys) { GNUNET_free (commitment->keys); commitment->keys = NULL; } GNUNET_free (commitment); } void TALER_age_proof_free ( struct TALER_AgeProof *proof) { if (NULL == proof) return; if (NULL != proof->keys) { GNUNET_CRYPTO_zero_keys ( proof->keys, sizeof(*proof->keys) * proof->num); GNUNET_free (proof->keys); proof->keys = NULL; } GNUNET_free (proof); } void TALER_age_commitment_proof_free ( struct TALER_AgeCommitmentProof *acp) { if (NULL == acp) return; if (NULL != acp->proof.keys) { GNUNET_CRYPTO_zero_keys ( acp->proof.keys, sizeof(*acp->proof.keys) * acp->proof.num); GNUNET_free (acp->proof.keys); acp->proof.keys = NULL; } if (NULL != acp->commitment.keys) { 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'=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 */