/* This file is part of GNUnet. Copyright (C) 2012, 2013, 2015 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file util/crypto_ecc_setup.c * @brief helper function for easy EdDSA key setup * @author Christian Grothoff */ #include "platform.h" #include #include "gnunet_util_lib.h" #define LOG(kind, ...) GNUNET_log_from (kind, "util-crypto-ecc", __VA_ARGS__) #define LOG_STRERROR(kind, syscall) \ GNUNET_log_from_strerror (kind, "util-crypto-ecc", syscall) #define LOG_STRERROR_FILE(kind, syscall, filename) \ GNUNET_log_from_strerror_file (kind, "util-crypto-ecc", syscall, filename) /** * Log an error message at log-level 'level' that indicates * a failure of the command 'cmd' with the message given * by gcry_strerror(rc). */ #define LOG_GCRY(level, cmd, rc) \ do \ { \ LOG (level, \ _ ("`%s' failed at %s:%d with error: %s\n"), \ cmd, \ __FILE__, \ __LINE__, \ gcry_strerror (rc)); \ } while (0) /** * Wait for a short time (we're trying to lock a file or want * to give another process a shot at finishing a disk write, etc.). * Sleeps for 100ms (as that should be long enough for virtually all * modern systems to context switch and allow another process to do * some 'real' work). */ static void short_wait () { struct GNUNET_TIME_Relative timeout; timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100); (void) GNUNET_NETWORK_socket_select (NULL, NULL, NULL, timeout); } /** * Create a new private key by reading it from a file. If the * files does not exist, create a new key and write it to the * file. Caller must free return value. Note that this function * can not guarantee that another process might not be trying * the same operation on the same file at the same time. * If the contents of the file * are invalid the old file is deleted and a fresh key is * created. * * @param filename name of file to use to store the key * @return new private key, NULL on error (for example, * permission denied) */ struct GNUNET_CRYPTO_EddsaPrivateKey * GNUNET_CRYPTO_eddsa_key_create_from_file (const char *filename) { struct GNUNET_CRYPTO_EddsaPrivateKey *priv; struct GNUNET_DISK_FileHandle *fd; unsigned int cnt; int ec; uint64_t fs; ssize_t sret; if (GNUNET_SYSERR == GNUNET_DISK_directory_create_for_file (filename)) return NULL; while (GNUNET_YES != GNUNET_DISK_file_test (filename)) { fd = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_FAILIFEXISTS, GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE); if (NULL == fd) { if (EEXIST == errno) { if (GNUNET_YES != GNUNET_DISK_file_test (filename)) { /* must exist but not be accessible, fail for good! */ if (0 != access (filename, R_OK)) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "access", filename); else GNUNET_break (0); /* what is going on!? */ return NULL; } continue; } LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "open", filename); return NULL; } cnt = 0; while (GNUNET_YES != GNUNET_DISK_file_lock (fd, 0, sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey), GNUNET_YES)) { short_wait (); if (0 == ++cnt % 10) { ec = errno; LOG (GNUNET_ERROR_TYPE_ERROR, _ ("Could not acquire lock on file `%s': %s...\n"), filename, strerror (ec)); } } LOG (GNUNET_ERROR_TYPE_INFO, _ ("Creating a new private key. This may take a while.\n")); priv = GNUNET_CRYPTO_eddsa_key_create (); GNUNET_assert (NULL != priv); GNUNET_assert (sizeof(*priv) == GNUNET_DISK_file_write (fd, priv, sizeof(*priv))); GNUNET_DISK_file_sync (fd); if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); GNUNET_assert (GNUNET_YES == GNUNET_DISK_file_close (fd)); return priv; } /* key file exists already, read it! */ fd = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE); if (NULL == fd) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "open", filename); return NULL; } cnt = 0; while (1) { if (GNUNET_YES != GNUNET_DISK_file_lock (fd, 0, sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey), GNUNET_NO)) { if (0 == ++cnt % 60) { ec = errno; LOG (GNUNET_ERROR_TYPE_ERROR, _ ("Could not acquire lock on file `%s': %s...\n"), filename, strerror (ec)); LOG ( GNUNET_ERROR_TYPE_ERROR, _ ( "This may be ok if someone is currently generating a private key.\n")); } short_wait (); continue; } if (GNUNET_YES != GNUNET_DISK_file_test (filename)) { /* eh, what!? File we opened is now gone!? */ LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", filename); if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof( struct GNUNET_CRYPTO_EddsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fd)); return NULL; } if (GNUNET_OK != GNUNET_DISK_file_size (filename, &fs, GNUNET_YES, GNUNET_YES)) fs = 0; if (fs < sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey)) { /* maybe we got the read lock before the key generating * process had a chance to get the write lock; give it up! */ if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof( struct GNUNET_CRYPTO_EddsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); if (0 == ++cnt % 10) { LOG (GNUNET_ERROR_TYPE_ERROR, _ ( "When trying to read key file `%s' I found %u bytes but I need at least %u.\n"), filename, (unsigned int) fs, (unsigned int) sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey)); LOG (GNUNET_ERROR_TYPE_ERROR, _ ("This may be ok if someone is currently generating a key.\n")); } short_wait (); /* wait a bit longer! */ continue; } break; } fs = sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey); priv = GNUNET_malloc (fs); sret = GNUNET_DISK_file_read (fd, priv, fs); GNUNET_assert ((sret >= 0) && (fs == (size_t) sret)); if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); GNUNET_assert (GNUNET_YES == GNUNET_DISK_file_close (fd)); #if CRYPTO_BUG if (GNUNET_OK != check_eddsa_key (priv)) { GNUNET_break (0); GNUNET_free (priv); return NULL; } #endif return priv; } /** * Create a new private key by reading it from a file. If the * files does not exist, create a new key and write it to the * file. Caller must free return value. Note that this function * can not guarantee that another process might not be trying * the same operation on the same file at the same time. * If the contents of the file * are invalid the old file is deleted and a fresh key is * created. * * @param filename name of file to use to store the key * @return new private key, NULL on error (for example, * permission denied) */ struct GNUNET_CRYPTO_EcdsaPrivateKey * GNUNET_CRYPTO_ecdsa_key_create_from_file (const char *filename) { struct GNUNET_CRYPTO_EcdsaPrivateKey *priv; struct GNUNET_DISK_FileHandle *fd; unsigned int cnt; int ec; uint64_t fs; ssize_t sret; if (GNUNET_SYSERR == GNUNET_DISK_directory_create_for_file (filename)) return NULL; while (GNUNET_YES != GNUNET_DISK_file_test (filename)) { fd = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_FAILIFEXISTS, GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE); if (NULL == fd) { if (EEXIST == errno) { if (GNUNET_YES != GNUNET_DISK_file_test (filename)) { /* must exist but not be accessible, fail for good! */ if (0 != access (filename, R_OK)) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "access", filename); else GNUNET_break (0); /* what is going on!? */ return NULL; } continue; } LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "open", filename); return NULL; } cnt = 0; while (GNUNET_YES != GNUNET_DISK_file_lock (fd, 0, sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey), GNUNET_YES)) { short_wait (); if (0 == ++cnt % 10) { ec = errno; LOG (GNUNET_ERROR_TYPE_ERROR, _ ("Could not acquire lock on file `%s': %s...\n"), filename, strerror (ec)); } } LOG (GNUNET_ERROR_TYPE_INFO, _ ("Creating a new private key. This may take a while.\n")); priv = GNUNET_CRYPTO_ecdsa_key_create (); GNUNET_assert (NULL != priv); GNUNET_assert (sizeof(*priv) == GNUNET_DISK_file_write (fd, priv, sizeof(*priv))); GNUNET_DISK_file_sync (fd); if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); GNUNET_assert (GNUNET_YES == GNUNET_DISK_file_close (fd)); return priv; } /* key file exists already, read it! */ fd = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE); if (NULL == fd) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "open", filename); return NULL; } cnt = 0; while (1) { if (GNUNET_YES != GNUNET_DISK_file_lock (fd, 0, sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey), GNUNET_NO)) { if (0 == ++cnt % 60) { ec = errno; LOG (GNUNET_ERROR_TYPE_ERROR, _ ("Could not acquire lock on file `%s': %s...\n"), filename, strerror (ec)); LOG ( GNUNET_ERROR_TYPE_ERROR, _ ( "This may be ok if someone is currently generating a private key.\n")); } short_wait (); continue; } if (GNUNET_YES != GNUNET_DISK_file_test (filename)) { /* eh, what!? File we opened is now gone!? */ LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", filename); if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof( struct GNUNET_CRYPTO_EcdsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fd)); return NULL; } if (GNUNET_OK != GNUNET_DISK_file_size (filename, &fs, GNUNET_YES, GNUNET_YES)) fs = 0; if (fs < sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey)) { /* maybe we got the read lock before the key generating * process had a chance to get the write lock; give it up! */ if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof( struct GNUNET_CRYPTO_EcdsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); if (0 == ++cnt % 10) { LOG (GNUNET_ERROR_TYPE_ERROR, _ ( "When trying to read key file `%s' I found %u bytes but I need at least %u.\n"), filename, (unsigned int) fs, (unsigned int) sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey)); LOG (GNUNET_ERROR_TYPE_ERROR, _ ("This may be ok if someone is currently generating a key.\n")); } short_wait (); /* wait a bit longer! */ continue; } break; } fs = sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey); priv = GNUNET_malloc (fs); sret = GNUNET_DISK_file_read (fd, priv, fs); GNUNET_assert ((sret >= 0) && (fs == (size_t) sret)); if (GNUNET_YES != GNUNET_DISK_file_unlock (fd, 0, sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey))) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "fcntl", filename); GNUNET_assert (GNUNET_YES == GNUNET_DISK_file_close (fd)); return priv; } /** * Create a new private key by reading our peer's key from * the file specified in the configuration. * * @param cfg the configuration to use * @return new private key, NULL on error (for example, * permission denied) */ struct GNUNET_CRYPTO_EddsaPrivateKey * GNUNET_CRYPTO_eddsa_key_create_from_configuration ( const struct GNUNET_CONFIGURATION_Handle *cfg) { struct GNUNET_CRYPTO_EddsaPrivateKey *priv; char *fn; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "PEER", "PRIVATE_KEY", &fn)) return NULL; priv = GNUNET_CRYPTO_eddsa_key_create_from_file (fn); GNUNET_free (fn); return priv; } /** * Retrieve the identity of the host's peer. * * @param cfg configuration to use * @param dst pointer to where to write the peer identity * @return #GNUNET_OK on success, #GNUNET_SYSERR if the identity * could not be retrieved */ int GNUNET_CRYPTO_get_peer_identity (const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_PeerIdentity *dst) { struct GNUNET_CRYPTO_EddsaPrivateKey *priv; if (NULL == (priv = GNUNET_CRYPTO_eddsa_key_create_from_configuration (cfg))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Could not load peer's private key\n")); return GNUNET_SYSERR; } GNUNET_CRYPTO_eddsa_key_get_public (priv, &dst->public_key); GNUNET_free (priv); return GNUNET_OK; } /** * Setup a key file for a peer given the name of the * configuration file (!). This function is used so that * at a later point code can be certain that reading a * key is fast (for example in time-dependent testcases). * * @param cfg_name name of the configuration file to use */ void GNUNET_CRYPTO_eddsa_setup_key (const char *cfg_name) { struct GNUNET_CONFIGURATION_Handle *cfg; struct GNUNET_CRYPTO_EddsaPrivateKey *priv; cfg = GNUNET_CONFIGURATION_create (); (void) GNUNET_CONFIGURATION_load (cfg, cfg_name); priv = GNUNET_CRYPTO_eddsa_key_create_from_configuration (cfg); if (NULL != priv) GNUNET_free (priv); GNUNET_CONFIGURATION_destroy (cfg); } /* end of crypto_ecc_setup.c */