more KS cleanups, including one race fix

This commit is contained in:
Christian Grothoff 2020-01-17 19:32:12 +01:00
parent bf2cdc7ea2
commit 2ce6c7a9d8
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
4 changed files with 296 additions and 263 deletions

View File

@ -288,10 +288,10 @@ verify_and_execute_deposit (struct MHD_Connection *connection,
/* check denomination */ /* check denomination */
{ {
struct TEH_KS_StateHandle *mks; struct TEH_KS_StateHandle *key_state;
mks = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
if (NULL == mks) if (NULL == key_state)
{ {
TALER_LOG_ERROR ("Lacking keys to operate\n"); TALER_LOG_ERROR ("Lacking keys to operate\n");
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
@ -299,14 +299,14 @@ verify_and_execute_deposit (struct MHD_Connection *connection,
TALER_EC_EXCHANGE_BAD_CONFIGURATION, TALER_EC_EXCHANGE_BAD_CONFIGURATION,
"no keys"); "no keys");
} }
dki = TEH_KS_denomination_key_lookup_by_hash (mks, dki = TEH_KS_denomination_key_lookup_by_hash (key_state,
&deposit->coin.denom_pub_hash, &deposit->coin.denom_pub_hash,
TEH_KS_DKU_DEPOSIT, TEH_KS_DKU_DEPOSIT,
&ec, &ec,
&hc); &hc);
if (NULL == dki) if (NULL == dki)
{ {
TEH_KS_release (mks); TEH_KS_release (key_state);
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
hc, hc,
ec, ec,
@ -314,7 +314,7 @@ verify_and_execute_deposit (struct MHD_Connection *connection,
} }
TALER_amount_ntoh (&dc.value, TALER_amount_ntoh (&dc.value,
&dki->issue.properties.value); &dki->issue.properties.value);
TEH_KS_release (mks); TEH_KS_release (key_state);
} }
/* execute transaction */ /* execute transaction */
dc.deposit = deposit; dc.deposit = deposit;

View File

@ -2273,13 +2273,19 @@ read_again:
void void
TEH_KS_free () TEH_KS_free ()
{ {
GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex));
if (NULL != internal_key_state) if (NULL != internal_key_state)
{ {
struct TEH_KS_StateHandle *ks = internal_key_state; struct TEH_KS_StateHandle *ks = internal_key_state;
internal_key_state = NULL; internal_key_state = NULL;
GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
TEH_KS_release (ks); TEH_KS_release (ks);
} }
else
{
GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
}
} }

View File

