X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=gcrypt.c;h=052546dd60733d143e6c12bdcfcc92ce36ac5e50;hb=23dd2200dd4fc74025ae87f5c2127f3b0ff71e9b;hp=0de619595f362788111eb24654a2c5749b74b782;hpb=56e2e6b938e7e3d318964f095ffcabc151411446;p=paraslash.git diff --git a/gcrypt.c b/gcrypt.c index 0de61959..052546dd 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -1,96 +1,653 @@ -/* - * Copyright (C) 2011 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2011 Andre Noll , see file COPYING. */ /** \file gcrypt.c Libgrcypt-based encryption/decryption routines. */ #include -#include -#include +#include #include "para.h" #include "error.h" #include "string.h" #include "crypt.h" +#include "crypt_backend.h" #include "fd.h" +#include "base64.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 (%d 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. The + * call to gcry_check_version() initializes the gcrypt library and checks that + * we have at least the minimal required version. + */ void init_random_seed_or_die(void) { + const char *req_ver = "1.5.0"; + int seed; + + if (!gcry_check_version(req_ver)) { + PARA_EMERG_LOG("fatal: need at least libgcrypt-%s, have: %s\n", + req_ver, gcry_check_version(NULL)); + exit(EXIT_FAILURE); + } + get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed)); + srandom(seed); } -int get_asymmetric_key(const char *key_file, int private, - struct asymmetric_key **result) +/** 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)))" +/** S-expression for decryption. */ +#define RSA_DECRYPT_SEXP "(enc-val(flags oaep)(rsa(a %m)))" + +struct asymmetric_key { + gcry_sexp_t sexp; + int num_bytes; +}; + +static const char *gcrypt_strerror(gcry_error_t gret) { - return 0; + return gcry_strerror(gcry_err_code(gret)); } -void free_asymmetric_key(struct asymmetric_key *key) +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) + goto out; + 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'; + ret = base64_decode(key, j, (char **)&blob, &blob_size); + free(key); + if (ret < 0) + goto free_unmap; + ret = blob_size; + 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; + } +out: + *result = blob; + return ret; } -int priv_decrypt(const char *key_file, unsigned char *outbuf, - unsigned char *inbuf, int inlen) +/** 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 0; + return (c & (1<<6)) == 0; } -int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, - unsigned len, unsigned char *outbuf) +static inline bool is_primitive_integer(unsigned char c) { - return 0; + if (!is_primitive(c)) + return false; + return (c & 0x1f) == ASN1_TYPE_INTEGER; } -struct stream_cipher { - int x; -}; +/* Bit 8 is zero (and bits 7-1 give the length) */ +static inline bool is_short_form(unsigned char c) +{ + return (c & 0x80) == 0; +} -struct stream_cipher *sc_new(const unsigned char *data, int len) +static inline int get_short_form_length(unsigned char c) { - return NULL; + return c & 0x7f; } -void sc_free(struct stream_cipher *sc) +static inline int get_long_form_num_length_bytes(unsigned char c) { + return c & 0x7f; } -int sc_send_bin_buffer(struct stream_cipher_context *scc, const char *buf, - size_t len) +/* + * 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) { - return 0; + 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, (unsigned)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; } -int sc_send_buffer(struct stream_cipher_context *scc, const char *buf) +static int find_privkey_bignum_offset(const unsigned char *data, int len) { - return 0; + 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; } -__printf_2_3 int sc_send_va_buffer(struct stream_cipher_context *scc, - const char *fmt, ...) +/** 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) { - return 0; + 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; + + *result = NULL; + 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; + /* + * 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; } -int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf, - size_t size) +static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result) { - return 0; + 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); + ret = uudecode((char *)data, size, (char **)&blob, &decoded_size); + if (ret < 0) + goto free_blob; + end = blob + decoded_size; + dump_buffer("decoded key", blob, decoded_size); + ret = check_ssh_key_header(blob, decoded_size); + if (ret < 0) + goto free_blob; + 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 %d 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 sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size) +int get_public_key(const char *key_file, 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; + + ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL); + if (ret < 0) + return ret; + ret = is_ssh_rsa_key(map, map_size); + if (!ret) { + para_munmap(map, map_size); + return -E_SSH_PARSE; + } + start = map + ret; + end = map + map_size; + ret = -E_SSH_PARSE; + if (start >= end) + goto unmap; + ret = get_ssh_public_key(start, end - start, &sexp); + if (ret < 0) + goto unmap; + key = para_malloc(sizeof(*key)); + key->num_bytes = ret; + key->sexp = sexp; + *result = key; +unmap: + ret2 = para_munmap(map, map_size); + if (ret >= 0 && ret2 < 0) + ret = ret2; + return ret; } -void hash_function(const char *data, unsigned long len, unsigned char *hash) +void free_public_key(struct asymmetric_key *key) +{ + if (!key) + return; + gcry_sexp_release(key->sexp); + free(key); +} + +static int decode_rsa(gcry_sexp_t sexp, unsigned char *outbuf, size_t *nbytes) +{ + 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; + struct asymmetric_key *priv; + gcry_mpi_t in_mpi = NULL; + gcry_sexp_t in, out, priv_key; + size_t nbytes; + + ret = check_private_key_file(key_file); + if (ret < 0) + return ret; + 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; + + /* 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, 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: + gcry_sexp_release(priv->sexp); + free(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; + 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; + 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)); + + 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; +} + +void sc_free(struct stream_cipher *sc) +{ + if (!sc) + return; + gcry_cipher_close(sc->handle); + free(sc); +} + +void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +{ + gcry_cipher_hd_t handle = sc->handle; + gcry_error_t gret; + + /* perform in-place encryption */ + *dst = *src; + gret = gcry_cipher_encrypt(handle, src->iov_base, src->iov_len, + NULL, 0); + assert(gret == 0); }