/* * 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 #include #include #ifdef HAVE_UNISTD_H #include #include #else #include "uniwin.h" #endif #include #ifdef HAVE_ZLIB_H #include #endif #ifdef HAVE_BZLIB_H #include #endif #include #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 #include /* 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 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 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 enckey; enckey[0] = param->ctx->ealg; memcpy(&enckey[1], key, keylen); /* Calculate checksum */ rnp::secure_array 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 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 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; }