Merge branch 'refs/heads/t/rm_rc4'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 16 Apr 2017 18:15:35 +0000 (20:15 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 16 Apr 2017 18:17:20 +0000 (20:17 +0200)
This patch removes support for RC4, making the AES-based stream
cipher mandadory. The aes_ctr128 server feature is made a no-op,
breaking support with very old clients (<= 0.5.1).

Cooking for three months.

* refs/heads/t/rm_rc4:
  crypt: Remove RC4 support.

1  2 
NEWS.md
crypt.c
crypt.h
gcrypt.c
web/manual.md

diff --combined NEWS.md
+++ b/NEWS.md
@@@ -1,30 -1,9 +1,33 @@@
  NEWS
  ====
  
 +------------------------------------
 +0.6.0 (to be announced) "fuzzy flux"
 +------------------------------------
 +- Support for Mac OS X has been removed.
 +- On Linux systems, glibc-2.17 or newer is required to build the
 +  source tree.
 +- Support for RSA public keys in ASN format (as generated by openssl
 +  genrsa) has been removed. These keys have been deprecated since
 +  2011, so users should have long switched to keys generated with
 +  ssh-keygen(1).
 +- If libgcrypt is used as the crypto library, we now require version
 +  1.5.0 (released in 2011) or later.
++- The insecure RC4 stream cipher has been removed. It was superseded
++  by aes_ctr128 three years ago but the RC4 code had been kept for
++  backwards compatibility.
 +
 +Downloads:
 +[tarball](./releases/paraslash-git.tar.bz2),
 +
  -------------------------------------
  0.5.7 (2016-12-31) "semantic density"
  -------------------------------------
 +
 +Mostly a bug fix release, and a bunch of internal improvements.
 +The only user-visible changes are the sanity checks for the touch
 +command and the new options to the ls command.
 +
  - Speedup of the base64 decoder.
  - One of the two source browsers has been removed from the web pages.
    The doxygen API reference still contains an HTML version of each
diff --combined crypt.c
+++ b/crypt.c
@@@ -11,7 -11,6 +11,6 @@@
  #include <sys/socket.h>
  #include <openssl/rand.h>
  #include <openssl/err.h>
- #include <openssl/rc4.h>
  #include <openssl/pem.h>
  #include <openssl/sha.h>
  #include <openssl/bn.h>
@@@ -61,24 -60,41 +60,24 @@@ void init_random_seed_or_die(void
        srandom(seed);
  }
  
 -static EVP_PKEY *load_key(const char *file, int private)
 +static int get_private_key(const char *path, RSA **rsa)
  {
 -      BIO *key;
 -      EVP_PKEY *pkey = NULL;
 -      int ret = check_key_file(file, private);
 +      EVP_PKEY *pkey;
 +      BIO *bio = BIO_new(BIO_s_file());
  
 -      if (ret < 0) {
 -              PARA_ERROR_LOG("%s\n", para_strerror(-ret));
 -              return NULL;
 -      }
 -      key = BIO_new(BIO_s_file());
 -      if (!key)
 -              return NULL;
 -      if (BIO_read_filename(key, file) > 0) {
 -              if (private == LOAD_PRIVATE_KEY)
 -                      pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
 -              else
 -                      pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL);
 -      }
 -      BIO_free(key);
 -      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);
 +      *rsa = NULL;
 +      if (!bio)
 +              return -E_PRIVATE_KEY;
 +      if (BIO_read_filename(bio, path) <= 0)
 +              goto bio_free;
 +      pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
 +      if (!pkey)
 +              goto bio_free;
 +      *rsa = EVP_PKEY_get1_RSA(pkey);
 +      EVP_PKEY_free(pkey);
 +bio_free:
 +      BIO_free(bio);
 +      return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
  }
  
  /*
@@@ -143,7 -159,8 +142,7 @@@ fail
        return ret;
  }
  
 -int get_asymmetric_key(const char *key_file, int private,
 -              struct asymmetric_key **result)
 +int get_public_key(const char *key_file, struct asymmetric_key **result)
  {
        struct asymmetric_key *key = NULL;
        void *map = NULL;
        char *cp;
  
        key = para_malloc(sizeof(*key));
 -      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;
 +              para_munmap(map, map_size);
 +              return -E_SSH_PARSE;
        }
        cp = map + ret;
        encoded_size = map_size - ret;
@@@ -189,7 -214,7 +188,7 @@@ out
        return ret;
  }
  
 -void free_asymmetric_key(struct asymmetric_key *key)
 +void free_public_key(struct asymmetric_key *key)
  {
        if (!key)
                return;
@@@ -203,17 -228,11 +202,17 @@@ int priv_decrypt(const char *key_file, 
        struct asymmetric_key *priv;
        int ret;
  
 +      ret = check_private_key_file(key_file);
 +      if (ret < 0)
 +              return ret;
        if (inlen < 0)
                return -E_RSA;
 -      ret = get_asymmetric_key(key_file, LOAD_PRIVATE_KEY, &priv);
 -      if (ret < 0)
 +      priv = para_malloc(sizeof(*priv));
 +      ret = get_private_key(key_file, &priv->rsa);
 +      if (ret < 0) {
 +              free(priv);
                return ret;
 +      }
        /*
         * RSA is vulnerable to timing attacks. Generate a random blinding
         * factor to protect against this kind of attack.
        if (ret <= 0)
                ret = -E_DECRYPT;
  out:
 -      free_asymmetric_key(priv);
 +      RSA_free(priv->rsa);
 +      free(priv);
        return ret;
  }
  
@@@ -245,26 -263,16 +244,16 @@@ int pub_encrypt(struct asymmetric_key *
  }
  
  struct stream_cipher {
-       bool use_aes;
-       union {
-               RC4_KEY rc4_key;
-               EVP_CIPHER_CTX *aes;
-       } context;
+       EVP_CIPHER_CTX *aes;
  };
  
- struct stream_cipher *sc_new(const unsigned char *data, int len,
-               bool use_aes)
+ struct stream_cipher *sc_new(const unsigned char *data, int len)
  {
        struct stream_cipher *sc = para_malloc(sizeof(*sc));
  
-       sc->use_aes = use_aes;
-       if (!use_aes) {
-               RC4_set_key(&sc->context.rc4_key, len, data);
-               return sc;
-       }
        assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
-       sc->context.aes = EVP_CIPHER_CTX_new();
-       EVP_EncryptInit_ex(sc->context.aes, EVP_aes_128_ctr(), NULL, data,
+       sc->aes = EVP_CIPHER_CTX_new();
+       EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data,
                data + AES_CRT128_BLOCK_SIZE);
        return sc;
  }
@@@ -273,40 -281,10 +262,10 @@@ void sc_free(struct stream_cipher *sc
  {
        if (!sc)
                return;
-       EVP_CIPHER_CTX_free(sc->context.aes);
+       EVP_CIPHER_CTX_free(sc->aes);
        free(sc);
  }
  
- /**
-  * The RC4() implementation of openssl apparently reads and writes data in
-  * blocks of 8 bytes. So we have to make sure our buffer sizes are a multiple
-  * of this.
-  */
- #define RC4_ALIGN 8
- static void rc4_crypt(RC4_KEY *key, struct iovec *src, struct iovec *dst)
- {
-       size_t len = src->iov_len, l1, l2;
-       assert(len > 0);
-       assert(len < ((typeof(src->iov_len))-1) / 2);
-       l1 = ROUND_DOWN(len, RC4_ALIGN);
-       l2 = ROUND_UP(len, RC4_ALIGN);
-       *dst = (typeof(*dst)) {
-               /* Add one for the terminating zero byte. */
-               .iov_base = para_malloc(l2 + 1),
-               .iov_len = len
-       };
-       RC4(key, l1, src->iov_base, dst->iov_base);
-       if (len > l1) {
-               unsigned char remainder[RC4_ALIGN] = "";
-               memcpy(remainder, src->iov_base + l1, len - l1);
-               RC4(key, len - l1, remainder, dst->iov_base + l1);
-       }
-       ((char *)dst->iov_base)[len] = '\0';
- }
  static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src,
                struct iovec *dst)
  {
  
  void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
  {
-       if (sc->use_aes)
-               return aes_ctr128_crypt(sc->context.aes, src, dst);
-       return rc4_crypt(&sc->context.rc4_key, src, dst);
+       return aes_ctr128_crypt(sc->aes, src, dst);
  }
  
  void hash_function(const char *data, unsigned long len, unsigned char *hash)
diff --combined crypt.h
+++ b/crypt.h
@@@ -51,21 -51,22 +51,21 @@@ int priv_decrypt(const char *key_file, 
   * 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);
 +int get_public_key(const char *key_file, struct asymmetric_key **result);
  
  /**
 - * Deallocate an asymmetric key structure.
 + * Deallocate a public key.
   *
   * \param key Pointer to the key structure to free.
   *
 - * This must be called for any key obtained by get_asymmetric_key().
 + * This should be called for keys obtained by get_public_key() if the key is no
 + * longer needed.
   */
 -void free_asymmetric_key(struct asymmetric_key *key);
 +void free_public_key(struct asymmetric_key *key);
  
  
  /**
@@@ -118,16 -119,14 +118,14 @@@ struct stream_cipher_context 
  };
  
  /**
-  * Allocate and initialize a stream cipher structure.
+  * Allocate and initialize an aes_ctr128 stream cipher structure.
   *
   * \param data The key.
   * \param len The size of the key.
-  * \param use_aes True: Use the aes_ctr128 stream cipher, false: Use RC4.
   *
   * \return A new stream cipher structure.
   */
- struct stream_cipher *sc_new(const unsigned char *data, int len,
-               bool use_aes);
+ struct stream_cipher *sc_new(const unsigned char *data, int len);
  
  /**
   * Encrypt or decrypt a buffer using a stream cipher.
diff --combined gcrypt.c
+++ b/gcrypt.c
@@@ -19,6 -19,9 +19,6 @@@
  
  //#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)
  {
@@@ -60,25 -63,134 +60,25 @@@ void get_random_bytes_or_die(unsigned c
   * 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.
 + * version.
   */
  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)))";
 -      }
 +      const char *req_ver = "1.5.0";
 +
 +      if (gcry_check_version(req_ver))
 +              return;
 +      PARA_EMERG_LOG("fatal: need at least libgcrypt-%s, have: %s\n",
 +              req_ver, gcry_check_version(NULL));
 +      exit(EXIT_FAILURE);
  }
  
  /** 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)
 -{
 -      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 1;
 -}
 +/** S-expression for decryption. */
 +#define RSA_DECRYPT_SEXP "(enc-val(flags oaep)(rsa(a %m)))"
  
  struct asymmetric_key {
        gcry_sexp_t sexp;
@@@ -189,6 -301,64 +189,6 @@@ static inline int get_long_form_num_len
        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 starts 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
@@@ -290,7 -460,6 +290,7 @@@ static int get_private_key(const char *
        gcry_sexp_t sexp;
        struct asymmetric_key *key;
  
 +      *result = NULL;
        ret = decode_key(key_file, PRIVATE_KEY_HEADER, PRIVATE_KEY_FOOTER,
                &blob);
        if (ret < 0)
@@@ -369,6 -538,65 +369,6 @@@ 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;
 -
 -      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;
 -      key->num_bytes = n_size;
 -      *result = key;
 -      ret = n_size;
 -      PARA_INFO_LOG("successfully read %d 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;
@@@ -430,7 -658,8 +430,7 @@@ free_blob
        return ret;
  }
  
 -int get_asymmetric_key(const char *key_file, int private,
 -              struct asymmetric_key **result)
 +int get_public_key(const char *key_file, struct asymmetric_key **result)
  {
        int ret, ret2;
        void *map;
        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);
 +              para_munmap(map, map_size);
 +              return -E_SSH_PARSE;
        }
        start = map + ret;
        end = map + map_size;
@@@ -466,7 -699,7 +466,7 @@@ unmap
        return ret;
  }
  
 -void free_asymmetric_key(struct asymmetric_key *key)
 +void free_public_key(struct asymmetric_key *key)
  {
        if (!key)
                return;
        free(key);
  }
  
 -static int decode_rsa(gcry_sexp_t sexp, int key_size, unsigned char *outbuf,
 -              size_t *nbytes)
 +static int decode_rsa(gcry_sexp_t sexp, 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);
 -      ret = unpad_oaep(oaep_buf, key_size, outbuf, nbytes);
 -      if (ret < 0)
 -              goto out_mpi_release;
 -      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;
 +      const char *p = gcry_sexp_nth_data(sexp, 1, nbytes);
 +
 +      if (!p)
 +              return -E_RSA_DECODE;
 +      memcpy(outbuf, p, *nbytes);
 +      return 1;
  }
  
  int priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen)
  {
        gcry_error_t gret;
 -      int ret, key_size;
 +      int ret;
        struct asymmetric_key *priv;
        gcry_mpi_t in_mpi = NULL;
        gcry_sexp_t in, out, priv_key;
        size_t nbytes;
  
 -      ret = check_key_file(key_file, true);
 +      ret = check_private_key_file(key_file);
        if (ret < 0)
                return ret;
        PARA_INFO_LOG("decrypting %d byte input\n", inlen);
        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;
                goto key_release;
        }
        /* in_mpi -> in sexp */
 -      gret = gcry_sexp_build(&in, NULL, rsa_decrypt_sexp, in_mpi);
 +      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;
                ret = -E_SEXP_DECRYPT;
                goto in_release;
        }
 -      ret = decode_rsa(out, key_size, outbuf, &nbytes);
 +      ret = decode_rsa(out, outbuf, &nbytes);
        if (ret < 0)
                goto out_release;
        PARA_INFO_LOG("successfully decrypted %zu byte message\n", nbytes);