@ -517,6 +517,275 @@ refresh_reveal_persist (void *cls,
} }
/**
* Resolve denomination hashes using the @a key_state
*
* @param key_state the key state
* @param connection the MHD connection to handle
* @param rctx context for the operation, partially built at this time
* @param transfer_pub transfer public key
* @param link_sigs_json link signatures in JSON format
* @param new_denoms_h_json requests for fresh coins to be created
* @param coin_evs envelopes of gamma-selected coins to be signed
* @return MHD result code
*/
static int
resolve_refresh_reveal_denominations (struct TEH_KS_StateHandle *key_state,
struct MHD_Connection *connection,
struct RevealContext *rctx,
const json_t *link_sigs_json,
const json_t *new_denoms_h_json,
const json_t *coin_evs)
{
unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
const struct
TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins];
struct GNUNET_HashCode dki_h[num_fresh_coins];
struct TALER_RefreshCoinData rcds[num_fresh_coins];
struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
struct TALER_EXCHANGEDB_RefreshMelt refresh_melt;
int res;
/* Parse denomination key hashes */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL,
&dki_h[i]),
GNUNET_JSON_spec_end ()
};
unsigned int hc;
enum TALER_ErrorCode ec;
res = TALER_MHD_parse_json_array (connection,
new_denoms_h_json,
spec,
i,
-1);
if (GNUNET_OK != res)
{
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
dkis[i] = TEH_KS_denomination_key_lookup_by_hash (key_state,
&dki_h[i],
TEH_KS_DKU_WITHDRAW,
&ec,
&hc);
if (NULL == dkis[i])
{
return TALER_MHD_reply_with_error (connection,
hc,
ec,
"failed to find denomination key");
}
GNUNET_assert (NULL != dkis[i]->denom_priv.rsa_private_key);
}
/* Parse coin envelopes */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct TALER_RefreshCoinData *rcd = &rcds[i];
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_varsize (NULL,
(void **) &rcd->coin_ev,
&rcd->coin_ev_size),
GNUNET_JSON_spec_end ()
};
res = TALER_MHD_parse_json_array (connection,
coin_evs,
spec,
i,
-1);
if (GNUNET_OK != res)
{
for (unsigned int j = 0; j<i; j++)
GNUNET_free_non_null (rcds[j].coin_ev);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
rcd->dk = &dkis[i]->denom_pub;
}
/* lookup old_coin_pub in database */
{
enum GNUNET_DB_QueryStatus qs;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
(qs = TEH_plugin->get_melt (TEH_plugin->cls,
NULL,
&rctx->rc,
&refresh_melt)))
{
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN,
"rc");
break;
case GNUNET_DB_STATUS_HARD_ERROR:
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR,
"failed to fetch session data");
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
default:
GNUNET_break (0); /* should be impossible */
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_INTERNAL_INVARIANT_FAILURE,
"assertion failed");
break;
}
goto cleanup;
}
}
/* Parse link signatures array */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct GNUNET_JSON_Specification link_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, &link_sigs[i]),
GNUNET_JSON_spec_end ()
};
int res;
res = TALER_MHD_parse_json_array (connection,
link_sigs_json,
link_spec,
i,
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
/* Check link_sigs[i] signature */
{
struct TALER_LinkDataPS ldp;
ldp.purpose.size = htonl (sizeof (ldp));
ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
ldp.h_denom_pub = dki_h[i];
ldp.old_coin_pub = refresh_melt.session.coin.coin_pub;
ldp.transfer_pub = rctx->gamma_tp;
GNUNET_CRYPTO_hash (rcds[i].coin_ev,
rcds[i].coin_ev_size,
&ldp.coin_envelope_hash);
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
&ldp.purpose,
&link_sigs[i].eddsa_signature,
&refresh_melt.session.coin.coin_pub.
eddsa_pub))
{
GNUNET_break_op (0);
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID,
"link_sig");
goto cleanup;
}
}
}
rctx->num_fresh_coins = num_fresh_coins;
rctx->rcds = rcds;
rctx->dkis = dkis;
rctx->link_sigs = link_sigs;
/* sign _early_ (optimistic!) to keep out of transaction scope! */
rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins,
struct TALER_DenominationSignature);
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
rctx->ev_sigs[i].rsa_signature
= GNUNET_CRYPTO_rsa_sign_blinded (
rctx->dkis[i]->denom_priv.rsa_private_key,
rctx->rcds[i].coin_ev,
rctx->rcds[i].coin_ev_size);
if (NULL == rctx->ev_sigs[i].rsa_signature)
{
GNUNET_break (0);
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_REFRESH_REVEAL_SIGNING_ERROR,
"internal signing error");
goto cleanup;
}
}
/* We try the three transactions a few times, as theoretically
the pre-check might be satisfied by a concurrent transaction
voiding our final commit due to uniqueness violation; naturally,
on hard errors we exit immediately */
for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++)
{
/* do transactional work */
rctx->preflight_ok = GNUNET_NO;
if ( (GNUNET_OK ==
TEH_DB_run_transaction (connection,
"reveal pre-check",
&res,
&refresh_reveal_preflight,
rctx)) &&
(GNUNET_YES == rctx->preflight_ok) )
{
/* Generate final (positive) response */
GNUNET_assert (NULL != rctx->ev_sigs);
res = reply_refresh_reveal_success (connection,
num_fresh_coins,
rctx->ev_sigs);
GNUNET_break (MHD_NO != res);
goto cleanup; /* aka 'break' */
}
if (GNUNET_SYSERR == rctx->preflight_ok)
{
GNUNET_break (0);
goto cleanup; /* aka 'break' */
}
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run reveal",
&res,
&refresh_reveal_transaction,
rctx))
{
/* reveal failed, too bad */
GNUNET_break_op (0);
goto cleanup; /* aka 'break' */
}
if (GNUNET_OK ==
TEH_DB_run_transaction (connection,
"persist reveal",
&res,
&refresh_reveal_persist,
rctx))
{
/* Generate final (positive) response */
GNUNET_assert (NULL != rctx->ev_sigs);
res = reply_refresh_reveal_success (connection,
num_fresh_coins,
rctx->ev_sigs);
break;
}
} /* end for (retries...) */
cleanup:
GNUNET_break (MHD_NO != res);
/* free resources */
if (NULL != rctx->ev_sigs)
{
for (unsigned int i = 0; i<num_fresh_coins; i++)
if (NULL != rctx->ev_sigs[i].rsa_signature)
GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature);
GNUNET_free (rctx->ev_sigs);
rctx->ev_sigs = NULL; /* just to be safe... */
}
for (unsigned int i = 0; i<num_fresh_coins; i++)
GNUNET_free_non_null (rcds[i].coin_ev);
return res;
}
/** /**
* Handle a "/refresh/reveal" request. Parses the given JSON * Handle a "/refresh/reveal" request. Parses the given JSON
* transfer private keys and if successful, passes everything to * transfer private keys and if successful, passes everything to
@ -543,8 +812,6 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
{ {
unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
unsigned int num_tprivs = json_array_size (tp_json); unsigned int num_tprivs = json_array_size (tp_json);
struct TEH_KS_StateHandle *key_state;
struct TALER_EXCHANGEDB_RefreshMelt refresh_melt;
GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1); GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1);
if ( (num_fresh_coins >= MAX_FRESH_COINS) || if ( (num_fresh_coins >= MAX_FRESH_COINS) ||
@ -594,16 +861,10 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
return (GNUNET_NO == res) ? MHD_YES : MHD_NO; return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
} }
/* Resolve denomination hashes */
{ {
const struct struct TEH_KS_StateHandle *key_state;
TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins]; int ret;
struct GNUNET_HashCode dki_h[num_fresh_coins];
struct TALER_RefreshCoinData rcds[num_fresh_coins];
struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
int res;
/* Resolve denomination hashes */
key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
if (NULL == key_state) if (NULL == key_state)
{ {
@ -613,248 +874,14 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
TALER_EC_REFRESH_REVEAL_KEYS_MISSING, TALER_EC_REFRESH_REVEAL_KEYS_MISSING,
"exchange lacks keys"); "exchange lacks keys");
} }
ret = resolve_refresh_reveal_denominations (key_state,
/* Parse denomination key hashes */ connection,
for (unsigned int i = 0; i<num_fresh_coins; i++) rctx,
{ link_sigs_json,
struct GNUNET_JSON_Specification spec[] = { new_denoms_h_json,
GNUNET_JSON_spec_fixed_auto (NULL, coin_evs);
&dki_h[i]),
GNUNET_JSON_spec_end ()
};
unsigned int hc;
enum TALER_ErrorCode ec;
res = TALER_MHD_parse_json_array (connection,
new_denoms_h_json,
spec,
i,
-1);
if (GNUNET_OK != res)
{
TEH_KS_release (key_state);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
dkis[i] = TEH_KS_denomination_key_lookup_by_hash (key_state,
&dki_h[i],
TEH_KS_DKU_WITHDRAW,
&ec,
&hc);
if (NULL == dkis[i])
{
TEH_KS_release (key_state);
return TALER_MHD_reply_with_error (connection,
hc,
ec,
"failed to find denomination key");
}
GNUNET_assert (NULL != dkis[i]->denom_priv.rsa_private_key);
}
/* Parse coin envelopes */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct TALER_RefreshCoinData *rcd = &rcds[i];
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_varsize (NULL,
(void **) &rcd->coin_ev,
&rcd->coin_ev_size),
GNUNET_JSON_spec_end ()
};
res = TALER_MHD_parse_json_array (connection,
coin_evs,
spec,
i,
-1);
if (GNUNET_OK != res)
{
for (unsigned int j = 0; j<i; j++)
GNUNET_free_non_null (rcds[j].coin_ev);
TEH_KS_release (key_state);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
rcd->dk = &dkis[i]->denom_pub;
}
/* lookup old_coin_pub in database */
{
enum GNUNET_DB_QueryStatus qs;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
(qs = TEH_plugin->get_melt (TEH_plugin->cls,
NULL,
&rctx->rc,
&refresh_melt)))
{
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN,
"rc");
break;
case GNUNET_DB_STATUS_HARD_ERROR:
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR,
"failed to fetch session data");
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
default:
GNUNET_break (0); /* should be impossible */
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_INTERNAL_INVARIANT_FAILURE,
"assertion failed");
break;
}
goto cleanup;
}
}
/* Parse link signatures array */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
struct GNUNET_JSON_Specification link_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, &link_sigs[i]),
GNUNET_JSON_spec_end ()
};
int res;
res = TALER_MHD_parse_json_array (connection,
link_sigs_json,
link_spec,
i,
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
/* Check link_sigs[i] signature */
{
struct TALER_LinkDataPS ldp;
ldp.purpose.size = htonl (sizeof (ldp));
ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
ldp.h_denom_pub = dki_h[i];
ldp.old_coin_pub = refresh_melt.session.coin.coin_pub;
ldp.transfer_pub = rctx->gamma_tp;
GNUNET_CRYPTO_hash (rcds[i].coin_ev,
rcds[i].coin_ev_size,
&ldp.coin_envelope_hash);
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
&ldp.purpose,
&link_sigs[i].eddsa_signature,
&refresh_melt.session.coin.coin_pub.
eddsa_pub))
{
GNUNET_break_op (0);
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID,
"link_sig");
goto cleanup;
}
}
}
rctx->num_fresh_coins = num_fresh_coins;
rctx->rcds = rcds;
rctx->dkis = dkis;
rctx->link_sigs = link_sigs;
/* sign _early_ (optimistic!) to keep out of transaction scope! */
rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins,
struct TALER_DenominationSignature);
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
rctx->ev_sigs[i].rsa_signature
= GNUNET_CRYPTO_rsa_sign_blinded (
rctx->dkis[i]->denom_priv.rsa_private_key,
rctx->rcds[i].coin_ev,
rctx->rcds[i].coin_ev_size);
if (NULL == rctx->ev_sigs[i].rsa_signature)
{
GNUNET_break (0);
res = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_REFRESH_REVEAL_SIGNING_ERROR,
"internal signing error");
goto cleanup;
}
}
/* We try the three transactions a few times, as theoretically
the pre-check might be satisfied by a concurrent transaction
voiding our final commit due to uniqueness violation; naturally,
on hard errors we exit immediately */
for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++)
{
/* do transactional work */
rctx->preflight_ok = GNUNET_NO;
if ( (GNUNET_OK ==
TEH_DB_run_transaction (connection,
"reveal pre-check",
&res,
&refresh_reveal_preflight,
rctx)) &&
(GNUNET_YES == rctx->preflight_ok) )
{
/* Generate final (positive) response */
GNUNET_assert (NULL != rctx->ev_sigs);
res = reply_refresh_reveal_success (connection,
num_fresh_coins,
rctx->ev_sigs);
GNUNET_break (MHD_NO != res);
goto cleanup; /* aka 'break' */
}
if (GNUNET_SYSERR == rctx->preflight_ok)
{
GNUNET_break (0);
goto cleanup; /* aka 'break' */
}
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run reveal",
&res,
&refresh_reveal_transaction,
rctx))
{
/* reveal failed, too bad */
GNUNET_break_op (0);
goto cleanup; /* aka 'break' */
}
if (GNUNET_OK ==
TEH_DB_run_transaction (connection,
"persist reveal",
&res,
&refresh_reveal_persist,
rctx))
{
/* Generate final (positive) response */
GNUNET_assert (NULL != rctx->ev_sigs);
res = reply_refresh_reveal_success (connection,
num_fresh_coins,
rctx->ev_sigs);
break;
}
} /* end for (retries...) */
GNUNET_break (MHD_NO != res);
cleanup:
GNUNET_break (MHD_NO != res);
/* free resources */
if (NULL != rctx->ev_sigs)
{
for (unsigned int i = 0; i<num_fresh_coins; i++)
if (NULL != rctx->ev_sigs[i].rsa_signature)
GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature);
GNUNET_free (rctx->ev_sigs);
}
for (unsigned int i = 0; i<num_fresh_coins; i++)
GNUNET_free_non_null (rcds[i].coin_ev);
TEH_KS_release (key_state); TEH_KS_release (key_state);
return res; return ret;
} }
} }

