Merge branch 't/crypto'
authorAndre Noll <maan@systemlinux.org>
Sun, 7 Aug 2011 11:13:43 +0000 (13:13 +0200)
committerAndre Noll <maan@systemlinux.org>
Sun, 7 Aug 2011 11:35:21 +0000 (13:35 +0200)
NEWS
configure.ac
crypt.c
crypt.h
crypt_backend.h [new file with mode: 0644]
crypt_common.c [new file with mode: 0644]
error.h
gcrypt.c [new file with mode: 0644]
server.c
web/manual.m4

diff --git a/NEWS b/NEWS
index 5105798f57fabc447fdc2c3630cb8363a6c7486b..7d330ce48c5528a43156fd7b12833859218e9f1e 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,12 +2,21 @@
 0.4.8 (to be announced) "nested assignment"
 -------------------------------------------
 
+Gcrypt support, the overhauled osx writer and regex format specifiers
+are the highlights of this release.
+
+       - support for libgcrypt as a drop-in replacement for openssl.
+         Run configure --enable-cryptolib=gcrypt to link against
+         libgcrypt. The two crypto implementations are compatible to
+         each other, i.e. a para_client executable linked against
+         gcrypt can connect to para_server linked against libssl
+         and vice versa.
+       - Rewrite of the osx writer (output plugin for Mac OS).
        - audiod: The format specifier for receivers, filters and
          writers is now treated as a regular expression. This allows
          to replace 5 lines in the config file (one for each audio
          format) by one single line. See the manual for details.
        - Compiles cleanly also with llvm/clang.
-       - Rewrite of the osx writer (output plugin for Mac OS).
 
 --------------------------------------
 0.4.7 (2011-06-01) "infinite rollback"
index ed1d710d2435095d7924c8ba12a188590dc50c7b..79aaee63b3f159e20941f137b4b8d279823f7bf0 100644 (file)
@@ -85,14 +85,14 @@ AC_DEFUN([add_cmdline],[$(for i in $@; do printf "${i}.cmdline "; done)])
 
 
 all_errlist_objs="server mp3_afh afh_common vss command net string signal time
-daemon stat crypt http_send close_on_fork ipc acl afh fade amp_filter
+daemon stat http_send close_on_fork ipc acl afh fade amp_filter
 dccp_send fd user_list chunk_queue afs aft mood score attribute blob ringbuffer
 playlist sched audiod grab_client filter_common wav_filter compress_filter
 http_recv dccp_recv recv_common write_common file_write audiod_command
 client_common recv stdout filter stdin audioc write client exec send_common ggo
 udp_recv udp_send color fec fecdec_filter prebuffer_filter mm
 server_command_list afs_command_list audiod_command_list bitstream imdct wma_afh
-wma_common wmadec_filter buffer_tree
+wma_common wmadec_filter buffer_tree crypt_common
 "
 
 executables="recv filter audioc write client afh audiod"
@@ -115,9 +115,9 @@ audioc_errlist_objs="audioc string net fd"
 audioc_ldflags=""
 
 audiod_cmdline_objs="add_cmdline(audiod compress_filter http_recv dccp_recv file_write client amp_filter udp_recv prebuffer_filter)"
-audiod_errlist_objs="audiod signal string daemon stat net
+audiod_errlist_objs="audiod signal string daemon stat net crypt_common
        time grab_client filter_common wav_filter compress_filter amp_filter http_recv dccp_recv
-       recv_common fd sched write_common file_write audiod_command crypt fecdec_filter
+       recv_common fd sched write_common file_write audiod_command fecdec_filter
        client_common ggo udp_recv color fec prebuffer_filter audiod_command_list
        bitstream imdct wma_common wmadec_filter buffer_tree"
 audiod_ldflags="-lm"
@@ -129,7 +129,7 @@ afh_ldflags=""
 
 server_cmdline_objs="add_cmdline(server)"
 server_errlist_objs="server afh_common mp3_afh vss command net string signal
-       time daemon crypt http_send close_on_fork mm
+       time daemon http_send close_on_fork mm crypt_common
        ipc dccp_send fd user_list chunk_queue afs aft mood score attribute
        blob playlist sched acl send_common udp_send color fec
        server_command_list afs_command_list wma_afh wma_common"
@@ -144,8 +144,8 @@ writers=" file"
 default_writer="FILE_WRITE"
 
 client_cmdline_objs="add_cmdline(client)"
-client_errlist_objs="client net string crypt fd sched stdin stdout time
-       client_common buffer_tree"
+client_errlist_objs="client net string fd sched stdin stdout time
+       client_common buffer_tree crypt_common"
 client_ldflags=""
 
 gui_cmdline_objs="add_cmdline(gui)"
@@ -273,52 +273,122 @@ fi
 CPPFLAGS="$OLD_CPPFLAGS"
 LDFLAGS="$OLD_LDFLAGS"
 LIBS="$OLD_LIBS"
+########################################################################### crypto
+AC_ARG_ENABLE(cryptolib, [AS_HELP_STRING(--enable-cryptolib=lib, [
+       Force using crypto library "lib". This package requires either
+       openssl or libgcrypt being installed. Possible values for "lib"
+       are thus "openssl" and "gcrypt". If this option is not given,
+       openssl is tried first. If openssl was not found, gcrypt is
+       tried next.])])
+
+case "$enable_cryptolib" in
+       "openssl") check_openssl="yes"; check_gcrypt="no";;
+       "gcrypt") check_openssl="no"; check_gcrypt="yes";;
+       "") check_openssl="yes"; check_gcrypt="yes";;
+       *) AC_MSG_ERROR([invalid value "$enable_cryptolib" for --enable-cryptolib]);;
+esac
 ###################################################################### openssl
-OLD_CPPFLAGS="$CPPFLAGS"
-OLD_LD_FLAGS="$LDFLAGS"
-OLD_LIBS="$LIBS"
-have_openssl="yes"
-AC_ARG_WITH(openssl_headers, [AC_HELP_STRING(--with-openssl-headers=dir,
-       [look for openssl headers also in dir])])
-if test -n "$with_openssl_headers"; then
-       openssl_cppflags="-I$with_openssl_headers"
-       CPPFLAGS="$CPPFLAGS $openssl_cppflags"
-fi
-AC_ARG_WITH(openssl_libs, [AC_HELP_STRING(--with-openssl-libs=dir,
-       [look for openssl libraries also in dir])])
-if test -n "$with_openssl_libs"; then
-       openssl_libs="-L$with_openssl_libs"
-       LDFLAGS="$LDFLAGS $openssl_libs"
-fi
-AC_CHECK_HEADER(openssl/ssl.h, [], [have_openssl="no"])
-AC_CHECK_LIB([crypto], [RAND_bytes], [], [have_openssl="no"])
-if test "$have_openssl" = "no" -a -z "$with_openssl_headers$with_openssl_libs"; then
-       # try harder: openssl is sometimes installed in /usr/local/ssl
-       openssl_cppflags="-I/usr/local/ssl/include"
-       CPPFLAGS="$CPPFLAGS $openssl_cppflags"
-       openssl_libs="-L/usr/local/ssl/lib"
-       LDFLAGS="$LDFLAGS $openssl_libs"
-       # clear cache
-       unset ac_cv_header_openssl_ssl_h 2> /dev/null
-       unset ac_cv_lib_crypto_RAND_bytes 2> /dev/null
-       AC_CHECK_HEADER(openssl/ssl.h, [have_openssl="yes"], [])
+if test "$check_openssl" = "yes"; then
+       OLD_CPPFLAGS="$CPPFLAGS"
+       OLD_LD_FLAGS="$LDFLAGS"
+       OLD_LIBS="$LIBS"
+       have_openssl="yes"
+       AC_ARG_WITH(openssl_headers, [AC_HELP_STRING(--with-openssl-headers=dir,
+               [look for openssl headers also in dir])])
+       if test -n "$with_openssl_headers"; then
+               openssl_cppflags="-I$with_openssl_headers"
+               CPPFLAGS="$CPPFLAGS $openssl_cppflags"
+       fi
+       AC_ARG_WITH(openssl_libs, [AC_HELP_STRING(--with-openssl-libs=dir,
+               [look for openssl libraries also in dir])])
+       if test -n "$with_openssl_libs"; then
+               openssl_libs="-L$with_openssl_libs"
+               LDFLAGS="$LDFLAGS $openssl_libs"
+       fi
+       AC_CHECK_HEADER(openssl/ssl.h, [], [have_openssl="no"])
+       AC_CHECK_LIB([crypto], [RAND_bytes], [], [have_openssl="no"])
+       if test "$have_openssl" = "no" -a -z "$with_openssl_headers$with_openssl_libs"; then
+               # try harder: openssl is sometimes installed in /usr/local/ssl
+               openssl_cppflags="-I/usr/local/ssl/include"
+               CPPFLAGS="$CPPFLAGS $openssl_cppflags"
+               openssl_libs="-L/usr/local/ssl/lib"
+               LDFLAGS="$LDFLAGS $openssl_libs"
+               # clear cache
+               unset ac_cv_header_openssl_ssl_h 2> /dev/null
+               unset ac_cv_lib_crypto_RAND_bytes 2> /dev/null
+               AC_CHECK_HEADER(openssl/ssl.h, [have_openssl="yes"], [])
+               if test "$have_openssl" = "yes"; then
+                       AC_CHECK_LIB([crypto], [RAND_bytes], [], [have_openssl="no"])
+               fi
+       fi
        if test "$have_openssl" = "yes"; then
-               AC_CHECK_LIB([crypto], [RAND_bytes], [], [have_openssl="no"])
+               AC_DEFINE(HAVE_OPENSSL, 1, [define to 1 to turn on openssl support])
+               AC_SUBST(openssl_cppflags)
+               openssl_libs="$openssl_libs -lssl -lcrypto"
+               server_ldflags="$server_ldflags $openssl_libs"
+               client_ldflags="$client_ldflags $openssl_libs"
+               audiod_ldflags="$audiod_ldflags $openssl_libs"
+
+               all_errlist_objs="$all_errlist_objs crypt"
+               server_errlist_objs="$server_errlist_objs crypt"
+               client_errlist_objs="$client_errlist_objs crypt"
+               audiod_errlist_objs="$audiod_errlist_objs crypt"
+
+               check_gcrypt="no"
+       else
+               AC_MSG_WARN([openssl libraries not found])
        fi
-fi
-if test "$have_openssl" = "yes"; then
-       AC_DEFINE(HAVE_OPENSSL, 1, [define to 1 to turn on openssl support])
-       AC_SUBST(openssl_cppflags)
-       openssl_libs="$openssl_libs -lssl -lcrypto"
-       server_ldflags="$server_ldflags $openssl_libs"
-       client_ldflags="$client_ldflags $openssl_libs"
-       audiod_ldflags="$audiod_ldflags $openssl_libs"
+       CPPFLAGS="$OLD_CPPFLAGS"
+       LDFLAGS="$OLD_LDFLAGS"
+       LIBS="$OLD_LIBS"
 else
