/* SPDX-License-Identifier: GPL-2.0 */ /** \file gcrypt.c Libgrcypt-based encryption/decryption routines. */ #include #include "para.h" #include "error.h" #include "string.h" #include "crypt.h" #include "crypt_backend.h" #include "fd.h" //#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_SHA256, 0); assert(gret == 0); gcry_md_write(handle, data, (size_t)len); gcry_md_final(handle); md = gcry_md_read(handle, GCRY_MD_SHA256); 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 crypt_init(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); } /* * Allocate a pool of secure memory. This also drops privileges where * needed. */ gcry_control(GCRYCTL_INIT_SECMEM, 65536, 0); /* Tell Libgcrypt that initialization has completed. */ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed)); srandom(seed); } void crypt_shutdown(void) { /* * WK does not see a way to apply a patch for the sake of Valgrind, so * as of 2018 libgrypt has no deinitialization routine to free the * resources on exit. */ } /** 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 bits; }; static const char *gcrypt_strerror(gcry_error_t gret) { return gcry_strerror(gcry_err_code(gret)); } /** 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; } struct rsa_params { gcry_mpi_t n, e, d, p, q, u; }; static int read_openssh_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn, unsigned *bitsp) { gcry_error_t gret; size_t nscanned; unsigned bits; gret = gcry_mpi_scan(bn, GCRYMPI_FMT_SSH, start, end - start, &nscanned); if (gret) { PARA_ERROR_LOG("gcry_mpi_scan: %s\n", gcry_strerror(gcry_err_code(gret))); return -E_MPI_SCAN; } bits = (nscanned - 4 - (start[4] == '\0')) * 8; if (bitsp) *bitsp = bits; PARA_DEBUG_LOG("scanned %u-bit bignum\n", bits); return nscanned; } static int read_openssh_rsa_params(unsigned char *start, unsigned char *end, struct rsa_params *p) { unsigned char *cp = start; unsigned bits; int ret; ret = read_openssh_bignum(cp, end, &p->n, &bits); if (ret < 0) return ret; cp += ret; ret = read_openssh_bignum(cp, end, &p->e, NULL); if (ret < 0) goto release_n; cp += ret; ret = read_openssh_bignum(cp, end, &p->d, NULL); if (ret < 0) goto release_e; cp += ret; ret = read_openssh_bignum(cp, end, &p->u, NULL); if (ret < 0) goto release_d; cp += ret; ret = read_openssh_bignum(cp, end, &p->p, NULL); if (ret < 0) goto release_u; cp += ret; ret = read_openssh_bignum(cp, end, &p->q, NULL); if (ret < 0) goto release_p; return bits; release_p: gcry_mpi_release(p->p); release_u: gcry_mpi_release(p->u); release_d: gcry_mpi_release(p->d); release_e: gcry_mpi_release(p->e); release_n: gcry_mpi_release(p->n); return ret; } static int get_private_key(const char *key_file, struct asymmetric_key **result) { struct rsa_params params; unsigned char *blob, *end; unsigned bits; int ret; gcry_error_t gret; size_t erroff, blob_size; gcry_sexp_t sexp; struct asymmetric_key *key; *result = NULL; ret = decode_private_key(key_file, &blob, &blob_size); if (ret < 0) return ret; end = blob + blob_size; ret = find_openssh_bignum_offset(blob, blob_size); if (ret < 0) goto free_blob; PARA_INFO_LOG("reading RSA params at offset %d\n", ret); ret = read_openssh_rsa_params(blob + ret, end, ¶ms); if (ret < 0) goto free_blob; bits = 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(params.p, params.q) > 0) { gcry_mpi_swap(params.p, params.q); gcry_mpi_invm(params.u, params.p, params.q); } gret = gcry_sexp_build(&sexp, &erroff, RSA_PRIVKEY_SEXP, params.n, params.e, params.d, params.p, params.q, params.u); if (gret) { PARA_ERROR_LOG("offset %zu: %s\n", erroff, gcry_strerror(gcry_err_code(gret))); ret = -E_SEXP_BUILD; goto free_params; } key = alloc(sizeof(*key)); key->sexp = sexp; *result = key; ret = bits; PARA_INFO_LOG("succesfully read %d bit private key\n", ret); free_params: gcry_mpi_release(params.n); gcry_mpi_release(params.e); gcry_mpi_release(params.d); gcry_mpi_release(params.u); gcry_mpi_release(params.p); gcry_mpi_release(params.q); free_blob: free(blob); return ret; } int apc_get_pubkey(const char *key_file, struct asymmetric_key **result) { unsigned char *blob, *p, *end; int ret; gcry_error_t gret; size_t erroff, decoded_size; gcry_mpi_t e, n; gcry_sexp_t sexp; struct asymmetric_key *key; unsigned bits; ret = decode_public_key(key_file, &blob, &decoded_size); if (ret < 0) return ret; p = blob + ret; end = blob + decoded_size; PARA_DEBUG_LOG("scanning modulus and public exponent\n"); ret = read_openssh_bignum(p, end, &e, NULL); if (ret < 0) goto free_blob; p += ret; ret = read_openssh_bignum(p, end, &n, &bits); if (ret < 0) goto release_e; 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_n; } PARA_INFO_LOG("successfully read %u bit ssh public key\n", bits); key = alloc(sizeof(*key)); key->sexp = sexp; key->bits = bits; *result = key; ret = bits / 8; release_n: gcry_mpi_release(n); release_e: gcry_mpi_release(e); free_blob: free(blob); return ret; } void apc_free_pubkey(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) { *outbuf = NULL; return -E_RSA_DECODE; } *outbuf = alloc(*nbytes); memcpy(*outbuf, p, *nbytes); return 1; } int apc_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; *outbuf = NULL; 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 apc_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; *outbuf = NULL; /* 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; } *outbuf = alloc(pub->bits); gret = gcry_mpi_print(GCRYMPI_FMT_USG, *outbuf, pub->bits, &nbytes, out_mpi); if (gret) { free(*outbuf); *outbuf = NULL; 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 = alloc(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); }