]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/ssh_keys'
authorAndre Noll <maan@systemlinux.org>
Sun, 15 May 2011 07:39:51 +0000 (09:39 +0200)
committerAndre Noll <maan@systemlinux.org>
Sun, 15 May 2011 07:44:18 +0000 (09:44 +0200)
NEWS
client_common.c
crypt.c
error.h
web/manual.m4

diff --git a/NEWS b/NEWS
index 4430343ec73e641f401263aee9e3dbefb2aa5993..bf6f2bab2f93c1ecd414de569d3469a8c0f89676 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@
 
        - Support for ESD, Pulseaudio, AIX, Solaris, IRIX and other
          platforms through the libao audio library.
+       - Support for RSA keys generated with ssh-keygen.
        - configure: improved options for ogg/vorbis/speex.
        - The git version reported by --version always matches HEAD.
        - The autogen script detects the number of processors and
index d3a9ede1f0e1d5b7238a387f0f3aa03650deed55..865a1797bd81bbd7e6308278c08249890c1bf241 100644 (file)
@@ -394,9 +394,16 @@ int client_open(int argc, char *argv[], struct client_task **ct_ptr,
        ct->user = ct->conf.user_given?
                para_strdup(ct->conf.user_arg) : para_logname();
 
-       ct->key_file = ct->conf.key_file_given?
-               para_strdup(ct->conf.key_file_arg) :
-               make_message("%s/.paraslash/key.%s", home, ct->user);
+       if (ct->conf.key_file_given)
+               ct->key_file = para_strdup(ct->conf.key_file_arg);
+       else {
+               ct->key_file = make_message("%s/.paraslash/key.%s",
+                       home, ct->user);
+               if (!file_exists(ct->key_file)) {
+                       free(ct->key_file);
+                       ct->key_file = make_message("%s/.ssh/id_rsa", home);
+               }
+       }
 
        if (loglevel)
                *loglevel = get_loglevel_by_name(ct->conf.loglevel_arg);
diff --git a/crypt.c b/crypt.c
index 8e1814dd3d82e5fd1d50301402faea6b0d9a9934..6f7e611d0364035560819f7659bbb71d62d4f1b1 100644 (file)
--- a/crypt.c
+++ b/crypt.c
@@ -14,6 +14,7 @@
 #include <openssl/rc4.h>
 #include <openssl/pem.h>
 #include <openssl/sha.h>
+#include <openssl/bn.h>
 
 #include "para.h"
 #include "error.h"
@@ -107,6 +108,284 @@ static EVP_PKEY *load_key(const char *file, int private)
        return pkey;
 }
 