-       AC_MSG_ERROR([openssl libraries not found])
+       have_openssl="no"
+fi
+########################################################################### gcrypt
+if test "$check_gcrypt" = "yes"; then
+       OLD_CPPFLAGS="$CPPFLAGS"
+       OLD_LD_FLAGS="$LDFLAGS"
+       OLD_LIBS="$LIBS"
+       have_gcrypt="yes"
+       AC_ARG_WITH(gcrypt_headers, [AC_HELP_STRING(--with-gcrypt-headers=dir,
+               [look for gcrypt headers also in dir])])
+       if test -n "$with_gcrypt_headers"; then
+               gcrypt_cppflags="-I$with_gcrypt_headers"
+               CPPFLAGS="$CPPFLAGS $gcrypt_cppflags"
+       fi
+       AC_ARG_WITH(gcrypt_libs, [AC_HELP_STRING(--with-gcrypt-libs=dir,
+               [look for libgcrypt also in dir])])
+       if test -n "$with_gcrypt_libs"; then
+               gcrypt_libs="-L$with_gcrypt_libs"
+               LDFLAGS="$LDFLAGS $gcrypt_libs"
+       fi
+       AC_CHECK_HEADER(gcrypt.h, [], [have_gcrypt="no"])
+       AC_CHECK_LIB([gcrypt], [gcry_randomize], [], [have_gcrypt="no"])
+       if test "$have_gcrypt" = "yes"; then
+               AC_DEFINE(HAVE_GCRYPT, 1, [define to 1 to turn on gcrypt support])
+               AC_SUBST(gcrypt_cppflags)
+               gcrypt_libs="$gcrypt_libs -lgcrypt"
+               server_ldflags="$server_ldflags $gcrypt_libs"
+               client_ldflags="$client_ldflags $gcrypt_libs"
+               audiod_ldflags="$audiod_ldflags $gcrypt_libs"
+
+               all_errlist_objs="$all_errlist_objs gcrypt"
+               server_errlist_objs="$server_errlist_objs gcrypt"
+               client_errlist_objs="$client_errlist_objs gcrypt"
+               audiod_errlist_objs="$audiod_errlist_objs gcrypt"
+       else
+               AC_MSG_WARN([gcrypt library not found])
+       fi
+       CPPFLAGS="$OLD_CPPFLAGS"
+       LDFLAGS="$OLD_LDFLAGS"
+       LIBS="$OLD_LIBS"
+else
+       have_gcrypt="no"
+fi
+###########################################################################
+if test "$have_openssl" = "no" -a "$have_gcrypt" = "no"; then
+       AC_MSG_ERROR([neither openssl nor gcrypt usable])
 fi
-CPPFLAGS="$OLD_CPPFLAGS"
-LDFLAGS="$OLD_LDFLAGS"
-LIBS="$OLD_LIBS"
 ########################################################################### libsocket
 AC_CHECK_LIB([c], [socket],
        [socket_lib=],
@@ -807,7 +877,7 @@ for obj in $all_errlist_objs; do
 done
 AC_DEFINE_UNQUOTED(DEFINE_ERRLIST_OBJECT_ENUM,
        [enum {$SS NUM_SS}],
-       [list of all objects that use paraslash's error facility]
+       [list of all objects that use the paraslash error facility]
 )
 
 ################################################################## status items
diff --git a/crypt.c b/crypt.c
index 6f7e611d0364035560819f7659bbb71d62d4f1b1..f064fb3a535d9df3306818d7e9c27ca85193bd50 100644 (file)
--- a/crypt.c
+++ b/crypt.c
@@ -7,6 +7,7 @@
 /** \file crypt.c Openssl-based encryption/decryption routines. */
 
 #include <regex.h>
+#include <stdbool.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <openssl/rand.h>
 #include "string.h"
 #include "crypt.h"
 #include "fd.h"
+#include "crypt_backend.h"
 
 struct asymmetric_key {
        RSA *rsa;
 };
 
-/**
- * Fill a buffer with random content.
- *
- * \param buf The buffer to fill.
- * \param num The size of \a buf in bytes.
- *
- * This function puts \a num cryptographically strong pseudo-random bytes into
- * buf. If libssl can not guarantee an unpredictable byte sequence (for example
- * because the PRNG has not been seeded with enough randomness) the function
- * logs an error message and calls exit().
- */
 void get_random_bytes_or_die(unsigned char *buf, int num)
 {
        unsigned long err;
@@ -49,13 +40,10 @@ void get_random_bytes_or_die(unsigned char *buf, int num)
        exit(EXIT_FAILURE);
 }
 
-/**
- * Seed pseudo random number generators.
- *
- * This function reads 64 bytes from /dev/urandom and adds them to the SSL
- * PRNG. It also seeds the PRNG used by random() with a random seed obtained
- * from SSL. If /dev/random could not be read, an error message is logged and
- * the function calls exit().
+/*
+ * Read 64 bytes from /dev/urandom and adds them to the SSL PRNG. Seed the PRNG
+ * used by random() with a random seed obtained from SSL. If /dev/random 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().
@@ -72,19 +60,6 @@ void init_random_seed_or_die(void)
        srandom(seed);
 }
 
-static int check_key_file(const char *file, int private)
-{
-       struct stat st;
-
-       if (stat(file, &st) != 0)
-               return -ERRNO_TO_PARA_ERROR(errno);
-       if (private != LOAD_PRIVATE_KEY)
-               return 0;
-       if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0)
-               return -E_KEY_PERM;
-       return 1;
-}
-
 static EVP_PKEY *load_key(const char *file, int private)
 {
        BIO *key;
@@ -122,203 +97,12 @@ static int get_openssl_key(const char *key_file, RSA **rsa, int private)
        return RSA_size(*rsa);
 }
 
-#define KEY_TYPE_TXT "ssh-rsa"
-
-/* check if it is an ssh rsa key */
-static size_t is_ssh_rsa_key(char *data, size_t size)
-{
-       char *cp;
-
-       if (size < strlen(KEY_TYPE_TXT) + 2)
-               return 0;
-       cp = memchr(data, ' ', size);
-       if (cp == NULL)
-               return 0;
-       if (strncmp(KEY_TYPE_TXT, data, strlen(KEY_TYPE_TXT)))
-               return 0;
-       cp++;
-       if (cp >= data + size)
-               return 0;
-       if (*cp == '\0')
-               return 0;
-       return cp - data;
-}
-
-/*
- * This base64/uudecode stuff below is taken from openssh-5.2p1, Copyright (c)
- * 1996 by Internet Software Consortium.  Portions Copyright (c) 1995 by
- * International Business Machines, Inc.
- */
-
-static const char Base64[] =
-       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-static const char Pad64 = '=';
-/*
- * Skips all whitespace anywhere. Converts characters, four at a time, starting
- * at (or after) src from base - 64 numbers into three 8 bit bytes in the
- * target area. it returns the number of data bytes stored at the target, or -1
- * on error.
- */
-static int base64_decode(char const *src, unsigned char *target, size_t targsize)
-{
-       unsigned int tarindex, state;
-       int ch;
-       char *pos;
-
-       state = 0;
-       tarindex = 0;
-
-       while ((ch = *src++) != '\0') {
-               if (para_isspace(ch)) /* Skip whitespace anywhere. */
-                       continue;
-
-               if (ch == Pad64)
-                       break;
-
-               pos = strchr(Base64, ch);
-               if (pos == 0) /* A non-base64 character. */
-                       return -1;
-
-               switch (state) {
-               case 0:
-                       if (target) {
-                               if (tarindex >= targsize)
-                                       return (-1);
-                               target[tarindex] = (pos - Base64) << 2;
-                       }
-                       state = 1;
-                       break;
-               case 1:
-                       if (target) {
-                               if (tarindex + 1 >= targsize)
-                                       return (-1);
-                               target[tarindex]   |=  (pos - Base64) >> 4;
-                               target[tarindex+1]  = ((pos - Base64) & 0x0f)
-                                                       << 4 ;
-                       }
-                       tarindex++;
-                       state = 2;
-                       break;
-               case 2:
-                       if (target) {
-                               if (tarindex + 1 >= targsize)
-                                       return (-1);
-                               target[tarindex]   |=  (pos - Base64) >> 2;
-                               target[tarindex+1]  = ((pos - Base64) & 0x03)
-                                                       << 6;
-                       }
-                       tarindex++;
-                       state = 3;
-                       break;
-               case 3:
-                       if (target) {
-                               if (tarindex >= targsize)
-                                       return (-1);
-                               target[tarindex] |= (pos - Base64);
-                       }
-                       tarindex++;
-                       state = 0;
-                       break;
-               }
-       }
-
-       /*
-        * We are done decoding Base-64 chars.  Let's see if we ended
-        * on a byte boundary, and/or with erroneous trailing characters.
-        */
-
-       if (ch == Pad64) {              /* We got a pad char. */
-               ch = *src++;            /* Skip it, get next. */
-               switch (state) {
-               case 0:         /* Invalid = in first position */
-               case 1:         /* Invalid = in second position */
-                       return (-1);
-
-               case 2:         /* Valid, means one byte of info */
-                       /* Skip any number of spaces. */
-                       for (; ch != '\0'; ch = *src++)
-                               if (!isspace(ch))
-                                       break;
-                       /* Make sure there is another trailing = sign. */
-                       if (ch != Pad64)
-                               return (-1);
-                       ch = *src++;            /* Skip the = */
-                       /* Fall through to "single trailing =" case. */
-                       /* FALLTHROUGH */
-
-               case 3:         /* Valid, means two bytes of info */
-                       /*
-                        * We know this char is an =.  Is there anything but
-                        * whitespace after it?
-                        */
-                       for (; ch != '\0'; ch = *src++)
-                               if (!isspace(ch))
-                                       return (-1);
-
-                       /*
-                        * Now make sure for cases 2 and 3 that the "extra"
-                        * bits that slopped past the last full byte were
-                        * zeros.  If we don't check them, they become a
-                        * subliminal channel.
-                        */
-                       if (target && target[tarindex] != 0)
-                               return (-1);
-               }
-       } else {
-               /*
-                * We ended by seeing the end of the string.  Make sure we
-                * have no partial bytes lying around.
-                */
-               if (state != 0)
-                       return (-1);
-       }
-
-       return (tarindex);
-}
-
-static int uudecode(const char *src, unsigned char *target, size_t targsize)
-{
-       int len;
-       char *encoded, *p;
-
-       /* copy the 'readonly' source */
-       encoded = para_strdup(src);
-       /* skip whitespace and data */
-       for (p = encoded; *p == ' ' || *p == '\t'; p++)
-               ;
-       for (; *p != '\0' && *p != ' ' && *p != '\t'; p++)
-               ;
-       /* and remove trailing whitespace because base64_decode needs this */
-       *p = '\0';
-       len = base64_decode(encoded, target, targsize);
-       free(encoded);
-       return len >= 0? len : -E_BASE64;
-}
-
 /*
  * 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.
  */
 
-
-/*
- * Can not use the inline functions of portable_io.h here because the byte
- * order is different.
- */
-static uint32_t read_ssh_u32(const void *vp)
-{
-       const unsigned char *p = (const unsigned char *)vp;
-       uint32_t v;
-
-       v  = (uint32_t)p[0] << 24;
-       v |= (uint32_t)p[1] << 16;
-       v |= (uint32_t)p[2] << 8;
-       v |= (uint32_t)p[3];
-
-       return v;
-}
-
 static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result)
 {
        const unsigned char *p = buf, *end = buf + len;
@@ -350,23 +134,6 @@ static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result)
        int ret;
        RSA *rsa;
        const unsigned char *p = blob, *end = blob + blen;
-       uint32_t rlen;
-
-       *result = NULL;
-       if (p + 4 > end)
-               return -E_BIGNUM;
-       rlen = read_ssh_u32(p);
-       p += 4;
-       if (p + rlen < p)
-               return -E_BIGNUM;
-       if (p + rlen > end)
-               return -E_BIGNUM;
-       if (rlen < strlen(KEY_TYPE_TXT))
-               return -E_BIGNUM;
-       PARA_DEBUG_LOG("type: %s, rlen: %d\n", p, rlen);
-       if (strncmp((char *)p, KEY_TYPE_TXT, strlen(KEY_TYPE_TXT)))
-               return -E_BIGNUM;
-       p += rlen;
 
        rsa = RSA_new();
        if (!rsa)
@@ -386,24 +153,13 @@ fail:
        return ret;
 }
 
