diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:32:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:32:49 +0000 |
commit | 8053187731ae8e3eb368d8360989cf5fd6eed9f7 (patch) | |
tree | 32bada84ff5d7460cdf3934fcbdbe770d6afe4cd /src/librepgp/stream-write.cpp | |
parent | Initial commit. (diff) | |
download | rnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.tar.xz rnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.zip |
Adding upstream version 0.17.0.upstream/0.17.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/librepgp/stream-write.cpp')
-rw-r--r-- | src/librepgp/stream-write.cpp | 1973 |
1 files changed, 1973 insertions, 0 deletions
diff --git a/src/librepgp/stream-write.cpp b/src/librepgp/stream-write.cpp new file mode 100644 index 0000000..60d867a --- /dev/null +++ b/src/librepgp/stream-write.cpp @@ -0,0 +1,1973 @@ +/* + * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <sys/param.h> +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif +#include <rnp/rnp_def.h> +#include "stream-def.h" +#include "stream-ctx.h" +#include "stream-write.h" +#include "stream-packet.h" +#include "stream-armor.h" +#include "stream-sig.h" +#include "pgp-key.h" +#include "fingerprint.h" +#include "types.h" +#include "crypto/signatures.h" +#include "defaults.h" +#include <time.h> +#include <algorithm> + +/* 8192 bytes, as GnuPG */ +#define PGP_PARTIAL_PKT_SIZE_BITS (13) +#define PGP_PARTIAL_PKT_BLOCK_SIZE (1 << PGP_PARTIAL_PKT_SIZE_BITS) + +/* common fields for encrypted, compressed and literal data */ +typedef struct pgp_dest_packet_param_t { + pgp_dest_t *writedst; /* destination to write to, could be partial */ + pgp_dest_t *origdst; /* original dest passed to init_*_dst */ + bool partial; /* partial length packet */ + bool indeterminate; /* indeterminate length packet */ + int tag; /* packet tag */ + uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* header, including length, as it was written */ + size_t hdrlen; /* number of bytes in hdr */ +} pgp_dest_packet_param_t; + +typedef struct pgp_dest_compressed_param_t { + pgp_dest_packet_param_t pkt; + pgp_compression_type_t alg; + union { + z_stream z; + bz_stream bz; + }; + bool zstarted; /* whether we initialize zlib/bzip2 */ + uint8_t cache[PGP_INPUT_CACHE_SIZE / 2]; /* pre-allocated cache for compression */ + size_t len; /* number of bytes cached */ +} pgp_dest_compressed_param_t; + +typedef struct pgp_dest_encrypted_param_t { + pgp_dest_packet_param_t pkt; /* underlying packet-related params */ + rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ + rnp::AuthType auth_type; /* Authentication type: MDC, AEAD or none */ + pgp_crypt_t encrypt; /* encrypting crypto */ + std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */ + pgp_aead_alg_t aalg; /* AEAD algorithm used */ + uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* iv for AEAD mode */ + uint8_t ad[PGP_AEAD_MAX_AD_LEN]; /* additional data for AEAD mode */ + size_t adlen; /* length of additional data, including chunk idx */ + size_t chunklen; /* length of the AEAD chunk in bytes */ + size_t chunkout; /* how many bytes from the chunk were written out */ + size_t chunkidx; /* index of the current AEAD chunk */ + size_t cachelen; /* how many bytes are in cache, for AEAD */ + uint8_t cache[PGP_AEAD_CACHE_LEN]; /* pre-allocated cache for encryption */ +} pgp_dest_encrypted_param_t; + +typedef struct pgp_dest_signer_info_t { + pgp_one_pass_sig_t onepass; + pgp_key_t * key; + pgp_hash_alg_t halg; + int64_t sigcreate; + uint64_t sigexpire; +} pgp_dest_signer_info_t; + +typedef struct pgp_dest_signed_param_t { + pgp_dest_t * writedst; /* destination to write to */ + rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ + pgp_password_provider_t *password_provider; /* password provider from write handler */ + std::vector<pgp_dest_signer_info_t> siginfos; /* list of pgp_dest_signer_info_t */ + rnp::HashList hashes; /* hashes to pass raw data through and then sign */ + bool clr_start; /* we are on the start of the line */ + uint8_t clr_buf[CT_BUF_LEN]; /* buffer to hold partial line data */ + size_t clr_buflen; /* number of bytes in buffer */ + + pgp_dest_signed_param_t() = default; + ~pgp_dest_signed_param_t() = default; +} pgp_dest_signed_param_t; + +typedef struct pgp_dest_partial_param_t { + pgp_dest_t *writedst; + uint8_t part[PGP_PARTIAL_PKT_BLOCK_SIZE]; + uint8_t parthdr; /* header byte for the current part */ + size_t partlen; /* length of the current part, up to PARTIAL_PKT_BLOCK_SIZE */ + size_t len; /* bytes cached in part */ +} pgp_dest_partial_param_t; + +static rnp_result_t +partial_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (len > param->partlen - param->len) { + /* we have full part - in block and in buf */ + size_t wrlen = param->partlen - param->len; + dst_write(param->writedst, ¶m->parthdr, 1); + dst_write(param->writedst, param->part, param->len); + dst_write(param->writedst, buf, wrlen); + + buf = (uint8_t *) buf + wrlen; + len -= wrlen; + param->len = 0; + + /* writing all full parts directly from buf */ + while (len >= param->partlen) { + dst_write(param->writedst, ¶m->parthdr, 1); + dst_write(param->writedst, buf, param->partlen); + buf = (uint8_t *) buf + param->partlen; + len -= param->partlen; + } + } + + /* caching rest of the buf */ + if (len > 0) { + memcpy(¶m->part[param->len], buf, len); + param->len += len; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +partial_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + uint8_t hdr[5]; + int lenlen; + + lenlen = write_packet_len(hdr, param->len); + dst_write(param->writedst, hdr, lenlen); + dst_write(param->writedst, param->part, param->len); + + return param->writedst->werr; +} + +static void +partial_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + + if (!param) { + return; + } + + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_partial_pkt_dst(pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_partial_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_partial_param_t *) dst->param; + param->writedst = writedst; + param->partlen = PGP_PARTIAL_PKT_BLOCK_SIZE; + param->parthdr = 0xE0 | PGP_PARTIAL_PKT_SIZE_BITS; + dst->param = param; + dst->write = partial_dst_write; + dst->finish = partial_dst_finish; + dst->close = partial_dst_close; + dst->type = PGP_STREAM_PARLEN_PACKET; + + return RNP_SUCCESS; +} + +/** @brief helper function for streamed packets (literal, encrypted and compressed). + * Allocates part len destination if needed and writes header + **/ +static bool +init_streamed_packet(pgp_dest_packet_param_t *param, pgp_dest_t *dst) +{ + rnp_result_t ret; + + if (param->partial) { + param->hdr[0] = param->tag | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + dst_write(dst, ¶m->hdr, 1); + + if ((param->writedst = (pgp_dest_t *) calloc(1, sizeof(*param->writedst))) == NULL) { + RNP_LOG("part len dest allocation failed"); + return false; + } + ret = init_partial_pkt_dst(param->writedst, dst); + if (ret != RNP_SUCCESS) { + free(param->writedst); + param->writedst = NULL; + return false; + } + param->origdst = dst; + + param->hdr[1] = ((pgp_dest_partial_param_t *) param->writedst->param)->parthdr; + param->hdrlen = 2; + return true; + } + + if (param->indeterminate) { + if (param->tag > 0xf) { + RNP_LOG("indeterminate tag > 0xf"); + } + + param->hdr[0] = ((param->tag & 0xf) << PGP_PTAG_OF_CONTENT_TAG_SHIFT) | + PGP_PTAG_OLD_LEN_INDETERMINATE; + param->hdrlen = 1; + dst_write(dst, ¶m->hdr, 1); + + param->writedst = dst; + param->origdst = dst; + return true; + } + + RNP_LOG("wrong call"); + return false; +} + +static rnp_result_t +finish_streamed_packet(pgp_dest_packet_param_t *param) +{ + if (param->partial) { + return dst_finish(param->writedst); + } + return RNP_SUCCESS; +} + +static void +close_streamed_packet(pgp_dest_packet_param_t *param, bool discard) +{ + if (param->partial) { + dst_close(param->writedst, discard); + free(param->writedst); + param->writedst = NULL; + } +} + +static rnp_result_t +encrypted_dst_write_cfb(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + size_t sz; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (param->auth_type == rnp::AuthType::MDC) { + try { + param->mdc->add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + while (len > 0) { + sz = len > sizeof(param->cache) ? sizeof(param->cache) : len; + pgp_cipher_cfb_encrypt(¶m->encrypt, param->cache, (const uint8_t *) buf, sz); + dst_write(param->pkt.writedst, param->cache, sz); + len -= sz; + buf = (uint8_t *) buf + sz; + } + + return RNP_SUCCESS; +} + +#if defined(ENABLE_AEAD) +static rnp_result_t +encrypted_start_aead_chunk(pgp_dest_encrypted_param_t *param, size_t idx, bool last) +{ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + size_t taglen; + bool res; + uint64_t total; + + taglen = pgp_cipher_aead_tag_len(param->aalg); + + /* finish the previous chunk if needed*/ + if ((idx > 0) && (param->chunkout + param->cachelen > 0)) { + if (param->cachelen + taglen > sizeof(param->cache)) { + RNP_LOG("wrong state in aead"); + return RNP_ERROR_BAD_STATE; + } + + if (!pgp_cipher_aead_finish( + ¶m->encrypt, param->cache, param->cache, param->cachelen)) { + return RNP_ERROR_BAD_STATE; + } + + dst_write(param->pkt.writedst, param->cache, param->cachelen + taglen); + } + + /* set chunk index for additional data */ + STORE64BE(param->ad + param->adlen - 8, idx); + + if (last) { + if (!(param->chunkout + param->cachelen)) { + /* we need to clearly reset it since cipher was initialized but not finished */ + pgp_cipher_aead_reset(¶m->encrypt); + } + + total = idx * param->chunklen; + if (param->cachelen + param->chunkout) { + if (param->chunklen < (param->cachelen + param->chunkout)) { + RNP_LOG("wrong last chunk state in aead"); + return RNP_ERROR_BAD_STATE; + } + total -= param->chunklen - param->cachelen - param->chunkout; + } + + STORE64BE(param->ad + param->adlen, total); + param->adlen += 8; + } + if (!pgp_cipher_aead_set_ad(¶m->encrypt, param->ad, param->adlen)) { + RNP_LOG("failed to set ad"); + return RNP_ERROR_BAD_STATE; + } + + /* set chunk index for nonce */ + nlen = pgp_cipher_aead_nonce(param->aalg, param->iv, nonce, idx); + + /* start cipher */ + res = pgp_cipher_aead_start(¶m->encrypt, nonce, nlen); + + /* write final authentication tag */ + if (last) { + res = res && pgp_cipher_aead_finish(¶m->encrypt, param->cache, param->cache, 0); + if (res) { + dst_write(param->pkt.writedst, param->cache, taglen); + } + } + + param->chunkidx = idx; + param->chunkout = 0; + + return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +#endif + +static rnp_result_t +encrypted_dst_write_aead(pgp_dest_t *dst, const void *buf, size_t len) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + return RNP_ERROR_WRITE; +#else + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + size_t sz; + size_t gran; + rnp_result_t res; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!len) { + return RNP_SUCCESS; + } + + /* because of botan's FFI granularity we need to make things a bit complicated */ + gran = pgp_cipher_aead_granularity(¶m->encrypt); + + if (param->cachelen > param->chunklen - param->chunkout) { + RNP_LOG("wrong AEAD cache state"); + return RNP_ERROR_BAD_STATE; + } + + while (len > 0) { + sz = std::min(sizeof(param->cache) - PGP_AEAD_MAX_TAG_LEN - param->cachelen, len); + sz = std::min(sz, param->chunklen - param->chunkout - param->cachelen); + memcpy(param->cache + param->cachelen, buf, sz); + param->cachelen += sz; + + if (param->cachelen == param->chunklen - param->chunkout) { + /* we have the tail of the chunk in cache */ + if ((res = encrypted_start_aead_chunk(param, param->chunkidx + 1, false))) { + return res; + } + param->cachelen = 0; + } else if (param->cachelen >= gran) { + /* we have part of the chunk - so need to adjust it to the granularity */ + size_t gransz = param->cachelen - param->cachelen % gran; + if (!pgp_cipher_aead_update(¶m->encrypt, param->cache, param->cache, gransz)) { + return RNP_ERROR_BAD_STATE; + } + dst_write(param->pkt.writedst, param->cache, gransz); + memmove(param->cache, param->cache + gransz, param->cachelen - gransz); + param->cachelen -= gransz; + param->chunkout += gransz; + } + + len -= sz; + buf = (uint8_t *) buf + sz; + } + + return RNP_SUCCESS; +#endif +} + +static rnp_result_t +encrypted_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + if (param->auth_type == rnp::AuthType::AEADv1) { +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + rnp_result_t res = RNP_ERROR_NOT_IMPLEMENTED; +#else + size_t chunks = param->chunkidx; + /* if we didn't write anything in current chunk then discard it and restart */ + if (param->chunkout || param->cachelen) { + chunks++; + } + + rnp_result_t res = encrypted_start_aead_chunk(param, chunks, true); + pgp_cipher_aead_destroy(¶m->encrypt); +#endif + if (res) { + finish_streamed_packet(¶m->pkt); + return res; + } + } else if (param->auth_type == rnp::AuthType::MDC) { + uint8_t mdcbuf[MDC_V1_SIZE]; + mdcbuf[0] = MDC_PKT_TAG; + mdcbuf[1] = MDC_V1_SIZE - 2; + try { + param->mdc->add(mdcbuf, 2); + param->mdc->finish(&mdcbuf[2]); + param->mdc = nullptr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + pgp_cipher_cfb_encrypt(¶m->encrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); + dst_write(param->pkt.writedst, mdcbuf, MDC_V1_SIZE); + } + + return finish_streamed_packet(¶m->pkt); +} + +static void +encrypted_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + if (!param) { + return; + } + + if (param->auth_type == rnp::AuthType::AEADv1) { +#if defined(ENABLE_AEAD) + pgp_cipher_aead_destroy(¶m->encrypt); +#endif + } else { + pgp_cipher_cfb_finish(¶m->encrypt); + } + close_streamed_packet(¶m->pkt, discard); + delete param; + dst->param = NULL; +} + +static rnp_result_t +encrypted_add_recipient(pgp_write_handler_t *handler, + pgp_dest_t * dst, + pgp_key_t * userkey, + const uint8_t * key, + const unsigned keylen) +{ + pgp_pk_sesskey_t pkey; + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* Use primary key if good for encryption, otherwise look in subkey list */ + userkey = find_suitable_key(PGP_OP_ENCRYPT, userkey, handler->key_provider); + if (!userkey) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + /* Fill pkey */ + pkey.version = PGP_PKSK_V3; + pkey.alg = userkey->alg(); + pkey.key_id = userkey->keyid(); + + /* Encrypt the session key */ + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 3> enckey; + enckey[0] = param->ctx->ealg; + memcpy(&enckey[1], key, keylen); + + /* Calculate checksum */ + rnp::secure_array<unsigned, 1> checksum; + + for (unsigned i = 1; i <= keylen; i++) { + checksum[0] += enckey[i]; + } + enckey[keylen + 1] = (checksum[0] >> 8) & 0xff; + enckey[keylen + 2] = checksum[0] & 0xff; + + pgp_encrypted_material_t material; + + switch (userkey->alg()) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: { + ret = rsa_encrypt_pkcs1(&handler->ctx->ctx->rng, + &material.rsa, + enckey.data(), + keylen + 3, + &userkey->material().rsa); + if (ret) { + RNP_LOG("rsa_encrypt_pkcs1 failed"); + return ret; + } + break; + } + case PGP_PKA_SM2: { +#if defined(ENABLE_SM2) + ret = sm2_encrypt(&handler->ctx->ctx->rng, + &material.sm2, + enckey.data(), + keylen + 3, + PGP_HASH_SM3, + &userkey->material().ec); + if (ret) { + RNP_LOG("sm2_encrypt failed"); + return ret; + } + break; +#else + RNP_LOG("sm2_encrypt is not available"); + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + } + case PGP_PKA_ECDH: { + if (!curve_supported(userkey->material().ec.curve)) { + RNP_LOG("ECDH encrypt: curve %d is not supported.", + (int) userkey->material().ec.curve); + return RNP_ERROR_NOT_SUPPORTED; + } + ret = ecdh_encrypt_pkcs5(&handler->ctx->ctx->rng, + &material.ecdh, + enckey.data(), + keylen + 3, + &userkey->material().ec, + userkey->fp()); + if (ret) { + RNP_LOG("ECDH encryption failed %d", ret); + return ret; + } + break; + } + case PGP_PKA_ELGAMAL: { + ret = elgamal_encrypt_pkcs1(&handler->ctx->ctx->rng, + &material.eg, + enckey.data(), + keylen + 3, + &userkey->material().eg); + if (ret) { + RNP_LOG("pgp_elgamal_public_encrypt failed"); + return ret; + } + break; + } + default: + RNP_LOG("unsupported alg: %d", (int) userkey->alg()); + return ret; + } + + /* Writing symmetric key encrypted session key packet */ + try { + pkey.write_material(material); + pkey.write(*param->pkt.origdst); + return param->pkt.origdst->werr; + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey) +{ + uint8_t ad_data[4]; + + ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + ad_data[1] = skey->version; + ad_data[2] = skey->alg; + ad_data[3] = skey->aalg; + + return pgp_cipher_aead_set_ad(crypt, ad_data, 4); +} +#endif + +static rnp_result_t +encrypted_add_password(rnp_symmetric_pass_info_t * pass, + pgp_dest_encrypted_param_t *param, + uint8_t * key, + const unsigned keylen, + bool singlepass) +{ + pgp_sk_sesskey_t skey = {}; + pgp_crypt_t kcrypt; + + skey.s2k = pass->s2k; + + if (param->auth_type != rnp::AuthType::AEADv1) { + skey.version = PGP_SKSK_V4; + if (singlepass) { + /* if there are no public keys then we do not encrypt session key in the packet */ + skey.alg = param->ctx->ealg; + skey.enckeylen = 0; + memcpy(key, pass->key.data(), keylen); + } else { + /* We may use different algo for CEK and KEK */ + skey.enckeylen = keylen + 1; + skey.enckey[0] = param->ctx->ealg; + memcpy(&skey.enckey[1], key, keylen); + skey.alg = pass->s2k_cipher; + if (!pgp_cipher_cfb_start(&kcrypt, skey.alg, pass->key.data(), NULL)) { + RNP_LOG("key encryption failed"); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_cipher_cfb_encrypt(&kcrypt, skey.enckey, skey.enckey, skey.enckeylen); + pgp_cipher_cfb_finish(&kcrypt); + } + } else { +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD support is not enabled."); + return RNP_ERROR_NOT_IMPLEMENTED; +#else + /* AEAD-encrypted v5 packet */ + if ((param->ctx->aalg != PGP_AEAD_EAX) && (param->ctx->aalg != PGP_AEAD_OCB)) { + RNP_LOG("unsupported AEAD algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + skey.version = PGP_SKSK_V5; + skey.alg = pass->s2k_cipher; + skey.aalg = param->ctx->aalg; + skey.ivlen = pgp_cipher_aead_nonce_len(skey.aalg); + skey.enckeylen = keylen + pgp_cipher_aead_tag_len(skey.aalg); + + try { + param->ctx->ctx->rng.get(skey.iv, skey.ivlen); + } catch (const std::exception &e) { + return RNP_ERROR_RNG; + } + + /* initialize cipher */ + if (!pgp_cipher_aead_init(&kcrypt, skey.alg, skey.aalg, pass->key.data(), false)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* set additional data */ + if (!encrypted_sesk_set_ad(&kcrypt, &skey)) { + return RNP_ERROR_BAD_STATE; + } + + /* calculate nonce */ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0); + + /* start cipher, encrypt key and get tag */ + bool res = pgp_cipher_aead_start(&kcrypt, nonce, nlen) && + pgp_cipher_aead_finish(&kcrypt, skey.enckey, key, keylen); + + pgp_cipher_aead_destroy(&kcrypt); + + if (!res) { + return RNP_ERROR_BAD_STATE; + } +#endif + } + + /* Writing symmetric key encrypted session key packet */ + try { + skey.write(*param->pkt.origdst); + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } + return param->pkt.origdst->werr; +} + +static rnp_result_t +encrypted_start_cfb(pgp_dest_encrypted_param_t *param, uint8_t *enckey) +{ + uint8_t mdcver = 1; + uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; /* encrypted header */ + unsigned blsize; + + if (param->auth_type == rnp::AuthType::MDC) { + /* initializing the mdc */ + dst_write(param->pkt.writedst, &mdcver, 1); + + try { + param->mdc = rnp::Hash::create(PGP_HASH_SHA1); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + return RNP_ERROR_GENERIC; + } + } + + /* initializing the crypto */ + if (!pgp_cipher_cfb_start(¶m->encrypt, param->ctx->ealg, enckey, NULL)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* generating and writing iv/password check bytes */ + blsize = pgp_block_size(param->ctx->ealg); + try { + param->ctx->ctx->rng.get(enchdr, blsize); + enchdr[blsize] = enchdr[blsize - 2]; + enchdr[blsize + 1] = enchdr[blsize - 1]; + + if (param->auth_type == rnp::AuthType::MDC) { + param->mdc->add(enchdr, blsize + 2); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + + pgp_cipher_cfb_encrypt(¶m->encrypt, enchdr, enchdr, blsize + 2); + + /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB + * resynchronization is done after encrypting this prefix data. */ + if (param->auth_type == rnp::AuthType::None) { + pgp_cipher_cfb_resync(¶m->encrypt, enchdr + 2); + } + + dst_write(param->pkt.writedst, enchdr, blsize + 2); + + return RNP_SUCCESS; +} + +static rnp_result_t +encrypted_start_aead(pgp_dest_encrypted_param_t *param, uint8_t *enckey) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD support is not enabled."); + return RNP_ERROR_NOT_IMPLEMENTED; +#else + uint8_t hdr[4 + PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + + if (pgp_block_size(param->ctx->ealg) != 16) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* fill header */ + hdr[0] = 1; + hdr[1] = param->ctx->ealg; + hdr[2] = param->ctx->aalg; + hdr[3] = param->ctx->abits; + + /* generate iv */ + nlen = pgp_cipher_aead_nonce_len(param->ctx->aalg); + try { + param->ctx->ctx->rng.get(param->iv, nlen); + } catch (const std::exception &e) { + return RNP_ERROR_RNG; + } + memcpy(hdr + 4, param->iv, nlen); + + /* output header */ + dst_write(param->pkt.writedst, hdr, 4 + nlen); + + /* initialize encryption */ + param->chunklen = 1L << (hdr[3] + 6); + param->chunkout = 0; + + /* fill additional/authenticated data */ + param->ad[0] = PGP_PKT_AEAD_ENCRYPTED | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + memcpy(param->ad + 1, hdr, 4); + memset(param->ad + 5, 0, 8); + param->adlen = 13; + + /* initialize cipher */ + if (!pgp_cipher_aead_init( + ¶m->encrypt, param->ctx->ealg, param->ctx->aalg, enckey, false)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return encrypted_start_aead_chunk(param, 0, false); +#endif +} + +static rnp_result_t +init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_encrypted_param_t *param; + bool singlepass = true; + unsigned pkeycount = 0; + unsigned skeycount = 0; + unsigned keylen; + rnp_result_t ret = RNP_ERROR_GENERIC; + + keylen = pgp_key_size(handler->ctx->ealg); + if (!keylen) { + RNP_LOG("unknown symmetric algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (handler->ctx->aalg) { + if ((handler->ctx->aalg != PGP_AEAD_EAX) && (handler->ctx->aalg != PGP_AEAD_OCB)) { + RNP_LOG("unknown AEAD algorithm: %d", (int) handler->ctx->aalg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((pgp_block_size(handler->ctx->ealg) != 16)) { + RNP_LOG("wrong AEAD symmetric algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((handler->ctx->abits < 0) || (handler->ctx->abits > 16)) { + RNP_LOG("wrong AEAD chunk bits: %d", handler->ctx->abits); + return RNP_ERROR_BAD_PARAMETERS; + } + } + + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_dest_encrypted_param_t(); + dst->param = param; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + param->auth_type = + handler->ctx->aalg == PGP_AEAD_NONE ? rnp::AuthType::MDC : rnp::AuthType::AEADv1; + param->aalg = handler->ctx->aalg; + param->ctx = handler->ctx; + param->pkt.origdst = writedst; + dst->write = param->auth_type == rnp::AuthType::AEADv1 ? encrypted_dst_write_aead : + encrypted_dst_write_cfb; + dst->finish = encrypted_dst_finish; + dst->close = encrypted_dst_close; + dst->type = PGP_STREAM_ENCRYPTED; + + pkeycount = handler->ctx->recipients.size(); + skeycount = handler->ctx->passwords.size(); + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> enckey; /* content encryption key */ + if (!pkeycount && !skeycount) { + RNP_LOG("no recipients"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if ((pkeycount > 0) || (skeycount > 1) || (param->auth_type == rnp::AuthType::AEADv1)) { + try { + handler->ctx->ctx->rng.get(enckey.data(), keylen); + } catch (const std::exception &e) { + ret = RNP_ERROR_RNG; + goto finish; + } + singlepass = false; + } + + /* Configuring and writing pk-encrypted session keys */ + for (auto recipient : handler->ctx->recipients) { + ret = encrypted_add_recipient(handler, dst, recipient, enckey.data(), keylen); + if (ret) { + goto finish; + } + } + + /* Configuring and writing sk-encrypted session key(s) */ + for (auto &passinfo : handler->ctx->passwords) { + ret = encrypted_add_password(&passinfo, param, enckey.data(), keylen, singlepass); + if (ret != RNP_SUCCESS) { + goto finish; + } + } + + /* Initializing partial packet writer */ + param->pkt.partial = true; + param->pkt.indeterminate = false; + if (param->auth_type == rnp::AuthType::AEADv1) { + param->pkt.tag = PGP_PKT_AEAD_ENCRYPTED; + } else { + /* We do not generate PGP_PKT_SE_DATA, leaving this just in case */ + param->pkt.tag = + param->auth_type == rnp::AuthType::MDC ? PGP_PKT_SE_IP_DATA : PGP_PKT_SE_DATA; + } + + /* initializing partial data length writer */ + /* we may use intederminate len packet here as well, for compatibility or so on */ + if (!init_streamed_packet(¶m->pkt, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (param->auth_type == rnp::AuthType::AEADv1) { + /* initialize AEAD encryption */ + ret = encrypted_start_aead(param, enckey.data()); + } else { + /* initialize old CFB or CFB with MDC */ + ret = encrypted_start_cfb(param, enckey.data()); + } +finish: + handler->ctx->passwords.clear(); + if (ret) { + encrypted_dst_close(dst, true); + } + return ret; +} + +static rnp_result_t +signed_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + dst_write(param->writedst, buf, len); + return RNP_SUCCESS; +} + +static void +cleartext_dst_writeline(pgp_dest_signed_param_t *param, + const uint8_t * buf, + size_t len, + bool eol) +{ + const uint8_t *ptr; + + /* dash-escaping line if needed */ + if (param->clr_start && len && + ((buf[0] == CH_DASH) || ((len >= 4) && !strncmp((const char *) buf, ST_FROM, 4)))) { + dst_write(param->writedst, ST_DASHSP, 2); + } + + /* output data */ + dst_write(param->writedst, buf, len); + + try { + if (eol) { + bool hashcrlf = false; + ptr = buf + len - 1; + + /* skipping trailing characters - space, tab, carriage return, line feed */ + while ((ptr >= buf) && ((*ptr == CH_SPACE) || (*ptr == CH_TAB) || + (*ptr == CH_CR) || (*ptr == CH_LF))) { + if (*ptr == CH_LF) { + hashcrlf = true; + } + ptr--; + } + + /* hashing line body and \r\n */ + param->hashes.add(buf, ptr + 1 - buf); + if (hashcrlf) { + param->hashes.add(ST_CRLF, 2); + } + param->clr_start = hashcrlf; + } else if (len > 0) { + /* hashing just line's data */ + param->hashes.add(buf, len); + param->clr_start = false; + } + } catch (const std::exception &e) { + RNP_LOG("failed to hash data: %s", e.what()); + } +} + +static size_t +cleartext_dst_scanline(const uint8_t *buf, size_t len, bool *eol) +{ + for (const uint8_t *ptr = buf, *end = buf + len; ptr < end; ptr++) { + if (*ptr == CH_LF) { + if (eol) { + *eol = true; + } + return ptr - buf + 1; + } + } + + if (eol) { + *eol = false; + } + return len; +} + +static rnp_result_t +cleartext_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + const uint8_t * linebg = (const uint8_t *) buf; + size_t linelen; + size_t cplen; + bool eol; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + if (param->clr_buflen > 0) { + /* number of edge cases may happen here */ + linelen = cleartext_dst_scanline(linebg, len, &eol); + + if (param->clr_buflen + linelen < sizeof(param->clr_buf)) { + /* fits into buffer */ + memcpy(param->clr_buf + param->clr_buflen, linebg, linelen); + param->clr_buflen += linelen; + if (!eol) { + /* do not write the line if we don't have whole */ + return RNP_SUCCESS; + } + + cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true); + param->clr_buflen = 0; + } else { + /* we have line longer than 4k */ + cplen = sizeof(param->clr_buf) - param->clr_buflen; + memcpy(param->clr_buf + param->clr_buflen, linebg, cplen); + cleartext_dst_writeline(param, param->clr_buf, sizeof(param->clr_buf), false); + + if (eol || (linelen >= sizeof(param->clr_buf))) { + cleartext_dst_writeline(param, linebg + cplen, linelen - cplen, eol); + param->clr_buflen = 0; + } else { + param->clr_buflen = linelen - cplen; + memcpy(param->clr_buf, linebg + cplen, param->clr_buflen); + return RNP_SUCCESS; + } + } + + linebg += linelen; + len -= linelen; + } + + /* if we get here then we don't have data in param->clr_buf */ + while (len > 0) { + linelen = cleartext_dst_scanline(linebg, len, &eol); + + if (!eol && (linelen < sizeof(param->clr_buf))) { + memcpy(param->clr_buf, linebg, linelen); + param->clr_buflen = linelen; + return RNP_SUCCESS; + } + + cleartext_dst_writeline(param, linebg, linelen, eol); + linebg += linelen; + len -= linelen; + } + + return RNP_SUCCESS; +} + +static void +signed_fill_signature(pgp_dest_signed_param_t ¶m, + pgp_signature_t & sig, + pgp_dest_signer_info_t & signer) +{ + /* fill signature fields, assuming sign_init was called on it */ + if (signer.sigcreate) { + sig.set_creation(signer.sigcreate); + } + sig.set_expiration(signer.sigexpire); + sig.fill_hashed_data(); + + auto listh = param.hashes.get(sig.halg); + if (!listh) { + RNP_LOG("failed to obtain hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* decrypt the secret key if needed */ + rnp::KeyLocker keylock(*signer.key); + if (signer.key->encrypted() && + !signer.key->unlock(*param.password_provider, PGP_OP_SIGN)) { + RNP_LOG("wrong secret key password"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PASSWORD); + } + /* calculate the signature */ + signature_calculate(sig, signer.key->material(), *listh->clone(), *param.ctx->ctx); +} + +static rnp_result_t +signed_write_signature(pgp_dest_signed_param_t *param, + pgp_dest_signer_info_t * signer, + pgp_dest_t * writedst) +{ + try { + pgp_signature_t sig; + if (signer->onepass.version) { + signer->key->sign_init(sig, signer->onepass.halg, param->ctx->ctx->time()); + sig.palg = signer->onepass.palg; + sig.set_type(signer->onepass.type); + } else { + signer->key->sign_init(sig, signer->halg, param->ctx->ctx->time()); + /* line below should be checked */ + sig.set_type(param->ctx->detached ? PGP_SIG_BINARY : PGP_SIG_TEXT); + } + signed_fill_signature(*param, sig, *signer); + sig.write(*writedst); + return writedst->werr; + } catch (const rnp::rnp_exception &e) { + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("Failed to write signature: %s", e.what()); + return RNP_ERROR_WRITE; + } +} + +static rnp_result_t +signed_dst_finish(pgp_dest_t *dst) +{ + rnp_result_t ret; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* attached signature, we keep onepasses in order of signatures */ + for (auto &sinfo : param->siginfos) { + if ((ret = signed_write_signature(param, &sinfo, param->writedst))) { + RNP_LOG("failed to calculate signature"); + return ret; + } + } + + return RNP_SUCCESS; +} + +static rnp_result_t +signed_detached_dst_finish(pgp_dest_t *dst) +{ + rnp_result_t ret; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* just calculating and writing signatures to the output */ + for (auto &sinfo : param->siginfos) { + if ((ret = signed_write_signature(param, &sinfo, param->writedst))) { + RNP_LOG("failed to calculate detached signature"); + return ret; + } + } + + return RNP_SUCCESS; +} + +static rnp_result_t +cleartext_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* writing cached line if any */ + if (param->clr_buflen > 0) { + cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true); + } + /* trailing \r\n which is not hashed */ + dst_write(param->writedst, ST_CRLF, 2); + + /* writing signatures to the armored stream, which outputs to param->writedst */ + try { + rnp::ArmoredDest armor(*param->writedst, PGP_ARMORED_SIGNATURE); + armor.set_discard(true); + for (auto &sinfo : param->siginfos) { + auto ret = signed_write_signature(param, &sinfo, &armor.dst()); + if (ret) { + return ret; + } + } + armor.set_discard(false); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to write armored signature: %s", e.what()); + return RNP_ERROR_WRITE; + } +} + +static void +signed_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + if (!param) { + return; + } + delete param; + dst->param = NULL; +} + +static void +signed_dst_update(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + param->hashes.add(buf, len); +} + +static rnp_result_t +signed_add_signer(pgp_dest_signed_param_t *param, rnp_signer_info_t *signer, bool last) +{ + pgp_dest_signer_info_t sinfo = {}; + + if (!signer->key->is_secret()) { + RNP_LOG("secret key required for signing"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* validate signing key material if didn't before */ + signer->key->pkt().material.validate(*param->ctx->ctx, false); + if (!signer->key->pkt().material.valid()) { + RNP_LOG("attempt to sign to the key with invalid material"); + return RNP_ERROR_NO_SUITABLE_KEY; + } + + /* copy fields */ + sinfo.key = signer->key; + sinfo.sigcreate = signer->sigcreate; + sinfo.sigexpire = signer->sigexpire; + + /* Add hash to the list */ + sinfo.halg = pgp_hash_adjust_alg_to_key(signer->halg, &signer->key->pkt()); + try { + param->hashes.add_alg(sinfo.halg); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } + + // Do not add onepass for detached/clearsign + if (param->ctx->detached || param->ctx->clearsign) { + sinfo.onepass.version = 0; + try { + param->siginfos.push_back(sinfo); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + // Setup and add onepass + sinfo.onepass.version = 3; + sinfo.onepass.type = PGP_SIG_BINARY; + sinfo.onepass.halg = sinfo.halg; + sinfo.onepass.palg = sinfo.key->alg(); + sinfo.onepass.keyid = sinfo.key->keyid(); + sinfo.onepass.nested = false; + try { + param->siginfos.push_back(sinfo); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + // write onepasses in reverse order so signature order will match signers list + if (!last) { + return RNP_SUCCESS; + } + try { + for (auto it = param->siginfos.rbegin(); it != param->siginfos.rend(); it++) { + pgp_dest_signer_info_t &sinfo = *it; + sinfo.onepass.nested = &sinfo == ¶m->siginfos.front(); + sinfo.onepass.write(*param->writedst); + } + return param->writedst->werr; + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } +} + +static rnp_result_t +init_signed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_signed_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!handler->key_provider) { + RNP_LOG("no key provider"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_dest_signed_param_t(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->param = param; + param->writedst = writedst; + param->ctx = handler->ctx; + param->password_provider = handler->password_provider; + if (param->ctx->clearsign) { + dst->type = PGP_STREAM_CLEARTEXT; + dst->write = cleartext_dst_write; + dst->finish = cleartext_dst_finish; + param->clr_start = true; + } else { + dst->type = PGP_STREAM_SIGNED; + dst->write = signed_dst_write; + dst->finish = param->ctx->detached ? signed_detached_dst_finish : signed_dst_finish; + } + dst->close = signed_dst_close; + + /* Getting signer's infos, writing one-pass signatures if needed */ + for (auto &sg : handler->ctx->signers) { + ret = signed_add_signer(param, &sg, &sg == &handler->ctx->signers.back()); + if (ret) { + RNP_LOG("failed to add one-pass signature for signer"); + goto finish; + } + } + + /* Do we have any signatures? */ + if (param->hashes.hashes.empty()) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* Writing headers for cleartext signed document */ + if (param->ctx->clearsign) { + dst_write(param->writedst, ST_CLEAR_BEGIN, strlen(ST_CLEAR_BEGIN)); + dst_write(param->writedst, ST_CRLF, strlen(ST_CRLF)); + dst_write(param->writedst, ST_HEADER_HASH, strlen(ST_HEADER_HASH)); + + for (const auto &hash : param->hashes.hashes) { + auto hname = rnp::Hash::name(hash->alg()); + dst_write(param->writedst, hname, strlen(hname)); + if (&hash != ¶m->hashes.hashes.back()) { + dst_write(param->writedst, ST_COMMA, 1); + } + } + + dst_write(param->writedst, ST_CRLFCRLF, strlen(ST_CRLFCRLF)); + } + + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + signed_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +compressed_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + int zret; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_in = (unsigned char *) buf; + param->z.avail_in = len; + param->z.next_out = param->cache + param->len; + param->z.avail_out = sizeof(param->cache) - param->len; + + while (param->z.avail_in > 0) { + zret = deflate(¶m->z, Z_NO_FLUSH); + /* Z_OK, Z_BUF_ERROR are ok for us, Z_STREAM_END will not happen here */ + if (zret == Z_STREAM_ERROR) { + RNP_LOG("wrong deflate state"); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->z.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->z.next_out = param->cache; + param->z.avail_out = sizeof(param->cache); + } + } + + param->len = sizeof(param->cache) - param->z.avail_out; + return RNP_SUCCESS; + } else if (param->alg == PGP_C_BZIP2) { +#ifdef HAVE_BZLIB_H + param->bz.next_in = (char *) buf; + param->bz.avail_in = len; + param->bz.next_out = (char *) (param->cache + param->len); + param->bz.avail_out = sizeof(param->cache) - param->len; + + while (param->bz.avail_in > 0) { + zret = BZ2_bzCompress(¶m->bz, BZ_RUN); + if (zret < 0) { + RNP_LOG("error %d", zret); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->bz.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->bz.next_out = (char *) param->cache; + param->bz.avail_out = sizeof(param->cache); + } + } + + param->len = sizeof(param->cache) - param->bz.avail_out; + return RNP_SUCCESS; +#else + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + } else { + RNP_LOG("unknown algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } +} + +static rnp_result_t +compressed_dst_finish(pgp_dest_t *dst) +{ + int zret; + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_in = Z_NULL; + param->z.avail_in = 0; + param->z.next_out = param->cache + param->len; + param->z.avail_out = sizeof(param->cache) - param->len; + do { + zret = deflate(¶m->z, Z_FINISH); + + if (zret == Z_STREAM_ERROR) { + RNP_LOG("wrong deflate state"); + return RNP_ERROR_BAD_STATE; + } + + if (param->z.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->z.next_out = param->cache; + param->z.avail_out = sizeof(param->cache); + } + } while (zret != Z_STREAM_END); + + param->len = sizeof(param->cache) - param->z.avail_out; + dst_write(param->pkt.writedst, param->cache, param->len); + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + param->bz.next_in = NULL; + param->bz.avail_in = 0; + param->bz.next_out = (char *) (param->cache + param->len); + param->bz.avail_out = sizeof(param->cache) - param->len; + + do { + zret = BZ2_bzCompress(¶m->bz, BZ_FINISH); + if (zret < 0) { + RNP_LOG("wrong bzip2 state %d", zret); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->bz.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->bz.next_out = (char *) param->cache; + param->bz.avail_out = sizeof(param->cache); + } + } while (zret != BZ_STREAM_END); + + param->len = sizeof(param->cache) - param->bz.avail_out; + dst_write(param->pkt.writedst, param->cache, param->len); + } +#endif + + if (param->pkt.writedst->werr) { + return param->pkt.writedst->werr; + } + + return finish_streamed_packet(¶m->pkt); +} + +static void +compressed_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + + if (!param) { + return; + } + + if (param->zstarted) { + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + deflateEnd(¶m->z); + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + BZ2_bzCompressEnd(¶m->bz); + } +#endif + } + + close_streamed_packet(¶m->pkt, discard); + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_compressed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_compressed_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t buf; + int zret; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_compressed_param_t *) dst->param; + dst->write = compressed_dst_write; + dst->finish = compressed_dst_finish; + dst->close = compressed_dst_close; + dst->type = PGP_STREAM_COMPRESSED; + param->alg = (pgp_compression_type_t) handler->ctx->zalg; + param->pkt.partial = true; + param->pkt.indeterminate = false; + param->pkt.tag = PGP_PKT_COMPRESSED; + + /* initializing partial length or indeterminate packet, writing header */ + if (!init_streamed_packet(¶m->pkt, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* compression algorithm */ + buf = param->alg; + dst_write(param->pkt.writedst, &buf, 1); + + /* initializing compression */ + switch (param->alg) { + case PGP_C_ZIP: + case PGP_C_ZLIB: + (void) memset(¶m->z, 0x0, sizeof(param->z)); + if (param->alg == PGP_C_ZIP) { + zret = deflateInit2( + ¶m->z, handler->ctx->zlevel, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + } else { + zret = deflateInit(¶m->z, handler->ctx->zlevel); + } + + if (zret != Z_OK) { + RNP_LOG("failed to init zlib, error %d", zret); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + break; +#ifdef HAVE_BZLIB_H + case PGP_C_BZIP2: + (void) memset(¶m->bz, 0x0, sizeof(param->bz)); + zret = BZ2_bzCompressInit(¶m->bz, handler->ctx->zlevel, 0, 0); + if (zret != BZ_OK) { + RNP_LOG("failed to init bz, error %d", zret); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + break; +#endif + default: + RNP_LOG("unknown compression algorithm"); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + param->zstarted = true; + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + compressed_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +literal_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + dst_write(param->writedst, buf, len); + return RNP_SUCCESS; +} + +static rnp_result_t +literal_dst_finish(pgp_dest_t *dst) +{ + return finish_streamed_packet((pgp_dest_packet_param_t *) dst->param); +} + +static void +literal_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param; + + if (!param) { + return; + } + + close_streamed_packet(param, discard); + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_literal_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_packet_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + size_t flen = 0; + uint8_t buf[4]; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_packet_param_t *) dst->param; + dst->write = literal_dst_write; + dst->finish = literal_dst_finish; + dst->close = literal_dst_close; + dst->type = PGP_STREAM_LITERAL; + param->partial = true; + param->indeterminate = false; + param->tag = PGP_PKT_LITDATA; + + /* initializing partial length or indeterminate packet, writing header */ + if (!init_streamed_packet(param, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + /* content type - forcing binary now */ + buf[0] = (uint8_t) 'b'; + /* filename */ + flen = handler->ctx->filename.size(); + if (flen > 255) { + RNP_LOG("filename too long, truncating"); + flen = 255; + } + buf[1] = (uint8_t) flen; + dst_write(param->writedst, buf, 2); + if (flen) { + dst_write(param->writedst, handler->ctx->filename.c_str(), flen); + } + /* timestamp */ + STORE32BE(buf, handler->ctx->filemtime); + dst_write(param->writedst, buf, 4); + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + literal_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +process_stream_sequence(pgp_source_t *src, + pgp_dest_t * streams, + unsigned count, + pgp_dest_t * sstream, + pgp_dest_t * wstream) +{ + std::unique_ptr<uint8_t[]> readbuf(new (std::nothrow) uint8_t[PGP_INPUT_CACHE_SIZE]); + if (!readbuf) { + RNP_LOG("allocation failure"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + /* processing source stream */ + while (!src->eof) { + size_t read = 0; + if (!src_read(src, readbuf.get(), PGP_INPUT_CACHE_SIZE, &read)) { + RNP_LOG("failed to read from source"); + return RNP_ERROR_READ; + } else if (!read) { + continue; + } + + if (sstream) { + signed_dst_update(sstream, readbuf.get(), read); + } + + if (wstream) { + dst_write(wstream, readbuf.get(), read); + + for (int i = count - 1; i >= 0; i--) { + if (streams[i].werr) { + RNP_LOG("failed to process data"); + return RNP_ERROR_WRITE; + } + } + } + } + + /* finalizing destinations */ + for (int i = count - 1; i >= 0; i--) { + rnp_result_t ret = dst_finish(&streams[i]); + if (ret) { + RNP_LOG("failed to finish stream"); + return ret; + } + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) +{ + /* stack of the streams would be as following: + [armoring stream] - if armoring is enabled + [compressing stream, partial writing stream] - compression is enabled, and not detached + signing stream + literal data stream, partial writing stream - if not detached or cleartext signature + */ + pgp_dest_t dests[4]; + unsigned destc = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + rnp_ctx_t & ctx = *handler->ctx; + pgp_dest_t * wstream = NULL; + pgp_dest_t * sstream = NULL; + + /* pushing armoring stream, which will write to the output */ + if (ctx.armor && !ctx.clearsign) { + pgp_armored_msg_t msgt = ctx.detached ? PGP_ARMORED_SIGNATURE : PGP_ARMORED_MESSAGE; + ret = init_armored_dst(&dests[destc], dst, msgt); + if (ret) { + goto finish; + } + destc++; + } + + /* if compression is enabled then pushing compressing stream */ + if (!ctx.detached && !ctx.clearsign && (ctx.zlevel > 0)) { + if ((ret = + init_compressed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + destc++; + } + + /* pushing signing stream, which will use handler->ctx to distinguish between + * attached/detached/cleartext signature */ + if ((ret = init_signed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + if (!ctx.clearsign) { + sstream = &dests[destc]; + } + if (!ctx.detached) { + wstream = &dests[destc]; + } + destc++; + + /* pushing literal data stream, if not detached/cleartext signature */ + if (!ctx.no_wrap && !ctx.detached && !ctx.clearsign) { + if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + wstream = &dests[destc]; + destc++; + } + + /* process source with streams stack */ + ret = process_stream_sequence(src, dests, destc, sstream, wstream); +finish: + for (int i = destc - 1; i >= 0; i--) { + dst_close(&dests[i], ret); + } + return ret; +} + +rnp_result_t +rnp_encrypt_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) +{ + /* stack of the streams would be as following: + [armoring stream] - if armoring is enabled + [encrypting stream, partial writing stream] + [compressing stream, partial writing stream] - compression is enabled + signing stream + literal data stream, partial writing stream + */ + pgp_dest_t dests[5]; + size_t destc = 0; + rnp_result_t ret = RNP_SUCCESS; + rnp_ctx_t & ctx = *handler->ctx; + pgp_dest_t * sstream = NULL; + + /* we may use only attached signatures here */ + if (ctx.clearsign || ctx.detached) { + RNP_LOG("cannot clearsign or sign detached together with encryption"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* pushing armoring stream, which will write to the output */ + if (ctx.armor) { + if ((ret = init_armored_dst(&dests[destc], dst, PGP_ARMORED_MESSAGE))) { + goto finish; + } + destc++; + } + + /* pushing encrypting stream, which will write to the output or armoring stream */ + if ((ret = init_encrypted_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + destc++; + + /* if compression is enabled then pushing compressing stream */ + if (ctx.zlevel > 0) { + if ((ret = init_compressed_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + destc++; + } + + /* pushing signing stream if we have signers */ + if (!ctx.signers.empty()) { + if ((ret = init_signed_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + sstream = &dests[destc]; + destc++; + } + + /* pushing literal data stream */ + if (!ctx.no_wrap) { + if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + destc++; + } + + /* process source with streams stack */ + ret = process_stream_sequence(src, dests, destc, sstream, &dests[destc - 1]); +finish: + for (size_t i = destc; i > 0; i--) { + dst_close(&dests[i - 1], ret); + } + return ret; +} + +rnp_result_t +rnp_compress_src(pgp_source_t &src, pgp_dest_t &dst, pgp_compression_type_t zalg, int zlevel) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + ctx.zalg = zalg; + ctx.zlevel = zlevel; + handler.ctx = &ctx; + + pgp_dest_t compressed = {}; + rnp_result_t ret = init_compressed_dst(&handler, &compressed, &dst); + if (ret) { + goto done; + } + ret = dst_write_src(&src, &compressed); +done: + dst_close(&compressed, ret); + return ret; +} + +rnp_result_t +rnp_wrap_src(pgp_source_t &src, pgp_dest_t &dst, const std::string &filename, uint32_t modtime) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + ctx.filename = filename; + ctx.filemtime = modtime; + handler.ctx = &ctx; + + pgp_dest_t literal = {}; + rnp_result_t ret = init_literal_dst(&handler, &literal, &dst); + if (ret) { + goto done; + } + + ret = dst_write_src(&src, &literal); +done: + dst_close(&literal, ret); + return ret; +} + +rnp_result_t +rnp_raw_encrypt_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string & password, + rnp::SecurityContext &secctx) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + + ctx.ctx = &secctx; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; + handler.ctx = &ctx; + pgp_dest_t encrypted = {}; + + rnp_result_t ret = RNP_ERROR_GENERIC; + try { + ret = + ctx.add_encryption_password(password, DEFAULT_PGP_HASH_ALG, DEFAULT_PGP_SYMM_ALG); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + goto done; + } + if (ret) { + goto done; + } + + ret = init_encrypted_dst(&handler, &encrypted, &dst); + if (ret) { + goto done; + } + + ret = dst_write_src(&src, &encrypted); +done: + dst_close(&encrypted, ret); + return ret; +} |