From: Andre Noll Date: Sun, 13 May 2018 12:03:20 +0000 (+0200) Subject: Merge branch 'refs/heads/t/crypt' X-Git-Tag: v0.6.2~5 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=3998a8c581623224b7b56bce593646b2c8516a0f;hp=f4019242252bf8e7594a72efdac6214a5abd4364 Merge branch 'refs/heads/t/crypt' A couple of simple patches which clean up the openssl and gcrypt crypto backends. The non-trivial parts are the get_public_key() unification, the memory leak fixes for openssl and the activation of the secmem pool for gcrypt. * refs/heads/t/crypt: gcrypt: Allocate a secmem pool at startup. crypt: Introduce crypt_shutdown(). crypt: Rename init_random_seed_or_die() -> crypt_init(). gcrypt: Use ROUND_DOWN() macro. crypt: Rename RSA functions. crypt: Deduplicate get_public_key(). crypt.h: Remove two unused constants. Rename crypt.c -> openssl.c. --- diff --git a/NEWS.md b/NEWS.md index 9ced85e9..11f275da 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,9 +6,9 @@ NEWS ------------------------------------------- - para_gui no longer waits up to one second to update the screen when -the geometry of the terminal changes. - + the geometry of the terminal changes. - Minor documentation improvements. +- Improvements to the crypto subsystem. ---------------------------------------- 0.6.1 (2017-09-23) "segmented iteration" diff --git a/audiod.c b/audiod.c index 8d152fa6..083c2a7a 100644 --- a/audiod.c +++ b/audiod.c @@ -1493,7 +1493,7 @@ int main(int argc, char *argv[]) version_handle_flag("audiod", OPT_GIVEN(VERSION)); handle_help_flags(); parse_config_or_die(); - init_random_seed_or_die(); + crypt_init(); daemon_set_priority(OPT_UINT32_VAL(PRIORITY)); recv_init(); if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO, @@ -1542,7 +1542,7 @@ int main(int argc, char *argv[]) audiod_cleanup(); sched_shutdown(&sched); signal_shutdown(signal_task); - + crypt_shutdown(); out: lls_free_parse_result(lpr, CMD_PTR); if (errctx) diff --git a/client.c b/client.c index 3e9219fd..c45826ab 100644 --- a/client.c +++ b/client.c @@ -618,7 +618,7 @@ int main(int argc, char *argv[]) { int ret; - init_random_seed_or_die(); + crypt_init(); sched.default_timeout.tv_sec = 1; ret = client_parse_config(argc, argv, &ct, &client_loglevel); @@ -664,6 +664,7 @@ int main(int argc, char *argv[]) } } sched_shutdown(&sched); + crypt_shutdown(); out: if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); diff --git a/client_common.c b/client_common.c index a8d48055..b28e8951 100644 --- a/client_common.c +++ b/client_common.c @@ -317,15 +317,15 @@ static int client_post_select(struct sched *s, void *context) } n = sbb.iov.iov_len; PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n); - ret = priv_decrypt(ct->key_file, crypt_buf, + ret = apc_priv_decrypt(ct->key_file, crypt_buf, sbb.iov.iov_base, n); free(sbb.iov.iov_base); if (ret < 0) goto out; ct->challenge_hash = para_malloc(HASH_SIZE); - hash_function((char *)crypt_buf, CHALLENGE_SIZE, ct->challenge_hash); - ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); - ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, + hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash); + ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN); + ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN, SESSION_KEY_LEN); hash_to_asc(ct->challenge_hash, buf); PARA_INFO_LOG("--> %s\n", buf); diff --git a/command.c b/command.c index 6db0c003..bfce809a 100644 --- a/command.c +++ b/command.c @@ -874,12 +874,12 @@ static int run_command(struct command_context *cc, struct iovec *iov) * the function if the connection was not authenticated when the timeout * expires. * - * \sa alarm(2), \ref crypt.c, \ref crypt.h. + * \sa alarm(2), \ref openssl.c, \ref crypt.h. */ __noreturn void handle_connect(int fd) { int ret; - unsigned char rand_buf[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN]; + unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN]; unsigned char challenge_hash[HASH_SIZE]; char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */; size_t numbytes; @@ -909,7 +909,7 @@ __noreturn void handle_connect(int fd) goto net_err; if (cc->u) { get_random_bytes_or_die(rand_buf, sizeof(rand_buf)); - ret = pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf), + ret = apc_pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf), (unsigned char *)buf); if (ret < 0) goto net_err; @@ -924,7 +924,7 @@ __noreturn void handle_connect(int fd) get_random_bytes_or_die((unsigned char *)buf, numbytes); } PARA_DEBUG_LOG("sending %d byte challenge + session key (%zu bytes)\n", - CHALLENGE_SIZE, numbytes); + APC_CHALLENGE_SIZE, numbytes); ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false); buf = NULL; if (ret < 0) @@ -940,21 +940,21 @@ __noreturn void handle_connect(int fd) if (!cc->u) goto net_err; /* - * The correct response is the hash of the first CHALLENGE_SIZE bytes + * The correct response is the hash of the first APC_CHALLENGE_SIZE bytes * of the random data. */ ret = -E_BAD_AUTH; if (numbytes != HASH_SIZE) goto net_err; - hash_function((char *)rand_buf, CHALLENGE_SIZE, challenge_hash); + hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash); if (memcmp(challenge_hash, buf, HASH_SIZE)) goto net_err; /* auth successful */ alarm(0); PARA_INFO_LOG("good auth for %s\n", cc->u->name); /* init stream cipher keys with the second part of the random buffer */ - cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); - cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, + cc->scc.recv = sc_new(rand_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN); + cc->scc.send = sc_new(rand_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN, SESSION_KEY_LEN); ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false); if (ret < 0) diff --git a/configure.ac b/configure.ac index 499571a7..9493f8e3 100644 --- a/configure.ac +++ b/configure.ac @@ -394,7 +394,7 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \ version " if test "$CRYPTOLIB" = openssl; then - server_errlist_objs="$server_errlist_objs crypt" + server_errlist_objs="$server_errlist_objs openssl" else server_errlist_objs="$server_errlist_objs gcrypt" fi @@ -432,7 +432,7 @@ if test -n "$CRYPTOLIB"; then version " if test "$CRYPTOLIB" = openssl; then - client_errlist_objs="$client_errlist_objs crypt" + client_errlist_objs="$client_errlist_objs openssl" else client_errlist_objs="$client_errlist_objs gcrypt" fi @@ -488,7 +488,7 @@ if test -n "$CRYPTOLIB"; then sync_filter " if test "$CRYPTOLIB" = openssl; then - audiod_errlist_objs="$audiod_errlist_objs crypt" + audiod_errlist_objs="$audiod_errlist_objs openssl" else audiod_errlist_objs="$audiod_errlist_objs gcrypt" fi diff --git a/crypt.c b/crypt.c deleted file mode 100644 index b8a587cd..00000000 --- a/crypt.c +++ /dev/null @@ -1,296 +0,0 @@ -/* Copyright (C) 2005 Andre Noll , see file COPYING. */ - -/** \file crypt.c Openssl-based encryption/decryption routines. */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "para.h" -#include "error.h" -#include "string.h" -#include "crypt.h" -#include "fd.h" -#include "crypt_backend.h" -#include "base64.h" -#include "portable_io.h" - -struct asymmetric_key { - RSA *rsa; -}; - -void get_random_bytes_or_die(unsigned char *buf, int num) -{ - unsigned long err; - - /* RAND_bytes() returns 1 on success, 0 otherwise. */ - if (RAND_bytes(buf, num) == 1) - return; - err = ERR_get_error(); - PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err)); - exit(EXIT_FAILURE); -} - -/* - * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Seed the PRNG - * used by random(3) with a random seed obtained from SSL. If /dev/urandom is - * not readable, the function calls exit(). - * - * \sa RAND_load_file(3), \ref get_random_bytes_or_die(), srandom(3), - * random(3), \ref para_random(). - */ -void init_random_seed_or_die(void) -{ - int seed, ret = RAND_load_file("/dev/urandom", 64); - - if (ret != 64) { - PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret); - exit(EXIT_FAILURE); - } - get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed)); - srandom(seed); -} - -static int get_private_key(const char *path, RSA **rsa) -{ - EVP_PKEY *pkey; - BIO *bio = BIO_new(BIO_s_file()); - - *rsa = NULL; - if (!bio) - return -E_PRIVATE_KEY; - if (BIO_read_filename(bio, path) <= 0) - goto bio_free; - pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); - if (!pkey) - goto bio_free; - *rsa = EVP_PKEY_get1_RSA(pkey); - EVP_PKEY_free(pkey); -bio_free: - BIO_free(bio); - return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY; -} - -/* - * The public key loading functions below were inspired by corresponding code - * of openssh-5.2p1, Copyright (c) 1995 Tatu Ylonen , Espoo, - * Finland. However, not much of the original code remains. - */ - -static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result) -{ - const unsigned char *p = buf, *end = buf + len; - uint32_t bnsize; - BIGNUM *bn; - - if (p + 4 < p) - return -E_BIGNUM; - if (p + 4 > end) - return -E_BIGNUM; - bnsize = read_u32_be(p); - PARA_DEBUG_LOG("bnsize: %u\n", bnsize); - p += 4; - if (p + bnsize < p) - return -E_BIGNUM; - if (p + bnsize > end) - return -E_BIGNUM; - if (bnsize > 8192) - return -E_BIGNUM; - bn = BN_bin2bn(p, bnsize, NULL); - if (!bn) - return -E_BIGNUM; - *result = bn; - return bnsize + 4; -} - -static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result) -{ - int ret; - RSA *rsa; - BIGNUM *n, *e; - const unsigned char *p = blob, *end = blob + blen; - - rsa = RSA_new(); - if (!rsa) - return -E_BIGNUM; - ret = read_bignum(p, end - p, &e); - if (ret < 0) - goto fail; - p += ret; - ret = read_bignum(p, end - p, &n); - if (ret < 0) - goto fail; -#ifdef HAVE_RSA_SET0_KEY - RSA_set0_key(rsa, n, e, NULL); -#else - rsa->n = n; - rsa->e = e; -#endif - *result = rsa; - return 1; -fail: - RSA_free(rsa); - return ret; -} - -int get_public_key(const char *key_file, struct asymmetric_key **result) -{ - struct asymmetric_key *key = NULL; - void *map = NULL; - unsigned char *blob = NULL; - size_t map_size, encoded_size, decoded_size; - int ret, ret2; - char *cp; - - key = para_malloc(sizeof(*key)); - ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL); - if (ret < 0) - goto out; - ret = is_ssh_rsa_key(map, map_size); - if (!ret) { - ret = -E_SSH_PARSE; - goto out_unmap; - } - cp = map + ret; - encoded_size = map_size - ret; - PARA_INFO_LOG("decoding public rsa-ssh key %s\n", key_file); - ret = uudecode(cp, encoded_size, (char **)&blob, &decoded_size); - if (ret < 0) - goto out_unmap; - ret = check_ssh_key_header(blob, decoded_size); - if (ret < 0) - goto out_unmap; - ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa); - if (ret < 0) - goto out_unmap; - ret = RSA_size(key->rsa); -out_unmap: - ret2 = para_munmap(map, map_size); - if (ret >= 0 && ret2 < 0) - ret = ret2; -out: - if (ret < 0) { - free(key); - *result = NULL; - PARA_ERROR_LOG("key %s: %s\n", key_file, para_strerror(-ret)); - } else - *result = key; - free(blob); - return ret; -} - -void free_public_key(struct asymmetric_key *key) -{ - if (!key) - return; - RSA_free(key->rsa); - free(key); -} - -int priv_decrypt(const char *key_file, unsigned char *outbuf, - unsigned char *inbuf, int inlen) -{ - struct asymmetric_key *priv; - int ret; - - ret = check_private_key_file(key_file); - if (ret < 0) - return ret; - if (inlen < 0) - return -E_RSA; - priv = para_malloc(sizeof(*priv)); - ret = get_private_key(key_file, &priv->rsa); - if (ret < 0) { - free(priv); - return ret; - } - /* - * RSA is vulnerable to timing attacks. Generate a random blinding - * factor to protect against this kind of attack. - */ - ret = -E_BLINDING; - if (RSA_blinding_on(priv->rsa, NULL) == 0) - goto out; - ret = RSA_private_decrypt(inlen, inbuf, outbuf, priv->rsa, - RSA_PKCS1_OAEP_PADDING); - RSA_blinding_off(priv->rsa); - if (ret <= 0) - ret = -E_DECRYPT; -out: - RSA_free(priv->rsa); - free(priv); - return ret; -} - -int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, - unsigned len, unsigned char *outbuf) -{ - int ret, flen = len; /* RSA_public_encrypt expects a signed int */ - - if (flen < 0) - return -E_ENCRYPT; - ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa, - RSA_PKCS1_OAEP_PADDING); - return ret < 0? -E_ENCRYPT : ret; -} - -struct stream_cipher { - EVP_CIPHER_CTX *aes; -}; - -struct stream_cipher *sc_new(const unsigned char *data, int len) -{ - struct stream_cipher *sc = para_malloc(sizeof(*sc)); - - assert(len >= 2 * AES_CRT128_BLOCK_SIZE); - sc->aes = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data, - data + AES_CRT128_BLOCK_SIZE); - return sc; -} - -void sc_free(struct stream_cipher *sc) -{ - if (!sc) - return; - EVP_CIPHER_CTX_free(sc->aes); - free(sc); -} - -static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src, - struct iovec *dst) -{ - int ret, inlen = src->iov_len, outlen, tmplen; - - *dst = (typeof(*dst)) { - /* Add one for the terminating zero byte. */ - .iov_base = para_malloc(inlen + 1), - .iov_len = inlen - }; - ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen); - assert(ret != 0); - ret = EVP_EncryptFinal_ex(ctx, dst->iov_base + outlen, &tmplen); - assert(ret != 0); - outlen += tmplen; - ((char *)dst->iov_base)[outlen] = '\0'; - dst->iov_len = outlen; -} - -void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) -{ - return aes_ctr128_crypt(sc->aes, src, dst); -} - -void hash_function(const char *data, unsigned long len, unsigned char *hash) -{ - SHA_CTX c; - SHA1_Init(&c); - SHA1_Update(&c, data, len); - SHA1_Final(hash, &c); -} diff --git a/crypt.h b/crypt.h index 85623fba..85629591 100644 --- a/crypt.h +++ b/crypt.h @@ -2,15 +2,14 @@ /** \file crypt.h Public crypto interface. */ +/* + * Asymmetric pubkey cryptosystem (apc). + * + * This is just RSA, but this fact is a hidden implementation detail. + */ -/* These are used to distinguish between loading of private/public key. */ - -/** The key to load is a public key. */ -#define LOAD_PUBLIC_KEY 0 -/** The key to load is a private key. */ -#define LOAD_PRIVATE_KEY 1 /** The size of the challenge sent to the client. */ -#define CHALLENGE_SIZE 64 +#define APC_CHALLENGE_SIZE 64 /** Opaque structure for public and private keys. */ struct asymmetric_key; @@ -25,7 +24,7 @@ struct asymmetric_key; * * \return The size of the encrypted data on success, negative on errors. */ -int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, +int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, unsigned len, unsigned char *outbuf); /** @@ -40,7 +39,7 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, * * \return The size of the recovered plaintext on success, negative on errors. */ -int priv_decrypt(const char *key_file, unsigned char *outbuf, +int apc_priv_decrypt(const char *key_file, unsigned char *outbuf, unsigned char *inbuf, int inlen); /** @@ -51,17 +50,17 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf, * * \return The size of the key on success, negative on errors. */ -int get_public_key(const char *key_file, struct asymmetric_key **result); +int apc_get_pubkey(const char *key_file, struct asymmetric_key **result); /** * Deallocate a public key. * * \param key Pointer to the key structure to free. * - * This should be called for keys obtained by get_public_key() if the key is no + * This should be called for keys obtained by \ref apc_get_pubkey() if the key is no * longer needed. */ -void free_public_key(struct asymmetric_key *key); +void apc_free_pubkey(struct asymmetric_key *key); /** @@ -78,17 +77,20 @@ void free_public_key(struct asymmetric_key *key); void get_random_bytes_or_die(unsigned char *buf, int num); /** - * Seed pseudo random number generators. + * Initialize the crypto backend. * - * This function seeds the PRNG used by random() with a random seed obtained - * from the crypto implementation. On errors, an error message is logged and - * the function calls exit(). + * This function initializes the crypto library and seeds the pseudo random + * number generator used by random() with a random seed obtained from the + * crypto implementation. On errors, an error message is logged and the + * function calls exit(). * * \sa \ref get_random_bytes_or_die(), srandom(3), random(3), \ref * para_random(). */ -void init_random_seed_or_die(void); +void crypt_init(void); +/** Allocate all resources of the crypto backend. */ +void crypt_shutdown(void); /** Opaque structure for stream ciphers. */ struct stream_cipher; diff --git a/crypt_backend.h b/crypt_backend.h index ff956ce3..175a6881 100644 --- a/crypt_backend.h +++ b/crypt_backend.h @@ -7,6 +7,6 @@ /** AES block size in bytes. */ #define AES_CRT128_BLOCK_SIZE 16 -size_t is_ssh_rsa_key(char *data, size_t size); -int check_ssh_key_header(const unsigned char *blob, int blen); +int decode_ssh_key(const char *filename, unsigned char **blob, + size_t *decoded_size); int check_private_key_file(const char *file); diff --git a/crypt_common.c b/crypt_common.c index 08361b27..235b8b8d 100644 --- a/crypt_common.c +++ b/crypt_common.c @@ -10,20 +10,19 @@ #include "crypt.h" #include "crypt_backend.h" #include "portable_io.h" +#include "fd.h" +#include "base64.h" /** If the key begins with this text, we treat it as an ssh key. */ #define KEY_TYPE_TXT "ssh-rsa" -/** - * Check if given buffer starts with a ssh rsa key signature. - * - * \param data The buffer. - * \param size Number of data bytes. +/* + * Check if the given buffer starts with an ssh rsa key signature. * - * \return Number of header bytes to be skipped on success, zero if - * ssh rsa signature was not found. + * Returns number of header bytes to be skipped on success, zero if no ssh rsa + * signature was found. */ -size_t is_ssh_rsa_key(char *data, size_t size) +static size_t is_ssh_rsa_key(char *data, size_t size) { char *cp; @@ -42,20 +41,13 @@ size_t is_ssh_rsa_key(char *data, size_t size) return cp - data; } -/** - * Sanity checks for the header of an ssh key. - * - * \param blob The buffer. - * \param blen The number of bytes of \a blob. +/* + * Perform some sanity checks on the decoded ssh key. * - * This performs some checks to make sure we really have an ssh key. It also - * computes the offset in bytes of the start of the key values (modulus, - * exponent..). - * - * \return The number of bytes to skip until the start of the first encoded - * number (usually 11). + * This function returns the size of the header. Usually, the header is 11 + * bytes long: four bytes for the length field, and the string "ssh-rsa". */ -int check_ssh_key_header(const unsigned char *blob, int blen) +static int check_ssh_key_header(const unsigned char *blob, int blen) { const unsigned char *p = blob, *end = blob + blen; uint32_t rlen; @@ -76,6 +68,51 @@ int check_ssh_key_header(const unsigned char *blob, int blen) return 4 + rlen; } +/** + * Perform sanity checks and base64-decode an ssh-rsa key. + * + * \param filename The public key file (usually id_rsa.pub). + * \param blob Pointer to base64-decoded blob is returned here. + * \param decoded_size The size of the decoded blob. + * + * The memory pointed at by the returned blob pointer has to be freed by the + * caller. + * + * \return On success, the offset in bytes of the start of the key values + * (modulus, exponent..). This is the number of bytes to skip from the blob + * until the start of the first encoded number. On failure, a negative error + * code is returned. + * + * \sa \ref uudecode(). + */ +int decode_ssh_key(const char *filename, unsigned char **blob, + size_t *decoded_size) +{ + int ret, ret2; + void *map; + size_t map_size; + + ret = mmap_full_file(filename, O_RDONLY, &map, &map_size, NULL); + if (ret < 0) + return ret; + ret = is_ssh_rsa_key(map, map_size); + if (ret == 0) { + ret = -E_SSH_PARSE; + goto unmap; + } + ret = uudecode(map + ret, map_size - ret, (char **)blob, decoded_size); + if (ret < 0) + goto unmap; + ret = check_ssh_key_header(*blob, *decoded_size); + if (ret < 0) + goto unmap; +unmap: + ret2 = para_munmap(map, map_size); + if (ret >= 0 && ret2 < 0) + ret = ret2; + return ret; +} + /** * Check existence and permissions of a private key file. * diff --git a/gcrypt.c b/gcrypt.c index 052546dd..ff4dab37 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -56,7 +56,7 @@ void get_random_bytes_or_die(unsigned char *buf, int num) * call to gcry_check_version() initializes the gcrypt library and checks that * we have at least the minimal required version. */ -void init_random_seed_or_die(void) +void crypt_init(void) { const char *req_ver = "1.5.0"; int seed; @@ -66,10 +66,29 @@ void init_random_seed_or_die(void) req_ver, gcry_check_version(NULL)); exit(EXIT_FAILURE); } + + /* + * Allocate a pool of secure memory. This also drops privileges where + * needed. + */ + gcry_control(GCRYCTL_INIT_SECMEM, 65536, 0); + + /* Tell Libgcrypt that initialization has completed. */ + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed)); srandom(seed); } +void crypt_shutdown(void) +{ + /* + * WK does not see a way to apply a patch for the sake of Valgrind, so + * as of 2018 libgrypt has no deinitialization routine to free the + * resources on exit. + */ +} + /** S-expression for the public part of an RSA key. */ #define RSA_PUBKEY_SEXP "(public-key (rsa (n %m) (e %m)))" /** S-expression for a private RSA key. */ @@ -366,27 +385,21 @@ free_blob: return ret; } -static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result) +int apc_get_pubkey(const char *key_file, struct asymmetric_key **result) { + unsigned char *blob, *p, *end; int ret; gcry_error_t gret; - unsigned char *blob = NULL, *p, *end; size_t nr_scanned, erroff, decoded_size; - gcry_mpi_t e = NULL, n = NULL; + gcry_mpi_t e, n; + gcry_sexp_t sexp; + struct asymmetric_key *key; - PARA_DEBUG_LOG("decoding %d byte public rsa-ssh key\n", size); - ret = uudecode((char *)data, size, (char **)&blob, &decoded_size); + ret = decode_ssh_key(key_file, &blob, &decoded_size); if (ret < 0) - goto free_blob; - end = blob + decoded_size; - dump_buffer("decoded key", blob, decoded_size); - ret = check_ssh_key_header(blob, decoded_size); - if (ret < 0) - goto free_blob; + return ret; p = blob + ret; - ret = -E_SSH_PARSE; - if (p >= end) - goto free_blob; + end = blob + decoded_size; PARA_DEBUG_LOG("scanning modulus and public exponent\n"); gret = gcry_mpi_scan(&e, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned); if (gret) { @@ -395,8 +408,6 @@ static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result goto free_blob; } PARA_DEBUG_LOG("scanned e (%zu bytes)\n", nr_scanned); -// gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, rsa_e); -// PARA_CRIT_LOG("e: %s\n", buf); p += nr_scanned; if (p >= end) goto release_e; @@ -407,17 +418,19 @@ static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result goto release_e; } PARA_DEBUG_LOG("scanned n (%zu bytes)\n", nr_scanned); -// gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, rsa_n); -// PARA_CRIT_LOG("n: %s\n", buf); - gret = gcry_sexp_build(result, &erroff, RSA_PUBKEY_SEXP, n, e); + gret = gcry_sexp_build(&sexp, &erroff, RSA_PUBKEY_SEXP, n, e); if (gret) { PARA_ERROR_LOG("offset %zu: %s\n", erroff, gcry_strerror(gcry_err_code(gret))); ret = -E_SEXP_BUILD; goto release_n; } - ret = nr_scanned / 32 * 32; + ret = ROUND_DOWN(nr_scanned, 32); PARA_INFO_LOG("successfully read %d bit ssh public key\n", ret * 8); + key = para_malloc(sizeof(*key)); + key->num_bytes = ret; + key->sexp = sexp; + *result = key; release_n: gcry_mpi_release(n); release_e: @@ -427,43 +440,7 @@ free_blob: return ret; } -int get_public_key(const char *key_file, struct asymmetric_key **result) -{ - int ret, ret2; - void *map; - size_t map_size; - unsigned char *start, *end; - gcry_sexp_t sexp; - struct asymmetric_key *key; - - ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL); - if (ret < 0) - return ret; - ret = is_ssh_rsa_key(map, map_size); - if (!ret) { - para_munmap(map, map_size); - return -E_SSH_PARSE; - } - start = map + ret; - end = map + map_size; - ret = -E_SSH_PARSE; - if (start >= end) - goto unmap; - ret = get_ssh_public_key(start, end - start, &sexp); - if (ret < 0) - goto unmap; - key = para_malloc(sizeof(*key)); - key->num_bytes = ret; - key->sexp = sexp; - *result = key; -unmap: - ret2 = para_munmap(map, map_size); - if (ret >= 0 && ret2 < 0) - ret = ret2; - return ret; -} - -void free_public_key(struct asymmetric_key *key) +void apc_free_pubkey(struct asymmetric_key *key) { if (!key) return; @@ -481,7 +458,7 @@ static int decode_rsa(gcry_sexp_t sexp, unsigned char *outbuf, size_t *nbytes) return 1; } -int priv_decrypt(const char *key_file, unsigned char *outbuf, +int apc_priv_decrypt(const char *key_file, unsigned char *outbuf, unsigned char *inbuf, int inlen) { gcry_error_t gret; @@ -548,7 +525,7 @@ free_key: return ret; } -int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, +int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, unsigned len, unsigned char *outbuf) { gcry_error_t gret; diff --git a/openssl.c b/openssl.c new file mode 100644 index 00000000..7d5bb25d --- /dev/null +++ b/openssl.c @@ -0,0 +1,280 @@ +/* Copyright (C) 2005 Andre Noll , see file COPYING. */ + +/** \file openssl.c Openssl-based encryption/decryption routines. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "string.h" +#include "crypt.h" +#include "crypt_backend.h" +#include "portable_io.h" + +struct asymmetric_key { + RSA *rsa; +}; + +void get_random_bytes_or_die(unsigned char *buf, int num) +{ + unsigned long err; + + /* RAND_bytes() returns 1 on success, 0 otherwise. */ + if (RAND_bytes(buf, num) == 1) + return; + err = ERR_get_error(); + PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err)); + exit(EXIT_FAILURE); +} + +/* + * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Seed the PRNG + * used by random(3) with a random seed obtained from SSL. If /dev/urandom is + * not readable, the function calls exit(). + * + * \sa RAND_load_file(3), \ref get_random_bytes_or_die(), srandom(3), + * random(3), \ref para_random(). + */ +void crypt_init(void) +{ + int seed, ret = RAND_load_file("/dev/urandom", 64); + + if (ret != 64) { + PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret); + exit(EXIT_FAILURE); + } + get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed)); + srandom(seed); +} + +void crypt_shutdown(void) +{ + CRYPTO_cleanup_all_ex_data(); +} + +static int get_private_key(const char *path, RSA **rsa) +{ + EVP_PKEY *pkey; + BIO *bio = BIO_new(BIO_s_file()); + + *rsa = NULL; + if (!bio) + return -E_PRIVATE_KEY; + if (BIO_read_filename(bio, path) <= 0) + goto bio_free; + pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (!pkey) + goto bio_free; + *rsa = EVP_PKEY_get1_RSA(pkey); + EVP_PKEY_free(pkey); +bio_free: + BIO_free(bio); + return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY; +} + +/* + * The public key loading functions below were inspired by corresponding code + * of openssh-5.2p1, Copyright (c) 1995 Tatu Ylonen , Espoo, + * Finland. However, not much of the original code remains. + */ + +static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result) +{ + const unsigned char *p = buf, *end = buf + len; + uint32_t bnsize; + BIGNUM *bn; + + if (p + 4 < p) + return -E_BIGNUM; + if (p + 4 > end) + return -E_BIGNUM; + bnsize = read_u32_be(p); + PARA_DEBUG_LOG("bnsize: %u\n", bnsize); + p += 4; + if (p + bnsize < p) + return -E_BIGNUM; + if (p + bnsize > end) + return -E_BIGNUM; + if (bnsize > 8192) + return -E_BIGNUM; + bn = BN_bin2bn(p, bnsize, NULL); + if (!bn) + return -E_BIGNUM; + *result = bn; + return bnsize + 4; +} + +static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result) +{ + int ret; + RSA *rsa; + BIGNUM *n, *e; + const unsigned char *p = blob, *end = blob + blen; + + rsa = RSA_new(); + if (!rsa) + return -E_BIGNUM; + ret = read_bignum(p, end - p, &e); + if (ret < 0) + goto fail; + p += ret; + ret = read_bignum(p, end - p, &n); + if (ret < 0) + goto fail; +#ifdef HAVE_RSA_SET0_KEY + RSA_set0_key(rsa, n, e, NULL); +#else + rsa->n = n; + rsa->e = e; +#endif + *result = rsa; + return 1; +fail: + RSA_free(rsa); + return ret; +} + +int apc_get_pubkey(const char *key_file, struct asymmetric_key **result) +{ + unsigned char *blob; + size_t decoded_size; + int ret; + struct asymmetric_key *key = para_malloc(sizeof(*key)); + + ret = decode_ssh_key(key_file, &blob, &decoded_size); + if (ret < 0) + goto out; + ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa); + if (ret < 0) + goto free_blob; + ret = RSA_size(key->rsa); + assert(ret > 0); + *result = key; +free_blob: + free(blob); +out: + if (ret < 0) { + free(key); + *result = NULL; + PARA_ERROR_LOG("can not load key %s\n", key_file); + } + return ret; +} + +void apc_free_pubkey(struct asymmetric_key *key) +{ + if (!key) + return; + RSA_free(key->rsa); + free(key); +} + +int apc_priv_decrypt(const char *key_file, unsigned char *outbuf, + unsigned char *inbuf, int inlen) +{ + struct asymmetric_key *priv; + int ret; + + ret = check_private_key_file(key_file); + if (ret < 0) + return ret; + if (inlen < 0) + return -E_RSA; + priv = para_malloc(sizeof(*priv)); + ret = get_private_key(key_file, &priv->rsa); + if (ret < 0) { + free(priv); + return ret; + } + /* + * RSA is vulnerable to timing attacks. Generate a random blinding + * factor to protect against this kind of attack. + */ + ret = -E_BLINDING; + if (RSA_blinding_on(priv->rsa, NULL) == 0) + goto out; + ret = RSA_private_decrypt(inlen, inbuf, outbuf, priv->rsa, + RSA_PKCS1_OAEP_PADDING); + RSA_blinding_off(priv->rsa); + if (ret <= 0) + ret = -E_DECRYPT; +out: + RSA_free(priv->rsa); + free(priv); + return ret; +} + +int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, + unsigned len, unsigned char *outbuf) +{ + int ret, flen = len; /* RSA_public_encrypt expects a signed int */ + + if (flen < 0) + return -E_ENCRYPT; + ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa, + RSA_PKCS1_OAEP_PADDING); + return ret < 0? -E_ENCRYPT : ret; +} + +struct stream_cipher { + EVP_CIPHER_CTX *aes; +}; + +struct stream_cipher *sc_new(const unsigned char *data, int len) +{ + struct stream_cipher *sc = para_malloc(sizeof(*sc)); + + assert(len >= 2 * AES_CRT128_BLOCK_SIZE); + sc->aes = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data, + data + AES_CRT128_BLOCK_SIZE); + return sc; +} + +void sc_free(struct stream_cipher *sc) +{ + if (!sc) + return; + EVP_CIPHER_CTX_free(sc->aes); + free(sc); +} + +static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src, + struct iovec *dst) +{ + int ret, inlen = src->iov_len, outlen, tmplen; + + *dst = (typeof(*dst)) { + /* Add one for the terminating zero byte. */ + .iov_base = para_malloc(inlen + 1), + .iov_len = inlen + }; + ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen); + assert(ret != 0); + ret = EVP_EncryptFinal_ex(ctx, dst->iov_base + outlen, &tmplen); + assert(ret != 0); + outlen += tmplen; + ((char *)dst->iov_base)[outlen] = '\0'; + dst->iov_len = outlen; +} + +void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +{ + return aes_ctr128_crypt(sc->aes, src, dst); +} + +void hash_function(const char *data, unsigned long len, unsigned char *hash) +{ + SHA_CTX c; + SHA1_Init(&c); + SHA1_Update(&c, data, len); + SHA1_Final(hash, &c); +} diff --git a/server.c b/server.c index 66c93ab2..011367e3 100644 --- a/server.c +++ b/server.c @@ -456,6 +456,7 @@ static int init_afs(int argc, char **argv) int i; afs_pid = getpid(); + crypt_shutdown(); for (i = argc - 1; i >= 0; i--) memset(argv[i], 0, strlen(argv[i])); i = argc - lls_num_inputs(cmdline_lpr) - 1; @@ -513,7 +514,7 @@ static void server_init(int argc, char **argv) /* become daemon */ if (OPT_GIVEN(DAEMON)) daemon_pipe = daemonize(true /* parent waits for SIGTERM */); - init_random_seed_or_die(); + crypt_init(); daemon_log_welcome("server"); init_ipc_or_die(); /* init mmd struct and mmd->lock */ daemon_set_start_time(); @@ -610,6 +611,7 @@ int main(int argc, char *argv[]) mutex_lock(mmd_mutex); ret = schedule(&sched); sched_shutdown(&sched); + crypt_shutdown(); lls_free_parse_result(server_lpr, CMD_PTR); if (server_lpr != cmdline_lpr) lls_free_parse_result(cmdline_lpr, CMD_PTR); diff --git a/user_list.c b/user_list.c index e4866029..9db08715 100644 --- a/user_list.c +++ b/user_list.c @@ -44,22 +44,22 @@ static void populate_user_list(char *user_list_file) if (strcmp(w, "user")) continue; PARA_DEBUG_LOG("found entry for user %s\n", n); - ret = get_public_key(k, &pubkey); + ret = apc_get_pubkey(k, &pubkey); if (ret < 0) { PARA_NOTICE_LOG("skipping entry for user %s: %s\n", n, para_strerror(-ret)); continue; } /* - * In order to encrypt len := CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + * In order to encrypt len := APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN * bytes using RSA_public_encrypt() with EME-OAEP padding mode, * RSA_size(rsa) must be greater than len + 41. So ignore keys * which are too short. For details see RSA_public_encrypt(3). */ - if (ret <= CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) { + if (ret <= APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) { PARA_WARNING_LOG("public key %s too short (%d)\n", k, ret); - free_public_key(pubkey); + apc_free_pubkey(pubkey); continue; } u = para_malloc(sizeof(*u)); @@ -110,7 +110,7 @@ void init_user_list(char *user_list_file) list_for_each_entry_safe(u, tmp, &user_list, node) { list_del(&u->node); free(u->name); - free_public_key(u->pubkey); + apc_free_pubkey(u->pubkey); free(u); } } else