-/**
- * Read an asymmetric key from a file.
- *
- * \param key_file The file containing the key.
- * \param private if non-zero, read the private key, otherwise the public key.
- * \param result The key structure is returned here.
- *
- * \return The size of the key on success, negative on errors.
- *
- * \sa openssl(1), rsa(1).
- */
 int get_asymmetric_key(const char *key_file, int private,
                struct asymmetric_key **result)
 {
        struct asymmetric_key *key = NULL;
        void *map = NULL;
        unsigned char *blob = NULL;
-       size_t map_size, blob_size;
+       size_t map_size, blob_size, decoded_size;
        int ret, ret2;
        char *cp;
 
@@ -434,7 +190,11 @@ int get_asymmetric_key(const char *key_file, int private,
        ret = uudecode(cp, blob, blob_size);
        if (ret < 0)
                goto out;
-       ret = read_rsa_bignums(blob, ret, &key->rsa);
+       decoded_size = ret;
+       ret = check_ssh_key_header(blob, decoded_size);
+       if (ret < 0)
+               goto out;
+       ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa);
        if (ret < 0)
                goto out;
        ret = RSA_size(key->rsa);
@@ -452,13 +212,6 @@ out:
        return ret;
 }
 
-/**
- * Deallocate an asymmetric key structure.
- *
- * \param key Pointer to the key structure to free.
- *
- * This must be called for any key obtained by get_asymmetric_key().
- */
 void free_asymmetric_key(struct asymmetric_key *key)
 {
        if (!key)
@@ -467,20 +220,6 @@ void free_asymmetric_key(struct asymmetric_key *key)
        free(key);
 }
 
-/**
- * Decrypt a buffer using a private key.
- *
- * \param key_file Full path of the key.
- * \param outbuf The output buffer.
- * \param inbuf The encrypted input buffer.
- * \param inlen The length of \a inbuf in bytes.
- *
- * The \a outbuf must be large enough to hold at least \a rsa_inlen bytes.
- *
- * \return The size of the recovered plaintext on success, negative on errors.
- *
- * \sa RSA_private_decrypt(3)
- **/
 int priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen)
 {
@@ -509,18 +248,6 @@ out:
        return ret;
 }
 
-/**
- * Encrypt a buffer using an RSA key
- *
- * \param pub: The public key.
- * \param inbuf The input buffer.
- * \param len The length of \a inbuf.
- * \param outbuf The output buffer.
- *
- * \return The size of the encrypted data on success, negative on errors.
- *
- * \sa RSA_public_encrypt(3)
- */
 int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
                unsigned len, unsigned char *outbuf)
 {
@@ -537,14 +264,6 @@ struct stream_cipher {
        RC4_KEY key;
 };
 
-/**
- * Allocate and initialize a stream cipher structure.
- *
- * \param data The key.
- * \param len The size of the key.
- *
- * \return A new stream cipher structure.
- */
 struct stream_cipher *sc_new(const unsigned char *data, int len)
 {
        struct stream_cipher *sc = para_malloc(sizeof(*sc));
@@ -552,11 +271,6 @@ struct stream_cipher *sc_new(const unsigned char *data, int len)
        return sc;
 }
 
-/**
- * Deallocate a stream cipher structure.
- *
- * \param sc A stream cipher previously obtained by sc_new().
- */
 void sc_free(struct stream_cipher *sc)
 {
        free(sc);
@@ -569,18 +283,7 @@ void sc_free(struct stream_cipher *sc)
  */
 #define RC4_ALIGN 8
 
-/**
- * Encrypt and send a buffer.
- *
- * \param scc The context.
- * \param buf The buffer to send.
- * \param len The size of \a buf in bytes.
- *
- * \return The return value of the underyling call to write_all().
- *
- * \sa \ref write_all(), RC4(3).
- */
-int sc_send_bin_buffer(struct stream_cipher_context *scc, const char *buf,
+int sc_send_bin_buffer(struct stream_cipher_context *scc, char *buf,
                size_t len)
 {
        int ret;
@@ -600,51 +303,6 @@ int sc_send_bin_buffer(struct stream_cipher_context *scc, const char *buf,
        return ret;
 }
 
-/**
- * Encrypt and send a \p NULL-terminated buffer.
- *
- * \param scc The context.
- * \param buf The buffer to send.
- *
- * \return The return value of the underyling call to sc_send_bin_buffer().
- */
-int sc_send_buffer(struct stream_cipher_context *scc, const char *buf)
-{
-       return sc_send_bin_buffer(scc, buf, strlen(buf));
-}
-
-/**
- * Format, encrypt and send a buffer.
- *
- * \param scc The context.
- * \param fmt A format string.
- *
- * \return The return value of the underyling call to sc_send_buffer().
- */
-__printf_2_3 int sc_send_va_buffer(struct stream_cipher_context *scc,
-               const char *fmt, ...)
-{
-       char *msg;
-       int ret;
-
-       PARA_VSPRINTF(fmt, msg);
-       ret = sc_send_buffer(scc, msg);
-       free(msg);
-       return ret;
-}
-
-/**
- * Receive a buffer and decrypt it.
- *
- * \param scc The context.
- * \param buf The buffer to write the decrypted data to.
- * \param size The size of \a buf.
- *
- * \return The number of bytes received on success, negative on errors, zero if
- * the peer has performed an orderly shutdown.
- *
- * \sa recv(2), RC4(3).
- */
 int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
                size_t size)
 {
@@ -659,43 +317,6 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
        return ret;
 }
 
-/**
- * Receive a buffer, decrypt it and write terminating NULL byte.
- *
- * \param scc The context.
- * \param buf The buffer to write the decrypted data to.
- * \param size The size of \a buf.
- *
- * Read at most \a size - 1 bytes from file descriptor given by \a scc, decrypt
- * the received data and write a NULL byte at the end of the decrypted data.
- *
- * \return The return value of the underlying call to \ref
- * sc_recv_bin_buffer().
- */
-int sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size)
-{
-       int n;
-
-       assert(size);
-       n = sc_recv_bin_buffer(scc, buf, size - 1);
-       if (n >= 0)
-               buf[n] = '\0';
-       else
-               *buf = '\0';
-       return n;
-}
-
-/**
- * Compute the hash of the given input data.
- *
- * \param data Pointer to the data to compute the hash value from.
- * \param len The length of \a data in bytes.
- * \param hash Result pointer.
- *
- * \a hash must point to an area at least \p HASH_SIZE bytes large.
- *
- * \sa sha(3), openssl(1).
- * */
 void hash_function(const char *data, unsigned long len, unsigned char *hash)
 {
        SHA_CTX c;
diff --git a/crypt.h b/crypt.h
index 21abe41f68cd8c5b335cc133b5c6f3050869c4ee..4696ee4a118c5791cfa40ae5cef2c4eb634807ef 100644 (file)
--- a/crypt.h
+++ b/crypt.h
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
-/** \file crypt.h Prototypes for paraslash crypto functions. */
+/** \file crypt.h Public crypto interface. */
+
+
+/** \cond used to distinguish between loading of private/public key */
+#define LOAD_PUBLIC_KEY 0
+#define LOAD_PRIVATE_KEY 1
+#define CHALLENGE_SIZE 64
+/** \endcond **/
+
+/* asymetric (public key) crypto */
 
 /** Opaque structure for public and private keys. */
 struct asymmetric_key;
 
+/**
+ * Encrypt a buffer using asymmetric keys.
+ *
+ * \param pub: The public key.
+ * \param inbuf The input buffer.
+ * \param len The length of \a inbuf.
+ * \param outbuf The output buffer.
+ *
+ * \return The size of the encrypted data on success, negative on errors.
+ */
 int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
                unsigned len, unsigned char *outbuf);
+
+/**
+ * Decrypt a buffer using a private key.
+ *
+ * \param key_file Full path of the key.
+ * \param outbuf The output buffer.
+ * \param inbuf The encrypted input buffer.
+ * \param inlen The length of \a inbuf.
+ *
+ * The \a outbuf must be large enough to hold at least 512 bytes.
+ *
+ * \return The size of the recovered plaintext on success, negative on errors.
+ */
 int priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen);
+
+/**
+ * Read an asymmetric key from a file.
+ *
+ * \param key_file The file containing the key.
+ * \param private if non-zero, read the private key, otherwise the public key.
+ * \param result The key structure is returned here.
+ *
+ * \return The size of the key on success, negative on errors.
+ */
 int get_asymmetric_key(const char *key_file, int private,
                struct asymmetric_key **result);
+
+/**
+ * Deallocate an asymmetric key structure.
+ *
+ * \param key Pointer to the key structure to free.
+ *
+ * This must be called for any key obtained by get_asymmetric_key().
+ */
 void free_asymmetric_key(struct asymmetric_key *key);
 
+
+/**
+ * Fill a buffer with random content.
+ *
+ * \param buf The buffer to fill.
+ * \param num The size of \a buf in bytes.
+ *
+ * This function puts \a num cryptographically strong pseudo-random bytes into
+ * buf. If it can not guarantee an unpredictable byte sequence (for example
+ * because the PRNG has not been seeded with enough randomness) the function
+ * logs an error message and calls exit().
+ */
 void get_random_bytes_or_die(unsigned char *buf, int num);
+
+/**
+ * Seed pseudo random number generators.
+ *
+ * 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().
+ *
+ * \sa \ref get_random_bytes_or_die(), srandom(3), random(3), \ref
+ * para_random().
+ */
 void init_random_seed_or_die(void);
 
-/** Opaque structure for stream cipher crypto. */
+
+/** Opaque structure for stream ciphers. */
 struct stream_cipher;
 
-/** Number of bytes of the session key. */
+/** Number of bytes of the session key for stream ciphers. */
 #define SESSION_KEY_LEN 32
 
 /**
- * Used on the server-side for client-server communication encryption.
+ * Used for client-server communication encryption.
  *
  * The traffic between (the forked child of) para_server and the remote client
  * process is crypted by a symmetric session key. This structure contains the
@@ -43,50 +117,105 @@ struct stream_cipher_context {
        struct stream_cipher *send;
 };
 
+/**
+ * Allocate and initialize a stream cipher structure.
+ *
+ * \param data The key.
+ * \param len The size of the key.
+ *
+ * \return A new stream cipher structure.
+ */
 struct stream_cipher *sc_new(const unsigned char *data, int len);
+
+/**
+ * Deallocate a stream cipher structure.
+ *
+ * \param sc A stream cipher previously obtained by sc_new().
+ */
 void sc_free(struct stream_cipher *sc);
 
-int sc_send_bin_buffer(struct stream_cipher_context *scc, const char *buf,
+/**
+ * Encrypt and send a buffer.
+ *
+ * \param scc The context.
+ * \param buf The buffer to send.
+ * \param len The size of \a buf in bytes.
+ *
+ * \return The return value of the underyling call to write_all().
+ *
+ * \sa \ref write_all(), RC4(3).
+ */
+int sc_send_bin_buffer(struct stream_cipher_context *scc, char *buf,
                size_t len);
-int sc_send_buffer(struct stream_cipher_context *scc, const char *buf);
+
+/**
+ * Encrypt and send a \p NULL-terminated buffer.
+ *
+ * \param scc The context.
+ * \param buf The buffer to send.
+ *
+ * \return The return value of the underyling call to sc_send_bin_buffer().
+ */
+int sc_send_buffer(struct stream_cipher_context *scc, char *buf);
+
+/**
+ * Format, encrypt and send a buffer.
+ *
+ * \param scc The context.
+ * \param fmt A format string.
+ *
+ * \return The return value of the underyling call to sc_send_buffer().
+ */
 __printf_2_3 int sc_send_va_buffer(struct stream_cipher_context *scc,
                const char *fmt, ...);
+
+/**
+ * Receive a buffer and decrypt it.
+ *
+ * \param scc The context.
+ * \param buf The buffer to write the decrypted data to.
+ * \param size The size of \a buf.
+ *
+ * \return The number of bytes received on success, negative on errors, zero if
+ * the peer has performed an orderly shutdown.
+ *
+ * \sa recv(2), RC4(3).
+ */
 int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
                size_t size);
+
+/**
+ * Receive a buffer, decrypt it and write terminating NULL byte.
+ *
+ * \param scc The context.
+ * \param buf The buffer to write the decrypted data to.
+ * \param size The size of \a buf.
+ *
+ * Read at most \a size - 1 bytes from file descriptor given by \a scc, decrypt
+ * the received data and write a NULL byte at the end of the decrypted data.
+ *
+ * \return The return value of the underlying call to \ref
+ * sc_recv_bin_buffer().
+ */
 int sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size);
 
