/* This file is part of GNUnet. Copyright (C) 2012, 2013, 2015, 2020 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) /** * Read file to @a buf. Fails if the file does not exist or * does not have precisely @a buf_size bytes. * * @param filename file to read * @param[out] buf where to write the file contents * @param buf_size number of bytes in @a buf * @return #GNUNET_OK on success */ static int read_from_file (const char *filename, void *buf, size_t buf_size) { int fd; struct stat sb; fd = open (filename, O_RDONLY); if (-1 == fd) { memset (buf, 0, buf_size); return GNUNET_SYSERR; } if (0 != fstat (fd, &sb)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", filename); GNUNET_assert (0 == close (fd)); memset (buf, 0, buf_size); return GNUNET_SYSERR; } if (sb.st_size != buf_size) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "File `%s' has wrong size (%llu), expected %llu bytes\n", filename, (unsigned long long) sb.st_size, (unsigned long long) buf_size); GNUNET_assert (0 == close (fd)); memset (buf, 0, buf_size); return GNUNET_SYSERR; } if (buf_size != read (fd, buf, buf_size)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "read", filename); GNUNET_assert (0 == close (fd)); memset (buf, 0, buf_size); return GNUNET_SYSERR; } GNUNET_assert (0 == close (fd)); return GNUNET_OK; } /** * Write contents of @a buf atomically to @a filename. * Fail if @a filename already exists or if not exactly * @a buf with @a buf_size bytes could be written to * @a filename. * * @param filename where to write * @param buf buffer to write * @param buf_size number of bytes in @a buf to write * @return #GNUNET_OK on success, * #GNUNET_NO if a file existed under @a filename * #GNUNET_SYSERR on failure */ static int atomic_write_to_file (const char *filename, const void *buf, size_t buf_size) { char *tmpl; int fd; if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (filename)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "mkstemp", filename); return GNUNET_SYSERR; } { char *dname; dname = GNUNET_strdup (filename); GNUNET_asprintf (&tmpl, "%s/XXXXXX", dirname (dname)); GNUNET_free (dname); } fd = mkstemp (tmpl); if (-1 == fd) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "mkstemp", tmpl); GNUNET_free (tmpl); return GNUNET_SYSERR; } if (0 != fchmod (fd, S_IRUSR)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chmod", tmpl); GNUNET_assert (0 == close (fd)); if (0 != unlink (tmpl)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "unlink", tmpl); GNUNET_free (tmpl); return GNUNET_SYSERR; } if (buf_size != write (fd, buf, buf_size)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", tmpl); GNUNET_assert (0 == close (fd)); if (0 != unlink (tmpl)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "unlink", tmpl); GNUNET_free (tmpl); return GNUNET_SYSERR; } GNUNET_assert (0 == close (fd)); if (0 != link (tmpl, filename)) { if (0 != unlink (tmpl)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "unlink", tmpl); GNUNET_free (tmpl); return GNUNET_NO; } if (0 != unlink (tmpl)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "unlink", tmpl); GNUNET_free (tmpl); return GNUNET_OK; } /** * @ingroup crypto * @brief Create a new private key by reading it from a file. * * If the files does not exist and @a do_create is set, creates a new key and * write it to the file. * * If the contents of the file are invalid, an error is returned. * * @param filename name of file to use to store the key * @param do_create should a file be created? * @param[out] pkey set to the private key from @a filename on success * @return #GNUNET_OK on success, #GNUNET_NO if @a do_create was set but * we found an existing file, #GNUNET_SYSERR on failure */ int GNUNET_CRYPTO_eddsa_key_from_file (const char *filename, int do_create, struct GNUNET_CRYPTO_EddsaPrivateKey *pkey) { int ret; if (GNUNET_OK == read_from_file (filename, pkey, sizeof (*pkey))) { /* file existed, report that we didn't create it... */ return (do_create) ? GNUNET_NO : GNUNET_OK; } GNUNET_CRYPTO_eddsa_key_create (pkey); ret = atomic_write_to_file (filename, pkey, sizeof (*pkey)); if ( (GNUNET_OK == ret) || (GNUNET_SYSERR == ret) ) return ret; /* maybe another process succeeded in the meantime, try reading one more time */ if (GNUNET_OK == read_from_file (filename, pkey, sizeof (*pkey))) { /* file existed, report that *we* didn't create it... */ return (do_create) ? GNUNET_NO : GNUNET_OK; } /* give up */ return GNUNET_SYSERR; } /** * @ingroup crypto * @brief Create a new private key by reading it from a file. * * If the files does not exist and @a do_create is set, creates a new key and * write it to the file. * * If the contents of the file are invalid, an error is returned. * * @param filename name of file to use to store the key * @param do_create should a file be created? * @param[out] pkey set to the private key from @a filename on success * @return #GNUNET_OK on success, #GNUNET_NO if @a do_create was set but * we found an existing file, #GNUNET_SYSERR on failure */ int GNUNET_CRYPTO_ecdsa_key_from_file (const char *filename, int do_create, struct GNUNET_CRYPTO_EcdsaPrivateKey *pkey) { if (GNUNET_OK == read_from_file (filename, pkey, sizeof (*pkey))) { /* file existed, report that we didn't create it... */ return (do_create) ? GNUNET_NO : GNUNET_OK; } GNUNET_CRYPTO_ecdsa_key_create (pkey); if (GNUNET_OK == atomic_write_to_file (filename, pkey, sizeof (*pkey))) return GNUNET_OK; /* maybe another process succeeded in the meantime, try reading one more time */ if (GNUNET_OK == read_from_file (filename, pkey, sizeof (*pkey))) { /* file existed, report that *we* didn't create it... */ return (do_create) ? GNUNET_NO : GNUNET_OK; } /* give up */ return GNUNET_SYSERR; } /** * 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_new (struct GNUNET_CRYPTO_EddsaPrivateKey); GNUNET_CRYPTO_eddsa_key_from_file (fn, GNUNET_YES, priv); 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 */