commit ece964144ce390aac8e713c464bac50d3066838c
parent 6fc2e6eb7c9c3b53b44df4e68d795abf18fc2033
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 22 Jul 2026 00:43:51 +0200
implement ANCHOR_ROUND in the secmod library (for Donau)
ANCHOR_ROUND has been documented in taler-exchange-secmod-rsa.conf ever
since db7558bbb ("improve secmod for donau"), but was never actually
implemented.
The exchange does not need the option, but Donau does: a donation unit
key must be valid for exactly one calendar year, as the Donau derives the
validity year of a key from its start date via GNUNET_TIME_time_to_year()
(donau-httpd_get-keys.c) and rejects receipts whose donation unit's
validity year does not match.
Donau's donau-secmod-{rsa,cs}.c already require ANCHOR_ROUND to be
present and to be exactly one year, so without an implementation on this
side the keys started whenever the secmod happened to be run first.
This implement the option in the shared secmod library:
- the start of the validity period of the first key of a denomination is
rounded down to the configured calendar interval;
- the end of each validity period is rounded up to it, which is also what
keeps subsequent anchors from drifting off the calendar boundary, as
GNUNET_TIME_UNIT_YEARS is 365 days and thus short in leap years;
- the "extend period to align end periods" fixup is skipped when rounding
is active, as the calendar interval already provides that alignment and
extending would make the last key cover more than one interval.
We use the UTC rounding variants, as GNUNET_TIME_time_to_year() is UTC-based
as well; with the local time variants the anchor would land on January 1st
00:00 local time, which is in the preceding year for any time zone east of
UTC. This requires libgnunetutil >= 24:0:1.
The value is read from the denomination section, falling back to the
$SECTION-secmod-{rsa,cs} section, and defaults to no rounding, so the
behaviour of the exchange is unchanged. Change the ANCHOR_ROUND example
from "1 ms" to "0", as a millisecond is not a supported calendar interval
and GNUNET_TIME_relative_to_round_interval() would GNUNET_break() on it.
Diffstat:
5 files changed, 147 insertions(+), 17 deletions(-)
diff --git a/src/testing/test_exchange_api.conf b/src/testing/test_exchange_api.conf
@@ -203,8 +203,8 @@ CLIENT_DIR = ${TALER_RUNTIME_DIR}secmod-rsa/clients
# Where should the security module store its own private key?
SM_PRIV_KEY = ${TALER_EXCHANGE_DATA_HOME}secmod-rsa/secmod-private-key
-# Round down anchor key start date to multiples of this time.
-ANCHOR_ROUND = 1 ms
+# Round the key validity period to calendar intervals (0: disabled).
+ANCHOR_ROUND = 0
[taler-exchange-secmod-eddsa]
diff --git a/src/util/secmod_cs.c b/src/util/secmod_cs.c
@@ -157,6 +157,16 @@ struct Denomination
struct GNUNET_TIME_Relative duration_withdraw;
/**
+ * Calendar interval the start of the validity period of our keys is
+ * rounded down to (and the end of the validity period rounded up to).
+ * #GNUNET_TIME_RI_NONE (the default) disables the rounding. Donau sets
+ * this to #GNUNET_TIME_RI_YEAR so that its keys are valid for exactly one
+ * calendar year (starting January 1st UTC), even if the key was generated
+ * in the middle of the year.
+ */
+ enum GNUNET_TIME_RounderInterval anchor_round;
+
+ /**
* What is the configuration section of this denomination type? Also used
* for the directory name where the denomination keys are stored.
*/
@@ -1721,24 +1731,43 @@ create_missing_keys (struct TALER_SECMOD_Options *opt,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting keys of denomination `%s'\n",
denom->section);
- anchor_start = start;
+ /* Round the very first anchor down to the configured calendar
+ interval; subsequent anchors inherit the alignment from the
+ (rounded up) end of the preceding key. The UTC variants are used
+ so that the result does not depend on the time zone the secmod
+ happens to run in. */
+ anchor_start = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_round_down_utc (start.abs_time,
+ denom->anchor_round));
}
finished = GNUNET_TIME_timestamp_cmp (anchor_start,
>=,
end);
while (! finished)
{
+ /* Round the end of the validity period up to the configured calendar
+ interval. As #GNUNET_TIME_UNIT_YEARS is 365 days, this is also what
+ keeps the anchors from drifting off the calendar boundary across leap
+ years. Without ANCHOR_ROUND, all of this is a no-op. */
anchor_end = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (anchor_start.abs_time,
- denom->duration_withdraw));
+ GNUNET_TIME_round_up_utc (
+ GNUNET_TIME_absolute_add (anchor_start.abs_time,
+ denom->duration_withdraw),
+ denom->anchor_round));
next_end = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (anchor_end.abs_time,
- denom->duration_withdraw));
+ GNUNET_TIME_round_up_utc (
+ GNUNET_TIME_absolute_add (anchor_end.abs_time,
+ denom->duration_withdraw),
+ denom->anchor_round));
if (GNUNET_TIME_timestamp_cmp (next_end,
>,
end))
{
- anchor_end = end; /* extend period to align end periods */
+ /* With ANCHOR_ROUND set the calendar interval already provides the
+ alignment, and stretching the last key would make it cover more
+ than the one interval it is supposed to cover. */
+ if (GNUNET_TIME_RI_NONE == denom->anchor_round)
+ anchor_end = end; /* extend period to align end periods */
finished = true;
}
/* adjust start time down to ensure overlap */
@@ -2125,6 +2154,35 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_free (secname);
return GNUNET_SYSERR;
}
+ {
+ struct GNUNET_TIME_Relative ar;
+
+ /* The denomination section takes precedence, the secmod section
+ provides the default for all denominations. */
+ if ( (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ ct,
+ "ANCHOR_ROUND",
+ &ar)) &&
+ (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "ANCHOR_ROUND",
+ &ar)) )
+ ar = GNUNET_TIME_UNIT_ZERO; /* not configured: do not round */
+ denom->anchor_round
+ = GNUNET_TIME_relative_to_round_interval (ar);
+ if ( (GNUNET_TIME_RI_NONE == denom->anchor_round) &&
+ (! GNUNET_TIME_relative_is_zero (ar)) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "ANCHOR_ROUND",
+ "Value given must be zero or exactly one second, minute, hour, day, week, month, quarter or year");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ }
GNUNET_free (secname);
denom->section = GNUNET_strdup (ct);
return GNUNET_OK;
diff --git a/src/util/secmod_rsa.c b/src/util/secmod_rsa.c
@@ -156,6 +156,16 @@ struct Denomination
struct GNUNET_TIME_Relative duration_withdraw;
/**
+ * Calendar interval the start of the validity period of our keys is
+ * rounded down to (and the end of the validity period rounded up to).
+ * #GNUNET_TIME_RI_NONE (the default) disables the rounding. Donau sets
+ * this to #GNUNET_TIME_RI_YEAR so that its keys are valid for exactly one
+ * calendar year (starting January 1st UTC), even if the key was generated
+ * in the middle of the year.
+ */
+ enum GNUNET_TIME_RounderInterval anchor_round;
+
+ /**
* What is the configuration section of this denomination type? Also used
* for the directory name where the denomination keys are stored.
*/
@@ -1453,24 +1463,43 @@ create_missing_keys (struct TALER_SECMOD_Options *opt,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting keys of denomination `%s'\n",
denom->section);
- anchor_start = start;
+ /* Round the very first anchor down to the configured calendar
+ interval; subsequent anchors inherit the alignment from the
+ (rounded up) end of the preceding key. The UTC variants are used
+ so that the result does not depend on the time zone the secmod
+ happens to run in. */
+ anchor_start = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_round_down_utc (start.abs_time,
+ denom->anchor_round));
}
finished = GNUNET_TIME_timestamp_cmp (anchor_start,
>=,
end);
while (! finished)
{
+ /* Round the end of the validity period up to the configured calendar
+ interval. As #GNUNET_TIME_UNIT_YEARS is 365 days, this is also what
+ keeps the anchors from drifting off the calendar boundary across leap
+ years. Without ANCHOR_ROUND, all of this is a no-op. */
anchor_end = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (anchor_start.abs_time,
- denom->duration_withdraw));
+ GNUNET_TIME_round_up_utc (
+ GNUNET_TIME_absolute_add (anchor_start.abs_time,
+ denom->duration_withdraw),
+ denom->anchor_round));
next_end = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (anchor_end.abs_time,
- denom->duration_withdraw));
+ GNUNET_TIME_round_up_utc (
+ GNUNET_TIME_absolute_add (anchor_end.abs_time,
+ denom->duration_withdraw),
+ denom->anchor_round));
if (GNUNET_TIME_timestamp_cmp (next_end,
>,
end))
{
- anchor_end = end; /* extend period to align end periods */
+ /* With ANCHOR_ROUND set the calendar interval already provides the
+ alignment, and stretching the last key would make it cover more
+ than the one interval it is supposed to cover. */
+ if (GNUNET_TIME_RI_NONE == denom->anchor_round)
+ anchor_end = end; /* extend period to align end periods */
finished = true;
}
/* adjust start time down to ensure overlap */
@@ -1883,6 +1912,35 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_free (secname);
return GNUNET_SYSERR;
}
+ {
+ struct GNUNET_TIME_Relative ar;
+
+ /* The denomination section takes precedence, the secmod section
+ provides the default for all denominations. */
+ if ( (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ ct,
+ "ANCHOR_ROUND",
+ &ar)) &&
+ (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "ANCHOR_ROUND",
+ &ar)) )
+ ar = GNUNET_TIME_UNIT_ZERO; /* not configured: do not round */
+ denom->anchor_round
+ = GNUNET_TIME_relative_to_round_interval (ar);
+ if ( (GNUNET_TIME_RI_NONE == denom->anchor_round) &&
+ (! GNUNET_TIME_relative_is_zero (ar)) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "ANCHOR_ROUND",
+ "Value given must be zero or exactly one second, minute, hour, day, week, month, quarter or year");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ }
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg,
ct,
diff --git a/src/util/taler-exchange-secmod-cs.conf b/src/util/taler-exchange-secmod-cs.conf
@@ -21,3 +21,12 @@ SM_PRIV_KEY = ${TALER_DATA_HOME}secmod-cs/secmod-private-key
# For how long into the future do we pre-generate keys?
LOOKAHEAD_SIGN = 1 year
+
+# Align the validity period of the keys to (UTC) calendar intervals: the start
+# date is rounded down and the end date rounded up to this interval. Only 0 (no
+# rounding, the default) and exactly "1 s", "1 minute", "1 hour", "1 day",
+# "1 week", "1 month", "3 months" and "1 year" are supported. Can also be set
+# per denomination, which takes precedence over the value given here.
+# The exchange does not need this; Donau uses "1 year" to have its keys be
+# valid for exactly one calendar year.
+ANCHOR_ROUND = 0
diff --git a/src/util/taler-exchange-secmod-rsa.conf b/src/util/taler-exchange-secmod-rsa.conf
@@ -22,5 +22,11 @@ SM_PRIV_KEY = ${TALER_DATA_HOME}secmod-rsa/secmod-private-key
# For how long into the future do we pre-generate keys?
LOOKAHEAD_SIGN = 1 year
-# Round down anchor key start date to multiples of this time.
-ANCHOR_ROUND = 1 ms
-\ No newline at end of file
+# Align the validity period of the keys to (UTC) calendar intervals: the start
+# date is rounded down and the end date rounded up to this interval. Only 0 (no
+# rounding, the default) and exactly "1 s", "1 minute", "1 hour", "1 day",
+# "1 week", "1 month", "3 months" and "1 year" are supported. Can also be set
+# per denomination, which takes precedence over the value given here.
+# The exchange does not need this; Donau uses "1 year" to have its keys be
+# valid for exactly one calendar year.
+ANCHOR_ROUND = 0