-/** \cond used to distinguish between loading of private/public key */
-#define LOAD_PUBLIC_KEY 0
-#define LOAD_PRIVATE_KEY 1
-#define CHALLENGE_SIZE 64
-/** \endcond **/
 
 /** Size of the hash value in bytes. */
 #define HASH_SIZE 20
 
-void hash_function(const char *data, unsigned long len, unsigned char *hash);
 
 /**
- * Compare two hashes.
+ * Compute the hash of the given input data.
  *
- * \param h1 Pointer to the first hash value.
- * \param h2 Pointer to the second hash value.
+ * \param data Pointer to the data to compute the hash value from.
+ * \param len The length of \a data in bytes.
+ * \param hash Result pointer.
  *
- * \return 1, -1, or zero, depending on whether \a h1 is greater than,
- * less than or equal to h2, respectively.
- */
-_static_inline_ int hash_compare(unsigned char *h1, unsigned char *h2)
-{
-       int i;
-
-       for (i = 0; i < HASH_SIZE; i++) {
-               if (h1[i] < h2[i])
-                       return -1;
-               if (h1[i] > h2[i])
-                       return 1;
-       }
-       return 0;
-}
+ * \a hash must point to an area at least \p HASH_SIZE bytes large.
+ *
+ * \sa sha(3), openssl(1).
+ * */
+void hash_function(const char *data, unsigned long len, unsigned char *hash);
 
 /**
  * Convert a hash value to ascii format.
@@ -98,14 +227,15 @@ _static_inline_ int hash_compare(unsigned char *h1, unsigned char *h2)
  * will be filled by the function with the ascii representation of the hash
  * value given by \a hash, and a terminating \p NULL byte.
  */