View File

@ -486,10 +486,10 @@ verify_and_execute_refund (struct MHD_Connection *connection,
} }
{ {
struct TEH_KS_StateHandle *mks; struct TEH_KS_StateHandle *key_state;
mks = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
if (NULL == mks) if (NULL == key_state)
{ {
TALER_LOG_ERROR ("Lacking keys to operate\n"); TALER_LOG_ERROR ("Lacking keys to operate\n");
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
@ -503,7 +503,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
unsigned int hc; unsigned int hc;
enum TALER_ErrorCode ec; enum TALER_ErrorCode ec;
dki = TEH_KS_denomination_key_lookup_by_hash (mks, dki = TEH_KS_denomination_key_lookup_by_hash (key_state,
&denom_hash, &denom_hash,
TEH_KS_DKU_DEPOSIT, TEH_KS_DKU_DEPOSIT,
&ec, &ec,
@ -513,7 +513,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
/* DKI not found, but we do have a coin with this DK in our database; /* DKI not found, but we do have a coin with this DK in our database;
not good... */ not good... */
GNUNET_break (0); GNUNET_break (0);
TEH_KS_release (mks); TEH_KS_release (key_state);
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
hc, hc,
ec, ec,
@ -522,7 +522,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
TALER_amount_ntoh (&rc.expect_fee, TALER_amount_ntoh (&rc.expect_fee,
&dki->issue.properties.fee_refund); &dki->issue.properties.fee_refund);
} }
TEH_KS_release (mks); TEH_KS_release (key_state);
} }
/* Finally run the actual transaction logic */ /* Finally run the actual transaction logic */