Merge branch 'refs/heads/t/crypt'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 13 May 2018 12:03:20 +0000 (14:03 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 13 May 2018 12:04:25 +0000 (14:04 +0200)
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.

14 files changed:
NEWS.md
audiod.c
client.c
client_common.c
command.c
configure.ac
crypt.c [deleted file]
crypt.h
crypt_backend.h
crypt_common.c
gcrypt.c
openssl.c [new file with mode: 0644]
server.c
user_list.c

diff --git a/NEWS.md b/NEWS.md
index 9ced85e..11f275d 100644 (file)
--- 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"
index 8d152fa..083c2a7 100644 (file)
--- 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)
index 3e9219f..c45826a 100644 (file)
--- 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));
index a8d4805..b28e895 100644 (file)
@@ -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);
index 6db0c00..bfce809 100644 (file)
--- 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)
index 499571a..9493f8e 100644 (file)
@@ -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 (file)
index b8a587c..0000000
--- a/crypt.c
+++ /dev/null
@@ -1,296 +0,0 @@
-/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file crypt.c Openssl-based encryption/decryption routines. */
-
-#include <regex.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <openssl/rand.h>
-#include <openssl/err.h>
-#include <openssl/pem.h>
-#include <openssl/sha.h>
-#include <openssl/bn.h>
-#include <openssl/aes.h>
-
-#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 <ylo@cs.hut.fi>, 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 85623fb..8562959 100644 (file)
--- 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;
index ff956ce..175a688 100644 (file)
@@ -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);
index 08361b2..235b8b8 100644 (file)
 #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.
  *
index 052546d..ff4dab3 100644 (file)
--- 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 (file)
index 0000000..7d5bb25
--- /dev/null
+++ b/openssl.c
@@ -0,0 +1,280 @@
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file openssl.c Openssl-based encryption/decryption routines. */
+
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/sha.h>
+#include <openssl/bn.h>
+#include <openssl/aes.h>
+
+#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 <ylo@cs.hut.fi>, 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);
+}
index 66c93ab..011367e 100644 (file)
--- 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);
index e486602..9db0871 100644 (file)
@@ -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