-_static_inline_ void hash_to_asc(unsigned char *hash, char *asc)
-{
-       int i;
-       const char hexchar[] = "0123456789abcdef";
-
-       for (i = 0; i < HASH_SIZE; i++) {
-               asc[2 * i] = hexchar[hash[i] >> 4];
-               asc[2 * i + 1] = hexchar[hash[i] & 0xf];
-       }
-       asc[2 * HASH_SIZE] = '\0';
-}
+void hash_to_asc(unsigned char *hash, char *asc);
+
+/**
+ * Compare two hashes.
+ *
+ * \param h1 Pointer to the first hash value.
+ * \param h2 Pointer to the second hash value.
+ *
+ * \return 1, -1, or zero, depending on whether \a h1 is greater than,
+ * less than or equal to h2, respectively.
+ */
+int hash_compare(unsigned char *h1, unsigned char *h2);
diff --git a/crypt_backend.h b/crypt_backend.h
new file mode 100644 (file)
index 0000000..3ea32c2
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file crypt_backend.h Non-public crypto interface. */
+
+/* This should only be incuded from files which provide crypto functions. */
+
+size_t is_ssh_rsa_key(char *data, size_t size);
+uint32_t read_ssh_u32(const void *vp);
+int uudecode(const char *src, unsigned char *target, size_t targsize);
+int check_ssh_key_header(const unsigned char *blob, int blen);
+int check_key_file(const char *file, bool private_key);
+int base64_decode(char const *src, unsigned char *target, size_t targsize);
diff --git a/crypt_common.c b/crypt_common.c
new file mode 100644 (file)
index 0000000..8fac0dc
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2005-2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file crypt_common.c Crypto functions independent of openssl/libgcrypt. */
+
+#include <regex.h>
+#include <stdbool.h>
+
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "crypt_backend.h"
+#include "crypt.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.
+ *
+ * \return Number of header bytes to be skipped on success, zero if
+ * ssh rsa signature was not found.
+ */
+size_t is_ssh_rsa_key(char *data, size_t size)
+{
+       char *cp;
+
+       if (size < strlen(KEY_TYPE_TXT) + 2)
+               return 0;
+       cp = memchr(data, ' ', size);
+       if (cp == NULL)
+               return 0;
+       if (strncmp(KEY_TYPE_TXT, data, strlen(KEY_TYPE_TXT)))
+               return 0;
+       cp++;
+       if (cp >= data + size)
+               return 0;
+       if (*cp == '\0')
+               return 0;
+       return cp - data;
+}
+
+/*
+ * This base64/uudecode stuff below is taken from openssh-5.2p1, Copyright (c)
+ * 1996 by Internet Software Consortium.  Portions Copyright (c) 1995 by
+ * International Business Machines, Inc.
+ */
+
+static const char Base64[] =
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char Pad64 = '=';
+
+/**
+ * base64-decode a buffer.
+ *
+ * \param src The buffer to decode.
+ * \param target Result is stored here.
+ * \param targsize Number of bytes of \a target.
+ *
+ * Skips all whitespace anywhere. Converts characters, four at a time, starting
+ * at (or after) src from base - 64 numbers into three 8 bit bytes in the
+ * target area.
+ *
+ * \return The number of data bytes stored at the target, -E_BASE64 on errors.
+ */
+int base64_decode(char const *src, unsigned char *target, size_t targsize)
+{
+       unsigned int tarindex, state;
+       int ch;
+       char *pos;
+
+       state = 0;
+       tarindex = 0;
+
+       while ((ch = *src++) != '\0') {
+               if (para_isspace(ch)) /* Skip whitespace anywhere. */
+                       continue;
+
+               if (ch == Pad64)
+                       break;
+
+               pos = strchr(Base64, ch);
+               if (pos == 0) /* A non-base64 character. */
+                       return -E_BASE64;
+
+               switch (state) {
+               case 0:
+                       if (target) {
+                               if (tarindex >= targsize)
+                                       return -E_BASE64;
+                               target[tarindex] = (pos - Base64) << 2;
+                       }
+                       state = 1;
+                       break;
+               case 1:
+                       if (target) {
+                               if (tarindex + 1 >= targsize)
+                                       return -E_BASE64;
+                               target[tarindex]   |=  (pos - Base64) >> 4;
+                               target[tarindex+1]  = ((pos - Base64) & 0x0f)
+                                                       << 4 ;
+                       }
+                       tarindex++;
+                       state = 2;
+                       break;
+               case 2:
+                       if (target) {
+                               if (tarindex + 1 >= targsize)
+                                       return -E_BASE64;
+                               target[tarindex]   |=  (pos - Base64) >> 2;
+                               target[tarindex+1]  = ((pos - Base64) & 0x03)
+                                                       << 6;
+                       }
+                       tarindex++;
+                       state = 3;
+                       break;
+               case 3:
+                       if (target) {
+                               if (tarindex >= targsize)
+                                       return -E_BASE64;
+                               target[tarindex] |= (pos - Base64);
+                       }
+                       tarindex++;
+                       state = 0;
+                       break;
+               }
+       }
+
+       /*
+        * We are done decoding Base-64 chars.  Let's see if we ended
+        * on a byte boundary, and/or with erroneous trailing characters.
+        */
+
+       if (ch == Pad64) {              /* We got a pad char. */
+               ch = *src++;            /* Skip it, get next. */
+               switch (state) {
+               case 0:         /* Invalid = in first position */
+               case 1:         /* Invalid = in second position */
+                       return -E_BASE64;
+
+               case 2:         /* Valid, means one byte of info */
+                       /* Skip any number of spaces. */
+                       for (; ch != '\0'; ch = *src++)
+                               if (!isspace(ch))
+                                       break;
+                       /* Make sure there is another trailing = sign. */
+                       if (ch != Pad64)
+                               return -E_BASE64;
+                       ch = *src++;            /* Skip the = */
+                       /* Fall through to "single trailing =" case. */
+                       /* FALLTHROUGH */
+
+               case 3:         /* Valid, means two bytes of info */
+                       /*
+                        * We know this char is an =.  Is there anything but
+                        * whitespace after it?
+                        */
+                       for (; ch != '\0'; ch = *src++)
+                               if (!isspace(ch))
+                                       return -E_BASE64;
+
+                       /*
+                        * Now make sure for cases 2 and 3 that the "extra"
+                        * bits that slopped past the last full byte were
+                        * zeros.  If we don't check them, they become a
+                        * subliminal channel.
+                        */
+                       if (target && target[tarindex] != 0)
+                               return -E_BASE64;
+               }
+       } else {
+               /*
+                * We ended by seeing the end of the string.  Make sure we
+                * have no partial bytes lying around.
+                */
+               if (state != 0)
+                       return -E_BASE64;
+       }
+
+       return tarindex;
+}
+
+/**
+ * uudecode a buffer.
+ *
+ * \param src The buffer to decode.
+ * \param target Result buffer.
+ * \param targsize The length of \a target in bytes.
+ *
+ * This is just a simple wrapper for base64_decode() which strips whitespace.
+ *
+ * \return The return value of the underlying call to base64_decode().
+ */
+int uudecode(const char *src, unsigned char *target, size_t targsize)
+{
+       int len;
+       char *encoded, *p;
+
+       /* copy the 'readonly' source */
+       encoded = para_strdup(src);
+       /* skip whitespace and data */
+       for (p = encoded; *p == ' ' || *p == '\t'; p++)
+               ;
+       for (; *p != '\0' && *p != ' ' && *p != '\t'; p++)
+               ;
+       /* and remove trailing whitespace because base64_decode needs this */
+       *p = '\0';
+       len = base64_decode(encoded, target, targsize);
+       free(encoded);
+       return len;
+}
+
+/**
+ * Read a 4-byte number from a buffer in big-endian format.
+ *
+ * \param vp The buffer.
+ *
+ * The byte-order of the buffer is expected to be big-endian, unlike read_u32()
+ * of portable_io.h which expects little endian.
+ *
+ * \return The 32 bit number given by \a vp.
+ */
+uint32_t read_ssh_u32(const void *vp)
+{
+       const unsigned char *p = (const unsigned char *)vp;
+       uint32_t v;
+
+       v  = (uint32_t)p[0] << 24;
+       v |= (uint32_t)p[1] << 16;
+       v |= (uint32_t)p[2] << 8;
+       v |= (uint32_t)p[3];
+
+       return v;
+}
+
+/**
+ * Sanity checks for the header of an ssh key.
+ *
+ * \param blob The buffer.
+ * \param blen The number of bytes of \a blob.
+ *
+ * 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).
+ */
+int check_ssh_key_header(const unsigned char *blob, int blen)
+{
+       const unsigned char *p = blob, *end = blob + blen;
+       uint32_t rlen;
+
+       if (p + 4 > end)
+               return -E_SSH_KEY_HEADER;
+       rlen = read_ssh_u32(p);
+       p += 4;
+       if (p + rlen < p)
+               return -E_SSH_KEY_HEADER;
+       if (p + rlen > end)
+               return -E_SSH_KEY_HEADER;
+       if (rlen < strlen(KEY_TYPE_TXT))
+               return -E_SSH_KEY_HEADER;
+       PARA_DEBUG_LOG("type: %s, rlen: %d\n", p, rlen);
+       if (strncmp((char *)p, KEY_TYPE_TXT, strlen(KEY_TYPE_TXT)))
+               return -E_SSH_KEY_HEADER;
+       return 4 + rlen;
+}
+
+/**
+ * Check existence and permissions of a key file.
+ *
+ * \param file The path of the key file.
+ * \param private_key Whether this is a private key.
+ *
+ * This checks whether the file exists. If it is a private key, we additionally
+ * check that the permissions are restrictive enough. It is considered an error
+ * if we own the file and it is readable for others.
+ *
+ * \return Standard.
+ */
+int check_key_file(const char *file, bool private_key)
+{
+       struct stat st;
+
+       if (stat(file, &st) != 0)
+               return -ERRNO_TO_PARA_ERROR(errno);
+       if (!private_key)
+               return 0;
+       if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0)
+               return -E_KEY_PERM;
+       return 1;
+}
+
+void hash_to_asc(unsigned char *hash, char *asc)
+{
+       int i;
+       const char hexchar[] = "0123456789abcdef";
+
+       for (i = 0; i < HASH_SIZE; i++) {
+               asc[2 * i] = hexchar[hash[i] >> 4];
+               asc[2 * i + 1] = hexchar[hash[i] & 0xf];
+       }
+       asc[2 * HASH_SIZE] = '\0';
+}
+
+int hash_compare(unsigned char *h1, unsigned char *h2)
+{
+       int i;
+
+       for (i = 0; i < HASH_SIZE; i++) {
+               if (h1[i] < h2[i])
+                       return -1;
+               if (h1[i] > h2[i])
+                       return 1;
+       }
+       return 0;
+}
+
+int sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size)
+{
+       int n;
+
+       assert(size);
+       n = sc_recv_bin_buffer(scc, buf, size - 1);
+       if (n >= 0)
+               buf[n] = '\0';
+       else
+               *buf = '\0';
+       return n;
+}
+
+int sc_send_buffer(struct stream_cipher_context *scc, char *buf)
+{
+       return sc_send_bin_buffer(scc, buf, strlen(buf));
+}
+
+__printf_2_3 int sc_send_va_buffer(struct stream_cipher_context *scc,
+               const char *fmt, ...)
+{
+       char *msg;
+       int ret;
+
+       PARA_VSPRINTF(fmt, msg);
+       ret = sc_send_buffer(scc, msg);
+       free(msg);
+       return ret;
+}
diff --git a/error.h b/error.h
index 3f5ff73be3b1b5b8314b836c794b7a62b8740e91..b8078770e5368c3840e0c6c91e3ff6267a7f7590 100644 (file)
--- a/error.h
+++ b/error.h
@@ -368,6 +368,12 @@ extern const char **para_errlist[];
        PARA_ERROR(AFS_SHORT_READ, "short read from afs socket"), \
 
 
+#define CRYPT_COMMON_ERRORS \
+       PARA_ERROR(SSH_KEY_HEADER, "ssh key header not found"), \
+       PARA_ERROR(BASE64, "failed to base64-decode ssh public key"), \
+       PARA_ERROR(KEY_PERM, "unprotected private key"), \
+
+
 #define CRYPT_ERRORS \
        PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
        PARA_ERROR(PUBLIC_KEY, "can not read public key"), \
@@ -375,10 +381,23 @@ extern const char **para_errlist[];
        PARA_ERROR(ENCRYPT, "encrypt error"), \
        PARA_ERROR(DECRYPT, "decrypt error"), \
        PARA_ERROR(BLINDING, "failed to activate key blinding"), \
-       PARA_ERROR(KEY_PERM, "unprotected private key"), \
-       PARA_ERROR(BASE64, "failed to base64-decode ssh private key"), \
        PARA_ERROR(BIGNUM, "bignum error"), \
 
+#define GCRYPT_ERRORS \
+       PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \
+       PARA_ERROR(MPI_PRINT, "could not convert multi-precision integer"), \
+       PARA_ERROR(SEXP_FIND, "could not find sublist in S-expression"), \
+       PARA_ERROR(SEXP_BUILD, "could not build S-expression"), \
+       PARA_ERROR(SEXP_ENCRYPT, "could not encrypt S-expression"), \
+       PARA_ERROR(SEXP_DECRYPT, "could not decrypt S-expression"), \
+       PARA_ERROR(MD_OPEN, "could not open message digest object"), \
+       PARA_ERROR(CIPHER_OPEN, "could not create stream cipher handle"), \
+       PARA_ERROR(BAD_PRIVATE_KEY, "invalid private key"), \
+       PARA_ERROR(KEY_MARKER, "invalid/missing key header or footer"), \
+       PARA_ERROR(ASN1_PARSE, "could not parse ASN.1 key"), \
+       PARA_ERROR(SSH_PARSE, "could not parse ssh public key"), \
+       PARA_ERROR(OEAP, "error during oeap (un)padding"), \
+
 
 #define COMMAND_ERRORS \
        PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \
