summaryrefslogtreecommitdiffstats
path: root/src/lib-dcrypt/ostream-encrypt.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-dcrypt/ostream-encrypt.c800
1 files changed, 800 insertions, 0 deletions
diff --git a/src/lib-dcrypt/ostream-encrypt.c b/src/lib-dcrypt/ostream-encrypt.c
new file mode 100644
index 0000000..c6e67f5
--- /dev/null
+++ b/src/lib-dcrypt/ostream-encrypt.c
@@ -0,0 +1,800 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "randgen.h"
+#include "dcrypt-iostream.h"
+#include "ostream-encrypt.h"
+#include "ostream-private.h"
+#include "hash-method.h"
+#include "sha2.h"
+#include "safe-memset.h"
+#include "dcrypt.h"
+
+#include <arpa/inet.h>
+
+/* file struct dcrypt_public_key syntax
+ * magic (14 bytes)
+ * version (1 bytes)
+ * flags (4 bytes)
+ * size of header (4 bytes)
+ * sha1 of key id (20 bytes)
+ * cipher oid
+ * mac oid
+ * rounds (4 bytes)
+ * key data size (4 bytes)
+ * key data
+ * cipher data
+ * mac data (mac specific bytes)
+ */
+
+#define IO_STREAM_ENCRYPT_SEED_SIZE 32
+#define IO_STREAM_ENCRYPT_ROUNDS 2048
+
+struct encrypt_ostream {
+ struct ostream_private ostream;
+
+ struct dcrypt_context_symmetric *ctx_sym;
+ struct dcrypt_context_hmac *ctx_mac;
+
+ enum io_stream_encrypt_flags flags;
+ struct dcrypt_public_key *pub;
+
+ unsigned char *key_data;
+ size_t key_data_len;
+
+ buffer_t *cipher_oid;
+ buffer_t *mac_oid;
+ size_t block_size;
+
+ bool finalized;
+ bool failed;
+ bool prefix_written;
+};
+
+static int
+o_stream_encrypt_send(struct encrypt_ostream *stream,
+ const unsigned char *data, size_t size)
+{
+ ssize_t ec;
+
+ ec = o_stream_send(stream->ostream.parent, data, size);
+ if (ec == (ssize_t)size)
+ return 0;
+ else if (ec < 0) {
+ o_stream_copy_error_from_parent(&stream->ostream);
+ return -1;
+ } else {
+ io_stream_set_error(&stream->ostream.iostream,
+ "ostream-encrypt: "
+ "Unexpectedly short write to parent stream");
+ stream->ostream.ostream.stream_errno = EINVAL;
+ return -1;
+ }
+}
+
+static int
+o_stream_encrypt_send_header_v1(struct encrypt_ostream *stream)
+{
+ unsigned char c;
+ unsigned short s;
+
+ i_assert(!stream->prefix_written);
+ stream->prefix_written = TRUE;
+
+ buffer_t *values = t_buffer_create(256);
+ buffer_append(values, IOSTREAM_CRYPT_MAGIC,
+ sizeof(IOSTREAM_CRYPT_MAGIC));
+ /* version */
+ c = 1;
+ buffer_append(values, &c, 1);
+ /* key data length */
+ s = htons(stream->key_data_len);
+ buffer_append(values, &s, 2);
+ /* then write key data */
+ buffer_append(values, stream->key_data, stream->key_data_len);
+ i_free_and_null(stream->key_data);
+
+ /* then send it to stream */
+ return o_stream_encrypt_send(stream, values->data, values->used);
+}
+
+static int
+o_stream_encrypt_send_header_v2(struct encrypt_ostream *stream)
+{
+ unsigned char c;
+ unsigned int i;
+
+ i_assert(!stream->prefix_written);
+ stream->prefix_written = TRUE;
+
+ buffer_t *values = t_buffer_create(256);
+ buffer_append(values, IOSTREAM_CRYPT_MAGIC,
+ sizeof(IOSTREAM_CRYPT_MAGIC));
+ c = 2;
+ buffer_append(values, &c, 1);
+ i = cpu32_to_be(stream->flags);
+ buffer_append(values, &i, 4);
+ /* store total length of header
+ 9 = version + flags + length
+ 8 = rounds + key data length
+ */
+ i = cpu32_to_be(sizeof(IOSTREAM_CRYPT_MAGIC) + 9 +
+ stream->cipher_oid->used + stream->mac_oid->used +
+ 8 + stream->key_data_len);
+ buffer_append(values, &i, 4);
+
+ buffer_append_buf(values, stream->cipher_oid, 0, SIZE_MAX);
+ buffer_append_buf(values, stream->mac_oid, 0, SIZE_MAX);
+ i = cpu32_to_be(IO_STREAM_ENCRYPT_ROUNDS);
+ buffer_append(values, &i, 4);
+ i = cpu32_to_be(stream->key_data_len);
+ buffer_append(values, &i, 4);
+ buffer_append(values, stream->key_data, stream->key_data_len);
+ i_free_and_null(stream->key_data);
+
+ return o_stream_encrypt_send(stream, values->data, values->used);
+}
+
+static int
+o_stream_encrypt_keydata_create_v1(struct encrypt_ostream *stream)
+{
+ buffer_t *encrypted_key, *ephemeral_key, *secret, *res, buf;
+ const char *error = NULL;
+ const struct hash_method *hash = &hash_method_sha256;
+
+ /* various temporary buffers */
+ unsigned char seed[IO_STREAM_ENCRYPT_SEED_SIZE];
+ unsigned char pkhash[hash->digest_size];
+ unsigned char ekhash[hash->digest_size];
+ unsigned char hres[hash->digest_size];
+
+ unsigned char hctx[hash->context_size];
+
+ /* hash the public key first */
+ buffer_create_from_data(&buf, pkhash, sizeof(pkhash));
+ if (!dcrypt_key_id_public_old(stream->pub, &buf, &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Key hash failed: %s", error);
+ return -1;
+ }
+
+ /* hash the key base */
+ hash->init(hctx);
+ hash->loop(hctx, seed, sizeof(seed));
+ hash->result(hctx, ekhash);
+
+ ephemeral_key = t_buffer_create(256);
+ encrypted_key = t_buffer_create(256);
+ secret = t_buffer_create(256);
+
+ if (!dcrypt_ecdh_derive_secret_peer(stream->pub, ephemeral_key,
+ secret, &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Cannot perform ECDH: %s", error);
+ return -1;
+ }
+
+ /* hash the secret data */
+ hash->init(hctx);
+ hash->loop(hctx, secret->data, secret->used);
+ hash->result(hctx, hres);
+ safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used);
+
+ /* use it to encrypt the actual encryption key */
+ struct dcrypt_context_symmetric *dctx;
+ if (!dcrypt_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_ENCRYPT,
+ &dctx, &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Key encryption error: %s", error);
+ return -1;
+ }
+
+ random_fill(seed, sizeof(seed));
+ hash->init(hctx);
+ hash->loop(hctx, seed, sizeof(seed));
+ hash->result(hctx, ekhash);
+
+ int ec = 0;
+
+ /* NB! The old code was broken and used this kind of IV - it is not
+ correct, but we need to stay compatible with old data */
+ dcrypt_ctx_sym_set_iv(dctx, (const unsigned char*)
+ "\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00", 16);
+ dcrypt_ctx_sym_set_key(dctx, hres, sizeof(hres));
+
+ if (!dcrypt_ctx_sym_init(dctx, &error) ||
+ !dcrypt_ctx_sym_update(dctx, seed, sizeof(seed),
+ encrypted_key, &error) ||
+ !dcrypt_ctx_sym_final(dctx, encrypted_key, &error)) {
+ ec = -1;
+ }
+ dcrypt_ctx_sym_destroy(&dctx);
+
+ if (ec != 0) {
+ safe_memset(seed, 0, sizeof(seed));
+ io_stream_set_error(&stream->ostream.iostream,
+ "Key encryption error: %s", error);
+ return -1;
+ }
+
+ /* same as above */
+ dcrypt_ctx_sym_set_iv(stream->ctx_sym, (const unsigned char*)
+ "\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00", 16);
+ dcrypt_ctx_sym_set_key(stream->ctx_sym, seed, sizeof(seed));
+ safe_memset(seed, 0, sizeof(seed));
+
+ if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Encryption init error: %s", error);
+ return -1;
+ }
+
+ res = buffer_create_dynamic(default_pool, 256);
+
+ /* ephemeral key */
+ unsigned short s;
+ s = htons(ephemeral_key->used);
+ buffer_append(res, &s, 2);
+ buffer_append(res, ephemeral_key->data, ephemeral_key->used);
+ /* public key hash */
+ s = htons(sizeof(pkhash));
+ buffer_append(res, &s, 2);
+ buffer_append(res, pkhash, sizeof(pkhash));
+ /* encrypted key hash */
+ s = htons(sizeof(ekhash));
+ buffer_append(res, &s, 2);
+ buffer_append(res, ekhash, sizeof(ekhash));
+ /* encrypted key */
+ s = htons(encrypted_key->used);
+ buffer_append(res, &s, 2);
+ buffer_append(res, encrypted_key->data, encrypted_key->used);
+
+ stream->key_data_len = res->used;
+ stream->key_data = buffer_free_without_data(&res);
+
+ return 0;
+}
+
+static int
+o_stream_encrypt_key_for_pubkey_v2(struct encrypt_ostream *stream,
+ const char *malg, const unsigned char *key,
+ size_t key_len,
+ struct dcrypt_public_key *pubkey,
+ buffer_t *res)
+{
+ enum dcrypt_key_type ktype;
+ const char *error;
+ buffer_t *encrypted_key, *ephemeral_key, *temp_key;
+
+ ephemeral_key = t_buffer_create(256);
+ encrypted_key = t_buffer_create(256);
+ temp_key = t_buffer_create(48);
+
+ ktype = dcrypt_key_type_public(pubkey);
+
+ if (ktype == DCRYPT_KEY_RSA) {
+ /* encrypt key as R (as we don't need DH with RSA)*/
+ if (!dcrypt_rsa_encrypt(pubkey, key, key_len, encrypted_key,
+ DCRYPT_PADDING_RSA_PKCS1_OAEP,
+ &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Cannot encrypt key data: %s",
+ error);
+ return -1;
+ }
+ } else if (ktype == DCRYPT_KEY_EC) {
+ /* R = our ephemeral public key */
+ buffer_t *secret = t_buffer_create(256);
+
+ /* derive ephemeral key and shared secret */
+ if (!dcrypt_ecdh_derive_secret_peer(pubkey, ephemeral_key,
+ secret, &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Cannot perform ECDH: %s", error);
+ return -1;
+ }
+
+ /* use shared secret and ephemeral key to generate encryption
+ key/iv */
+ if (!dcrypt_pbkdf2(secret->data, secret->used,
+ ephemeral_key->data, ephemeral_key->used,
+ malg, IO_STREAM_ENCRYPT_ROUNDS, temp_key,
+ 48, &error)) {
+ safe_memset(buffer_get_modifiable_data(secret, 0),
+ 0, secret->used);
+ io_stream_set_error(&stream->ostream.iostream,
+ "Cannot perform key encryption: %s",
+ error);
+ }
+ safe_memset(buffer_get_modifiable_data(secret, 0),
+ 0, secret->used);
+
+ /* encrypt key with shared secret */
+ struct dcrypt_context_symmetric *dctx;
+ if (!dcrypt_ctx_sym_create("AES-256-CBC", DCRYPT_MODE_ENCRYPT,
+ &dctx, &error)) {
+ safe_memset(buffer_get_modifiable_data(temp_key, 0),
+ 0, temp_key->used);
+ io_stream_set_error(&stream->ostream.iostream,
+ "Cannot perform key encryption: %s",
+ error);
+ return -1;
+ }
+
+ const unsigned char *ptr = temp_key->data;
+ i_assert(temp_key->used == 48);
+
+ dcrypt_ctx_sym_set_key(dctx, ptr, 32);
+ dcrypt_ctx_sym_set_iv(dctx, ptr+32, 16);
+ safe_memset(buffer_get_modifiable_data(temp_key, 0),
+ 0, temp_key->used);
+
+ int ec = 0;
+ if (!dcrypt_ctx_sym_init(dctx, &error) ||
+ !dcrypt_ctx_sym_update(dctx, key, key_len,
+ encrypted_key, &error) ||
+ !dcrypt_ctx_sym_final(dctx, encrypted_key, &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Cannot perform key encryption: %s",
+ error);
+ ec = -1;
+ }
+
+ dcrypt_ctx_sym_destroy(&dctx);
+ if (ec != 0) return ec;
+ } else {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Unsupported key type");
+ return -1;
+ }
+
+ /* store key type */
+ char kt = ktype;
+ buffer_append(res, &kt, 1);
+ /* store hash of public key as ID */
+ dcrypt_key_id_public(stream->pub, "sha256", res, NULL);
+ /* store ephemeral key (if present) */
+ unsigned int val = cpu32_to_be(ephemeral_key->used);
+ buffer_append(res, &val, 4);
+ buffer_append_buf(res, ephemeral_key, 0, SIZE_MAX);
+ /* store encrypted key */
+ val = cpu32_to_be(encrypted_key->used);
+ buffer_append(res, &val, 4);
+ buffer_append_buf(res, encrypted_key, 0, SIZE_MAX);
+
+ return 0;
+}
+
+static int
+o_stream_encrypt_keydata_create_v2(struct encrypt_ostream *stream,
+ const char *malg)
+{
+ const struct hash_method *hash = hash_method_lookup(malg);
+ const char *error;
+ size_t tagsize;
+ const unsigned char *ptr;
+ size_t kl;
+ unsigned int val;
+
+ buffer_t *keydata, *res;
+
+ if (hash == NULL) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Encryption init error: "
+ "Hash algorithm '%s' not supported", malg);
+ return -1;
+ }
+
+ /* key data length for internal use */
+ if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) ==
+ IO_STREAM_ENC_INTEGRITY_HMAC) {
+ tagsize = IOSTREAM_TAG_SIZE;
+ } else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) ==
+ IO_STREAM_ENC_INTEGRITY_AEAD) {
+ tagsize = IOSTREAM_TAG_SIZE;
+ } else {
+ /* do not include MAC */
+ tagsize = 0;
+ }
+
+ /* generate keydata length of random data for key/iv/mac */
+ kl = dcrypt_ctx_sym_get_key_length(stream->ctx_sym) +
+ dcrypt_ctx_sym_get_iv_length(stream->ctx_sym) + tagsize;
+ keydata = t_buffer_create(kl);
+ random_fill(buffer_append_space_unsafe(keydata, kl), kl);
+ buffer_set_used_size(keydata, kl);
+ ptr = keydata->data;
+
+ res = buffer_create_dynamic(default_pool, 256);
+
+ /* store number of public key(s) */
+ buffer_append(res, "\1", 1); /* one key for now */
+
+ /* we can do multiple keys at this point, but do it only once now */
+ if (o_stream_encrypt_key_for_pubkey_v2(stream, malg, ptr, kl,
+ stream->pub, res) != 0) {
+ buffer_free(&res);
+ return -1;
+ }
+
+ /* create hash of the key data */
+ unsigned char hctx[hash->context_size];
+ unsigned char hres[hash->digest_size];
+ hash->init(hctx);
+ hash->loop(hctx, ptr, kl);
+ hash->result(hctx, hres);
+
+ for(int i = 1; i < 2049; i++) {
+ uint32_t i_msb = cpu32_to_be(i);
+
+ hash->init(hctx);
+ hash->loop(hctx, hres, sizeof(hres));
+ hash->loop(hctx, &i_msb, sizeof(i_msb));
+ hash->result(hctx, hres);
+ }
+
+ /* store key data hash */
+ val = cpu32_to_be(sizeof(hres));
+ buffer_append(res, &val, 4);
+ buffer_append(res, hres, sizeof(hres));
+
+ /* pick up key data that goes into stream */
+ stream->key_data_len = res->used;
+ stream->key_data = buffer_free_without_data(&res);
+
+ /* prime contexts */
+ dcrypt_ctx_sym_set_key(stream->ctx_sym, ptr,
+ dcrypt_ctx_sym_get_key_length(stream->ctx_sym));
+ ptr += dcrypt_ctx_sym_get_key_length(stream->ctx_sym);
+ dcrypt_ctx_sym_set_iv(stream->ctx_sym, ptr,
+ dcrypt_ctx_sym_get_iv_length(stream->ctx_sym));
+ ptr += dcrypt_ctx_sym_get_iv_length(stream->ctx_sym);
+
+ if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) ==
+ IO_STREAM_ENC_INTEGRITY_HMAC) {
+ dcrypt_ctx_hmac_set_key(stream->ctx_mac, ptr, tagsize);
+ dcrypt_ctx_hmac_init(stream->ctx_mac, &error);
+ } else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) ==
+ IO_STREAM_ENC_INTEGRITY_AEAD) {
+ dcrypt_ctx_sym_set_aad(stream->ctx_sym, ptr, tagsize);
+ }
+
+ /* clear out private key data */
+ safe_memset(buffer_get_modifiable_data(keydata, 0), 0, keydata->used);
+
+ if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) {
+ io_stream_set_error(&stream->ostream.iostream,
+ "Encryption init error: %s", error);
+ return -1;
+ }
+ return 0;
+}
+
+static ssize_t
+o_stream_encrypt_sendv(struct ostream_private *stream,
+ const struct const_iovec *iov, unsigned int iov_count)
+{
+ struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+ const char *error;
+ ssize_t ec,total = 0;
+
+ /* not if finalized */
+ i_assert(!estream->finalized);
+
+ /* write prefix */
+ if (!estream->prefix_written) {
+ T_BEGIN {
+ if ((estream->flags & IO_STREAM_ENC_VERSION_1) ==
+ IO_STREAM_ENC_VERSION_1)
+ ec = o_stream_encrypt_send_header_v1(estream);
+ else
+ ec = o_stream_encrypt_send_header_v2(estream);
+ } T_END;
+ if (ec < 0) {
+ return -1;
+ }
+ }
+
+ /* buffer for encrypted data */
+ unsigned char ciphertext[IO_BLOCK_SIZE];
+ buffer_t buf;
+ buffer_create_from_data(&buf, ciphertext, sizeof(ciphertext));
+
+ /* encrypt & send all blocks of data at max ciphertext buffer's
+ length */
+ for(unsigned int i = 0; i < iov_count; i++) {
+ size_t bl, off = 0, len = iov[i].iov_len;
+ const unsigned char *ptr = iov[i].iov_base;
+ while(len > 0) {
+ buffer_set_used_size(&buf, 0);
+ /* update can emite twice the size of input */
+ bl = I_MIN(sizeof(ciphertext)/2, len);
+
+ if (!dcrypt_ctx_sym_update(estream->ctx_sym, ptr + off,
+ bl, &buf, &error)) {
+ io_stream_set_error(&stream->iostream,
+ "Encryption failure: %s",
+ error);
+ return -1;
+ }
+ if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) ==
+ IO_STREAM_ENC_INTEGRITY_HMAC) {
+ /* update mac */
+ if (!dcrypt_ctx_hmac_update(estream->ctx_mac,
+ buf.data, buf.used, &error)) {
+ io_stream_set_error(&stream->iostream,
+ "MAC failure: %s", error);
+ return -1;
+ }
+ }
+
+ /* hopefully upstream can accommodate */
+ if (o_stream_encrypt_send(estream, buf.data, buf.used) < 0) {
+ return -1;
+ }
+
+ len -= bl;
+ off += bl;
+ total += bl;
+ }
+ }
+
+ stream->ostream.offset += total;
+ return total;
+}
+
+static int
+o_stream_encrypt_finalize(struct ostream_private *stream)
+{
+ const char *error;
+ struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+
+ if (estream->finalized) {
+ /* we've already flushed the encrypted output. */
+ return 0;
+ }
+ estream->finalized = TRUE;
+
+ /* if nothing was written, we are done */
+ if (!estream->prefix_written) return 0;
+
+ /* acquire last block */
+ buffer_t *buf = t_buffer_create(
+ dcrypt_ctx_sym_get_block_size(estream->ctx_sym));
+ if (!dcrypt_ctx_sym_final(estream->ctx_sym, buf, &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Encryption failure: %s", error);
+ return -1;
+ }
+ /* sometimes final does not emit anything */
+ if (buf->used > 0) {
+ /* update mac */
+ if (((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) ==
+ IO_STREAM_ENC_INTEGRITY_HMAC)) {
+ if (!dcrypt_ctx_hmac_update(estream->ctx_mac, buf->data,
+ buf->used, &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "MAC failure: %s", error);
+ return -1;
+ }
+ }
+ if (o_stream_encrypt_send(estream, buf->data, buf->used) < 0) {
+ return -1;
+ }
+ }
+
+ /* write last mac bytes */
+ buffer_set_used_size(buf, 0);
+ if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) ==
+ IO_STREAM_ENC_INTEGRITY_HMAC) {
+ if (!dcrypt_ctx_hmac_final(estream->ctx_mac, buf, &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "MAC failure: %s", error);
+ return -1;
+ }
+ } else if ((estream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) ==
+ IO_STREAM_ENC_INTEGRITY_AEAD) {
+ dcrypt_ctx_sym_get_tag(estream->ctx_sym, buf);
+ i_assert(buf->used > 0);
+ }
+ if (buf->used > 0 &&
+ o_stream_encrypt_send(estream, buf->data, buf->used) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+o_stream_encrypt_flush(struct ostream_private *stream)
+{
+ struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+
+ if (stream->finished && estream->ctx_sym != NULL &&
+ !estream->finalized) {
+ if (o_stream_encrypt_finalize(&estream->ostream) < 0)
+ return -1;
+ }
+
+ return o_stream_flush_parent(stream);
+}
+
+static void
+o_stream_encrypt_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+
+ i_assert(estream->finalized || estream->ctx_sym == NULL ||
+ estream->ostream.ostream.stream_errno != 0);
+ if (close_parent)
+ o_stream_close(estream->ostream.parent);
+}
+
+static void
+o_stream_encrypt_destroy(struct iostream_private *stream)
+{
+ struct encrypt_ostream *estream = (struct encrypt_ostream *)stream;
+
+ /* release resources */
+ if (estream->ctx_sym != NULL)
+ dcrypt_ctx_sym_destroy(&estream->ctx_sym);
+ if (estream->ctx_mac != NULL)
+ dcrypt_ctx_hmac_destroy(&estream->ctx_mac);
+ if (estream->key_data != NULL)
+ i_free(estream->key_data);
+ if (estream->cipher_oid != NULL)
+ buffer_free(&estream->cipher_oid);
+ if (estream->mac_oid != NULL)
+ buffer_free(&estream->mac_oid);
+ if (estream->pub != NULL)
+ dcrypt_key_unref_public(&estream->pub);
+ o_stream_unref(&estream->ostream.parent);
+}
+
+static int
+o_stream_encrypt_init(struct encrypt_ostream *estream, const char *algorithm)
+{
+ const char *error;
+ char *calg, *malg;
+
+ if ((estream->flags & IO_STREAM_ENC_VERSION_1) ==
+ IO_STREAM_ENC_VERSION_1) {
+ if (!dcrypt_ctx_sym_create("AES-256-CTR", DCRYPT_MODE_ENCRYPT,
+ &estream->ctx_sym, &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Cannot create ostream-encrypt: %s",
+ error);
+ return -1;
+ }
+ /* disable MAC */
+ estream->flags |= IO_STREAM_ENC_INTEGRITY_NONE;
+ /* then do keying */
+ return o_stream_encrypt_keydata_create_v1(estream);
+ } else {
+ calg = t_strdup_noconst(algorithm);
+ malg = strrchr(calg, '-');
+
+ if (malg == NULL) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Invalid algorithm "
+ "(must be cipher-mac)");
+ return -1;
+ }
+ (*malg++) = '\0';
+
+ if (!dcrypt_ctx_sym_create(calg, DCRYPT_MODE_ENCRYPT,
+ &estream->ctx_sym, &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Cannot create ostream-encrypt: %s",
+ error);
+ return -1;
+ }
+
+ /* create cipher and mac context, take note of OIDs */
+ estream->cipher_oid = buffer_create_dynamic(default_pool, 12);
+ estream->block_size =
+ dcrypt_ctx_sym_get_block_size(estream->ctx_sym);
+ if (!dcrypt_name2oid(calg, estream->cipher_oid, &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Cannot create ostream-encrypt: %s",
+ error);
+ return -1;
+ }
+
+ /* mac context is optional */
+ if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) ==
+ IO_STREAM_ENC_INTEGRITY_HMAC) {
+ if (!dcrypt_ctx_hmac_create(malg, &estream->ctx_mac,
+ &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Cannot create ostream-encrypt: %s",
+ error);
+ return -1;
+ }
+ }
+
+ estream->mac_oid = buffer_create_dynamic(default_pool, 12);
+ if (!dcrypt_name2oid(malg, estream->mac_oid, &error)) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Cannot create ostream-encrypt: %s", error);
+ return -1;
+ }
+
+ /* MAC algorithm is used for PBKDF2 and keydata hashing */
+ return o_stream_encrypt_keydata_create_v2(estream, malg);
+ }
+}
+
+static struct encrypt_ostream *
+o_stream_create_encrypt_common(enum io_stream_encrypt_flags flags)
+{
+ struct encrypt_ostream *estream;
+
+ estream = i_new(struct encrypt_ostream, 1);
+ estream->ostream.sendv = o_stream_encrypt_sendv;
+ estream->ostream.flush = o_stream_encrypt_flush;
+ estream->ostream.iostream.close = o_stream_encrypt_close;
+ estream->ostream.iostream.destroy = o_stream_encrypt_destroy;
+
+ estream->flags = flags;
+
+ return estream;
+}
+
+struct ostream *
+o_stream_create_encrypt(struct ostream *output, const char *algorithm,
+ struct dcrypt_public_key *box_pub, enum io_stream_encrypt_flags flags)
+{
+ struct encrypt_ostream *estream = o_stream_create_encrypt_common(flags);
+ int ec;
+
+ dcrypt_key_ref_public(box_pub);
+ estream->pub = box_pub;
+
+ T_BEGIN {
+ ec = o_stream_encrypt_init(estream, algorithm);
+ } T_END;
+
+ struct ostream *os = o_stream_create(&estream->ostream, output,
+ o_stream_get_fd(output));
+
+ if (ec != 0) {
+ os->stream_errno = EINVAL;
+ }
+
+ return os;
+}
+
+struct ostream *
+o_stream_create_sym_encrypt(struct ostream *output,
+ struct dcrypt_context_symmetric *ctx)
+{
+ struct encrypt_ostream *estream =
+ o_stream_create_encrypt_common(IO_STREAM_ENC_INTEGRITY_NONE);
+ const char *error;
+ int ec;
+
+ estream->prefix_written = TRUE;
+
+ if (!dcrypt_ctx_sym_init(estream->ctx_sym, &error))
+ ec = -1;
+ else
+ ec = 0;
+
+ estream->ctx_sym = ctx;
+
+ struct ostream *os = o_stream_create(&estream->ostream, output,
+ o_stream_get_fd(output));
+ if (ec != 0) {
+ io_stream_set_error(&estream->ostream.iostream,
+ "Could not initialize stream: %s",
+ error);
+ os->stream_errno = EINVAL;
+ }
+
+ return os;
+}