Add alternative crypto implementation.
authorAndre Noll <maan@systemlinux.org>
Sat, 4 Jun 2011 13:26:15 +0000 (15:26 +0200)
committerAndre Noll <maan@systemlinux.org>
Wed, 6 Jul 2011 06:41:26 +0000 (08:41 +0200)
This fills gcrypt.c (which contained only dummy functions so far)
with contents.

The old openssl-based crypto API uses OAEP padding exclusively, as
this padding method is recommended for new applications which do not
have to care about backwards compatibility. Unfortunately, libcrypt
only supports the older pkcs1 padding method. Since we want older
para_client versions to be compatible with a newer para_server, even
if this para_server was compiled against libgcrypt, we must implement
our own OAEP padding functions. This turned out to be quite simple,
given the good documentation in rfc 3447.

This together with the fact that there is no ASN1 parser in libgcrypt
makes the patch quite large though.

On the other hand, SHA1, random numbers and RC4 were straight-forward
to implement using the primitives provided by libgcrypt.

error.h
gcrypt.c

diff --git a/error.h b/error.h
index 3740f09d02f206dfe9d0159acd3d63ed5d050bde..b8078770e5368c3840e0c6c91e3ff6267a7f7590 100644 (file)
--- a/error.h
+++ b/error.h
@@ -384,6 +384,20 @@ extern const char **para_errlist[];
        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"), \
index 0de619595f362788111eb24654a2c5749b74b782..1312aaedc275206ba88ce4cd44cd89c01d2ba9ba 100644 (file)
--- a/gcrypt.c
+++ b/gcrypt.c
 /** \file gcrypt.c Libgrcypt-based encryption/decryption routines. */
 
 #include <regex.h>
-#include <sys/types.h>
-#include <sys/socket.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"
 
-struct asymmetric_key {
-       int x;
-};
+//#define GCRYPT_DEBUG 1
+
+#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 the call to gcry_control
+ * is necessary to avoid warnings of the form "missing initialization - please
+ * fix the application".
+ */
 void init_random_seed_or_die(void)
 {
+       gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+}
+
+/** 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 * 8;
+       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)
 {
-       return 0;
+       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);
 }
 
 int priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen)
 {
-       return 0;
+       gcry_error_t gret;
+       int ret, key_size;
+       struct asymmetric_key *priv;
+       gcry_mpi_t in_mpi = NULL, out_mpi = NULL;
+       gcry_sexp_t in, out, priv_key;
+       size_t nbytes;
+       unsigned char oaep_buf[512];
+
+       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, "(enc-val(rsa(a %m)))", 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;
+       }
+       out_mpi = gcry_sexp_nth_mpi(out, 0, GCRYMPI_FMT_USG);
+       if (!out_mpi) {
+               ret = -E_SEXP_FIND;
+               goto out_release;
+       }
+       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 = nbytes;
+       PARA_INFO_LOG("successfully decrypted %u byte message\n", ret);
+out_mpi_release:
+       gcry_mpi_release(out_mpi);
+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)
 {
-       return 0;
+       gcry_error_t gret;
+       const size_t pad_size = 256;
+       gcry_sexp_t pub_key, in, out, out_a;
+       gcry_mpi_t out_mpi = NULL;
+       size_t nbytes;
+       int ret;
+       unsigned char padded_input[256];
+
+       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;
+
+       /* 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_STD);
+       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 {
-       int x;
+       gcry_cipher_hd_t handle;
 };
 
 struct stream_cipher *sc_new(const unsigned char *data, int len)
 {
-       return NULL;
+       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, const char *buf,
-               size_t len)
+int sc_send_bin_buffer(struct stream_cipher_context *scc, char *buf,
+               size_t size)
 {
-       return 0;
-}
+       gcry_error_t gret;
+       int ret;
+       unsigned char *tmp = para_malloc(size);
 
-int sc_send_buffer(struct stream_cipher_context *scc, const char *buf)
-{
-       return 0;
-}
-
-__printf_2_3 int sc_send_va_buffer(struct stream_cipher_context *scc,
-               const char *fmt, ...)
-{
-       return 0;
+       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)
 {
-       return 0;
-}
+       gcry_error_t gret;
+       ssize_t ret = recv(scc->fd, buf, size, 0);
 
-int sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size)
-{
-       return 0;
-}
-
-void hash_function(const char *data, unsigned long len, unsigned char *hash)
-{
+       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;
 }