diff --git a/gcrypt.c b/gcrypt.c
new file mode 100644 (file)
index 0000000..b40b7b6
--- /dev/null
+++ b/gcrypt.c
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file gcrypt.c Libgrcypt-based encryption/decryption routines. */
+
+#include <regex.h>
+#include <stdbool.h>
+#include <gcrypt.h>
+
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "crypt.h"
+#include "crypt_backend.h"
+#include "fd.h"
+
+//#define GCRYPT_DEBUG 1
+
+static bool libgcrypt_has_oaep;
+static const char *rsa_decrypt_sexp;
+
+#ifdef GCRYPT_DEBUG
+static void dump_buffer(const char *msg, unsigned char *buf, int len)
+{
+       int i;
+
+       fprintf(stderr, "%s (%u bytes): ", msg, len);
+       for (i = 0; i < len; i++)
+               fprintf(stderr, "%02x ", buf[i]);
+       fprintf(stderr, "\n");
+}
+#else
+/** Empty. Define GCRYPT_DEBUG to dump buffers. */
+#define dump_buffer(a, b, c)
+#endif
+
+void hash_function(const char *data, unsigned long len, unsigned char *hash)
+{
+       gcry_error_t gret;
+       gcry_md_hd_t handle;
+       unsigned char *md;
+
+       gret = gcry_md_open(&handle, GCRY_MD_SHA1, 0);
+       assert(gret == 0);
+       gcry_md_write(handle, data, (size_t)len);
+       gcry_md_final(handle);
+       md = gcry_md_read(handle, GCRY_MD_SHA1);
+       assert(md);
+       memcpy(hash, md, HASH_SIZE);
+       gcry_md_close(handle);
+}
+
+void get_random_bytes_or_die(unsigned char *buf, int num)
+{
+       gcry_randomize(buf, (size_t)num, GCRY_STRONG_RANDOM);
+}
+
+/*
+ * This is called at the beginning of every program that uses libgcrypt. We
+ * don't have to initialize any random seed here, but we must initialize the
+ * gcrypt library. This task is performed by gcry_check_version() which can
+ * also check that the gcrypt library version is at least the minimal required
+ * version. This function also tells us whether we have to use our own OAEP
+ * padding code.
+ */
+void init_random_seed_or_die(void)
+{
+       const char *ver, *req_ver;
+
+       ver = gcry_check_version(NULL);
+       req_ver = "1.4.0";
+       if (!gcry_check_version(req_ver)) {
+               PARA_EMERG_LOG("fatal: need at least libgcrypt-%s, have: %s\n",
+                       req_ver, ver);
+               exit(EXIT_FAILURE);
+       }
+       req_ver = "1.5.0";
+       if (gcry_check_version(req_ver)) {
+               libgcrypt_has_oaep = true;
+               rsa_decrypt_sexp = "(enc-val(flags oaep)(rsa(a %m)))";
+       } else {
+               libgcrypt_has_oaep = false;
+               rsa_decrypt_sexp = "(enc-val(rsa(a %m)))";
+       }
+}
+
+/** 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. */
+#define RSA_PRIVKEY_SEXP "(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))"
+
+/* rfc 3447, appendix B.2 */
+static void mgf1(unsigned char *seed, size_t seed_len, unsigned result_len,
+               unsigned char *result)
+{
+       gcry_error_t gret;
+       gcry_md_hd_t handle;
+       size_t n;;
+       unsigned char *md;
+       unsigned char octet_string[4], *rp = result, *end = rp + result_len;
+
+       assert(result_len / HASH_SIZE < 1ULL << 31);
+       gret = gcry_md_open(&handle, GCRY_MD_SHA1, 0);
+       assert(gret == 0);
+       for (n = 0; rp < end; n++) {
+               gcry_md_write(handle, seed, seed_len);
+               octet_string[0] = (unsigned char)((n >> 24) & 255);
+               octet_string[1] = (unsigned char)((n >> 16) & 255);
+               octet_string[2] = (unsigned char)((n >> 8)) & 255;
+               octet_string[3] = (unsigned char)(n & 255);
+               gcry_md_write(handle, octet_string, 4);
+               gcry_md_final(handle);
+               md = gcry_md_read(handle, GCRY_MD_SHA1);
+               memcpy(rp, md, PARA_MIN(HASH_SIZE, (int)(end - rp)));
+               rp += HASH_SIZE;
+               gcry_md_reset(handle);
+       }
+       gcry_md_close(handle);
+}
+
+/** The sha1 hash of an empty file. */
+static const unsigned char empty_hash[HASH_SIZE] =
+       "\xda" "\x39" "\xa3" "\xee" "\x5e"
+       "\x6b" "\x4b" "\x0d" "\x32" "\x55"
+       "\xbf" "\xef" "\x95" "\x60" "\x18"
+       "\x90" "\xaf" "\xd8" "\x07" "\x09";
+
+/* rfc3447, section 7.1.1 */
+static void pad_oaep(unsigned char *in, size_t in_len, unsigned char *out,
+               size_t out_len)
+{
+       size_t ps_len = out_len - in_len - 2 * HASH_SIZE - 2;
+       size_t n, mask_len = out_len - HASH_SIZE - 1;
+       unsigned char *seed = out + 1, *db = seed + HASH_SIZE,
+               *ps = db + HASH_SIZE, *one = ps + ps_len;
+       unsigned char *db_mask, seed_mask[HASH_SIZE];
+
+       assert(in_len <= out_len - 2 - 2 * HASH_SIZE);
+       assert(out_len > 2 * HASH_SIZE + 2);
+       PARA_DEBUG_LOG("padding %zu byte input -> %zu byte output\n",
+               in_len, out_len);
+       dump_buffer("unpadded buffer", in, in_len);
+
+       out[0] = '\0';
+       get_random_bytes_or_die(seed, HASH_SIZE);
+       memcpy(db, empty_hash, HASH_SIZE);
+       memset(ps, 0, ps_len);
+       *one = 0x01;
+       memcpy(one + 1, in, in_len);
+       db_mask = para_malloc(mask_len);
+       mgf1(seed, HASH_SIZE, mask_len, db_mask);
+       for (n = 0; n < mask_len; n++)
+               db[n] ^= db_mask[n];
+       mgf1(db, mask_len, HASH_SIZE, seed_mask);
+       for (n = 0; n < HASH_SIZE; n++)
+               seed[n] ^= seed_mask[n];
+       free(db_mask);
+       dump_buffer("padded buffer", out, out_len);
+}
+
+/* rfc 3447, section 7.1.2 */
+static int unpad_oaep(unsigned char *in, size_t in_len, unsigned char *out,
+               size_t *out_len)
+{      int ret;
+       unsigned char *masked_seed = in + 1;
+       unsigned char *db = in + 1 + HASH_SIZE;
+       unsigned char seed[HASH_SIZE], seed_mask[HASH_SIZE];
+       unsigned char *db_mask, *p;
+       size_t n, mask_len = in_len - HASH_SIZE - 1;
+
+       mgf1(db, mask_len, HASH_SIZE, seed_mask);
+       for (n = 0; n < HASH_SIZE; n++)
+               seed[n] = masked_seed[n] ^ seed_mask[n];
+       db_mask = para_malloc(mask_len);
+       mgf1(seed, HASH_SIZE, mask_len, db_mask);
+       for (n = 0; n < mask_len; n++)
+               db[n] ^= db_mask[n];
+       free(db_mask);
+       if (memcmp(db, empty_hash, HASH_SIZE))
+               return -E_OEAP;
+       for (p = db + HASH_SIZE; p < in + in_len - 1; p++)
+               if (*p != '\0')
+                       break;
+       if (p >= in + in_len - 1)
+               return -E_OEAP;
+       p++;
+       *out_len = in + in_len - p;
+       memcpy(out, p, *out_len);
+       return ret;
+}
+
+struct asymmetric_key {
+       gcry_sexp_t sexp;
+       int num_bytes;
+};
+
+static const char *gcrypt_strerror(gcry_error_t gret)
+{
+       return gcry_strerror(gcry_err_code(gret));
+}
+
+static int decode_key(const char *key_file, const char *header_str,
+               const char *footer_str, unsigned char **result)
+{
+       int ret, ret2, i, j;
+       void *map;
+       size_t map_size, key_size, blob_size;
+       unsigned char *blob = NULL;
+       char *begin, *footer, *key;
+
+       ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
+       if (ret < 0)
+               return ret;
+       ret = -E_KEY_MARKER;
+       if (strncmp(map, header_str, strlen(header_str)))
+               goto unmap;
+       footer = strstr(map, footer_str);
+       ret = -E_KEY_MARKER;
+       if (!footer)
+               goto unmap;
+       begin = map + strlen(header_str);
+       /* skip whitespace at the beginning */
+       for (; begin < footer; begin++) {
+               if (para_isspace(*begin))
+                       continue;
+               break;
+       }
+       ret = -E_KEY_MARKER;
+       if (begin >= footer)
+               goto unmap;
+
+       key_size = footer - begin;
+       key = para_malloc(key_size + 1);
+       for (i = 0, j = 0; begin + i < footer; i++) {
+               if (para_isspace(begin[i]))
+                       continue;
+               key[j++] = begin[i];
+       }
+       key[j] = '\0';
+       //PARA_CRIT_LOG("key: %s\n", key);
+       blob_size = key_size * 2;
+       blob = para_malloc(blob_size);
+       ret = base64_decode(key, blob, blob_size);
+       free(key);
+       if (ret < 0)
+               goto free_unmap;
+       goto unmap;
+free_unmap:
+       free(blob);
+       blob = NULL;
+unmap:
+       ret2 = para_munmap(map, map_size);
+       if (ret >= 0 && ret2 < 0)
+               ret = ret2;
+       if (ret < 0) {
+               free(blob);
+               blob = NULL;
+       }
+       *result = blob;
+       return ret;
+}
+
+/** ASN Types and their code. */
+enum asn1_types {
+       /** The next object is an integer. */
+       ASN1_TYPE_INTEGER = 0x2,
+       /** Bit string object. */
+       ASN1_TYPE_BIT_STRING = 0x03,
+       /** Keys start with one big type sequence. */
+       ASN1_TYPE_SEQUENCE = 0x30,
+};
+
+/* bit 6 has value 0 */
+static inline bool is_primitive(unsigned char c)
+{
+       return ((c & (1<<6)) == 0);
+}
+
+static inline bool is_primitive_integer(unsigned char c)
+{
+       if (!is_primitive(c))
+               return false;
+       return ((c & 0x1f) == ASN1_TYPE_INTEGER);
+}
+
+/* Bit 8 is zero (and bits 7-1 give the length) */
+static inline bool is_short_form(unsigned char c)
+{
+       return (c & 0x80) == 0;
+}
+
+static inline int get_short_form_length(unsigned char c)
+{
+       return c & 0x7f;
+}
+
+static inline int get_long_form_num_length_bytes(unsigned char c)
+{
+       return c & 0x7f;
+}
+
+static int find_pubkey_bignum_offset(const unsigned char *data, int len)
+{
+       const unsigned char *p = data, *end = data + len;
+
+       /* the whole thing istarts with one sequence */
+       if (*p != ASN1_TYPE_SEQUENCE)
+               return -E_ASN1_PARSE;
+       p++;
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       if (is_short_form(*p))
+               p++;
+       else
+               p += 1 + get_long_form_num_length_bytes(*p);
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       /* another sequence containing the object id, skip it */
+       if (*p != ASN1_TYPE_SEQUENCE)
+               return -E_ASN1_PARSE;
+       p++;
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       if (!is_short_form(*p))
+               return -E_ASN1_PARSE;
+       p += 1 + get_short_form_length(*p);
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       /* all numbers are wrapped in a bit string object that follows */
+       if (*p != ASN1_TYPE_BIT_STRING)
+               return -E_ASN1_PARSE;
+       p++;
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       if (is_short_form(*p))
+               p++;
+       else
+               p += 1 + get_long_form_num_length_bytes(*p);
+       p++; /* skip number of unused bits in the bit string */
+       if (p >= end)
+               return -E_ASN1_PARSE;
+
+       /* next, we have a sequence of two integers (n and e) */
+       if (*p != ASN1_TYPE_SEQUENCE)
+               return -E_ASN1_PARSE;
+       p++;
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       if (is_short_form(*p))
+               p++;
+       else
+               p += 1 + get_long_form_num_length_bytes(*p);
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       if (*p != ASN1_TYPE_INTEGER)
+               return -E_ASN1_PARSE;
+       return p - data;
+}
+
+/*
+ * Returns: Number of bytes scanned. This may differ from the value returned via
+ * bn_bytes because the latter does not include the ASN.1 prefix and a leading
+ * zero is not considered as an additional byte for bn_bytes.
+ */
+static int read_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
+               int *bn_bytes)
+{
+       int i, bn_size;
+       gcry_error_t gret;
+       unsigned char *cp = start;
+
+       if (!is_primitive_integer(*cp))
+               return -E_BAD_PRIVATE_KEY;
+       cp++;
+       if (is_short_form(*cp)) {
+               bn_size = get_short_form_length(*cp);
+               cp++;
+       } else {
+               int num_bytes = get_long_form_num_length_bytes(*cp);
+               if (cp + num_bytes > end)
+                       return -E_BAD_PRIVATE_KEY;
+               if (num_bytes > 4) /* nobody has such a large modulus */
+                       return -E_BAD_PRIVATE_KEY;
+               cp++;
+               bn_size = 0;
+               for (i = 0; i < num_bytes; i++, cp++)
+                       bn_size = (bn_size << 8) + *cp;
+       }
+       PARA_DEBUG_LOG("bn_size %d (0x%x)\n", bn_size, bn_size);
+       gret = gcry_mpi_scan(bn, GCRYMPI_FMT_STD, cp, bn_size, NULL);
+       if (gret) {
+               PARA_ERROR_LOG("%s while scanning n\n",
+                       gcry_strerror(gcry_err_code(gret)));
+               return-E_MPI_SCAN;
+       }
+       /*
+        * Don't take the first leading zero into account for the size of the
+        * bignum.
+        */
+       if (*cp == '\0') {
+               cp++;
+               bn_size--;
+       }
+       if (bn_bytes)
+               *bn_bytes = bn_size;
+       cp += bn_size;
+//     unsigned char *buf;
+//     gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, *bn);
+//     PARA_CRIT_LOG("bn: %s\n", buf);
+       return cp - start;
+}
+
+static int find_privkey_bignum_offset(const unsigned char *data, int len)
+{
+       const unsigned char *p = data, *end = data + len;
+
+       /* like the public key, the whole thing is contained in a sequence */
+       if (*p != ASN1_TYPE_SEQUENCE)
+               return -E_ASN1_PARSE;
+       p++;
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       if (is_short_form(*p))
+               p++;
+       else
+               p += 1 + get_long_form_num_length_bytes(*p);
+       if (p >= end)
+               return -E_ASN1_PARSE;
+
+       /* Skip next integer  */
+       if (*p != ASN1_TYPE_INTEGER)
+               return -E_ASN1_PARSE;
+       p++;
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       if (is_short_form(*p))
+               p += 1 + get_short_form_length(*p);
+       else
+               p += 1 + get_long_form_num_length_bytes(*p);
+       if (p >= end)
+               return -E_ASN1_PARSE;
+       return p - data;
+}
+
+/** Private keys start with this header. */
+#define PRIVATE_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
+/** Private keys end with this footer. */
+#define PRIVATE_KEY_FOOTER "-----END RSA PRIVATE KEY-----"
+
+static int get_private_key(const char *key_file, struct asymmetric_key **result)
+{
+       gcry_mpi_t n = NULL, e = NULL, d = NULL, p = NULL, q = NULL,
+               u = NULL;
+       unsigned char *blob, *cp, *end;
+       int blob_size, ret, n_size;
+       gcry_error_t gret;
+       size_t erroff;
+       gcry_sexp_t sexp;
+       struct asymmetric_key *key;
+
+       ret = decode_key(key_file, PRIVATE_KEY_HEADER, PRIVATE_KEY_FOOTER,
+               &blob);
+       if (ret < 0)
+               return ret;
+       blob_size = ret;
+       end = blob + blob_size;
+       ret = find_privkey_bignum_offset(blob, blob_size);
+       if (ret < 0)
+               goto free_blob;
+       PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
+       cp = blob + ret;
+
+       ret = read_bignum(cp, end, &n, &n_size);
+       if (ret < 0)
+               goto free_blob;
+       cp += ret;
+
+       ret = read_bignum(cp, end, &e, NULL);
+       if (ret < 0)
+               goto release_n;
+       cp += ret;
+
+       ret = read_bignum(cp, end, &d, NULL);
+       if (ret < 0)
+               goto release_e;
+       cp += ret;
+
+       ret = read_bignum(cp, end, &p, NULL);
+       if (ret < 0)
+               goto release_d;
+       cp += ret;
+
+       ret = read_bignum(cp, end, &q, NULL);
+       if (ret < 0)
+               goto release_p;
+       cp += ret;
+       ret = read_bignum(cp, end, &u, NULL);
+       if (ret < 0)
+               goto release_q;
+       cp += ret;
+       /*
+        * OpenSSL uses slightly different parameters than gcrypt. To use these
+        * parameters we need to swap the values of p and q and recompute u.
+        */
+       if (gcry_mpi_cmp(p, q) > 0) {
+               gcry_mpi_swap(p, q);
+               gcry_mpi_invm(u, p, q);
+       }
+       gret = gcry_sexp_build(&sexp, &erroff, RSA_PRIVKEY_SEXP,
+               n, e, d, p, q, u);
+
+       if (gret) {
+               PARA_ERROR_LOG("offset %zu: %s\n", erroff,
+                       gcry_strerror(gcry_err_code(gret)));
+               ret = -E_SEXP_BUILD;
+               goto release_u;
+       }
+       key = para_malloc(sizeof(*key));
+       key->sexp = sexp;
+       *result = key;
+       ret = n_size * 8;
+       PARA_INFO_LOG("succesfully read %d bit private key\n", ret);
+release_u:
+       gcry_mpi_release(u);
+release_q:
+       gcry_mpi_release(q);
+release_p:
+       gcry_mpi_release(p);
+release_d:
+       gcry_mpi_release(d);
+release_e:
+       gcry_mpi_release(e);
+release_n:
+       gcry_mpi_release(n);
+free_blob:
+       free(blob);
+       return ret;
+}
+
+/** Public keys start with this header. */
+#define PUBLIC_KEY_HEADER "-----BEGIN PUBLIC KEY-----"
+/** Public keys end with this footer. */
+#define PUBLIC_KEY_FOOTER "-----END PUBLIC KEY-----"
+
+static int get_asn_public_key(const char *key_file, struct asymmetric_key **result)
+{
+       gcry_mpi_t n = NULL, e = NULL;
+       unsigned char *blob, *cp, *end;
+       int blob_size, ret, n_size;
+       gcry_error_t gret;
+       size_t erroff;
+       gcry_sexp_t sexp;
+       struct asymmetric_key *key;
+
+       ret = decode_key(key_file, PUBLIC_KEY_HEADER, PUBLIC_KEY_FOOTER,
+               &blob);
+       if (ret < 0)
+               return ret;
+       blob_size = ret;
+       end = blob + blob_size;
+       ret = find_pubkey_bignum_offset(blob, blob_size);
+       if (ret < 0)
+               goto free_blob;
+       PARA_DEBUG_LOG("decoding public RSA params at offset %d\n", ret);
+       cp = blob + ret;
+
+       ret = read_bignum(cp, end, &n, &n_size);
+       if (ret < 0)
+               goto free_blob;
+       cp += ret;
+
+       ret = read_bignum(cp, end, &e, NULL);
+       if (ret < 0)
+               goto release_n;
+       cp += ret;
+
+       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_e;
+       }
+       key = para_malloc(sizeof(*key));
+       key->sexp = sexp;
+       *result = key;
+       ret = n_size;
+       PARA_INFO_LOG("successfully read %u bit asn public key\n", n_size * 8);
+
+release_e:
+       gcry_mpi_release(e);
+release_n:
+       gcry_mpi_release(n);
+free_blob:
+       free(blob);
+       return ret;
+}
+
+static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result)
+{
+       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;
+
+       PARA_DEBUG_LOG("decoding %d byte  public rsa-ssh key\n", size);
+       if (size > INT_MAX / 4)
+               return -ERRNO_TO_PARA_ERROR(EOVERFLOW);
+       blob = para_malloc(2 * size);
+       ret = uudecode((char *)data, blob, 2 * size);
+       if (ret < 0)
+               goto free_blob;
+       decoded_size = ret;
+       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;
+       p = blob + ret;
+       ret = -E_SSH_PARSE;
+       if (p >= end)
+               goto free_blob;
+       PARA_DEBUG_LOG("scanning modulus and public exponent\n");
+       gret = gcry_mpi_scan(&e, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned);
+       if (gret) {
+               ret = -E_MPI_SCAN;
+               PARA_CRIT_LOG("%s\n", gcry_strerror(gcry_err_code(gret)));
+               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;
+       gret = gcry_mpi_scan(&n, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned);
+       if (gret) {
+               ret = -E_MPI_SCAN;
+               PARA_ERROR_LOG("%s\n", gcry_strerror(gcry_err_code(gret)));
+               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);
+       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;
+       PARA_INFO_LOG("successfully read %u bit ssh public key\n", ret * 8);
+release_n:
+       gcry_mpi_release(n);
+release_e:
+       gcry_mpi_release(e);
+free_blob:
+       free(blob);
+       return ret;
+}
+
+int get_asymmetric_key(const char *key_file, int private,
+               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;
+
+       if (private)
+               return get_private_key(key_file, result);
+       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) {
+               ret = para_munmap(map, map_size);
+               if (ret < 0)
+                       return ret;
+               return get_asn_public_key(key_file, result);
+       }
+       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;
+       ret = key->num_bytes;
+unmap:
+       ret2 = para_munmap(map, map_size);
+       if (ret >= 0 && ret2 < 0)
+               ret = ret2;
+       return ret;
+}
+
+void free_asymmetric_key(struct asymmetric_key *key)
+{
+       if (!key)
+               return;
+       gcry_sexp_release(key->sexp);
+       free(key);
+}
+
+static int decode_rsa(gcry_sexp_t sexp, int key_size, unsigned char *outbuf,
+               size_t *nbytes)
+{
+       int ret;
+       gcry_error_t gret;
+       unsigned char oaep_buf[512];
+       gcry_mpi_t out_mpi;
+
+       if (libgcrypt_has_oaep) {
+               const char *p = gcry_sexp_nth_data(sexp, 1, nbytes);
+
+               if (!p) {
+                       PARA_ERROR_LOG("could not get data from list\n");
+                       return -E_OEAP;
+               }
+               memcpy(outbuf, p, *nbytes);
+               return 1;
+       }
+       out_mpi = gcry_sexp_nth_mpi(sexp, 0, GCRYMPI_FMT_USG);
+       if (!out_mpi)
+               return -E_SEXP_FIND;
+       gret = gcry_mpi_print(GCRYMPI_FMT_USG, oaep_buf, sizeof(oaep_buf),
+               nbytes, out_mpi);
+       if (gret) {
+               PARA_ERROR_LOG("mpi_print: %s\n", gcrypt_strerror(gret));
+               ret = -E_MPI_PRINT;
+               goto out_mpi_release;
+       }
+       /*
+        * An oaep-encoded buffer always starts with at least one zero byte.
+        * However, leading zeroes in an mpi are omitted in the output of
+        * gcry_mpi_print() when using the GCRYMPI_FMT_USG format. The
+        * alternative, GCRYMPI_FMT_STD, does not work either because here the
+        * leading zero(es) might also be omitted, depending on the value of
+        * the second byte.
+        *
+        * To circumvent this, we shift the oaep buffer to the right. But first
+        * we check that the buffer actually started with a zero byte, i.e. that
+        * nbytes < key_size. Otherwise a decoding error occurred.
+        */
+       ret = -E_SEXP_DECRYPT;
+       if (*nbytes >= key_size)
+               goto out_mpi_release;
+       memmove(oaep_buf + key_size - *nbytes, oaep_buf, *nbytes);
+       memset(oaep_buf, 0, key_size - *nbytes);
+
+       PARA_DEBUG_LOG("decrypted buffer before unpad (%d bytes):\n",
+               key_size);
+       dump_buffer("non-unpadded decrypted buffer", oaep_buf, key_size);;
+       unpad_oaep(oaep_buf, key_size, outbuf, nbytes);
+       PARA_DEBUG_LOG("decrypted buffer after unpad (%zu bytes):\n",
+               *nbytes);
+       dump_buffer("unpadded decrypted buffer", outbuf, *nbytes);;
+       ret = 1;
+out_mpi_release:
+       gcry_mpi_release(out_mpi);
+       return ret;
+}
+
+int priv_decrypt(const char *key_file, unsigned char *outbuf,
+               unsigned char *inbuf, int inlen)
+{
+       gcry_error_t gret;
+       int ret, key_size;
+       struct asymmetric_key *priv;
+       gcry_mpi_t in_mpi = NULL;
+       gcry_sexp_t in, out, priv_key;
+       size_t nbytes;
+
+       PARA_INFO_LOG("decrypting %d byte input\n", inlen);
+       /* key_file -> asymmetric key priv */
+       ret = get_private_key(key_file, &priv);
+       if (ret < 0)
+               return ret;
+       key_size = ret / 8;
+
+       /* asymmetric key priv -> sexp priv_key */
+       ret = -E_SEXP_FIND;
+       priv_key = gcry_sexp_find_token(priv->sexp, "private-key", 0);
+       if (!priv_key)
+               goto free_key;
+
+       /* inbuf -> in_mpi */
+       gret = gcry_mpi_scan(&in_mpi, GCRYMPI_FMT_USG, inbuf,
+               inlen, NULL);
+       if (gret) {
+               PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
+               ret = -E_MPI_SCAN;
+               goto key_release;
+       }
+       /* in_mpi -> in sexp */
+       gret = gcry_sexp_build(&in, NULL, rsa_decrypt_sexp, in_mpi);
+       if (gret) {
+               PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
+               ret = -E_SEXP_BUILD;
+               goto in_mpi_release;
+       }
+
+       /* rsa decryption: in sexp -> out sexp */
+       gret = gcry_pk_decrypt(&out, in, priv_key);
+       if (gret) {
+               PARA_ERROR_LOG("decrypt: %s\n", gcrypt_strerror(gret));
+               ret = -E_SEXP_DECRYPT;
+               goto in_release;
+       }
+       ret = decode_rsa(out, key_size, outbuf, &nbytes);
+       if (ret < 0)
+               goto out_release;
+       PARA_INFO_LOG("successfully decrypted %zu byte message\n", nbytes);
+       ret = nbytes;
+out_release:
+       gcry_sexp_release(out);
+in_release:
+       gcry_sexp_release(in);
+in_mpi_release:
+       gcry_mpi_release(in_mpi);
+key_release:
+       gcry_sexp_release(priv_key);
+free_key:
+       free_asymmetric_key(priv);
+       return ret;
+}
+
+int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
+               unsigned len, unsigned char *outbuf)
+{
+       gcry_error_t gret;
+       gcry_sexp_t pub_key, in, out, out_a;
+       gcry_mpi_t out_mpi = NULL;
+       size_t nbytes;
+       int ret;
+
+       PARA_INFO_LOG("encrypting %u byte input with %d-byte key\n", len, pub->num_bytes);
+
+       /* get pub key */
+       pub_key = gcry_sexp_find_token(pub->sexp, "public-key", 0);
+       if (!pub_key)
+               return -E_SEXP_FIND;
+       if (libgcrypt_has_oaep) {
+               gret = gcry_sexp_build(&in, NULL,
+                       "(data(flags oaep)(value %b))", len, inbuf);
+       } else {
+               unsigned char padded_input[256];
+               const size_t pad_size = 256;
+               /* inbuf -> padded inbuf */
+               pad_oaep(inbuf, len, padded_input, pad_size);
+               /* padded inbuf -> in sexp */
+               gret = gcry_sexp_build(&in, NULL,
+                       "(data(flags raw)(value %b))", pad_size, padded_input);
+       }
+       if (gret) {
+               PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
+               ret = -E_SEXP_BUILD;
+               goto key_release;
+       }
+       /* rsa sexp encryption: in -> out */
+       gret = gcry_pk_encrypt(&out, in, pub_key);
+       if (gret) {
+               PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
+               ret = -E_SEXP_ENCRYPT;
+               goto in_release;
+       }
+       /* extract a, an MPI with the result of the RSA operation */
+       ret = -E_SEXP_FIND;
+       out_a = gcry_sexp_find_token(out, "a", 0);
+       if (!out_a)
+               goto out_release;
+       /* convert sexp out_a -> out_mpi */
+       out_mpi = gcry_sexp_nth_mpi(out_a, 1, GCRYMPI_FMT_USG);
+       if (!out_mpi) {
+               ret = -E_SEXP_FIND;
+               goto out_a_release;
+       }
+       gret = gcry_mpi_print(GCRYMPI_FMT_USG, outbuf, 512 /* FIXME */, &nbytes, out_mpi);
+       if (gret) {
+               PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
+               ret = -E_SEXP_ENCRYPT;
+               goto out_mpi_release;
+       }
+       PARA_INFO_LOG("encrypted buffer is %zu bytes\n", nbytes);
+       dump_buffer("enc buf", outbuf, nbytes);
+       ret = nbytes;
+
+out_mpi_release:
+       gcry_mpi_release(out_mpi);
+out_a_release:
+       gcry_sexp_release(out_a);
+out_release:
+       gcry_sexp_release(out);
+in_release:
+       gcry_sexp_release(in);
+key_release:
+       gcry_sexp_release(pub_key);
+       return ret;
+}
+
+struct stream_cipher {
+       gcry_cipher_hd_t handle;
+};
+
+struct stream_cipher *sc_new(const unsigned char *data, int len)
+{
+       gcry_error_t gret;
+
+       struct stream_cipher *sc = para_malloc(sizeof(*sc));
+       gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_ARCFOUR,
+               GCRY_CIPHER_MODE_STREAM, 0);
+       if (gret) {
+               PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
+               free(sc);
+               return NULL;
+       }
+       gret = gcry_cipher_setkey(sc->handle, data, (size_t)len);
+       assert(gret == 0);
+       return sc;
+}
+
+void sc_free(struct stream_cipher *sc)
+{
+       if (!sc)
+               return;
+       gcry_cipher_close(sc->handle);
+       free(sc);
+}
+
+int sc_send_bin_buffer(struct stream_cipher_context *scc, char *buf,
+               size_t size)
+{
+       gcry_error_t gret;
+       int ret;
+       unsigned char *tmp = para_malloc(size);
+
+       assert(size);
+       gret = gcry_cipher_encrypt(scc->send->handle, tmp, size,
+               (unsigned char *)buf, size);
+       assert(gret == 0);
+       ret = write_all(scc->fd, (char *)tmp, &size);
+       free(tmp);
+       return ret;
+}
+
+int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
+               size_t size)
+{
+       gcry_error_t gret;
+       ssize_t ret = recv(scc->fd, buf, size, 0);
+
+       if (ret < 0)
+               ret = -ERRNO_TO_PARA_ERROR(errno);
+       if (ret <= 0)
+               return ret;
+       /* perform in-place encryption */
+       gret = gcry_cipher_encrypt(scc->recv->handle, (unsigned char *)buf, ret,
+               NULL, 0);
+       assert(gret == 0);
+       return ret;
+}
index e8dddf5bc27a80238436811b7d2b46680cddb3d7..142e2abb4c982457e2b51b250207e4eedeb77605 100644 (file)
--- a/server.c
+++ b/server.c
  *     - Blob tables: \ref blob.c,
  *     - The error subssystem: \ref error.h.
  *     - Access control for paraslash senders: \ref acl.c, \ref acl.h.