+static int get_openssl_key(const char *key_file, RSA **rsa, int private)
+{
+       EVP_PKEY *key = load_key(key_file, private);
+
+       if (!key)
+               return (private == LOAD_PRIVATE_KEY)? -E_PRIVATE_KEY
+                       : -E_PUBLIC_KEY;
+       *rsa = EVP_PKEY_get1_RSA(key);
+       EVP_PKEY_free(key);
+       if (!*rsa)
+               return -E_RSA;
+       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;
+       uint32_t bnsize;
+       BIGNUM *bn;
+
+       if (p + 4 < p)
+               return -E_BIGNUM;
+       if (p + 4 > end)
+               return -E_BIGNUM;
+       bnsize = read_ssh_u32(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;
+       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)
+               return -E_BIGNUM;
+       ret = read_bignum(p, end - p, &rsa->e);
+       if (ret < 0)
+               goto fail;
+       p += ret;
+       ret = read_bignum(p, end - p, &rsa->n);
+       if (ret < 0)
+               goto fail;
+       *result = rsa;
+       return 1;
+fail:
+       if (rsa)
+               RSA_free(rsa);
+       return ret;
+}
+
 /**
  * Read an asymmetric key from a file.
  *
@@ -121,21 +400,56 @@ static EVP_PKEY *load_key(const char *file, int private)
 int get_asymmetric_key(const char *key_file, int private,
                struct asymmetric_key **result)
 {
-       struct asymmetric_key *key;
-       RSA *rsa;
-       EVP_PKEY *pkey = load_key(key_file, private);
+       struct asymmetric_key *key = NULL;
+       void *map = NULL;
+       unsigned char *blob = NULL;
+       size_t map_size, blob_size;
+       int ret, ret2;
+       char *cp;
 
-       if (!pkey)
-               return (private == LOAD_PRIVATE_KEY)? -E_PRIVATE_KEY
-                       : -E_PUBLIC_KEY;
-       rsa = EVP_PKEY_get1_RSA(pkey);
-       EVP_PKEY_free(pkey);
-       if (!rsa)
-               return -E_RSA;
        key = para_malloc(sizeof(*key));
-       key->rsa = rsa;
-       *result = key;
-       return RSA_size(rsa);
+       if (private) {
+               ret = get_openssl_key(key_file, &key->rsa, LOAD_PRIVATE_KEY);
+               goto out;
+       }
+       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 = para_munmap(map, map_size);
+               map = NULL;
+               if (ret < 0)
+                       goto out;
+               ret = get_openssl_key(key_file, &key->rsa, LOAD_PUBLIC_KEY);
+               goto out;
+       }
+       cp = map + ret;
+       PARA_INFO_LOG("decoding public rsa-ssh key %s\n", key_file);
+       ret = -ERRNO_TO_PARA_ERROR(EOVERFLOW);
+       if (map_size > INT_MAX / 4)
+               goto out;
+       blob_size = 2 * map_size;
+       blob = para_malloc(blob_size);
+       ret = uudecode(cp, blob, blob_size);
+       if (ret < 0)
+               goto out;
+       ret = read_rsa_bignums(blob, ret, &key->rsa);
+       if (ret < 0)
+               goto out;
+       ret = RSA_size(key->rsa);
+out:
+       ret2 = para_munmap(map, map_size);
+       if (ret >= 0 && ret2 < 0)
+               ret = ret2;
+       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;
 }
 
 /**
diff --git a/error.h b/error.h
index a24af894ef1ae49bd505ac82f093fde1e66dc6f1..3f5ff73be3b1b5b8314b836c794b7a62b8740e91 100644 (file)
--- a/error.h
+++ b/error.h
@@ -376,6 +376,8 @@ extern const char **para_errlist[];
        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 COMMAND_ERRORS \
index ea5ebeb7ac2cfb701fd0b4b676f525b7fbc5c74f..46a2cd62f58e4c25eada652b10a855a60be60f1e 100644 (file)
@@ -315,7 +315,7 @@ following commands:
 
        user=bar
        target=~/.paraslash/server.users
-       key=~/.paraslash/key.pub.$user
+       key=~/.paraslash/id_rsa.pub.$user
        perms=AFS_READ,AFS_WRITE,VSS_READ,VSS_WRITE
        mkdir -p ~/.paraslash
        echo "user $user $key $perms" >> $target
@@ -323,20 +323,25 @@ following commands:
 Next, change to the "bar" account on client_host and generate the
 key pair with the commands
 
-       key=~/.paraslash/key.$LOGNAME
-       mkdir -p ~/.paraslash
-       (umask 077 && openssl genrsa -out $key 2048)
+       ssh-keygen -t rsa -b 2048
+       # hit enter twice to create a key with no passphrase
 
-para_server only needs to know the public key of the key pair just
-created. It can be extracted with
+This generates the two files id_rsa and id_rsa.pub in ~/.ssh. Note
+that paraslash can also read keys generated by the "openssl genrsa"
+command. However, since keys created with ssh-keygen can also be used
+for ssh, this method is recommended.
 
-       pubkey=~/.paraslash/key.pub.$LOGNAME
-       openssl rsa -in $key -pubout -out $pubkey
+Note that para_server refuses to use a key if it is shorter than 2048
+bits. In particular, the RSA keys of paraslash 0.3.x will not work
+with version 0.4.x. Moreover, para_client refuses to use a (private)
+key which is world-readable.
 
-Copy the public key just created to server_host (you may skip this step
-for a single-user setup, i.e. if foo=bar and server_host=client_host):
+para_server only needs to know the public key of the key pair just
+created. Copy this public key to server_host:
 
-       scp $pubkey foo@server_host:.paraslash/
+       src=~/.ssh/id_rsa.pub
+       dest=.paraslash/id_rsa.pub.$LOGNAME
+       scp $src foo@server_host:$dest
 
 Finally, tell para_client to connect to server_host:
 
@@ -568,19 +573,6 @@ execute. The output of
 contains in the third column the permissions needed to execute the
 command.
 
-A new RSA key can be created with
-
-       openssl genrsa -out <private_key> 2048
-
-and the public part may be extracted with
-
-       openssl rsa -in <private_key> -pubout -out <public_key>
-
-Note that para_server refuses to use a key if it is shorter than 2048
-bits. In particular, the RSA keys of paraslash 0.3.x will not work
-with version 0.4.x. Moreover, para_client refuses to use a (private)
-key which is world-readable.
-
 It is possible to make para_server reread the user_list file by
 executing the paraslash "hup" command or by sending SIGHUP to the
 PID of para_server.