diff options
-rw-r--r-- | doc/sphinx/rest.rst | 15 | ||||
-rw-r--r-- | src/backend/anastasis-httpd_policy-meta.c | 16 | ||||
-rw-r--r-- | src/include/anastasis.h | 67 | ||||
-rw-r--r-- | src/include/anastasis_database_plugin.h | 2 | ||||
-rw-r--r-- | src/include/anastasis_service.h | 5 | ||||
-rw-r--r-- | src/lib/Makefile.am | 1 | ||||
-rw-r--r-- | src/lib/anastasis_backup.c | 27 | ||||
-rw-r--r-- | src/lib/anastasis_meta.c | 180 | ||||
-rw-r--r-- | src/restclient/anastasis_api_policy_meta_lookup.c | 33 | ||||
-rw-r--r-- | src/stasis/plugin_anastasis_postgres.c | 13 | ||||
-rw-r--r-- | src/stasis/stasis-0001.sql | 3 |
11 files changed, 324 insertions, 38 deletions
diff --git a/doc/sphinx/rest.rst b/doc/sphinx/rest.rst index 7341992..b5ab50d 100644 --- a/doc/sphinx/rest.rst +++ b/doc/sphinx/rest.rst @@ -167,11 +167,18 @@ In the following, UUID is always defined and used according to `RFC 4122`_. .. ts:def:: RecoveryMetaSummary interface RecoveryMetaSummary { - // Version numbers as a string (!) are used as keys, - // the value being the base32-encoded encrypted meta data - // for that version. A value can be NULL if the document + // Version numbers as a string (!) are used as keys. + "$VERSION": MetaData; + } + + interface MetaData { + // The meta value can be NULL if the document // exists but no meta data was provided. - "$VERSION": EncryptedMetaData; + meta?: String; + + // Server-time indicative of when the recovery + // document was uploaded. + upload_time: Timestamp; } .. http:get:: /policy/$ACCOUNT_PUB[?version=$NUMBER] diff --git a/src/backend/anastasis-httpd_policy-meta.c b/src/backend/anastasis-httpd_policy-meta.c index a786c68..7d143ef 100644 --- a/src/backend/anastasis-httpd_policy-meta.c +++ b/src/backend/anastasis-httpd_policy-meta.c @@ -36,6 +36,7 @@ * * @param cls closure with a `json_t *` to build up * @param version the version of the recovery document + * @param ts timestamp when the document was created * @param recovery_meta_data contains meta data about the encrypted recovery document * @param recovery_meta_data_size size of @a recovery_meta_data blob * @return #GNUNET_OK to continue to iterate, #GNUNET_NO to abort iteration @@ -43,6 +44,7 @@ static enum GNUNET_GenericReturnValue build_meta_result (void *cls, uint32_t version, + struct GNUNET_TIME_Timestamp ts, const void *recovery_meta_data, size_t recovery_meta_data_size) { @@ -57,11 +59,15 @@ build_meta_result (void *cls, json_object_set_new ( result, version_s, - (NULL == recovery_meta_data) - ? json_null () - : GNUNET_JSON_from_data ( - recovery_meta_data, - recovery_meta_data_size))); + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_varsize ( + "meta", + recovery_meta_data, + recovery_meta_data_size)), + GNUNET_JSON_pack_timestamp ( + "upload_time", + ts)))); return GNUNET_OK; } diff --git a/src/include/anastasis.h b/src/include/anastasis.h index b957f18..92c0745 100644 --- a/src/include/anastasis.h +++ b/src/include/anastasis.h @@ -374,6 +374,67 @@ ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c); /** + * Handle for an operation to get available recovery + * document versions. + */ +struct ANASTASIS_VersionCheck; + + +/** + * Callback which passes back meta data about one of the + * recovery documents available at the provider. + * + * @param cls closure for the callback + * @param version version number of the policy document, + * 0 for the end of the list + * @param server_time time of the backup at the provider + * @param recdoc_id hash of the compressed recovery document, uniquely + * identifies the document; NULL for the end of the list + * @param secret_name name of the secret as chosen by the user, + * or NULL if the user did not provide a name + */ +typedef void +(*ANASTASIS_MetaPolicyCallback)(void *cls, + uint32_t version, + struct GNUNET_TIME_Timestamp server_time, + const struct GNUNET_HashCode *recdoc_id, + const char *secret_name); + + +/** + * Obtain an overview of available recovery policies from the + * specified provider. + * + * @param ctx context for making HTTP requests + * @param id_data contains the users identity, (user account on providers) + * @param version defines the version which will be downloaded, 0 for latest version + * @param anastasis_provider_url provider url + * @param provider_salt the server salt + * @param mpc function called with the available versions + * @param mpc_cls closure for @a mpc callback + * @return recovery operation handle + */ +struct ANASTASIS_VersionCheck * +ANASTASIS_recovery_get_versions ( + struct GNUNET_CURL_Context *ctx, + const json_t *id_data, + unsigned int max_version, + const char *anastasis_provider_url, + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt, + ANASTASIS_MetaPolicyCallback mpc, + void *mpc_cls); + + +/** + * Cancel version check operation. + * + * @param vc operation to cancel + */ +void +ANASTASIS_recovery_get_versions_cancel (struct ANASTASIS_VersionCheck *vc); + + +/** * Defines a Decryption Policy with multiple escrow methods */ struct ANASTASIS_DecryptionPolicy @@ -528,9 +589,9 @@ struct ANASTASIS_Recovery; * * @param ctx context for making HTTP requests * @param id_data contains the users identity, (user account on providers) - * @param version defines the version which will be downloaded NULL for latest version - * @param anastasis_provider_url NULL terminated list of possible provider urls - * @param provider_salt the server salt + * @param version defines the version which will be downloaded, 0 for latest version + * @param anastasis_provider_url provider REST API endpoint url + * @param provider_salt the provider's salt * @param pc opens the policy call back which holds the downloaded version and the policies * @param pc_cls closure for callback * @param csc core secret callback is opened, with this the core secert is passed to the client after the authentication diff --git a/src/include/anastasis_database_plugin.h b/src/include/anastasis_database_plugin.h index 20e87d1..2082bf9 100644 --- a/src/include/anastasis_database_plugin.h +++ b/src/include/anastasis_database_plugin.h @@ -165,6 +165,7 @@ typedef bool * * @param cls closure * @param version the version of the recovery document + * @param ts timestamp when the document was uploaded * @param recovery_meta_data contains meta data about the encrypted recovery document * @param recovery_meta_data_size size of @a recovery_meta_data blob * @return #GNUNET_OK to continue to iterate, #GNUNET_NO to abort iteration @@ -172,6 +173,7 @@ typedef bool typedef enum GNUNET_GenericReturnValue (*ANASTASIS_DB_RecoveryMetaCallback)(void *cls, uint32_t version, + struct GNUNET_TIME_Timestamp ts, const void *recovery_meta_data, size_t recovery_meta_data_size); diff --git a/src/include/anastasis_service.h b/src/include/anastasis_service.h index ee24b7a..3bc8f87 100644 --- a/src/include/anastasis_service.h +++ b/src/include/anastasis_service.h @@ -162,6 +162,11 @@ struct ANASTASIS_MetaDataEntry { /** + * Timestamp of the backup at the server. + */ + struct GNUNET_TIME_Timestamp server_time; + + /** * The encrypted meta data we downloaded. */ const void *meta_data; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 07460d4..6f71418 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -14,6 +14,7 @@ libanastasis_la_LDFLAGS = \ -no-undefined libanastasis_la_SOURCES = \ anastasis_backup.c \ + anastasis_meta.c \ anastasis_recovery.c libanastasis_la_LIBADD = \ $(top_builddir)/src/util/libanastasisutil.la \ diff --git a/src/lib/anastasis_backup.c b/src/lib/anastasis_backup.c index 20c77e4..2e769ca 100644 --- a/src/lib/anastasis_backup.c +++ b/src/lib/anastasis_backup.c @@ -734,6 +734,8 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, json_t *esc_methods; size_t recovery_document_size; char *recovery_document_str; + size_t meta_size; + void *meta; if (0 == pss_length) { @@ -889,6 +891,18 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, recovery_document_str = (char *) cbuf; } + meta_size = sizeof (struct GNUNET_HashCode); + if (NULL != secret_name) + meta_size += strlen (secret_name) + 1; + meta = GNUNET_malloc (meta_size); + GNUNET_CRYPTO_hash (recovery_document_str, + recovery_document_size, + (struct GNUNET_HashCode *) meta); + if (NULL != secret_name) + memcpy (meta + sizeof (struct GNUNET_HashCode), + secret_name, + strlen (secret_name) + 1); + for (unsigned int l = 0; l < ss->pss_length; l++) { struct PolicyStoreState *pss = &ss->pss[l]; @@ -905,12 +919,11 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, ANASTASIS_CRYPTO_user_identifier_derive (id_data, &pss->server_salt, &pss->id); - if (NULL != secret_name) - ANASTASIS_CRYPTO_recovery_metadata_encrypt (&pss->id, - secret_name, - strlen (secret_name), - &enc_meta, - &enc_meta_size); + ANASTASIS_CRYPTO_recovery_metadata_encrypt (&pss->id, + meta, + meta_size, + &enc_meta, + &enc_meta_size); ANASTASIS_CRYPTO_account_private_key_derive (&pss->id, &anastasis_priv); ANASTASIS_CRYPTO_recovery_document_encrypt (&pss->id, @@ -943,9 +956,11 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, GNUNET_break (0); ANASTASIS_secret_share_cancel (ss); GNUNET_free (recovery_document_str); + GNUNET_free (meta); return NULL; } } + GNUNET_free (meta); GNUNET_free (recovery_document_str); return ss; } diff --git a/src/lib/anastasis_meta.c b/src/lib/anastasis_meta.c new file mode 100644 index 0000000..7812f6b --- /dev/null +++ b/src/lib/anastasis_meta.c @@ -0,0 +1,180 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 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/> +*/ +/** + * @brief anastasis client api to get recovery document meta data + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis.h" +#include <taler/taler_json_lib.h> +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * Handle for a version check operation. + */ +struct ANASTASIS_VersionCheck +{ + /** + * Function to call with results. + */ + ANASTASIS_MetaPolicyCallback mpc; + + /** + * Closure for @e mpc. + */ + void *mpc_cls; + + /** + * Handle for the actual REST operation. + */ + struct ANASTASIS_PolicyMetaLookupOperation *plm; + + /** + * User identifier (needed to decrypt). + */ + struct ANASTASIS_CRYPTO_UserIdentifierP id; +}; + + +/** + * Function called with results from a GET /policy/$POL/meta request + * + * @param cls closure with the `struct ANASTASIS_VersionCheck *` + * @param http_status HTTP status code for this request + * @param dd the response details + */ +static void +meta_cb ( + void *cls, + unsigned int http_status, + const struct ANASTASIS_MetaDownloadDetails *dd) +{ + struct ANASTASIS_VersionCheck *vc = cls; + + vc->plm = NULL; + if ( (MHD_HTTP_OK != http_status) || + (NULL == dd) ) + { + vc->mpc (vc->mpc_cls, + 0, + GNUNET_TIME_UNIT_ZERO_TS, + NULL, + NULL); + ANASTASIS_recovery_get_versions_cancel (vc); + return; + } + for (size_t i = 0; i<dd->metas_length; i++) + { + const struct ANASTASIS_MetaDataEntry *meta = &dd->metas[i]; + const char *secret_name = NULL; + const struct GNUNET_HashCode *eph; + void *dec; + size_t dec_len; + + if (GNUNET_OK != + ANASTASIS_CRYPTO_recovery_metadata_decrypt ( + &vc->id, + meta->meta_data, + meta->meta_data_size, + &dec, + &dec_len)) + { + GNUNET_break_op (0); + continue; + } + if (sizeof (*eph) > dec_len) + { + GNUNET_break_op (0); + GNUNET_free (dec); + continue; + } + eph = dec; + if (sizeof (*eph) < dec_len) + { + secret_name = (const char *) &eph[1]; + dec_len -= sizeof (*eph); + if ('\0' != secret_name[dec_len - 1]) + { + GNUNET_break_op (0); + GNUNET_free (dec); + continue; + } + } + vc->mpc (vc->mpc_cls, + meta->version, + meta->server_time, + eph, + secret_name); + GNUNET_free (dec); + } + vc->mpc (vc->mpc_cls, + 0, + GNUNET_TIME_UNIT_ZERO_TS, + NULL, + NULL); + ANASTASIS_recovery_get_versions_cancel (vc); +} + + +struct ANASTASIS_VersionCheck * +ANASTASIS_recovery_get_versions ( + struct GNUNET_CURL_Context *ctx, + const json_t *id_data, + unsigned int max_version, + const char *anastasis_provider_url, + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt, + ANASTASIS_MetaPolicyCallback mpc, + void *mpc_cls) +{ + struct ANASTASIS_VersionCheck *vc; + struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub; + + vc = GNUNET_new (struct ANASTASIS_VersionCheck); + vc->mpc = mpc; + vc->mpc_cls = mpc_cls; + ANASTASIS_CRYPTO_user_identifier_derive (id_data, + provider_salt, + &vc->id); + ANASTASIS_CRYPTO_account_public_key_derive (&vc->id, + &account_pub); + vc->plm = ANASTASIS_policy_meta_lookup (ctx, + anastasis_provider_url, + &account_pub, + max_version, + &meta_cb, + vc); + if (NULL == vc->plm) + { + GNUNET_break (0); + GNUNET_free (vc); + return NULL; + } + return vc; +} + + +void +ANASTASIS_recovery_get_versions_cancel (struct ANASTASIS_VersionCheck *vc) +{ + if (NULL != vc->plm) + { + ANASTASIS_policy_meta_lookup_cancel (vc->plm); + vc->plm = NULL; + } + GNUNET_free (vc); +} diff --git a/src/restclient/anastasis_api_policy_meta_lookup.c b/src/restclient/anastasis_api_policy_meta_lookup.c index 9be49ca..b49d1b8 100644 --- a/src/restclient/anastasis_api_policy_meta_lookup.c +++ b/src/restclient/anastasis_api_policy_meta_lookup.c @@ -28,6 +28,7 @@ #include <microhttpd.h> /* just for HTTP status codes */ #include "anastasis_service.h" #include "anastasis_api_curl_defaults.h" +#include <gnunet/gnunet_json_lib.h> #include <taler/taler_signatures.h> @@ -132,7 +133,15 @@ handle_policy_meta_lookup_finished (void *cls, { unsigned int ver; char dummy; - const char *vals; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_varsize ("meta", + &md[off], + &metas[off].meta_data_size)), + GNUNET_JSON_spec_timestamp ("upload_time", + &metas[off].server_time), + GNUNET_JSON_spec_end () + }; if (1 != sscanf (label, "%u%c", @@ -142,27 +151,15 @@ handle_policy_meta_lookup_finished (void *cls, GNUNET_break (0); break; } - metas[off].version = (uint32_t) ver; - if (json_is_null (val)) - { - metas[off].meta_data = NULL; - metas[off].meta_data_size = 0; - off++; - continue; - } - vals = json_string_value (val); - if ( (NULL == vals) || - (GNUNET_OK != - GNUNET_STRINGS_string_to_data_alloc (vals, - strlen (vals), - &md[off], - &metas[off].meta_data_size)) ) + if (GNUNET_OK != + GNUNET_JSON_parse (val, + spec, + NULL, NULL)) { - GNUNET_break (0); + GNUNET_break_op (0); break; } metas[off].version = (uint32_t) ver; - metas[off].meta_data = md[off]; off++; } if (off < mlen) diff --git a/src/stasis/plugin_anastasis_postgres.c b/src/stasis/plugin_anastasis_postgres.c index ef13c6c..bfe86da 100644 --- a/src/stasis/plugin_anastasis_postgres.c +++ b/src/stasis/plugin_anastasis_postgres.c @@ -334,9 +334,10 @@ prepare_statements (void *cls) ",recovery_data_hash" ",recovery_data" ",recovery_meta_data" + ",creation_date" ") VALUES " - "($1, $2, $3, $4, $5, $6);", - 6), + "($1, $2, $3, $4, $5, $6, $7);", + 7), GNUNET_PQ_make_prepare ("truth_select", "SELECT " " method_name" @@ -348,6 +349,7 @@ prepare_statements (void *cls) GNUNET_PQ_make_prepare ("recoverydocument_select_meta", "SELECT " " version" + ",creation_date" ",recovery_meta_data" " FROM anastasis_recoverydocument" " WHERE user_id=$1" @@ -1019,6 +1021,8 @@ postgres_store_recovery_document ( /* finally, actually insert the recovery document */ { + struct GNUNET_TIME_Timestamp now + = GNUNET_TIME_timestamp_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_uint32 (version), @@ -1030,6 +1034,7 @@ postgres_store_recovery_document ( ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_fixed_size (recovery_meta_data, recovery_meta_data_size), + GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_end }; @@ -2248,10 +2253,13 @@ meta_iterator (void *cls, uint32_t version; void *meta_data = NULL; size_t meta_data_size = 0; + struct GNUNET_TIME_Timestamp ts; bool unused = false; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint32 ("version", &version), + GNUNET_PQ_result_spec_timestamp ("creation_date", + &ts), GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_variable_size ("recovery_meta_data", &meta_data, @@ -2272,6 +2280,7 @@ meta_iterator (void *cls, } ret = ctx->cb (ctx->cb_cls, version, + ts, meta_data, meta_data_size); GNUNET_PQ_cleanup_result (rs); diff --git a/src/stasis/stasis-0001.sql b/src/stasis/stasis-0001.sql index e3c9c5c..d512473 100644 --- a/src/stasis/stasis-0001.sql +++ b/src/stasis/stasis-0001.sql @@ -141,6 +141,7 @@ CREATE TABLE IF NOT EXISTS anastasis_recoverydocument recovery_data_hash BYTEA NOT NULL CHECK(length(recovery_data_hash)=64), recovery_data BYTEA NOT NULL, recovery_meta_data BYTEA DEFAULT NULL, + creation_date INT8 NOT NULL, PRIMARY KEY (user_id, version)); COMMENT ON TABLE anastasis_recoverydocument IS 'Stores a recovery document which contains the policy and the encrypted core secret'; @@ -152,6 +153,8 @@ COMMENT ON COLUMN anastasis_recoverydocument.account_sig IS 'Signature of the recovery document'; COMMENT ON COLUMN anastasis_recoverydocument.recovery_data_hash IS 'Hash of the recovery document to prevent unnecessary uploads'; +COMMENT ON COLUMN anastasis_recoverydocument.creation_date + IS 'Creation date of the recovery document (when it was uploaded)'; COMMENT ON COLUMN anastasis_recoverydocument.recovery_data IS 'Contains the encrypted policy and core secret'; COMMENT ON COLUMN anastasis_recoverydocument.recovery_meta_data |