anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

commit 902b2e7ca5946190de0163ee56ff90247a9df19a
parent c663b879320216d93db9f21ff76257e724b2291d
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 29 Jul 2026 18:33:14 +0200

resolve validation-logic against a fixed table instead of dlsym

Diffstat:
Msrc/cli/resources/03-backup.json | 2+-
Msrc/cli/resources/04-backup.json | 2+-
Msrc/cli/resources/05-backup.json | 2+-
Msrc/cli/resources/06-backup.json | 2+-
Msrc/reducer/anastasis_api_redux.c | 22++++++++++++++--------
Msrc/reducer/anastasis_api_redux_parse.c | 13+++++++++++++
Msrc/reducer/meson.build | 21+++++++++++++++++++++
Asrc/reducer/test_validation.c | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/reducer/validation.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/reducer/validation.h | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/reducer/validation_AL_NID.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/reducer/validation_BE_NRN.c | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 494 insertions(+), 12 deletions(-)

diff --git a/src/cli/resources/03-backup.json b/src/cli/resources/03-backup.json @@ -117,7 +117,7 @@ }, "widget": "anastasis_gtk_ia_ahv", "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$", - "validation-logic": "CH_AVH_check" + "validation-logic": "CH_AHV_check" } ], "identity_attributes": { diff --git a/src/cli/resources/04-backup.json b/src/cli/resources/04-backup.json @@ -118,7 +118,7 @@ }, "widget": "anastasis_gtk_ia_ahv", "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$", - "validation-logic": "CH_AVH_check" + "validation-logic": "CH_AHV_check" } ], "identity_attributes": { diff --git a/src/cli/resources/05-backup.json b/src/cli/resources/05-backup.json @@ -141,7 +141,7 @@ }, "widget": "anastasis_gtk_ia_ahv", "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$", - "validation-logic": "CH_AVH_check" + "validation-logic": "CH_AHV_check" } ], "identity_attributes": { diff --git a/src/cli/resources/06-backup.json b/src/cli/resources/06-backup.json @@ -141,7 +141,7 @@ }, "widget": "anastasis_gtk_ia_ahv", "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$", - "validation-logic": "CH_AVH_check" + "validation-logic": "CH_AHV_check" } ], "identity_attributes": { diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c @@ -27,7 +27,7 @@ #include <taler/taler_json_lib.h> #include "anastasis_api_redux.h" #include "anastasis_api_redux_state.h" -#include <dlfcn.h> +#include "validation.h" /** @@ -1510,18 +1510,24 @@ enter_user_attributes (struct ANASTASIS_ReduxState *rs, } if (NULL != a->validation_logic) { - bool (*regfun)(const char *); + ANASTASIS_ValidationLogic regfun; - regfun = dlsym (RTLD_DEFAULT, - a->validation_logic); + regfun = ANASTASIS_REDUX_validation_lookup_ (a->validation_logic); if (NULL == regfun) { + /* The name comes from the (untrusted) state, so an unknown one must + fail the validation instead of skipping it. */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Custom validation function `%s' is not available: %s\n", - a->validation_logic, - dlerror ()); + "Validation function `%s' is not known\n", + a->validation_logic); + ANASTASIS_REDUX_fail_ (rs, + cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED, + a->name); + return NULL; } - else if (! regfun (attribute_value)) + if (! regfun (attribute_value)) { ANASTASIS_REDUX_fail_ (rs, cb, diff --git a/src/reducer/anastasis_api_redux_parse.c b/src/reducer/anastasis_api_redux_parse.c @@ -30,6 +30,7 @@ #include "anastasis_redux.h" #include "anastasis_api_redux.h" #include "anastasis_api_redux_state.h" +#include "validation.h" #include <taler/taler_json_lib.h> @@ -447,6 +448,18 @@ parse_required_attributes (const json_t *arr, a->uuid = dup_or_null (uuid); a->tooltip = dup_or_null (tooltip); a->validation_regex = dup_or_null (regex); + if ( (NULL != logic) && + (NULL == ANASTASIS_REDUX_validation_lookup_ (logic)) ) + { + /* reject unknown validation logic already here, so that it cannot even + be round-tripped through a state we hand back to the application */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Validation function `%s' is not known\n", + logic); + *detail = set_detail ("required_attributes", + "validation-logic"); + return GNUNET_SYSERR; + } a->validation_logic = dup_or_null (logic); a->autocomplete = dup_or_null (autocomplete); a->have_optional = ! no_optional; diff --git a/src/reducer/meson.build b/src/reducer/meson.build @@ -9,6 +9,9 @@ libanastasisredux_SOURCES = [ 'anastasis_api_redux_free.c', 'anastasis_api_recovery_redux.c', 'anastasis_api_backup_redux.c', + 'validation.c', + 'validation_AL_NID.c', + 'validation_BE_NRN.c', 'validation_CH_AHV.c', 'validation_CZ_BN.c', 'validation_DE_SVN.c', @@ -75,6 +78,24 @@ test( suite: ['reducer'], ) +test_validation = executable( + 'test_validation', + ['test_validation.c'], + dependencies: [ + gnunetutil_dep, + json_dep, + declare_dependency(link_with: libanastasisredux), + ], + include_directories: [incdir, configuration_inc], + build_by_default: false, + install: false, +) +test( + 'test_validation', + test_validation, + suite: ['reducer'], +) + libanastasisredux_dep = declare_dependency(link_with: libanastasisredux) pkg.generate( libanastasisredux, diff --git a/src/reducer/test_validation.c b/src/reducer/test_validation.c @@ -0,0 +1,154 @@ +/* + This file is part of Anastasis + Copyright (C) 2026 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/test_validation.c + * @brief testcase for the validation-logic dispatch table + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "validation.h" + +/** + * Every name that appears as a "validation-logic" value in + * contrib/redux.*.json. All of them must resolve. + */ +static const char *known[] = { + "AL_NID_check", + "BE_NRN_check", + "CH_AHV_check", + "CZ_BN_check", + "DE_SVN_check", + "DE_TIN_check", + "ES_DNI_check", + "FR_INSEE_check", + "IN_AADHAR_check", + "IT_CF_check", + "NL_BSN_check", + "XX_SQUARE_check", + "XY_PRIME_check", + NULL +}; + +/** + * Names that must NOT resolve; "system" and "abort" are the reason the old + * dlsym() dispatch was a remote code execution bug. + */ +static const char *unknown[] = { + "system", + "abort", + "printf", + "", + "NL_BSN_check ", + "nl_bsn_check", + NULL +}; + + +int +main (int argc, + const char *const argv[]) +{ + GNUNET_log_setup ("test-validation", + "WARNING", + NULL); + for (unsigned int i = 0; NULL != known[i]; i++) + if (NULL == ANASTASIS_REDUX_validation_lookup_ (known[i])) + { + GNUNET_break (0); + fprintf (stderr, + "validation logic `%s' does not resolve\n", + known[i]); + return 1; + } + for (unsigned int i = 0; NULL != unknown[i]; i++) + if (NULL != ANASTASIS_REDUX_validation_lookup_ (unknown[i])) + { + GNUNET_break (0); + fprintf (stderr, + "`%s' must not resolve to a validation function\n", + unknown[i]); + return 1; + } + if (NULL != ANASTASIS_REDUX_validation_lookup_ (NULL)) + { + GNUNET_break (0); + return 1; + } + + /* Belgian national register number: 93.05.18-223.61 is the documented + example; 97 - (930518223 % 97) == 61. As the century is not encoded we + also accept the variant computed with the '2' prefix (check digits 90). */ + if (! BE_NRN_check ("93051822361")) + { + GNUNET_break (0); + return 1; + } + if (! BE_NRN_check ("93051822390")) + { + GNUNET_break (0); + return 1; + } + if (BE_NRN_check ("93051822362")) + { + GNUNET_break (0); + return 1; + } + /* born in 2017: only the '2'-prefixed variant matches */ + if (! BE_NRN_check ("17060612328")) + { + GNUNET_break (0); + return 1; + } + if (BE_NRN_check ("17060612327")) + { + GNUNET_break (0); + return 1; + } + /* malformed input */ + if (BE_NRN_check ("")) + { + GNUNET_break (0); + return 1; + } + if (BE_NRN_check ("9305182236")) /* too short */ + { + GNUNET_break (0); + return 1; + } + if (BE_NRN_check ("930518223611")) /* too long */ + { + GNUNET_break (0); + return 1; + } + if (BE_NRN_check ("9305182236x")) /* not a digit */ + { + GNUNET_break (0); + return 1; + } + + /* AL_NID_check has no implementation yet and deliberately accepts + everything; when it is implemented, this expectation must change. */ + if (! AL_NID_check ("I05101999I")) + { + GNUNET_break (0); + return 1; + } + return 0; +} + + +/* end of test_validation.c */ diff --git a/src/reducer/validation.c b/src/reducer/validation.c @@ -0,0 +1,79 @@ +/* + This file is part of Anastasis + Copyright (C) 2026 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + Anastasis; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation.c + * @brief table of the national identity number validation functions + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "validation.h" + + +/** + * Mapping of a "validation-logic" name to its implementation. + */ +struct ValidationLogic +{ + /** + * Name used in the redux.<cc>.json country files. + */ + const char *name; + + /** + * Function implementing the check. + */ + ANASTASIS_ValidationLogic logic; +}; + + +/** + * All validation functions we support. Every "validation-logic" value + * appearing in contrib/redux.*.json must have an entry here; anything else is + * rejected by #ANASTASIS_REDUX_validation_lookup_(). + */ +static const struct ValidationLogic logics[] = { + { "AL_NID_check", &AL_NID_check }, + { "BE_NRN_check", &BE_NRN_check }, + { "CH_AHV_check", &CH_AHV_check }, + { "CZ_BN_check", &CZ_BN_check }, + { "DE_SVN_check", &DE_SVN_check }, + { "DE_TIN_check", &DE_TIN_check }, + { "ES_DNI_check", &ES_DNI_check }, + { "FR_INSEE_check", &FR_INSEE_check }, + { "IN_AADHAR_check", &IN_AADHAR_check }, + { "IT_CF_check", &IT_CF_check }, + { "NL_BSN_check", &NL_BSN_check }, + { "XX_SQUARE_check", &XX_SQUARE_check }, + { "XY_PRIME_check", &XY_PRIME_check }, + { NULL, NULL } +}; + + +ANASTASIS_ValidationLogic +ANASTASIS_REDUX_validation_lookup_ (const char *name) +{ + if (NULL == name) + return NULL; + for (unsigned int i = 0; NULL != logics[i].name; i++) + if (0 == strcmp (name, + logics[i].name)) + return logics[i].logic; + return NULL; +} + + +/* end of validation.c */ diff --git a/src/reducer/validation.h b/src/reducer/validation.h @@ -0,0 +1,90 @@ +/* + This file is part of Anastasis + Copyright (C) 2026 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + Anastasis; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation.h + * @brief lookup of the national identity number validation functions + * @author Christian Grothoff + * + * The `validation-logic` name of an attribute comes from the reducer state, + * which is attacker-influenceable. It must therefore never be used to look up + * a symbol at run time; the name is resolved against the fixed table in + * validation.c instead, and an unknown name is a validation failure. + */ +#ifndef ANASTASIS_VALIDATION_H +#define ANASTASIS_VALIDATION_H + +#include <stdbool.h> + +/** + * Signature of the per-country identity number check functions. + * + * @param number the identity number entered by the user + * @return true if @a number is well-formed + */ +typedef bool +(*ANASTASIS_ValidationLogic)(const char *number); + + +/** + * Resolve @a name to the validation function implementing it. + * + * @param name value of the "validation-logic" field, must not be NULL + * @return NULL if @a name is not a known validation function + */ +ANASTASIS_ValidationLogic +ANASTASIS_REDUX_validation_lookup_ (const char *name); + + +bool +AL_NID_check (const char *nid_number); + +bool +BE_NRN_check (const char *nrn_number); + +bool +CH_AHV_check (const char *ahv_number); + +bool +CZ_BN_check (const char *b_number); + +bool +DE_SVN_check (const char *ssn_number); + +bool +DE_TIN_check (const char *tin_number); + +bool +ES_DNI_check (const char *dni_number); + +bool +FR_INSEE_check (const char *insee_number); + +bool +IN_AADHAR_check (const char *aadhar_number); + +bool +IT_CF_check (const char *cf_number); + +bool +NL_BSN_check (const char *bsn_number); + +bool +XX_SQUARE_check (const char *sq_number); + +bool +XY_PRIME_check (const char *pr_number); + +#endif diff --git a/src/reducer/validation_AL_NID.c b/src/reducer/validation_AL_NID.c @@ -0,0 +1,50 @@ +/* + This file is part of Anastasis + Copyright (C) 2026 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation_AL_NID.c + * @brief Validation for Albanian national identity numbers + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <string.h> +#include <stdbool.h> + + +/** + * Function to validate an Albanian national identity number (NIPT/NID). + * + * NOT YET IMPLEMENTED: the algorithm computing the trailing check letter has + * not been confirmed against an authoritative specification, and guessing it + * would reject valid numbers. Until it is, this deliberately accepts any + * input and logs that no check was performed. The + * "validation-regex" in contrib/redux.al.json still applies. + * + * @param nid_number identity number to validate (input) + * @return true, always, as no check is implemented yet + */ +bool +AL_NID_check (const char *nid_number); + +/* declaration to fix compiler warning */ +bool +AL_NID_check (const char *nid_number) +{ + (void) nid_number; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Albanian identity numbers are not checked: AL_NID_check is not implemented\n"); + return true; +} diff --git a/src/reducer/validation_BE_NRN.c b/src/reducer/validation_BE_NRN.c @@ -0,0 +1,69 @@ +/* + This file is part of Anastasis + Copyright (C) 2026 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation_BE_NRN.c + * @brief Validation for Belgian national register numbers + * @author Christian Grothoff + */ +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> + + +/** + * Function to validate a Belgian national register number (rijksregisternummer + * / numéro de registre national). + * + * The number is YYMMDD (date of birth) + SSS (serial) + CC (check). The check + * is 97 minus the first nine digits taken as a number, modulo 97. The century + * is not encoded, so for people born from 2000 on a '2' is prepended to the + * nine digits before the computation. Since we cannot tell the two cases + * apart from the number alone, either variant matching is accepted. + * + * See https://nl.wikipedia.org/wiki/Rijksregisternummer + * + * @param nrn_number national register number to validate (input) + * @return true if validation passed, else false + */ +bool +BE_NRN_check (const char *nrn_number); + +/* declaration to fix compiler warning */ +bool +BE_NRN_check (const char *nrn_number) +{ + unsigned long long base = 0; + unsigned int check = 0; + + if (strlen (nrn_number) != 11) + return false; + for (unsigned int i = 0; i<11; i++) + { + unsigned char c = (unsigned char) nrn_number[i]; + + if ( ('0' > c) || ('9' < c) ) + return false; + if (i < 9) + base = base * 10 + (c - '0'); + else + check = check * 10 + (c - '0'); + } + /* born before 2000: the nine digits are used as they are */ + if (check == 97 - (base % 97)) + return true; + /* born from 2000 on: a '2' is prepended */ + return (check == 97 - ((2000000000ULL + base) % 97)); +}