@@@ -546,8 -831,7 +546,8 @@@ in_mpi_release
  key_release:
        gcry_sexp_release(priv_key);
  free_key:
 -      free_asymmetric_key(priv);
 +      gcry_sexp_release(priv->sexp);
 +      free(priv);
        return ret;
  }
  
@@@ -566,7 -850,18 +566,7 @@@ int pub_encrypt(struct asymmetric_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);
 -      }
 +      gret = gcry_sexp_build(&in, NULL, "(data(flags oaep)(value %b))", len, inbuf);
        if (gret) {
                PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
                ret = -E_SEXP_BUILD;
@@@ -617,33 -912,20 +617,20 @@@ struct stream_cipher 
        gcry_cipher_hd_t handle;
  };
  
- struct stream_cipher *sc_new(const unsigned char *data, int len,
-               bool use_aes)
+ struct stream_cipher *sc_new(const unsigned char *data, int len)
  {
        gcry_error_t gret;
        struct stream_cipher *sc = para_malloc(sizeof(*sc));
  
-       if (use_aes) {
-               assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
-               gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128,
-                       GCRY_CIPHER_MODE_CTR, 0);
-               assert(gret == 0);
-               gret = gcry_cipher_setkey(sc->handle, data,
-                       AES_CRT128_BLOCK_SIZE);
-               assert(gret == 0);
-               gret = gcry_cipher_setctr(sc->handle,
-                       data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE);
-               assert(gret == 0);
-               return 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(len >= 2 * AES_CRT128_BLOCK_SIZE);
+       gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128,
+               GCRY_CIPHER_MODE_CTR, 0);
+       assert(gret == 0);
+       gret = gcry_cipher_setkey(sc->handle, data,
+               AES_CRT128_BLOCK_SIZE);
+       assert(gret == 0);
+       gret = gcry_cipher_setctr(sc->handle,
+               data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE);
        assert(gret == 0);
        return sc;
  }
