commit 6af6cd9c8e60bd0a581279bd503c838914a28275
parent 902b2e7ca5946190de0163ee56ff90247a9df19a
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 29 Jul 2026 18:38:11 +0200
propagate decryption failures from the crypto wrappers instead of asserting
Diffstat:
10 files changed, 243 insertions(+), 120 deletions(-)
diff --git a/meson.build b/meson.build
@@ -310,7 +310,7 @@ if not get_option('only-doc')
libltversions = [
['libanastasis', '0:0:0'],
- ['libanastasisutil', '0:0:0'],
+ ['libanastasisutil', '1:0:0'],
['libanastasisauthorization', '0:0:0'],
['libanastasiseufin', '0:0:0'],
['libanastasisdb', '3:0:0'],
diff --git a/src/backend/anastasis-httpd_truth-challenge.c b/src/backend/anastasis-httpd_truth-challenge.c
@@ -1232,21 +1232,23 @@ AH_handler_truth_challenge (
/* We've been paid, now validate response */
{
+ enum GNUNET_GenericReturnValue res;
+
/* decrypt encrypted_truth */
- ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
- encrypted_truth,
- encrypted_truth_size,
- &decrypted_truth,
- &decrypted_truth_size);
+ res = ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
+ encrypted_truth,
+ encrypted_truth_size,
+ &decrypted_truth,
+ &decrypted_truth_size);
GNUNET_free (encrypted_truth);
- }
- if (NULL == decrypted_truth)
- {
- GNUNET_free (truth_mime);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
- NULL);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
+ NULL);
+ }
}
/* Not security question and no answer: use plugin to check if
diff --git a/src/backend/anastasis-httpd_truth-solve.c b/src/backend/anastasis-httpd_truth-solve.c
@@ -1373,21 +1373,25 @@ AH_handler_truth_solve (
/* We've been paid, now validate the response */
/* decrypt encrypted_truth */
- ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
- encrypted_truth,
- encrypted_truth_size,
- &decrypted_truth,
- &decrypted_truth_size);
- GNUNET_free (encrypted_truth);
- if (NULL == decrypted_truth)
{
- /* most likely, the decryption key is simply wrong */
- GNUNET_break_op (0);
- GNUNET_free (truth_mime);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
- NULL);
+ enum GNUNET_GenericReturnValue res;
+
+ res = ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
+ encrypted_truth,
+ encrypted_truth_size,
+ &decrypted_truth,
+ &decrypted_truth_size);
+ GNUNET_free (encrypted_truth);
+ if (GNUNET_OK != res)
+ {
+ /* most likely, the decryption key is simply wrong */
+ GNUNET_break_op (0);
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
+ NULL);
+ }
}
/* Special case for secure question: we do not generate a numeric challenge,
diff --git a/src/include/anastasis.h b/src/include/anastasis.h
@@ -686,7 +686,19 @@ enum ANASTASIS_RecoveryStatus
/**
* The Anastasis server reported that the account is unknown.
*/
- ANASTASIS_RS_POLICY_UNKNOWN
+ ANASTASIS_RS_POLICY_UNKNOWN,
+
+ /**
+ * The recovery document could not be decrypted with the key derived
+ * from the identity attributes.
+ */
+ ANASTASIS_RS_POLICY_DECRYPTION_FAILED,
+
+ /**
+ * The core secret could not be reassembled from the key shares we
+ * obtained; at least one of them must have been wrong.
+ */
+ ANASTASIS_RS_CORE_SECRET_RECOVERY_FAILED
};
diff --git a/src/include/anastasis_crypto_lib.h b/src/include/anastasis_crypto_lib.h
@@ -385,8 +385,10 @@ ANASTASIS_CRYPTO_recovery_document_encrypt (
* @param[out] rec_doc return from the result, which contains the encrypted recovery document
* and the nonce and iv used for the encryption as Additional Data
* @param[out] rd_size size of the result
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if @a enc_rec_doc could not
+ * be decrypted (it comes from an untrusted provider)
*/
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_recovery_document_decrypt (
const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
const void *enc_rec_doc,
@@ -456,8 +458,11 @@ ANASTASIS_CRYPTO_keyshare_encrypt (
* @param id the user identification which is the entropy source for the key generation
* @param xsalt answer to security question, otherwise NULL; used as extra salt in KDF
* @param[out] key_share the result of decryption
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if @a enc_key_share could not
+ * be decrypted or had the wrong size (it comes from an untrusted
+ * provider)
*/
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_keyshare_decrypt (
const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share,
const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
@@ -498,8 +503,10 @@ ANASTASIS_CRYPTO_truth_encrypt (
* @param ect_size size of the truth data
* @param truth return from the result, which contains the truth
* @param truth_size size of the result
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if @a enc_truth could not be
+ * decrypted
*/
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_truth_decrypt (
const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
const void *enc_truth,
@@ -577,8 +584,10 @@ ANASTASIS_CRYPTO_destroy_encrypted_core_secret (
* @param encrypted_core_secret_size size of the encrypted core secret
* @param[out] core_secret decrypted core secret will be returned
* @param[out] core_secret_size size of core secret
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if the master key or the core
+ * secret could not be decrypted; @a core_secret is then NULL
*/
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_core_secret_recover (
const void *encrypted_master_key,
size_t encrypted_master_key_size,
diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c
@@ -380,10 +380,19 @@ truth_solve_cb (void *cls,
ANASTASIS_CRYPTO_user_identifier_derive (recovery->id_data,
&c->provider_salt,
&id);
- ANASTASIS_CRYPTO_keyshare_decrypt (&tsr->details.success.eks,
- &id,
- c->answer,
- &c->key_share);
+ if (GNUNET_OK !=
+ ANASTASIS_CRYPTO_keyshare_decrypt (&tsr->details.success.eks,
+ &id,
+ c->answer,
+ &c->key_share))
+ {
+ /* the provider answered, but with a key share we cannot use */
+ GNUNET_break_op (0);
+ csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE;
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
recovery->solved_challenges[recovery->solved_challenge_pos++] = c;
c->ci.solved = true;
csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED;
@@ -442,13 +451,25 @@ truth_solve_cb (void *cls,
&policy_key);
GNUNET_assert (NULL != rdps->emk);
GNUNET_assert (rdps->emk_size > 0);
- ANASTASIS_CRYPTO_core_secret_recover (rdps->emk,
- rdps->emk_size,
- &policy_key,
- recovery->enc_core_secret,
- recovery->enc_core_secret_size,
- &core_secret,
- &core_secret_size);
+ if (GNUNET_OK !=
+ ANASTASIS_CRYPTO_core_secret_recover (rdps->emk,
+ rdps->emk_size,
+ &policy_key,
+ recovery->enc_core_secret,
+ recovery->enc_core_secret_size,
+ &core_secret,
+ &core_secret_size))
+ {
+ /* do NOT hand a garbage buffer to the application as if it were
+ the recovered secret */
+ GNUNET_break_op (0);
+ recovery->csc (recovery->csc_cls,
+ ANASTASIS_RS_CORE_SECRET_RECOVERY_FAILED,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (recovery);
+ return;
+ }
recovery->csc (recovery->csc_cls,
ANASTASIS_RS_SUCCESS,
core_secret,
@@ -725,11 +746,21 @@ policy_lookup_cb (void *cls,
ANASTASIS_recovery_abort (r);
return;
}
- ANASTASIS_CRYPTO_recovery_document_decrypt (&r->id,
- dd->details.ok.policy,
- dd->details.ok.policy_size,
- &plaintext,
- &size_plaintext);
+ if (GNUNET_OK !=
+ ANASTASIS_CRYPTO_recovery_document_decrypt (&r->id,
+ dd->details.ok.policy,
+ dd->details.ok.policy_size,
+ &plaintext,
+ &size_plaintext))
+ {
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DECRYPTION_FAILED,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
if (size_plaintext < sizeof (uint32_t))
{
GNUNET_break_op (0);
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
@@ -367,6 +367,16 @@ error_by_status (enum ANASTASIS_RecoveryStatus rc,
*msg = gettext_noop ("account unknown to Anastasis server");
ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
break;
+ case ANASTASIS_RS_POLICY_DECRYPTION_FAILED:
+ *msg = gettext_noop (
+ "failed to decrypt policy document with the given identity attributes");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_CORE_SECRET_RECOVERY_FAILED:
+ *msg = gettext_noop (
+ "failed to reassemble the core secret from the recovered key shares");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
}
return ec;
}
diff --git a/src/testing/testing_api_cmd_truth_store.c b/src/testing/testing_api_cmd_truth_store.c
@@ -247,11 +247,17 @@ truth_store_run (void *cls,
void *t;
size_t t_size;
- ANASTASIS_CRYPTO_truth_decrypt (&tss->key,
- encrypted_truth,
- size_encrypted_truth,
- &t,
- &t_size);
+ if (GNUNET_OK !=
+ ANASTASIS_CRYPTO_truth_decrypt (&tss->key,
+ encrypted_truth,
+ size_encrypted_truth,
+ &t,
+ &t_size))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
if ( (t_size != tss->truth_data_size) ||
(0 != memcmp (tss->truth_data,
t,
diff --git a/src/util/anastasis_crypto.c b/src/util/anastasis_crypto.c
@@ -201,6 +201,7 @@ anastasis_decrypt (const void *key,
{
GNUNET_break (0);
GNUNET_free (*res);
+ *res = NULL;
*res_size = 0;
return GNUNET_SYSERR;
}
@@ -288,7 +289,7 @@ ANASTASIS_CRYPTO_recovery_document_encrypt (
}
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_recovery_document_decrypt (
const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
const void *enc_rec_doc,
@@ -298,13 +299,13 @@ ANASTASIS_CRYPTO_recovery_document_decrypt (
{
const char *salt = "erd";
- anastasis_decrypt (id,
- sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
- enc_rec_doc,
- erd_size,
- salt,
- rec_doc,
- rd_size);
+ return anastasis_decrypt (id,
+ sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
+ enc_rec_doc,
+ erd_size,
+ salt,
+ rec_doc,
+ rd_size);
}
@@ -339,7 +340,7 @@ ANASTASIS_CRYPTO_keyshare_encrypt (
}
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_keyshare_decrypt (
const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share,
const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
@@ -350,19 +351,29 @@ ANASTASIS_CRYPTO_keyshare_decrypt (
size_t ks_size = 0;
void *ks = NULL;
- anastasis_decrypt (id,
- sizeof (*id),
- enc_key_share,
- sizeof (*enc_key_share),
- (NULL == xsalt) ? salt : xsalt,
- &ks,
- &ks_size);
- GNUNET_assert (ks_size ==
- sizeof (struct ANASTASIS_CRYPTO_KeyShareP));
+ if (GNUNET_OK !=
+ anastasis_decrypt (id,
+ sizeof (*id),
+ enc_key_share,
+ sizeof (*enc_key_share),
+ (NULL == xsalt) ? salt : xsalt,
+ &ks,
+ &ks_size))
+ return GNUNET_SYSERR;
+ if (ks_size !=
+ sizeof (struct ANASTASIS_CRYPTO_KeyShareP))
+ {
+ /* the ciphertext came from an untrusted provider, so a wrong
+ plaintext size must not be an assertion */
+ GNUNET_break_op (0);
+ GNUNET_free (ks);
+ return GNUNET_SYSERR;
+ }
memcpy (key_share,
ks,
sizeof (struct ANASTASIS_CRYPTO_KeyShareP));
GNUNET_free (ks);
+ return GNUNET_OK;
}
@@ -388,7 +399,7 @@ ANASTASIS_CRYPTO_truth_encrypt (
}
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_truth_decrypt (
const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
const void *enc_truth,
@@ -398,13 +409,13 @@ ANASTASIS_CRYPTO_truth_decrypt (
{
const char *salt = "ect";
- anastasis_decrypt (truth_enc_key,
- sizeof (struct ANASTASIS_CRYPTO_TruthKeyP),
- enc_truth,
- ect_size,
- salt,
- truth,
- truth_size);
+ return anastasis_decrypt (truth_enc_key,
+ sizeof (struct ANASTASIS_CRYPTO_TruthKeyP),
+ enc_truth,
+ ect_size,
+ salt,
+ truth,
+ truth_size);
}
@@ -494,7 +505,7 @@ ANASTASIS_CRYPTO_core_secret_encrypt (
}
-void
+enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_core_secret_recover (
const void *encrypted_master_key,
size_t encrypted_master_key_size,
@@ -507,34 +518,47 @@ ANASTASIS_CRYPTO_core_secret_recover (
void *master_key;
size_t master_key_size;
- *core_secret = GNUNET_malloc (encrypted_core_secret_size);
- anastasis_decrypt (&policy_key->key,
- sizeof (struct GNUNET_HashCode),
- encrypted_master_key,
- encrypted_master_key_size,
- "emk",
- &master_key,
- &master_key_size);
- GNUNET_break (NULL != master_key);
+ *core_secret = NULL;
+ *core_secret_size = 0;
+ if (GNUNET_OK !=
+ anastasis_decrypt (&policy_key->key,
+ sizeof (struct GNUNET_HashCode),
+ encrypted_master_key,
+ encrypted_master_key_size,
+ "emk",
+ &master_key,
+ &master_key_size))
+ {
+ /* the key shares came from providers we do not trust, so failing to
+ reassemble the master key is an expected outcome, not a bug */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"At %s:%d encrypted core secret is %s-%llu b\n", __FILE__,
__LINE__,
TALER_b2s (encrypted_core_secret, encrypted_core_secret_size),
(unsigned long long) encrypted_core_secret_size);
- anastasis_decrypt (master_key,
- master_key_size,
- encrypted_core_secret,
- encrypted_core_secret_size,
- "cse",
- core_secret,
- core_secret_size);
- GNUNET_break (NULL != *core_secret);
+ if (GNUNET_OK !=
+ anastasis_decrypt (master_key,
+ master_key_size,
+ encrypted_core_secret,
+ encrypted_core_secret_size,
+ "cse",
+ core_secret,
+ core_secret_size))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (master_key);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (master_key);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"At %s:%d decrypted core secret is %s-%llu b\n", __FILE__,
__LINE__,
TALER_b2s (*core_secret, *core_secret_size),
(unsigned long long) *core_secret_size);
- GNUNET_assert (GNUNET_SYSERR != *core_secret_size);
+ return GNUNET_OK;
}
diff --git a/src/util/test_anastasis_crypto.c b/src/util/test_anastasis_crypto.c
@@ -117,11 +117,12 @@ test_recovery_document (void)
&ciphertext,
&size_ciphertext);
- ANASTASIS_CRYPTO_recovery_document_decrypt (&id,
- ciphertext,
- size_ciphertext,
- &plaintext,
- &size_plaintext);
+ GNUNET_assert (GNUNET_OK ==
+ ANASTASIS_CRYPTO_recovery_document_decrypt (&id,
+ ciphertext,
+ size_ciphertext,
+ &plaintext,
+ &size_plaintext));
GNUNET_assert (strlen (test) == size_plaintext);
ret = strncmp (plaintext, test, strlen (test));
json_decref (id_data);
@@ -156,10 +157,11 @@ test_key_share (void)
&id,
NULL,
&ciphertext);
- ANASTASIS_CRYPTO_keyshare_decrypt (&ciphertext,
- &id,
- NULL,
- &plaintext);
+ GNUNET_assert (GNUNET_OK ==
+ ANASTASIS_CRYPTO_keyshare_decrypt (&ciphertext,
+ &id,
+ NULL,
+ &plaintext));
return GNUNET_memcmp (&key_share,
&plaintext);
}
@@ -192,11 +194,12 @@ test_truth (void)
&ciphertext,
&size_ciphertext);
- ANASTASIS_CRYPTO_truth_decrypt (&truth_enc_key,
- ciphertext,
- size_ciphertext,
- &plaintext,
- &size_plaintext);
+ GNUNET_assert (GNUNET_OK ==
+ ANASTASIS_CRYPTO_truth_decrypt (&truth_enc_key,
+ ciphertext,
+ size_ciphertext,
+ &plaintext,
+ &size_plaintext));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"TRUTH_AFTER: %s\n",
TALER_b2s (plaintext, size_plaintext));
@@ -262,13 +265,15 @@ test_core_secret (void)
void *dec_core_secret;
size_t core_secret_size;
- ANASTASIS_CRYPTO_core_secret_recover (cser->enc_master_keys[k],
- cser->enc_master_key_sizes[k],
- &policy_keys[k],
- cser->enc_core_secret,
- cser->enc_core_secret_size,
- &dec_core_secret,
- &core_secret_size);
+ GNUNET_assert (GNUNET_OK ==
+ ANASTASIS_CRYPTO_core_secret_recover (
+ cser->enc_master_keys[k],
+ cser->enc_master_key_sizes[k],
+ &policy_keys[k],
+ cser->enc_core_secret,
+ cser->enc_core_secret_size,
+ &dec_core_secret,
+ &core_secret_size));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"CORE_SECRET_AFTER_%i: %s\n",
k,
@@ -281,6 +286,26 @@ test_core_secret (void)
test)));
GNUNET_free (dec_core_secret);
}
+ /* recovering with a key that does not belong to the ciphertext must fail
+ instead of returning garbage as if it had succeeded */
+ {
+ void *dec_core_secret;
+ size_t core_secret_size;
+ struct ANASTASIS_CRYPTO_PolicyKeyP wrong_key;
+
+ GNUNET_CRYPTO_random_block (&wrong_key,
+ sizeof (wrong_key));
+ GNUNET_assert (GNUNET_OK !=
+ ANASTASIS_CRYPTO_core_secret_recover (
+ cser->enc_master_keys[0],
+ cser->enc_master_key_sizes[0],
+ &wrong_key,
+ cser->enc_core_secret,
+ cser->enc_core_secret_size,
+ &dec_core_secret,
+ &core_secret_size));
+ GNUNET_assert (NULL == dec_core_secret);
+ }
ANASTASIS_CRYPTO_destroy_encrypted_core_secret (cser);
return 0;
}