commit 55e033da608a6bf60e0b08050cd8e548b23fd5ea
parent 17a5c6286d278a26527e3c21c4952e32661f764c
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 11 Oct 2026 14:46:23 +0200
make sure revocation reaches secmod
Diffstat:
4 files changed, 244 insertions(+), 1 deletion(-)
diff --git a/src/exchange/taler-exchange-httpd_post-management-signkeys-EXCHANGE_PUB-revoke.c b/src/exchange/taler-exchange-httpd_post-management-signkeys-EXCHANGE_PUB-revoke.c
@@ -28,6 +28,7 @@
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_secmod-helpers.h"
#include "exchange-database/insert_signkey_revocation.h"
@@ -80,7 +81,9 @@ TEH_handler_management_signkeys_EP_revoke (
TALER_EC_GENERIC_DB_STORE_FAILED,
"signkey revocation");
}
- TEH_keys_update_states ();
+ /* Tell the security module to stop using the key (and to create a
+ replacement); this also updates our key state. */
+ TEH_SECMOD_esign_revoke (exchange_pub);
return TALER_MHD_reply_static (
connection,
MHD_HTTP_NO_CONTENT,
diff --git a/src/include/taler/taler_testing_lib.h b/src/include/taler/taler_testing_lib.h
@@ -2306,6 +2306,30 @@ TALER_TESTING_cmd_revoke_sign_key (
/**
+ * Check that an online signing key was revoked: it was valid in the
+ * /keys of @a keys_before_ref, it is not listed in the /keys of
+ * @a keys_after_ref, and the client therefore rejects replies signed
+ * with it (TALER_EXCHANGE_test_signing_key()).
+ *
+ * @param label command label.
+ * @param signkey_ref reference to a command that identifies the
+ * revoked signing key (i.e. because it was used to sign a
+ * deposit confirmation).
+ * @param keys_before_ref "get exchange" command that downloaded /keys
+ * before the revocation
+ * @param keys_after_ref "get exchange" command that downloaded /keys
+ * after the revocation
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_signkey_revoked (
+ const char *label,
+ const char *signkey_ref,
+ const char *keys_before_ref,
+ const char *keys_after_ref);
+
+
+/**
* Create a request for a wallet's KYC UUID.
*
* @param label command label.
diff --git a/src/testing/test_exchange_api_revocation.c b/src/testing/test_exchange_api_revocation.c
@@ -312,6 +312,55 @@ run (void *cls,
"batch-withdraw-revocation",
"0,2",
"EUR:10"),
+ /* Revocation of an online signing key: get a coin to spend after
+ the revocation (age-restricted, as the plain denominations of
+ all these values were revoked above) ... */
+ TALER_TESTING_cmd_admin_add_incoming ("create-reserve-3",
+ "EUR:1.01",
+ &cred.ba,
+ cred.user42_payto),
+ CMD_EXEC_WIREWATCH ("wirewatch-6"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-signkey-revocation",
+ "create-reserve-3",
+ "EUR:1",
+ 13, /* age restriction */
+ MHD_HTTP_OK),
+ /* ... revoke the key that signed "deposit-full", which the exchange
+ must refuse with a bad master signature ... */
+ TALER_TESTING_cmd_revoke_sign_key ("revoke-signkey-bad-sig",
+ MHD_HTTP_FORBIDDEN,
+ true,
+ "deposit-full"),
+ TALER_TESTING_cmd_revoke_sign_key ("revoke-signkey",
+ MHD_HTTP_NO_CONTENT,
+ false,
+ "deposit-full"),
+ /* ... sign the replacement key the security module created ... */
+ TALER_TESTING_cmd_exec_offline_sign_keys ("sign-replacement-signkey",
+ config_file),
+ /* ... then /keys must no longer offer the revoked key, so that
+ clients reject replies signed with it ... */
+ TALER_TESTING_cmd_get_exchange ("get-exchange-after-signkey-revocation",
+ cred.cfg,
+ NULL,
+ true,
+ true),
+ TALER_TESTING_cmd_check_signkey_revoked (
+ "check-signkey-revoked",
+ "deposit-full",
+ "get-exchange",
+ "get-exchange-after-signkey-revocation"),
+ /* ... and the exchange must sign with the replacement key, which
+ the client accepts. */
+ TALER_TESTING_cmd_deposit (
+ "deposit-after-signkey-revocation",
+ "withdraw-signkey-revocation",
+ 0,
+ cred.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:1",
+ MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
diff --git a/src/testing/testing_api_cmd_revoke_sign_key.c b/src/testing/testing_api_cmd_revoke_sign_key.c
@@ -255,3 +255,170 @@ TALER_TESTING_cmd_revoke_sign_key (
return cmd;
}
}
+
+
+/**
+ * State for a "check signkey revoked" CMD.
+ */
+struct CheckRevokedState
+{
+ /**
+ * Command that offers the revoked signing key.
+ */
+ const char *signkey_ref;
+
+ /**
+ * "get exchange" command that downloaded /keys before the revocation.
+ */
+ const char *keys_before_ref;
+
+ /**
+ * "get exchange" command that downloaded /keys after the revocation.
+ */
+ const char *keys_after_ref;
+};
+
+
+/**
+ * Obtain the /keys of the "get exchange" command @a label.
+ *
+ * @param is interpreter state
+ * @param label label of the command
+ * @return NULL on error
+ */
+static const struct TALER_EXCHANGE_Keys *
+get_keys_of (struct TALER_TESTING_Interpreter *is,
+ const char *label)
+{
+ const struct TALER_TESTING_Command *cmd;
+ struct TALER_EXCHANGE_Keys *keys;
+
+ cmd = TALER_TESTING_interpreter_lookup_command (is,
+ label);
+ if ( (NULL == cmd) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_keys (cmd,
+ &keys)) )
+ return NULL;
+ return keys;
+}
+
+
+/**
+ * Run the "check signkey revoked" command.
+ *
+ * @param cls a `struct CheckRevokedState`
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+check_revoked_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CheckRevokedState *crs = cls;
+ const struct TALER_TESTING_Command *sk_cmd;
+ const struct TALER_ExchangePublicKeyP *exchange_pub;
+ const struct TALER_EXCHANGE_Keys *before;
+ const struct TALER_EXCHANGE_Keys *after;
+
+ (void) cmd;
+ sk_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ crs->signkey_ref);
+ before = get_keys_of (is,
+ crs->keys_before_ref);
+ after = get_keys_of (is,
+ crs->keys_after_ref);
+ if ( (NULL == sk_cmd) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_pub (sk_cmd,
+ 0,
+ &exchange_pub)) ||
+ (NULL == before) ||
+ (NULL == after) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* The key was valid before the revocation ... */
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (before,
+ exchange_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Signing key %s was not valid before its revocation\n",
+ TALER_B2S (exchange_pub));
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* ... /keys no longer lists it after the revocation ... */
+ for (unsigned int i = 0; i<after->num_sign_keys; i++)
+ {
+ if (0 ==
+ GNUNET_memcmp (exchange_pub,
+ &after->sign_keys[i].key))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "/keys still lists revoked signing key %s\n",
+ TALER_B2S (exchange_pub));
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ }
+ /* ... so the client rejects replies signed with it. */
+ if (GNUNET_SYSERR !=
+ TALER_EXCHANGE_test_signing_key (after,
+ exchange_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Client still accepts revoked signing key %s\n",
+ TALER_B2S (exchange_pub));
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Free the state of a "check signkey revoked" CMD.
+ *
+ * @param cls a `struct CheckRevokedState`
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+check_revoked_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CheckRevokedState *crs = cls;
+
+ (void) cmd;
+ GNUNET_free (crs);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_signkey_revoked (
+ const char *label,
+ const char *signkey_ref,
+ const char *keys_before_ref,
+ const char *keys_after_ref)
+{
+ struct CheckRevokedState *crs;
+
+ crs = GNUNET_new (struct CheckRevokedState);
+ crs->signkey_ref = signkey_ref;
+ crs->keys_before_ref = keys_before_ref;
+ crs->keys_after_ref = keys_after_ref;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = crs,
+ .label = label,
+ .run = &check_revoked_run,
+ .cleanup = &check_revoked_cleanup
+ };
+
+ return cmd;
+ }
+}