/*
This file is part of Anastasis
Copyright (C) 2020,2021 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
*/
/**
* @file util/anastasis-crypto-tgv.c
* @brief Generate test vectors for cryptographic operations.
* @author Florian Dold
*
*
* Test vectors have the following format (TypeScript pseudo code):
*
* interface TestVectorFile {
* encoding: "base32crockford";
* producer?: string;
* vectors: TestVector[];
* }
*
* enum Operation {
* Hash("hash"),
* ...
* }
*
* interface TestVector {
* operation: Operation;
* // Inputs for the operation
* [ k: string]: string | number;
* };
*
*
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include "anastasis_crypto_lib.h"
/**
* Should we verify or output test vectors?
*/
static int verify_flag = GNUNET_NO;
/**
* Global exit code.
*/
static int global_ret = 0;
/**
* Create a fresh test vector for a given operation label.
*
* @param vecs array of vectors to append the new vector to
* @param vecname label for the operation of the vector
* @returns the fresh test vector
*/
static json_t *
vec_for (json_t *vecs, const char *vecname)
{
json_t *t = json_object ();
json_object_set_new (t,
"operation",
json_string (vecname));
json_array_append_new (vecs, t);
return t;
}
/**
* Add a base32crockford encoded value
* to a test vector.
*
* @param vec test vector to add to
* @param label label for the value
* @param data data to add
* @param size size of data
*/
static void
d2j (json_t *vec,
const char *label,
const void *data,
size_t size)
{
char *buf;
json_t *json;
buf = GNUNET_STRINGS_data_to_string_alloc (data, size);
json = json_string (buf);
GNUNET_free (buf);
GNUNET_break (NULL != json);
json_object_set_new (vec, label, json);
}
static void
d2j_append (json_t *arr,
const void *data,
size_t size)
{
char *buf;
json_t *json;
buf = GNUNET_STRINGS_data_to_string_alloc (data, size);
json = json_string (buf);
GNUNET_free (buf);
GNUNET_break (NULL != json);
json_array_append_new (arr,
json);
}
#define d2j_auto(vec, label, d) d2j (vec, label, d, sizeof (*d))
#define d2j_append_auto(arr, d) d2j_append (arr, d, sizeof (*d))
#define random_auto(d) GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, \
d, \
sizeof (*d));
static int
expect_data_fixed (json_t *vec,
const char *name,
void *data,
size_t expect_len)
{
const char *s = json_string_value (json_object_get (vec, name));
if (NULL == s)
return GNUNET_NO;
if (GNUNET_OK != GNUNET_STRINGS_string_to_data (s,
strlen (s),
data,
expect_len))
return GNUNET_NO;
return GNUNET_OK;
}
static int
expect_data_dynamic (json_t *vec,
const char *name,
void **data,
size_t *ret_len)
{
const char *s = json_string_value (json_object_get (vec, name));
char *tmp;
size_t len;
if (NULL == s)
return GNUNET_NO;
len = (strlen (s) * 5) / 8;
if (NULL != ret_len)
*ret_len = len;
tmp = GNUNET_malloc (len);
if (GNUNET_OK != GNUNET_STRINGS_string_to_data (s, strlen (s), tmp, len))
{
GNUNET_free (tmp);
return GNUNET_NO;
}
*data = tmp;
return GNUNET_OK;
}
/**
* Check a single vector.
*
* @param operation operator of the vector
* @param vec the vector, a JSON object.
*
* @returns GNUNET_OK if the vector is okay
*/
static int
checkvec (const char *operation,
json_t *vec)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"checking %s\n", operation);
if (0 == strcmp (operation, "hash"))
{
void *data;
size_t data_len;
struct GNUNET_HashCode hash_out;
struct GNUNET_HashCode hc;
if (GNUNET_OK != expect_data_dynamic (vec,
"input",
&data,
&data_len))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK != expect_data_fixed (vec,
"output",
&hash_out,
sizeof (hash_out)))
{
GNUNET_free (data);
GNUNET_break (0);
return GNUNET_NO;
}
GNUNET_CRYPTO_hash (data, data_len, &hc);
if (0 != GNUNET_memcmp (&hc, &hash_out))
{
GNUNET_free (data);
GNUNET_break (0);
return GNUNET_NO;
}
GNUNET_free (data);
}
return GNUNET_OK;
}
/**
* Check test vectors from stdin.
*
* @returns global exit code
*/
static int
check_vectors ()
{
json_error_t err;
json_t *vecfile = json_loadf (stdin, 0, &err);
const char *encoding;
json_t *vectors;
if (NULL == vecfile)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unable to parse JSON\n");
return 1;
}
encoding = json_string_value (json_object_get (vecfile,
"encoding"));
if ( (NULL == encoding) || (0 != strcmp (encoding, "base32crockford")) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unsupported or missing encoding\n");
json_decref (vecfile);
return 1;
}
vectors = json_object_get (vecfile, "vectors");
if (! json_is_array (vectors))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "bad vectors\n");
json_decref (vecfile);
return 1;
}
{
/* array is a JSON array */
size_t index;
json_t *value;
int ret;
json_array_foreach (vectors, index, value) {
const char *op = json_string_value (json_object_get (value,
"operation"));
if (NULL == op)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"missing operation\n");
ret = GNUNET_SYSERR;
break;
}
ret = checkvec (op, value);
if (GNUNET_OK != ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"bad vector %u\n",
(unsigned int) index);
break;
}
}
return (ret == GNUNET_OK) ? 0 : 1;
}
}
/**
* Output test vectors.
*
* @returns global exit code
*/
static int
output_vectors ()
{
json_t *vecfile = json_object ();
json_t *vecs = json_array ();
json_object_set_new (vecfile,
"encoding",
json_string ("base32crockford"));
json_object_set_new (vecfile,
"producer",
json_string (
"GNU Anastasis (C implementation) " PACKAGE_VERSION " "
VCS_VERSION));
json_object_set_new (vecfile,
"vectors",
vecs);
{
json_t *vec = vec_for (vecs, "hash");
struct GNUNET_HashCode hc;
char *str = "Hello, GNUnet";
GNUNET_CRYPTO_hash (str, strlen (str), &hc);
d2j (vec, "input", str, strlen (str));
d2j (vec, "output", &hc, sizeof (struct GNUNET_HashCode));
}
{
json_t *vec = vec_for (vecs, "user_identifier_derive");
struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
struct ANASTASIS_CRYPTO_UserIdentifierP id;
json_t *id_data = json_pack ("{s:s, s:s}",
"name",
"Fleabag",
"ssn",
"AB123");
GNUNET_assert (NULL != id_data);
random_auto (&provider_salt);
ANASTASIS_CRYPTO_user_identifier_derive (id_data,
&provider_salt,
&id);
json_object_set_new (vec, "input_id_data", id_data);
d2j_auto (vec, "input_provider_salt", &provider_salt);
d2j_auto (vec, "output_id", &id);
}
{
json_t *vec = vec_for (vecs, "account_keypair_derive");
struct ANASTASIS_CRYPTO_UserIdentifierP id;
struct ANASTASIS_CRYPTO_AccountPrivateKeyP priv_key;
struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key;
random_auto (&id);
ANASTASIS_CRYPTO_account_public_key_derive (&id, &pub_key);
ANASTASIS_CRYPTO_account_private_key_derive (&id, &priv_key);
d2j_auto (vec, "input_id", &id);
d2j_auto (vec, "output_priv_key", &priv_key);
d2j_auto (vec, "output_pub_key", &pub_key);
}
{
json_t *vec = vec_for (vecs, "secure_answer_hash");
const char *answer = "Blah";
struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
struct ANASTASIS_CRYPTO_QuestionSaltP salt;
struct GNUNET_HashCode result;
random_auto (&uuid);
random_auto (&salt);
ANASTASIS_CRYPTO_secure_answer_hash (answer, &uuid, &salt, &result);
json_object_set_new (vec, "input_answer", json_string (answer));
d2j_auto (vec, "input_uuid", &uuid);
d2j_auto (vec, "input_salt", &salt);
d2j_auto (vec, "output_hash", &result);
}
{
json_t *vec = vec_for (vecs, "recovery_document_encryption");
struct ANASTASIS_CRYPTO_UserIdentifierP id;
void *rec_doc = "my recovery doc";
size_t rd_size = strlen (rec_doc) + 1;
void *enc_rec_doc;
size_t erd_size;
random_auto (&id);
ANASTASIS_CRYPTO_recovery_document_encrypt (&id,
rec_doc,
rd_size,
&enc_rec_doc,
&erd_size);
d2j_auto (vec, "input_user_id", &id);
d2j (vec, "input_recovery_document", rec_doc, rd_size);
d2j (vec, "output_encrypted_recovery_document", &enc_rec_doc, erd_size);
}
{
/* With extra salt */
json_t *vec = vec_for (vecs, "keyshare_encryption");
struct ANASTASIS_CRYPTO_KeyShareP key_share;
struct ANASTASIS_CRYPTO_UserIdentifierP id;
char *xsalt = "myanswer";
struct ANASTASIS_CRYPTO_EncryptedKeyShareP enc_key_share;
random_auto (&key_share);
random_auto (&id);
ANASTASIS_CRYPTO_keyshare_encrypt (&key_share,
&id,
xsalt,
&enc_key_share);
d2j_auto (vec, "input_key_share", &key_share);
d2j_auto (vec, "input_user_id", &id);
json_object_set_new (vec, "input_xsalt", json_string (xsalt));
d2j_auto (vec, "output_enc_key_share", &enc_key_share);
}
{
/* Without extra salt */
json_t *vec = vec_for (vecs, "keyshare_encryption");
struct ANASTASIS_CRYPTO_KeyShareP key_share;
struct ANASTASIS_CRYPTO_UserIdentifierP id;
char *xsalt = NULL;
struct ANASTASIS_CRYPTO_EncryptedKeyShareP enc_key_share;
random_auto (&key_share);
random_auto (&id);
ANASTASIS_CRYPTO_keyshare_encrypt (&key_share,
&id,
xsalt,
&enc_key_share);
d2j_auto (vec, "input_key_share", &key_share);
d2j_auto (vec, "input_user_id", &id);
json_object_set_new (vec, "input_xsalt", json_null ());
d2j_auto (vec, "output_enc_key_share", &enc_key_share);
}
{
json_t *vec = vec_for (vecs, "truth_encryption");
struct ANASTASIS_CRYPTO_NonceP nonce;
struct ANASTASIS_CRYPTO_TruthKeyP truth_enc_key;
char truth[256];
size_t truth_size = 256;
void *enc_truth;
size_t ect_size;
random_auto (&nonce);
random_auto (&truth);
random_auto (&truth_enc_key);
ANASTASIS_CRYPTO_truth_encrypt (&nonce,
&truth_enc_key,
truth,
truth_size,
&enc_truth,
&ect_size);
d2j_auto (vec, "input_nonce", &nonce);
d2j_auto (vec, "input_truth_enc_key", &truth_enc_key);
d2j (vec, "input_truth", &truth, truth_size);
d2j (vec, "output_encrypted_truth", enc_truth, ect_size);
}
{
json_t *vec = vec_for (vecs, "policy_key_derive");
struct ANASTASIS_CRYPTO_KeyShareP key_shares[2];
unsigned int keyshare_length = 2;
struct ANASTASIS_CRYPTO_MasterSaltP salt;
struct ANASTASIS_CRYPTO_PolicyKeyP policy_key;
json_t *key_shares_json = json_array ();
random_auto (&key_shares[0]);
random_auto (&key_shares[1]);
random_auto (&salt);
ANASTASIS_CRYPTO_policy_key_derive (key_shares,
keyshare_length,
&salt,
&policy_key);
d2j_append_auto (key_shares_json, &key_shares[0]);
d2j_append_auto (key_shares_json, &key_shares[1]);
json_object_set_new (vec, "input_key_shares", key_shares_json);
d2j_auto (vec, "input_salt", &salt);
d2j_auto (vec, "output_policy_key", &policy_key);
}
{
// json_t *vec = vec_for (vecs, "core_secret_encryption");
// struct ANASTASIS_CRYPTO_PolicyKeyP policy_keys[2];
// unsigned int policy_keys_length = 2;
// char core_secret[256];
// size_t core_secret_size = 256;
// void *enc_core_secret;
// struct ANASTASIS_CRYPTO_EncryptedMasterKeyP encrypted_master_keys[2];
// json_t *policy_keys_json = json_array ();
// json_t *encrypted_master_keys_json = json_array ();
// random_auto (&policy_keys[0]);
// random_auto (&policy_keys[1]);
// random_auto (&core_secret);
// ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys, policy_keys_length,
// core_secret, core_secret_size,
// &enc_core_secret,
// encrypted_master_keys);
// d2j_append_auto (policy_keys_json, &policy_keys_json[0]);
// d2j_append_auto (policy_keys_json, &policy_keys_json[1]);
// d2j_append_auto (encrypted_master_keys_json, &encrypted_master_keys[0]);
// d2j_append_auto (encrypted_master_keys_json, &encrypted_master_keys[1]);
// d2j_auto (vec, "input_core_secret", &core_secret);
// json_object_set_new (vec, "input_policy_keys", policy_keys_json);
// json_object_set_new (vec, "output_encrypted_core_secret", encrypted_master_keys_json);
// json_object_set_new (vec, "output_encrypted_master_keys", encrypted_master_keys_json);
}
json_dumpf (vecfile, stdout, JSON_INDENT (2));
json_decref (vecfile);
printf ("\n");
return 0;
}
/**
* Main function that will be run.
*
* @param cls closure
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param cfg configuration
*/
static void
run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *cfg)
{
if (GNUNET_YES == verify_flag)
global_ret = check_vectors ();
else
global_ret = output_vectors ();
}
/**
* The main function of the test vector generation tool.
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, 1 on error
*/
int
main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_flag ('V',
"verify",
gettext_noop (
"verify a test vector from stdin"),
&verify_flag),
GNUNET_GETOPT_OPTION_END
};
GNUNET_assert (GNUNET_OK ==
GNUNET_log_setup ("anastasis-crypto-tvg",
"INFO",
NULL));
if (GNUNET_OK !=
GNUNET_PROGRAM_run (argc, argv,
"anastasis-crypto-tvg",
"Generate test vectors for cryptographic operations",
options,
&run, NULL))
return 1;
return global_ret;
}
/* end of anastasis-crypto-tvg.c */