+ *     - Internal crypto API: \ref crypt.h.
  *
  * Low-level data structures:
  *
  *     - Doubly linked lists: \ref list.h,
  *     - Ring buffer: \ref ringbuffer.c, \ref ringbuffer.h,
- *     - Crypto: \ref crypt.c, \ref crypt.h.
+ *     - openssl: \ref crypt.c
+ *     - libgcrypt: \ref gcrypt.c
  *     - Forward error correction: \ref fec.c.
  */
 
index 3d5bc2ee6327f754b2da031c928c091d8fa049d3..f7071167a62067a9dfbf0c4d594ed49d59a33f0e 100644 (file)
@@ -215,11 +215,13 @@ In any case you'll need
        scripts which run during compilation require the EMPH(Bourne
        again shell).  It is most likely already installed.
 
-       - XREFERENCE(http://www.openssl.org/, openssl). The EMPH(Secure
-       Sockets Layer) library is needed for cryptographic routines
-       on both the server and the client side. It is usually shipped
-       with the distro, but you might have to install the "development
-       package" (called libssl-dev on debian systems) as well.
+       - XREFERENCE(http://www.openssl.org/, openssl) or
+       XREFERENCE(ftp://ftp.gnupg.org/gcrypt/libgcrypt/, libgcrypt).
+       At least one of these two libraries is needed as the backend
+       for cryptographic routines on both the server and the client
+       side. Both openssl and libgcrypt are usually shipped with the
+       distro, but you might have to install the development package
+       (libssl-dev or libgcrypt-dev on debian systems) as well.
 
        - XREFERENCE(ftp://ftp.gnu.org/pub/gnu/help2man, help2man)
        is used to create the man pages.
@@ -527,10 +529,10 @@ as follows:
        this point on the communication is encrypted using the RC4
        stream cipher with the session key known to both peers.
 
-paraslash relies on the quality of openssl's cryptographically strong
-pseudo-random bytes, on the security of the implementation of the
-openssl RSA and RC4 crypto routines and on the infeasibility to invert
-the SHA1 function.
+paraslash relies on the quality of the pseudo-random bytes provided
+by the crypto library (openssl or libgcrypt), on the security of
+the implementation of the RSA and RC4 crypto routines and on the
+infeasibility to invert the SHA1 function.
 
 Neither para_server or para_client create RSA keys on their own. This
 has to be done once for each user as sketched in REFERENCE(Quick start,