diff --combined web/manual.md
@@@ -152,8 -152,9 +152,8 @@@ an array of offsets within the audio fi
  ### para_write ###
  
  A modular audio stream writer. It supports a simple file writer
 -output plug-in and optional WAV/raw players for ALSA (Linux) and for
 -coreaudio (Mac OS). para_write can also be used as a stand-alone WAV
 -or raw audio player.
 +output plug-in and optional WAV/raw players for ALSA (Linux) and OSS.
 +para_write can also be used as a stand-alone WAV or raw audio player.
  
  ### para_play ###
  
@@@ -447,9 -448,9 +447,9 @@@ User managemen
  para_server uses a challenge-response mechanism to authenticate
  requests from incoming connections, similar to ssh's public key
  authentication method. Authenticated connections are encrypted using
a stream cipher, either RC4 or AES in integer counter mode.
the AES stream cipher in integer counter mode.
  
- In this chapter we briefly describe RSA, RC4 and AES, and sketch the
+ In this chapter we briefly describe RSA and AES, and sketch the
  [authentication handshake](#Client-server.authentication)
  between para_client and para_server. User management is discussed
  in the section on [the user_list file](#The.user_list.file).
@@@ -457,33 -458,33 +457,33 @@@ These sections are all about communicat
  server. Connecting para_audiod is a different matter and is described
  in a [separate section](#Connecting.para_audiod).
  
- RSA, RC4, AES
- -------------
+ RSA and AES
+ -----------
  
- RSA is an asymmetric block cipher which is used in many applications,
- including ssh and gpg. An RSA key consists in fact of two keys,
+ A block cipher is a transformation which operates on fixed-length
+ blocks. For symmetric block ciphers the transformation is determined
+ by a single key for both encryption and decryption. For asymmetric
+ block ciphers, on the other hand, the key consists of two parts,
  called the public key and the private key. A message can be encrypted
- with either key and only the counterpart of that key can decrypt
- the message. While RSA can be used for both signing and encrypting
- a message, paraslash uses RSA only for the latter purpose. The
- RSA public key encryption and signatures algorithms are defined in
- detail in RFC 2437.
- RC4 is a stream cipher, i.e. the input is XORed with a pseudo-random
- key stream to produce the output. Decryption uses the same function
- calls as encryption. While RC4 supports variable key lengths,
- paraslash uses a fixed length of 256 bits, which is considered a
- strong encryption by today's standards. Since the same key must never
- be used twice, a different, randomly-generated key is used for every
- new connection.
+ with either key and only the counterpart of that key can decrypt the
+ message. Asymmetric block ciphers can be used for both signing and
+ encrypting a message.
+ RSA is an asymmetric block cipher which is used in many applications,
+ including ssh and gpg. The RSA public key encryption and signatures
+ algorithms are defined in detail in RFC 2437. Paraslash relies on
+ RSA for authentication.
+ Stream ciphers XOR the input with a pseudo-random key stream to produce
+ the output. Decryption uses the same function calls as encryption.
+ Any block cipher can be turned into a stream cipher by generating the
+ pseudo-random key stream by encrypting successive values of a counter
+ (counter mode).
  
  AES, the advanced encryption standard, is a well-known symmetric block
- cipher, i.e. a transformation operating on fixed-length blocks which
- is determined by a single key for both encryption and decryption. Any
- block cipher can be turned into a stream cipher by generating
- a pseudo-random key stream by encrypting successive values of a
- counter. The AES_CTR128 stream cipher used in paraslash is obtained
- in this way from the AES block cipher with a 128 bit block size.
+ cipher. Paraslash employs AES in counter mode as described above to
+ encrypt communications. Since a stream cipher key must not be used
+ twice, a random key is generated for every new connection.
  
  Client-server authentication
  ----------------------------
@@@ -523,8 -524,8 +523,8 @@@ point on the communication is encrypte
  the session key known to both peers.
  
  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, RC4 and AES crypto routines and on the
+ by the crypto library (openssl or libgcrypt), on the security of
the implementation of the RSA and AES crypto routines and on the
  infeasibility to invert the SHA1 function.
  
  Neither para_server or para_client create RSA keys on their
@@@ -1699,6 -1700,10 +1699,6 @@@ emulation for backwards compatibility. 
  also limited. For example only one application can open the device
  at any time. The OSS writer is activated by default on BSD Systems.
  
 -- *OSX*. Mac OS X has yet another API called CoreAudio. The OSX writer
 -for this API is only compiled in on such systems and is of course
 -the default there.
 -
  - *FILE*. The file writer allows to capture the audio stream and
  write the PCM data to a file on the file system rather than playing
  it through a sound device. It is supported on all platforms and is