diff options
Diffstat (limited to 'src/librepgp/stream-parse.cpp')
-rw-r--r-- | src/librepgp/stream-parse.cpp | 2636 |
1 files changed, 2636 insertions, 0 deletions
diff --git a/src/librepgp/stream-parse.cpp b/src/librepgp/stream-parse.cpp new file mode 100644 index 0000000..5ec4d64 --- /dev/null +++ b/src/librepgp/stream-parse.cpp @@ -0,0 +1,2636 @@ +/* + * 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> +#include <string.h> +#include <string> +#include <vector> +#include <time.h> +#include <cinttypes> +#include <cassert> +#include <rnp/rnp_def.h> +#include "stream-ctx.h" +#include "stream-def.h" +#include "stream-parse.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-sig.h" +#include "str-utils.h" +#include "types.h" +#include "crypto/s2k.h" +#include "crypto.h" +#include "crypto/signatures.h" +#include "fingerprint.h" +#include "pgp-key.h" + +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif + +typedef enum pgp_message_t { + PGP_MESSAGE_UNKNOWN = 0, + PGP_MESSAGE_NORMAL, + PGP_MESSAGE_DETACHED, + PGP_MESSAGE_CLEARTEXT +} pgp_message_t; + +typedef struct pgp_processing_ctx_t { + pgp_parse_handler_t handler; + pgp_source_t * signed_src; + pgp_source_t * literal_src; + pgp_message_t msg_type; + pgp_dest_t output; + std::list<pgp_source_t> sources; + + ~pgp_processing_ctx_t(); +} pgp_processing_ctx_t; + +/* common fields for encrypted, compressed and literal data */ +typedef struct pgp_source_packet_param_t { + pgp_source_t * readsrc; /* source to read from, could be partial*/ + pgp_source_t * origsrc; /* original source passed to init_*_src */ + pgp_packet_hdr_t hdr; /* packet header info */ +} pgp_source_packet_param_t; + +typedef struct pgp_source_encrypted_param_t { + pgp_source_packet_param_t pkt{}; /* underlying packet-related params */ + std::vector<pgp_sk_sesskey_t> symencs; /* array of sym-encrypted session keys */ + std::vector<pgp_pk_sesskey_t> pubencs; /* array of pk-encrypted session keys */ + rnp::AuthType auth_type; /* Authentication type */ + bool auth_validated{}; /* Auth tag (MDC or AEAD) was already validated */ + pgp_crypt_t decrypt{}; /* decrypting crypto */ + std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */ + size_t chunklen{}; /* size of AEAD chunk in bytes */ + size_t chunkin{}; /* number of bytes read from the current chunk */ + size_t chunkidx{}; /* index of the current chunk */ + uint8_t cache[PGP_AEAD_CACHE_LEN]; /* read cache */ + size_t cachelen{}; /* number of bytes in the cache */ + size_t cachepos{}; /* index of first unread byte in the cache */ + pgp_aead_hdr_t aead_hdr; /* AEAD encryption parameters */ + uint8_t aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */ + size_t aead_adlen{}; /* length of the additional data */ + pgp_symm_alg_t salg; /* data encryption algorithm */ + pgp_parse_handler_t * handler{}; /* parsing handler with callbacks */ + + pgp_source_encrypted_param_t() : auth_type(rnp::AuthType::None), salg(PGP_SA_UNKNOWN) + { + } + + bool + use_cfb() + { + return auth_type != rnp::AuthType::AEADv1; + } +} pgp_source_encrypted_param_t; + +typedef struct pgp_source_signed_param_t { + pgp_parse_handler_t *handler; /* parsing handler with callbacks */ + pgp_source_t * readsrc; /* source to read from */ + bool detached; /* detached signature */ + bool cleartext; /* source is cleartext signed */ + bool clr_eod; /* cleartext data is over */ + bool clr_fline; /* first line of the cleartext */ + bool clr_mline; /* in the middle of the very long line */ + uint8_t out[CT_BUF_LEN]; /* cleartext output cache for easier parsing */ + size_t outlen; /* total bytes in out */ + size_t outpos; /* offset of first available byte in out */ + bool max_line_warn; /* warning about too long line is already issued */ + size_t text_line_len; /* length of a current line in a text document */ + long stripped_crs; /* number of trailing CR characters stripped from the end of the last + processed chunk */ + + std::vector<pgp_one_pass_sig_t> onepasses; /* list of one-pass singatures */ + std::list<pgp_signature_t> sigs; /* list of signatures */ + std::vector<pgp_signature_info_t> siginfos; /* signature validation info */ + rnp::HashList hashes; /* hash contexts */ + rnp::HashList txt_hashes; /* hash contexts for text-mode sigs */ + + pgp_source_signed_param_t() = default; + ~pgp_source_signed_param_t() = default; +} pgp_source_signed_param_t; + +typedef struct pgp_source_compressed_param_t { + pgp_source_packet_param_t pkt; /* underlying packet-related params */ + pgp_compression_type_t alg; + union { + z_stream z; + bz_stream bz; + }; + uint8_t in[PGP_INPUT_CACHE_SIZE / 2]; + size_t inpos; + size_t inlen; + bool zend; +} pgp_source_compressed_param_t; + +typedef struct pgp_source_literal_param_t { + pgp_source_packet_param_t pkt; /* underlying packet-related params */ + pgp_literal_hdr_t hdr; /* literal packet fields */ +} pgp_source_literal_param_t; + +typedef struct pgp_source_partial_param_t { + pgp_source_t *readsrc; /* source to read from */ + int type; /* type of the packet */ + size_t psize; /* size of the current part */ + size_t pleft; /* bytes left to read from the current part */ + bool last; /* current part is last */ +} pgp_source_partial_param_t; + +static bool +is_pgp_source(pgp_source_t &src) +{ + uint8_t buf; + if (!src_peek_eq(&src, &buf, 1)) { + return false; + } + + switch (get_packet_type(buf)) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SIGNATURE: + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_COMPRESSED: + case PGP_PKT_LITDATA: + case PGP_PKT_MARKER: + return true; + default: + return false; + } +} + +static bool +partial_pkt_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + if (src->eof) { + *readres = 0; + return true; + } + + pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param; + if (!param) { + return false; + } + + size_t read; + size_t write = 0; + while (len > 0) { + if (!param->pleft && param->last) { + // we have the last chunk + *readres = write; + return true; + } + if (!param->pleft) { + // reading next chunk + if (!stream_read_partial_chunk_len(param->readsrc, &read, ¶m->last)) { + return false; + } + param->psize = read; + param->pleft = read; + } + + if (!param->pleft) { + *readres = write; + return true; + } + + read = param->pleft > len ? len : param->pleft; + if (!src_read(param->readsrc, buf, read, &read)) { + RNP_LOG("failed to read data chunk"); + return false; + } + if (!read) { + RNP_LOG("unexpected eof"); + *readres = write; + return true; + } + write += read; + len -= read; + buf = (uint8_t *) buf + read; + param->pleft -= read; + } + + *readres = write; + return true; +} + +static void +partial_pkt_src_close(pgp_source_t *src) +{ + pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param; + if (param) { + free(src->param); + src->param = NULL; + } +} + +static rnp_result_t +init_partial_pkt_src(pgp_source_t *src, pgp_source_t *readsrc, pgp_packet_hdr_t &hdr) +{ + pgp_source_partial_param_t *param; + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + assert(hdr.partial); + /* we are sure that header is indeterminate */ + param = (pgp_source_partial_param_t *) src->param; + param->type = hdr.tag; + param->psize = get_partial_pkt_len(hdr.hdr[1]); + param->pleft = param->psize; + param->last = false; + param->readsrc = readsrc; + + src->read = partial_pkt_src_read; + src->close = partial_pkt_src_close; + src->type = PGP_STREAM_PARLEN_PACKET; + + if (param->psize < PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE) { + RNP_LOG("first part of partial length packet sequence has size %d and that's less " + "than allowed by the protocol", + (int) param->psize); + } + + return RNP_SUCCESS; +} + +static bool +literal_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param; + if (!param) { + return false; + } + return src_read(param->pkt.readsrc, buf, len, read); +} + +static void +literal_src_close(pgp_source_t *src) +{ + pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param; + if (param) { + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + + free(src->param); + src->param = NULL; + } +} + +static bool +compressed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param; + if (!param) { + return false; + } + + if (src->eof || param->zend) { + *readres = 0; + return true; + } + + if (param->alg == PGP_C_NONE) { + if (!src_read(param->pkt.readsrc, buf, len, readres)) { + RNP_LOG("failed to read uncompressed data"); + return false; + } + return true; + } + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_out = (Bytef *) buf; + param->z.avail_out = len; + param->z.next_in = param->in + param->inpos; + param->z.avail_in = param->inlen - param->inpos; + + while ((param->z.avail_out > 0) && (!param->zend)) { + if (param->z.avail_in == 0) { + size_t read = 0; + if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) { + RNP_LOG("failed to read data"); + return false; + } + param->z.next_in = param->in; + param->z.avail_in = read; + param->inlen = read; + param->inpos = 0; + } + int ret = inflate(¶m->z, Z_SYNC_FLUSH); + if (ret == Z_STREAM_END) { + param->zend = true; + if (param->z.avail_in > 0) { + RNP_LOG("data beyond the end of z stream"); + } + break; + } + if (ret != Z_OK) { + RNP_LOG("inflate error %d", ret); + return false; + } + if (!param->z.avail_in && src_eof(param->pkt.readsrc)) { + RNP_LOG("unexpected end of zlib stream"); + return false; + } + } + param->inpos = param->z.next_in - param->in; + *readres = len - param->z.avail_out; + return true; + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + param->bz.next_out = (char *) buf; + param->bz.avail_out = len; + param->bz.next_in = (char *) (param->in + param->inpos); + param->bz.avail_in = param->inlen - param->inpos; + + while ((param->bz.avail_out > 0) && (!param->zend)) { + if (param->bz.avail_in == 0) { + size_t read = 0; + if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) { + RNP_LOG("failed to read data"); + return false; + } + param->bz.next_in = (char *) param->in; + param->bz.avail_in = read; + param->inlen = read; + param->inpos = 0; + } + int ret = BZ2_bzDecompress(¶m->bz); + if (ret == BZ_STREAM_END) { + param->zend = true; + if (param->bz.avail_in > 0) { + RNP_LOG("data beyond the end of z stream"); + } + break; + } + if (ret != BZ_OK) { + RNP_LOG("bzdecompress error %d", ret); + return false; + } + if (!param->bz.avail_in && src_eof(param->pkt.readsrc)) { + RNP_LOG("unexpected end of bzip stream"); + return false; + } + } + + param->inpos = (uint8_t *) param->bz.next_in - param->in; + *readres = len - param->bz.avail_out; + return true; + } +#endif + return false; +} + +static void +compressed_src_close(pgp_source_t *src) +{ + pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param; + if (!param) { + return; + } + + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + BZ2_bzDecompressEnd(¶m->bz); + } +#endif + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + inflateEnd(¶m->z); + } + + free(src->param); + src->param = NULL; +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool last) +{ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + + /* set chunk index for additional data */ + STORE64BE(param->aead_ad + param->aead_adlen - 8, idx); + + if (last) { + uint64_t total = idx * param->chunklen; + if (idx && param->chunkin) { + total -= param->chunklen - param->chunkin; + } + + if (!param->chunkin) { + /* reset the crypto in case we had empty chunk before the last one */ + pgp_cipher_aead_reset(¶m->decrypt); + } + STORE64BE(param->aead_ad + param->aead_adlen, total); + param->aead_adlen += 8; + } + + if (!pgp_cipher_aead_set_ad(¶m->decrypt, param->aead_ad, param->aead_adlen)) { + RNP_LOG("failed to set ad"); + return false; + } + + /* setup chunk */ + param->chunkidx = idx; + param->chunkin = 0; + + /* set chunk index for nonce */ + nlen = pgp_cipher_aead_nonce(param->aead_hdr.aalg, param->aead_hdr.iv, nonce, idx); + + /* start cipher */ + return pgp_cipher_aead_start(¶m->decrypt, nonce, nlen); +} + +/* read and decrypt bytes to the cache. Should be called only on empty cache. */ +static bool +encrypted_src_read_aead_part(pgp_source_encrypted_param_t *param) +{ + bool lastchunk = false; + bool chunkend = false; + bool res = false; + size_t read; + size_t tagread; + size_t taglen; + + param->cachepos = 0; + param->cachelen = 0; + + if (param->auth_validated) { + return true; + } + + /* it is always 16 for defined EAX and OCB, however this may change in future */ + taglen = pgp_cipher_aead_tag_len(param->aead_hdr.aalg); + read = sizeof(param->cache) - 2 * PGP_AEAD_MAX_TAG_LEN; + + if (read >= param->chunklen - param->chunkin) { + read = param->chunklen - param->chunkin; + chunkend = true; + } else { + read = read - read % pgp_cipher_aead_granularity(¶m->decrypt); + } + + if (!src_read(param->pkt.readsrc, param->cache, read, &read)) { + return false; + } + + /* checking whether we have enough input for the final tags */ + if (!src_peek(param->pkt.readsrc, param->cache + read, taglen * 2, &tagread)) { + return false; + } + + if (tagread < taglen * 2) { + /* this would mean the end of the stream */ + if ((param->chunkin == 0) && (read + tagread == taglen)) { + /* we have empty chunk and final tag */ + chunkend = false; + lastchunk = true; + } else if (read + tagread >= 2 * taglen) { + /* we have end of chunk and final tag */ + chunkend = true; + lastchunk = true; + } else { + RNP_LOG("unexpected end of data"); + return false; + } + } + + if (!chunkend && !lastchunk) { + param->chunkin += read; + res = pgp_cipher_aead_update(¶m->decrypt, param->cache, param->cache, read); + if (res) { + param->cachelen = read; + } + return res; + } + + if (chunkend) { + if (tagread > taglen) { + src_skip(param->pkt.readsrc, tagread - taglen); + } + + res = pgp_cipher_aead_finish( + ¶m->decrypt, param->cache, param->cache, read + tagread - taglen); + if (!res) { + RNP_LOG("failed to finalize aead chunk"); + return res; + } + param->cachelen = read + tagread - 2 * taglen; + param->chunkin += param->cachelen; + } + + size_t chunkidx = param->chunkidx; + if (chunkend && param->chunkin) { + chunkidx++; + } + + if (!(res = encrypted_start_aead_chunk(param, chunkidx, lastchunk))) { + RNP_LOG("failed to start aead chunk"); + return res; + } + + if (lastchunk) { + if (tagread > 0) { + src_skip(param->pkt.readsrc, tagread); + } + + size_t off = read + tagread - taglen; + res = pgp_cipher_aead_finish( + ¶m->decrypt, param->cache + off, param->cache + off, taglen); + if (!res) { + RNP_LOG("wrong last chunk"); + return res; + } + param->auth_validated = true; + } + + return res; +} +#endif + +static bool +encrypted_src_read_aead(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ +#if !defined(ENABLE_AEAD) + return false; +#else + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + size_t cbytes; + size_t left = len; + + do { + /* check whether we have something in the cache */ + cbytes = param->cachelen - param->cachepos; + if (cbytes > 0) { + if (cbytes >= left) { + memcpy(buf, param->cache + param->cachepos, left); + param->cachepos += left; + if (param->cachepos == param->cachelen) { + param->cachepos = param->cachelen = 0; + } + *read = len; + return true; + } + memcpy(buf, param->cache + param->cachepos, cbytes); + buf = (uint8_t *) buf + cbytes; + left -= cbytes; + param->cachepos = param->cachelen = 0; + } + + /* read something into cache */ + if (!encrypted_src_read_aead_part(param)) { + return false; + } + } while ((left > 0) && (param->cachelen > 0)); + + *read = len - left; + return true; +#endif +} + +static bool +encrypted_src_read_cfb(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + if (param == NULL) { + return false; + } + + if (src->eof) { + *readres = 0; + return true; + } + + size_t read; + if (!src_read(param->pkt.readsrc, buf, len, &read)) { + return false; + } + if (!read) { + *readres = 0; + return true; + } + + bool parsemdc = false; + uint8_t mdcbuf[MDC_V1_SIZE]; + if (param->auth_type == rnp::AuthType::MDC) { + size_t mdcread = 0; + /* make sure there are always 22 bytes left on input */ + if (!src_peek(param->pkt.readsrc, mdcbuf, MDC_V1_SIZE, &mdcread) || + (mdcread + read < MDC_V1_SIZE)) { + RNP_LOG("wrong mdc read state"); + return false; + } + if (mdcread < MDC_V1_SIZE) { + src_skip(param->pkt.readsrc, mdcread); + size_t mdcsub = MDC_V1_SIZE - mdcread; + memmove(&mdcbuf[mdcsub], mdcbuf, mdcread); + memcpy(mdcbuf, (uint8_t *) buf + read - mdcsub, mdcsub); + read -= mdcsub; + parsemdc = true; + } + } + + pgp_cipher_cfb_decrypt(¶m->decrypt, (uint8_t *) buf, (uint8_t *) buf, read); + + if (param->auth_type == rnp::AuthType::MDC) { + try { + param->mdc->add(buf, read); + + if (parsemdc) { + pgp_cipher_cfb_decrypt(¶m->decrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); + pgp_cipher_cfb_finish(¶m->decrypt); + param->mdc->add(mdcbuf, 2); + uint8_t hash[PGP_SHA1_HASH_SIZE] = {0}; + param->mdc->finish(hash); + param->mdc = nullptr; + + if ((mdcbuf[0] != MDC_PKT_TAG) || (mdcbuf[1] != MDC_V1_SIZE - 2)) { + RNP_LOG("mdc header check failed"); + return false; + } + + if (memcmp(&mdcbuf[2], hash, PGP_SHA1_HASH_SIZE) != 0) { + RNP_LOG("mdc hash check failed"); + return false; + } + param->auth_validated = true; + } + } catch (const std::exception &e) { + RNP_LOG("mdc update failed: %s", e.what()); + return false; + } + } + *readres = read; + return true; +} + +static rnp_result_t +encrypted_src_finish(pgp_source_t *src) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + + /* report to the handler that decryption is finished */ + if (param->handler->on_decryption_done) { + bool validated = (param->auth_type != rnp::AuthType::None) && param->auth_validated; + param->handler->on_decryption_done(validated, param->handler->param); + } + + if ((param->auth_type == rnp::AuthType::None) || param->auth_validated) { + return RNP_SUCCESS; + } + switch (param->auth_type) { + case rnp::AuthType::MDC: + RNP_LOG("mdc was not validated"); + break; + case rnp::AuthType::AEADv1: + RNP_LOG("aead last chunk was not validated"); + break; + default: + RNP_LOG("auth was not validated"); + break; + } + return RNP_ERROR_BAD_STATE; +} + +static void +encrypted_src_close(pgp_source_t *src) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + if (!param) { + return; + } + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + + if (!param->use_cfb()) { +#if defined(ENABLE_AEAD) + pgp_cipher_aead_destroy(¶m->decrypt); +#endif + } else { + pgp_cipher_cfb_finish(¶m->decrypt); + } + + delete param; + src->param = NULL; +} + +static void +add_hash_for_sig(pgp_source_signed_param_t *param, pgp_sig_type_t stype, pgp_hash_alg_t halg) +{ + /* Cleartext always uses param->hashes instead of param->txt_hashes */ + if (!param->cleartext && (stype == PGP_SIG_TEXT)) { + param->txt_hashes.add_alg(halg); + } + param->hashes.add_alg(halg); +} + +static const rnp::Hash * +get_hash_for_sig(pgp_source_signed_param_t ¶m, pgp_signature_info_t &sinfo) +{ + /* Cleartext always uses param->hashes instead of param->txt_hashes */ + if (!param.cleartext && (sinfo.sig->type() == PGP_SIG_TEXT)) { + return param.txt_hashes.get(sinfo.sig->halg); + } + return param.hashes.get(sinfo.sig->halg); +} + +static void +signed_validate_signature(pgp_source_signed_param_t ¶m, pgp_signature_info_t &sinfo) +{ + /* Check signature type */ + if (!sinfo.sig->is_document()) { + RNP_LOG("Invalid document signature type: %d", (int) sinfo.sig->type()); + sinfo.valid = false; + return; + } + /* Find signing key */ + pgp_key_request_ctx_t keyctx(PGP_OP_VERIFY, false, PGP_KEY_SEARCH_FINGERPRINT); + + /* Get signer's fp or keyid */ + if (sinfo.sig->has_keyfp()) { + keyctx.search.by.fingerprint = sinfo.sig->keyfp(); + } else if (sinfo.sig->has_keyid()) { + keyctx.search.type = PGP_KEY_SEARCH_KEYID; + keyctx.search.by.keyid = sinfo.sig->keyid(); + } else { + RNP_LOG("cannot get signer's key fp or id from signature."); + sinfo.unknown = true; + return; + } + /* Get the public key */ + pgp_key_t *key = pgp_request_key(param.handler->key_provider, &keyctx); + if (!key) { + /* fallback to secret key */ + keyctx.secret = true; + if (!(key = pgp_request_key(param.handler->key_provider, &keyctx))) { + RNP_LOG("signer's key not found"); + sinfo.no_signer = true; + return; + } + } + try { + /* Get the hash context and clone it. */ + auto hash = get_hash_for_sig(param, sinfo); + if (!hash) { + RNP_LOG("failed to get hash context."); + return; + } + auto shash = hash->clone(); + key->validate_sig(sinfo, *shash, *param.handler->ctx->ctx); + } catch (const std::exception &e) { + RNP_LOG("Signature validation failed: %s", e.what()); + sinfo.valid = false; + } +} + +static long +stripped_line_len(uint8_t *begin, uint8_t *end) +{ + uint8_t *stripped_end = end; + + while (stripped_end >= begin && (*stripped_end == CH_CR || *stripped_end == CH_LF)) { + stripped_end--; + } + + return stripped_end - begin + 1; +} + +static void +signed_src_update(pgp_source_t *src, const void *buf, size_t len) +{ + if (!len) { + return; + } + /* check for extremely unlikely pointer overflow/wrap case */ + if (((uint8_t *) buf + len) < ((uint8_t *) buf + len - 1)) { + signed_src_update(src, buf, len - 1); + uint8_t last = *((uint8_t *) buf + len - 1); + signed_src_update(src, &last, 1); + } + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + try { + param->hashes.add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + /* update text-mode sig hashes */ + if (param->txt_hashes.hashes.empty()) { + return; + } + + uint8_t *ch = (uint8_t *) buf; + uint8_t *linebeg = ch; + uint8_t *end = (uint8_t *) buf + len; + /* we support LF and CRLF line endings */ + while (ch < end) { + /* continue if not reached LF */ + if (*ch != CH_LF) { + if (*ch != CH_CR && param->stripped_crs > 0) { + while (param->stripped_crs--) { + try { + param->txt_hashes.add(ST_CR, 1); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + param->stripped_crs = 0; + } + + if (!param->max_line_warn && param->text_line_len >= MAXIMUM_GNUPG_LINELEN) { + RNP_LOG("Canonical text document signature: line is too long, may cause " + "incompatibility with other implementations. Consider using binary " + "signature instead."); + param->max_line_warn = true; + } + + ch++; + param->text_line_len++; + continue; + } + /* reached eol: dump line contents */ + param->stripped_crs = 0; + param->text_line_len = 0; + if (ch > linebeg) { + long stripped_len = stripped_line_len(linebeg, ch); + if (stripped_len > 0) { + try { + param->txt_hashes.add(linebeg, stripped_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + } + /* dump EOL */ + try { + param->txt_hashes.add(ST_CRLF, 2); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + ch++; + linebeg = ch; + } + /* check if we have undumped line contents */ + if (linebeg < end) { + long stripped_len = stripped_line_len(linebeg, end - 1); + if (stripped_len < end - linebeg) { + param->stripped_crs = end - linebeg - stripped_len; + } + if (stripped_len > 0) { + try { + param->txt_hashes.add(linebeg, stripped_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + } +} + +static bool +signed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return false; + } + return src_read(param->readsrc, buf, len, read); +} + +static void +signed_src_close(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return; + } + delete param; + src->param = NULL; +} + +#define MAX_SIGNATURES 16384 + +static rnp_result_t +signed_read_single_signature(pgp_source_signed_param_t *param, + pgp_source_t * readsrc, + pgp_signature_t ** sig) +{ + uint8_t ptag; + if (!src_peek_eq(readsrc, &ptag, 1)) { + RNP_LOG("failed to read signature packet header"); + return RNP_ERROR_READ; + } + + int ptype = get_packet_type(ptag); + if (ptype != PGP_PKT_SIGNATURE) { + RNP_LOG("unexpected packet %d", ptype); + return RNP_ERROR_BAD_FORMAT; + } + + if (param->siginfos.size() >= MAX_SIGNATURES) { + RNP_LOG("Too many signatures in the stream."); + return RNP_ERROR_BAD_FORMAT; + } + + try { + param->siginfos.emplace_back(); + pgp_signature_info_t &siginfo = param->siginfos.back(); + pgp_signature_t readsig; + if (readsig.parse(*readsrc)) { + RNP_LOG("failed to parse signature"); + siginfo.unknown = true; + if (sig) { + *sig = NULL; + } + return RNP_SUCCESS; + } + param->sigs.push_back(std::move(readsig)); + siginfo.sig = ¶m->sigs.back(); + if (sig) { + *sig = siginfo.sig; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } +} + +static rnp_result_t +signed_read_cleartext_signatures(pgp_source_t &src, pgp_source_signed_param_t *param) +{ + try { + rnp::ArmoredSource armor(*param->readsrc); + while (!armor.eof()) { + auto ret = signed_read_single_signature(param, &armor.src(), NULL); + if (ret) { + return ret; + } + } + return RNP_SUCCESS; + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s", e.what()); + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_FORMAT; + } +} + +static rnp_result_t +signed_read_signatures(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + + /* reading signatures */ + for (auto op = param->onepasses.rbegin(); op != param->onepasses.rend(); op++) { + pgp_signature_t *sig = NULL; + rnp_result_t ret = signed_read_single_signature(param, src, &sig); + /* we have more onepasses then signatures */ + if (ret == RNP_ERROR_READ) { + RNP_LOG("Warning: premature end of signatures"); + return param->siginfos.size() ? RNP_SUCCESS : ret; + } + if (ret) { + return ret; + } + if (sig && !sig->matches_onepass(*op)) { + RNP_LOG("Warning: signature doesn't match one-pass"); + } + } + return RNP_SUCCESS; +} + +static rnp_result_t +signed_src_finish(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (param->cleartext) { + ret = signed_read_cleartext_signatures(*src, param); + } else { + ret = signed_read_signatures(src); + } + + if (ret) { + return ret; + } + + if (!src_eof(src)) { + RNP_LOG("warning: unexpected data on the stream end"); + } + + /* validating signatures */ + for (auto &sinfo : param->siginfos) { + if (!sinfo.sig) { + continue; + } + signed_validate_signature(*param, sinfo); + } + + /* checking the validation results */ + ret = RNP_ERROR_SIGNATURE_INVALID; + for (auto &sinfo : param->siginfos) { + if (sinfo.valid) { + /* If we have at least one valid signature then data is safe to process */ + ret = RNP_SUCCESS; + break; + } + } + + /* call the callback with signature infos */ + if (param->handler->on_signatures) { + param->handler->on_signatures(param->siginfos, param->handler->param); + } + return ret; +} + +/* + * str is a string to tokenize. + * delims is a string containing a list of delimiter characters. + * result is a container<string_type> that supports push_back. + */ +template <typename T> +static void +tokenize(const typename T::value_type &str, const typename T::value_type &delims, T &result) +{ + typedef typename T::value_type::size_type string_size_t; + const string_size_t npos = T::value_type::npos; + + result.clear(); + string_size_t current; + string_size_t next = 0; + do { + next = str.find_first_not_of(delims, next); + if (next == npos) { + break; + } + current = next; + next = str.find_first_of(delims, current); + string_size_t count = (next == npos) ? npos : (next - current); + result.push_back(str.substr(current, count)); + } while (next != npos); +} + +static bool +cleartext_parse_headers(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + char hdr[1024] = {0}; + char * hval; + pgp_hash_alg_t halg; + size_t hdrlen; + + do { + if (!src_peek_line(param->readsrc, hdr, sizeof(hdr), &hdrlen)) { + RNP_LOG("failed to peek line"); + return false; + } + + if (!hdrlen) { + break; + } + + if (rnp::is_blank_line(hdr, hdrlen)) { + src_skip(param->readsrc, hdrlen); + break; + } + + try { + if ((hdrlen >= 6) && !strncmp(hdr, ST_HEADER_HASH, 6)) { + hval = hdr + 6; + + std::string remainder = hval; + + const std::string delimiters = ", \t"; + std::vector<std::string> tokens; + + tokenize(remainder, delimiters, tokens); + + for (const auto &token : tokens) { + if ((halg = rnp::Hash::alg(token.c_str())) == PGP_HASH_UNKNOWN) { + RNP_LOG("unknown halg: %s", token.c_str()); + continue; + } + add_hash_for_sig(param, PGP_SIG_TEXT, halg); + } + } else { + RNP_LOG("unknown header '%s'", hdr); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + + src_skip(param->readsrc, hdrlen); + + if (!src_skip_eol(param->readsrc)) { + return false; + } + } while (1); + + /* we have exactly one empty line after the headers */ + return src_skip_eol(param->readsrc); +} + +static void +cleartext_process_line(pgp_source_t *src, const uint8_t *buf, size_t len, bool eol) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + uint8_t * bufen = (uint8_t *) buf + len - 1; + + /* check for dashes only if we are not in the middle */ + if (!param->clr_mline && (len > 0) && (buf[0] == CH_DASH)) { + if ((len > 1) && (buf[1] == CH_SPACE)) { + buf += 2; + len -= 2; + } else if ((len > 5) && !memcmp(buf, ST_DASHES, 5)) { + param->clr_eod = true; + return; + } else { + RNP_LOG("dash at the line begin"); + } + } + + /* hash eol if it is not the first line and we are not in the middle */ + if (!param->clr_fline && !param->clr_mline) { + /* we hash \r\n after the previous line to not hash the last eol before the sig */ + signed_src_update(src, ST_CRLF, 2); + } + + if (!len) { + return; + } + + if (len + param->outlen > sizeof(param->out)) { + RNP_LOG("wrong state"); + return; + } + + /* if we have eol after this line then strip trailing spaces and tabs */ + if (eol) { + for (; (bufen >= buf) && + ((*bufen == CH_SPACE) || (*bufen == CH_TAB) || (*bufen == CH_CR)); + bufen--) + ; + } + + if ((len = bufen + 1 - buf)) { + memcpy(param->out + param->outlen, buf, len); + param->outlen += len; + signed_src_update(src, buf, len); + } +} + +static bool +cleartext_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return false; + } + + uint8_t srcb[CT_BUF_LEN]; + uint8_t *cur, *en, *bg; + size_t read = 0; + size_t origlen = len; + + read = param->outlen - param->outpos; + if (read >= len) { + memcpy(buf, param->out + param->outpos, len); + param->outpos += len; + if (param->outpos == param->outlen) { + param->outpos = param->outlen = 0; + } + *readres = len; + return true; + } else if (read > 0) { + memcpy(buf, param->out + param->outpos, read); + len -= read; + buf = (uint8_t *) buf + read; + param->outpos = param->outlen = 0; + } + + if (param->clr_eod) { + *readres = origlen - len; + return true; + } + + do { + if (!src_peek(param->readsrc, srcb, sizeof(srcb), &read)) { + return false; + } else if (!read) { + break; + } + + /* processing data line by line, eol could be \n or \r\n */ + for (cur = srcb, bg = srcb, en = cur + read; cur < en; cur++) { + if ((*cur == CH_LF) || + ((*cur == CH_CR) && (cur + 1 < en) && (*(cur + 1) == CH_LF))) { + cleartext_process_line(src, bg, cur - bg, true); + /* processing eol */ + if (param->clr_eod) { + break; + } + + /* processing eol */ + param->clr_fline = false; + param->clr_mline = false; + if (*cur == CH_CR) { + param->out[param->outlen++] = *cur++; + } + param->out[param->outlen++] = *cur; + bg = cur + 1; + } + } + + /* if line is larger then 4k then just dump it out */ + if ((bg == srcb) && !param->clr_eod) { + /* if last char is \r, and it's not the end of stream, then do not dump it */ + if ((en > bg) && (*(en - 1) == CH_CR) && (read > 1)) { + en--; + } + cleartext_process_line(src, bg, en - bg, false); + param->clr_mline = true; + bg = en; + } + src_skip(param->readsrc, bg - srcb); + + /* put data from the param->out to buf */ + read = param->outlen > len ? len : param->outlen; + memcpy(buf, param->out, read); + buf = (uint8_t *) buf + read; + len -= read; + + if (read == param->outlen) { + param->outlen = 0; + } else { + param->outpos = read; + } + + /* we got to the signature marker */ + if (param->clr_eod || !len) { + break; + } + } while (1); + + *readres = origlen - len; + return true; +} + +static bool +encrypted_decrypt_cfb_header(pgp_source_encrypted_param_t *param, + pgp_symm_alg_t alg, + uint8_t * key) +{ + pgp_crypt_t crypt; + uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; + uint8_t dechdr[PGP_MAX_BLOCK_SIZE + 2]; + unsigned blsize; + + if (!(blsize = pgp_block_size(alg))) { + return false; + } + + /* reading encrypted header to check the password validity */ + if (!src_peek_eq(param->pkt.readsrc, enchdr, blsize + 2)) { + RNP_LOG("failed to read encrypted header"); + return false; + } + + /* having symmetric key in keybuf let's decrypt blocksize + 2 bytes and check them */ + if (!pgp_cipher_cfb_start(&crypt, alg, key, NULL)) { + RNP_LOG("failed to start cipher"); + return false; + } + + pgp_cipher_cfb_decrypt(&crypt, dechdr, enchdr, blsize + 2); + + if ((dechdr[blsize] != dechdr[blsize - 2]) || (dechdr[blsize + 1] != dechdr[blsize - 1])) { + RNP_LOG("checksum check failed"); + goto error; + } + + src_skip(param->pkt.readsrc, blsize + 2); + param->decrypt = crypt; + + /* init mdc if it is here */ + /* 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->decrypt, enchdr + 2); + return true; + } + + try { + param->mdc = rnp::Hash::create(PGP_HASH_SHA1); + param->mdc->add(dechdr, blsize + 2); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + goto error; + } + return true; +error: + pgp_cipher_cfb_finish(&crypt); + return false; +} + +static bool +encrypted_start_aead(pgp_source_encrypted_param_t *param, pgp_symm_alg_t alg, uint8_t *key) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + return false; +#else + size_t gran; + + if (alg != param->aead_hdr.ealg) { + return false; + } + + /* initialize cipher with key */ + if (!pgp_cipher_aead_init( + ¶m->decrypt, param->aead_hdr.ealg, param->aead_hdr.aalg, key, true)) { + return false; + } + + gran = pgp_cipher_aead_granularity(¶m->decrypt); + if (gran > sizeof(param->cache)) { + RNP_LOG("wrong granularity"); + return false; + } + + return encrypted_start_aead_chunk(param, 0, false); +#endif +} + +static bool +encrypted_try_key(pgp_source_encrypted_param_t *param, + pgp_pk_sesskey_t * sesskey, + pgp_key_pkt_t * seckey, + rnp::SecurityContext & ctx) +{ + pgp_encrypted_material_t encmaterial; + try { + if (!sesskey->parse_material(encmaterial)) { + return false; + } + seckey->material.validate(ctx, false); + if (!seckey->material.valid()) { + RNP_LOG("Attempt to decrypt using the key with invalid material."); + return false; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + + rnp::secure_array<uint8_t, PGP_MPINT_SIZE> decbuf; + /* Decrypting session key value */ + rnp_result_t err; + bool res = false; + pgp_key_material_t *keymaterial = &seckey->material; + size_t declen = 0; + switch (sesskey->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + err = rsa_decrypt_pkcs1( + &ctx.rng, decbuf.data(), &declen, &encmaterial.rsa, &keymaterial->rsa); + if (err) { + RNP_LOG("RSA decryption failure"); + return false; + } + break; + case PGP_PKA_SM2: +#if defined(ENABLE_SM2) + declen = decbuf.size(); + err = sm2_decrypt(decbuf.data(), &declen, &encmaterial.sm2, &keymaterial->ec); + if (err != RNP_SUCCESS) { + RNP_LOG("SM2 decryption failure, error %x", (int) err); + return false; + } + break; +#else + RNP_LOG("SM2 decryption is not available."); + return false; +#endif + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: { + const rnp_result_t ret = elgamal_decrypt_pkcs1( + &ctx.rng, decbuf.data(), &declen, &encmaterial.eg, &keymaterial->eg); + if (ret) { + RNP_LOG("ElGamal decryption failure [%X]", ret); + return false; + } + break; + } + case PGP_PKA_ECDH: { + if (!curve_supported(keymaterial->ec.curve)) { + RNP_LOG("ECDH decrypt: curve %d is not supported.", (int) keymaterial->ec.curve); + return false; + } + pgp_fingerprint_t fingerprint; + if (pgp_fingerprint(fingerprint, *seckey)) { + RNP_LOG("ECDH fingerprint calculation failed"); + return false; + } + if ((keymaterial->ec.curve == PGP_CURVE_25519) && + !x25519_bits_tweaked(keymaterial->ec)) { + RNP_LOG("Warning: bits of 25519 secret key are not tweaked."); + } + declen = decbuf.size(); + err = ecdh_decrypt_pkcs5( + decbuf.data(), &declen, &encmaterial.ecdh, &keymaterial->ec, fingerprint); + if (err != RNP_SUCCESS) { + RNP_LOG("ECDH decryption error %u", err); + return false; + } + break; + } + default: + RNP_LOG("unsupported public key algorithm %d\n", seckey->alg); + return false; + } + + /* Check algorithm and key length */ + if (!pgp_is_sa_supported(decbuf[0])) { + RNP_LOG("Unsupported symmetric algorithm %" PRIu8, decbuf[0]); + return false; + } + + pgp_symm_alg_t salg = static_cast<pgp_symm_alg_t>(decbuf[0]); + size_t keylen = pgp_key_size(salg); + if (declen != keylen + 3) { + RNP_LOG("invalid symmetric key length"); + return false; + } + + /* Validate checksum */ + rnp::secure_array<unsigned, 1> checksum; + for (unsigned i = 1; i <= keylen; i++) { + checksum[0] += decbuf[i]; + } + + if ((checksum[0] & 0xffff) != + (decbuf[keylen + 2] | ((unsigned) decbuf[keylen + 1] << 8))) { + RNP_LOG("wrong checksum\n"); + return false; + } + + if (param->use_cfb()) { + /* Decrypt header */ + res = encrypted_decrypt_cfb_header(param, salg, &decbuf[1]); + } else { + /* Start AEAD decrypting, assuming we have correct key */ + res = encrypted_start_aead(param, salg, &decbuf[1]); + } + if (res) { + param->salg = salg; + } + return res; +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey) +{ + /* TODO: this method is exact duplicate as in stream-write.c. Not sure where to put it */ + 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 int +encrypted_try_password(pgp_source_encrypted_param_t *param, const char *password) +{ + bool keyavail = false; /* tried password at least once */ + + for (auto &skey : param->symencs) { + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 1> keybuf; + /* deriving symmetric key from password */ + size_t keysize = pgp_key_size(skey.alg); + if (!keysize || !pgp_s2k_derive_key(&skey.s2k, password, keybuf.data(), keysize)) { + continue; + } + pgp_crypt_t crypt; + pgp_symm_alg_t alg; + + if (skey.version == PGP_SKSK_V4) { + /* v4 symmetrically-encrypted session key */ + if (skey.enckeylen > 0) { + /* decrypting session key */ + if (!pgp_cipher_cfb_start(&crypt, skey.alg, keybuf.data(), NULL)) { + continue; + } + + pgp_cipher_cfb_decrypt(&crypt, keybuf.data(), skey.enckey, skey.enckeylen); + pgp_cipher_cfb_finish(&crypt); + + alg = (pgp_symm_alg_t) keybuf[0]; + keysize = pgp_key_size(alg); + if (!keysize || (keysize + 1 != skey.enckeylen)) { + continue; + } + memmove(keybuf.data(), keybuf.data() + 1, keysize); + } else { + alg = (pgp_symm_alg_t) skey.alg; + } + + if (!pgp_block_size(alg)) { + continue; + } + keyavail = true; + } else if (skey.version == PGP_SKSK_V5) { +#if !defined(ENABLE_AEAD) + continue; +#else + /* v5 AEAD-encrypted session key */ + size_t taglen = pgp_cipher_aead_tag_len(skey.aalg); + size_t ceklen = pgp_key_size(param->aead_hdr.ealg); + if (!taglen || !ceklen || (ceklen + taglen != skey.enckeylen)) { + RNP_LOG("CEK len/alg mismatch"); + continue; + } + alg = skey.alg; + + /* initialize cipher */ + if (!pgp_cipher_aead_init(&crypt, skey.alg, skey.aalg, keybuf.data(), true)) { + continue; + } + + /* set additional data */ + if (!encrypted_sesk_set_ad(&crypt, &skey)) { + RNP_LOG("failed to set ad"); + continue; + } + + /* calculate nonce */ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t noncelen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0); + + /* start cipher, decrypt key and verify tag */ + keyavail = + pgp_cipher_aead_start(&crypt, nonce, noncelen) && + pgp_cipher_aead_finish(&crypt, keybuf.data(), skey.enckey, skey.enckeylen); + pgp_cipher_aead_destroy(&crypt); + + /* we have decrypted key so let's start decryption */ + if (!keyavail) { + continue; + } +#endif + } else { + continue; + } + + /* Decrypt header for CFB */ + if (param->use_cfb() && !encrypted_decrypt_cfb_header(param, alg, keybuf.data())) { + continue; + } + if (!param->use_cfb() && + !encrypted_start_aead(param, param->aead_hdr.ealg, keybuf.data())) { + continue; + } + + param->salg = param->use_cfb() ? alg : param->aead_hdr.ealg; + /* inform handler that we used this symenc */ + if (param->handler->on_decryption_start) { + param->handler->on_decryption_start(NULL, &skey, param->handler->param); + } + return 1; + } + + if (!param->use_cfb() && pgp_block_size(param->aead_hdr.ealg)) { + /* we know aead symm alg even if we wasn't able to start decryption */ + param->salg = param->aead_hdr.ealg; + } + + if (!keyavail) { + RNP_LOG("no supported sk available"); + return -1; + } + return 0; +} + +/** @brief Initialize common to stream packets params, including partial data source */ +static rnp_result_t +init_packet_params(pgp_source_packet_param_t ¶m) +{ + param.origsrc = NULL; + + /* save packet header */ + rnp_result_t ret = stream_peek_packet_hdr(param.readsrc, ¶m.hdr); + if (ret) { + return ret; + } + src_skip(param.readsrc, param.hdr.hdr_len); + if (!param.hdr.partial) { + return RNP_SUCCESS; + } + + /* initialize partial reader if needed */ + pgp_source_t *partsrc = (pgp_source_t *) calloc(1, sizeof(*partsrc)); + if (!partsrc) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t errcode = init_partial_pkt_src(partsrc, param.readsrc, param.hdr); + if (errcode) { + free(partsrc); + return errcode; + } + param.origsrc = param.readsrc; + param.readsrc = partsrc; + return RNP_SUCCESS; +} + +rnp_result_t +init_literal_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + pgp_source_literal_param_t *param; + uint8_t format = 0; + uint8_t nlen = 0; + uint8_t timestamp[4]; + + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_literal_param_t *) src->param; + param->pkt.readsrc = readsrc; + src->read = literal_src_read; + src->close = literal_src_close; + src->type = PGP_STREAM_LITERAL; + + /* Reading packet length/checking whether it is partial */ + if ((ret = init_packet_params(param->pkt))) { + goto finish; + } + + /* data format */ + if (!src_read_eq(param->pkt.readsrc, &format, 1)) { + RNP_LOG("failed to read data format"); + ret = RNP_ERROR_READ; + goto finish; + } + + switch (format) { + case 'b': + case 't': + case 'u': + case 'l': + case '1': + break; + default: + RNP_LOG("unknown data format %" PRIu8, format); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + param->hdr.format = format; + /* file name */ + if (!src_read_eq(param->pkt.readsrc, &nlen, 1)) { + RNP_LOG("failed to read file name length"); + ret = RNP_ERROR_READ; + goto finish; + } + if (nlen && !src_read_eq(param->pkt.readsrc, param->hdr.fname, nlen)) { + RNP_LOG("failed to read file name"); + ret = RNP_ERROR_READ; + goto finish; + } + param->hdr.fname[nlen] = 0; + param->hdr.fname_len = nlen; + /* timestamp */ + if (!src_read_eq(param->pkt.readsrc, timestamp, 4)) { + RNP_LOG("failed to read file timestamp"); + ret = RNP_ERROR_READ; + goto finish; + } + param->hdr.timestamp = read_uint32(timestamp); + + if (!param->pkt.hdr.indeterminate && !param->pkt.hdr.partial) { + /* format filename-length filename timestamp */ + const uint16_t nbytes = 1 + 1 + nlen + 4; + if (param->pkt.hdr.pkt_len < nbytes) { + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + src->size = param->pkt.hdr.pkt_len - nbytes; + src->knownsize = 1; + } + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + src_close(src); + } + return ret; +} + +bool +get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr) +{ + pgp_source_literal_param_t *param; + + if (src->type != PGP_STREAM_LITERAL) { + RNP_LOG("wrong stream"); + return false; + } + + param = (pgp_source_literal_param_t *) src->param; + *hdr = param->hdr; + return true; +} + +rnp_result_t +init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t errcode = RNP_ERROR_GENERIC; + pgp_source_compressed_param_t *param; + uint8_t alg; + int zret; + + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_compressed_param_t *) src->param; + param->pkt.readsrc = readsrc; + src->read = compressed_src_read; + src->close = compressed_src_close; + src->type = PGP_STREAM_COMPRESSED; + + /* Reading packet length/checking whether it is partial */ + errcode = init_packet_params(param->pkt); + if (errcode != RNP_SUCCESS) { + goto finish; + } + + /* Reading compression algorithm */ + if (!src_read_eq(param->pkt.readsrc, &alg, 1)) { + RNP_LOG("failed to read compression algorithm"); + errcode = RNP_ERROR_READ; + goto finish; + } + + /* Initializing decompression */ + switch (alg) { + case PGP_C_NONE: + break; + case PGP_C_ZIP: + case PGP_C_ZLIB: + (void) memset(¶m->z, 0x0, sizeof(param->z)); + zret = + alg == PGP_C_ZIP ? (int) inflateInit2(¶m->z, -15) : (int) inflateInit(¶m->z); + if (zret != Z_OK) { + RNP_LOG("failed to init zlib, error %d", zret); + errcode = RNP_ERROR_READ; + goto finish; + } + break; +#ifdef HAVE_BZLIB_H + case PGP_C_BZIP2: + (void) memset(¶m->bz, 0x0, sizeof(param->bz)); + zret = BZ2_bzDecompressInit(¶m->bz, 0, 0); + if (zret != BZ_OK) { + RNP_LOG("failed to init bz, error %d", zret); + errcode = RNP_ERROR_READ; + goto finish; + } + break; +#endif + default: + RNP_LOG("unknown compression algorithm: %d", (int) alg); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + param->alg = (pgp_compression_type_t) alg; + param->inlen = 0; + param->inpos = 0; + + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + return errcode; +} + +bool +get_compressed_src_alg(pgp_source_t *src, uint8_t *alg) +{ + pgp_source_compressed_param_t *param; + + if (src->type != PGP_STREAM_COMPRESSED) { + RNP_LOG("wrong stream"); + return false; + } + + param = (pgp_source_compressed_param_t *) src->param; + *alg = param->alg; + return true; +} + +bool +get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr) +{ + uint8_t hdrbt[4] = {0}; + + if (!src_read_eq(src, hdrbt, 4)) { + return false; + } + + hdr->version = hdrbt[0]; + hdr->ealg = (pgp_symm_alg_t) hdrbt[1]; + hdr->aalg = (pgp_aead_alg_t) hdrbt[2]; + hdr->csize = hdrbt[3]; + + if (!(hdr->ivlen = pgp_cipher_aead_nonce_len(hdr->aalg))) { + RNP_LOG("wrong aead nonce length: alg %d", (int) hdr->aalg); + return false; + } + + return src_read_eq(src, hdr->iv, hdr->ivlen); +} + +#define MAX_RECIPIENTS 16384 + +static rnp_result_t +encrypted_read_packet_data(pgp_source_encrypted_param_t *param) +{ + int ptype; + /* Reading pk/sk encrypted session key(s) */ + try { + size_t errors = 0; + bool stop = false; + while (!stop) { + if (param->pubencs.size() + param->symencs.size() + errors > MAX_RECIPIENTS) { + RNP_LOG("Too many recipients of the encrypted message. Aborting."); + return RNP_ERROR_BAD_STATE; + } + uint8_t ptag; + if (!src_peek_eq(param->pkt.readsrc, &ptag, 1)) { + RNP_LOG("failed to read packet header"); + return RNP_ERROR_READ; + } + ptype = get_packet_type(ptag); + switch (ptype) { + case PGP_PKT_SK_SESSION_KEY: { + pgp_sk_sesskey_t skey; + rnp_result_t ret = skey.parse(*param->pkt.readsrc); + if (ret == RNP_ERROR_READ) { + RNP_LOG("SKESK: Premature end of data."); + return ret; + } + if (ret) { + RNP_LOG("Failed to parse SKESK, skipping."); + errors++; + continue; + } + param->symencs.push_back(skey); + break; + } + case PGP_PKT_PK_SESSION_KEY: { + pgp_pk_sesskey_t pkey; + rnp_result_t ret = pkey.parse(*param->pkt.readsrc); + if (ret == RNP_ERROR_READ) { + RNP_LOG("PKESK: Premature end of data."); + return ret; + } + if (ret) { + RNP_LOG("Failed to parse PKESK, skipping."); + errors++; + continue; + } + param->pubencs.push_back(pkey); + break; + } + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + stop = true; + break; + default: + RNP_LOG("unknown packet type: %d", ptype); + return RNP_ERROR_BAD_FORMAT; + } + } + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s: %d", e.what(), e.code()); + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } + + /* Reading packet length/checking whether it is partial */ + rnp_result_t errcode = init_packet_params(param->pkt); + if (errcode) { + return errcode; + } + + /* Reading header of encrypted packet */ + if (ptype == PGP_PKT_AEAD_ENCRYPTED) { + param->auth_type = rnp::AuthType::AEADv1; + uint8_t hdr[4]; + if (!src_peek_eq(param->pkt.readsrc, hdr, 4)) { + return RNP_ERROR_READ; + } + + if (!get_aead_src_hdr(param->pkt.readsrc, ¶m->aead_hdr)) { + RNP_LOG("failed to read AEAD header"); + return RNP_ERROR_READ; + } + + /* check AEAD encrypted data packet header */ + if (param->aead_hdr.version != 1) { + RNP_LOG("unknown aead ver: %d", param->aead_hdr.version); + return RNP_ERROR_BAD_FORMAT; + } + if ((param->aead_hdr.aalg != PGP_AEAD_EAX) && (param->aead_hdr.aalg != PGP_AEAD_OCB)) { + RNP_LOG("unknown aead alg: %d", (int) param->aead_hdr.aalg); + return RNP_ERROR_BAD_FORMAT; + } + if (param->aead_hdr.csize > 56) { + RNP_LOG("too large chunk size: %d", param->aead_hdr.csize); + return RNP_ERROR_BAD_FORMAT; + } + if (param->aead_hdr.csize > 16) { + RNP_LOG("Warning: AEAD chunk bits > 16."); + } + param->chunklen = 1L << (param->aead_hdr.csize + 6); + + /* build additional data */ + param->aead_adlen = 13; + param->aead_ad[0] = param->pkt.hdr.hdr[0]; + memcpy(param->aead_ad + 1, hdr, 4); + memset(param->aead_ad + 5, 0, 8); + } else if (ptype == PGP_PKT_SE_IP_DATA) { + uint8_t mdcver; + if (!src_read_eq(param->pkt.readsrc, &mdcver, 1)) { + return RNP_ERROR_READ; + } + + if (mdcver != 1) { + RNP_LOG("unknown mdc ver: %d", (int) mdcver); + return RNP_ERROR_BAD_FORMAT; + } + param->auth_type = rnp::AuthType::MDC; + } + param->auth_validated = false; + + return RNP_SUCCESS; +} + +#define MAX_HIDDEN_TRIES 64 + +static rnp_result_t +init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc) +{ + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_source_encrypted_param_t *param = new (std::nothrow) pgp_source_encrypted_param_t(); + if (!param) { + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = param; + param->pkt.readsrc = readsrc; + param->handler = handler; + + src->close = encrypted_src_close; + src->finish = encrypted_src_finish; + src->type = PGP_STREAM_ENCRYPTED; + + /* Read the packet-related information */ + rnp_result_t errcode = encrypted_read_packet_data(param); + if (errcode) { + goto finish; + } + + src->read = !param->use_cfb() ? encrypted_src_read_aead : encrypted_src_read_cfb; + + /* Obtaining the symmetric key */ + if (!handler->password_provider) { + RNP_LOG("no password provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* informing handler about the available pubencs/symencs */ + if (handler->on_recipients) { + handler->on_recipients(param->pubencs, param->symencs, handler->param); + } + + bool have_key; + have_key = false; + /* Trying public-key decryption */ + if (!param->pubencs.empty()) { + if (!handler->key_provider) { + RNP_LOG("no key provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + pgp_key_request_ctx_t keyctx(PGP_OP_DECRYPT, true, PGP_KEY_SEARCH_KEYID); + + size_t pubidx = 0; + size_t hidden_tries = 0; + errcode = RNP_ERROR_NO_SUITABLE_KEY; + while (pubidx < param->pubencs.size()) { + auto &pubenc = param->pubencs[pubidx]; + keyctx.search.by.keyid = pubenc.key_id; + /* Get the key if any */ + pgp_key_t *seckey = pgp_request_key(handler->key_provider, &keyctx); + if (!seckey) { + pubidx++; + continue; + } + /* Check whether key fits our needs */ + bool hidden = pubenc.key_id == pgp_key_id_t({}); + if (!hidden || (++hidden_tries >= MAX_HIDDEN_TRIES)) { + pubidx++; + } + if (!seckey->has_secret() || !seckey->can_encrypt()) { + continue; + } + /* Check whether key is of required algorithm for hidden keyid */ + if (hidden && seckey->alg() != pubenc.alg) { + continue; + } + /* Decrypt key */ + rnp::KeyLocker seclock(*seckey); + if (!seckey->unlock(*handler->password_provider, PGP_OP_DECRYPT)) { + errcode = RNP_ERROR_BAD_PASSWORD; + continue; + } + + /* Try to initialize the decryption */ + rnp::LogStop logstop(hidden); + if (encrypted_try_key(param, &pubenc, &seckey->pkt(), *handler->ctx->ctx)) { + have_key = true; + /* inform handler that we used this pubenc */ + if (handler->on_decryption_start) { + handler->on_decryption_start(&pubenc, NULL, handler->param); + } + break; + } + } + } + + /* Trying password-based decryption */ + if (!have_key && !param->symencs.empty()) { + rnp::secure_array<char, MAX_PASSWORD_LENGTH> password; + pgp_password_ctx_t pass_ctx(PGP_OP_DECRYPT_SYM); + if (!pgp_request_password( + handler->password_provider, &pass_ctx, password.data(), password.size())) { + errcode = RNP_ERROR_BAD_PASSWORD; + goto finish; + } + + int intres = encrypted_try_password(param, password.data()); + if (intres > 0) { + have_key = true; + } else if (intres < 0) { + errcode = RNP_ERROR_NOT_SUPPORTED; + } else { + errcode = RNP_ERROR_BAD_PASSWORD; + } + } + + /* report decryption start to the handler */ + if (handler->on_decryption_info) { + handler->on_decryption_info(param->auth_type == rnp::AuthType::MDC, + param->aead_hdr.aalg, + param->salg, + handler->param); + } + + if (!have_key) { + RNP_LOG("failed to obtain decrypting key or password"); + if (!errcode) { + errcode = RNP_ERROR_NO_SUITABLE_KEY; + } + goto finish; + } + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + return errcode; +} + +static rnp_result_t +init_cleartext_signed_src(pgp_source_t *src) +{ + char buf[64]; + size_t hdrlen = strlen(ST_CLEAR_BEGIN); + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + + /* checking header line */ + if (!src_read_eq(param->readsrc, buf, hdrlen)) { + RNP_LOG("failed to read header"); + return RNP_ERROR_READ; + } + + if (memcmp(ST_CLEAR_BEGIN, buf, hdrlen)) { + RNP_LOG("wrong header"); + return RNP_ERROR_BAD_FORMAT; + } + + /* eol */ + if (!src_skip_eol(param->readsrc)) { + RNP_LOG("no eol after the cleartext header"); + return RNP_ERROR_BAD_FORMAT; + } + + /* parsing Hash headers */ + if (!cleartext_parse_headers(src)) { + return RNP_ERROR_BAD_FORMAT; + } + + /* now we are good to go */ + param->clr_fline = true; + return RNP_SUCCESS; +} + +#define MAX_SIG_ERRORS 65536 + +static rnp_result_t +init_signed_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t errcode = RNP_ERROR_GENERIC; + pgp_source_signed_param_t *param; + uint8_t ptag; + int ptype; + pgp_signature_t * sig = NULL; + bool cleartext; + size_t sigerrors = 0; + + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_source_signed_param_t(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = param; + + cleartext = is_cleartext_source(readsrc); + param->readsrc = readsrc; + param->handler = handler; + param->cleartext = cleartext; + param->stripped_crs = 0; + src->read = cleartext ? cleartext_src_read : signed_src_read; + src->close = signed_src_close; + src->finish = signed_src_finish; + src->type = cleartext ? PGP_STREAM_CLEARTEXT : PGP_STREAM_SIGNED; + + /* we need key provider to validate signatures */ + if (!handler->key_provider) { + RNP_LOG("no key provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (cleartext) { + errcode = init_cleartext_signed_src(src); + goto finish; + } + + /* Reading one-pass and signature packets */ + while (true) { + /* stop early if we are in zip-bomb with erroneous packets */ + if (sigerrors >= MAX_SIG_ERRORS) { + RNP_LOG("Too many one-pass/signature errors. Stopping."); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + + size_t readb = readsrc->readb; + if (!src_peek_eq(readsrc, &ptag, 1)) { + RNP_LOG("failed to read packet header"); + errcode = RNP_ERROR_READ; + goto finish; + } + + ptype = get_packet_type(ptag); + + if (ptype == PGP_PKT_ONE_PASS_SIG) { + if (param->onepasses.size() >= MAX_SIGNATURES) { + RNP_LOG("Too many one-pass signatures."); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + pgp_one_pass_sig_t onepass; + try { + errcode = onepass.parse(*readsrc); + } catch (const std::exception &e) { + errcode = RNP_ERROR_GENERIC; + } + if (errcode) { + if (errcode == RNP_ERROR_READ) { + goto finish; + } + if (readb == readsrc->readb) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + sigerrors++; + continue; + } + + try { + param->onepasses.push_back(onepass); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + errcode = RNP_ERROR_OUT_OF_MEMORY; + goto finish; + } + + /* adding hash context */ + try { + add_hash_for_sig(param, onepass.type, onepass.halg); + } catch (const std::exception &e) { + RNP_LOG("Failed to create hash %d for onepass %d : %s.", + (int) onepass.halg, + (int) onepass.type, + e.what()); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (onepass.nested) { + /* despite the name non-zero value means that it is the last one-pass */ + break; + } + } else if (ptype == PGP_PKT_SIGNATURE) { + /* no need to check the error here - we already know tag */ + if (signed_read_single_signature(param, readsrc, &sig)) { + sigerrors++; + } + /* adding hash context */ + if (sig) { + try { + add_hash_for_sig(param, sig->type(), sig->halg); + } catch (const std::exception &e) { + RNP_LOG("Failed to create hash %d for sig %d : %s.", + (int) sig->halg, + (int) sig->type(), + e.what()); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + } + } else { + break; + } + + /* check if we are not it endless loop */ + if (readb == readsrc->readb) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* for detached signature we'll get eof */ + if (src_eof(readsrc)) { + param->detached = true; + break; + } + } + + /* checking what we have now */ + if (param->onepasses.empty() && param->sigs.empty()) { + RNP_LOG("no signatures"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + if (!param->onepasses.empty() && !param->sigs.empty()) { + RNP_LOG("warning: one-passes are mixed with signatures"); + } + + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + + return errcode; +} + +pgp_processing_ctx_t::~pgp_processing_ctx_t() +{ + for (auto &src : sources) { + src_close(&src); + } +} + +/** @brief build PGP source sequence down to the literal data packet + * + **/ +static rnp_result_t +init_packet_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t *lsrc = &src; + size_t srcnum = ctx.sources.size(); + + while (1) { + uint8_t ptag = 0; + if (!src_peek_eq(lsrc, &ptag, 1)) { + RNP_LOG("cannot read packet tag"); + return RNP_ERROR_READ; + } + + int type = get_packet_type(ptag); + if (type < 0) { + RNP_LOG("wrong pkt tag %d", (int) ptag); + return RNP_ERROR_BAD_FORMAT; + } + + if (ctx.sources.size() - srcnum == MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many nested OpenPGP packets"); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_source_t psrc = {}; + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + switch (type) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + ret = init_encrypted_src(&ctx.handler, &psrc, lsrc); + break; + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SIGNATURE: + ret = init_signed_src(&ctx.handler, &psrc, lsrc); + break; + case PGP_PKT_COMPRESSED: + ret = init_compressed_src(&psrc, lsrc); + break; + case PGP_PKT_LITDATA: + if ((lsrc != &src) && (lsrc->type != PGP_STREAM_ENCRYPTED) && + (lsrc->type != PGP_STREAM_SIGNED) && (lsrc->type != PGP_STREAM_COMPRESSED)) { + RNP_LOG("unexpected literal pkt"); + ret = RNP_ERROR_BAD_FORMAT; + break; + } + ret = init_literal_src(&psrc, lsrc); + break; + case PGP_PKT_MARKER: + if (ctx.sources.size() != srcnum) { + RNP_LOG("Warning: marker packet wrapped in pgp stream."); + } + ret = stream_parse_marker(*lsrc); + if (ret) { + RNP_LOG("Invalid marker packet"); + return ret; + } + continue; + default: + RNP_LOG("unexpected pkt %d", type); + ret = RNP_ERROR_BAD_FORMAT; + } + + if (ret) { + return ret; + } + + try { + ctx.sources.push_back(psrc); + lsrc = &ctx.sources.back(); + } catch (const std::exception &e) { + src_close(&psrc); + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (lsrc->type == PGP_STREAM_LITERAL) { + ctx.literal_src = lsrc; + ctx.msg_type = PGP_MESSAGE_NORMAL; + return RNP_SUCCESS; + } + if (lsrc->type == PGP_STREAM_SIGNED) { + ctx.signed_src = lsrc; + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) lsrc->param; + if (param->detached) { + ctx.msg_type = PGP_MESSAGE_DETACHED; + return RNP_SUCCESS; + } + } + } +} + +static rnp_result_t +init_cleartext_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t clrsrc = {}; + rnp_result_t res; + + if ((res = init_signed_src(&ctx.handler, &clrsrc, &src))) { + return res; + } + try { + ctx.sources.push_back(clrsrc); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + src_close(&clrsrc); + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +init_armored_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t armorsrc = {}; + rnp_result_t res; + + if ((res = init_armored_src(&armorsrc, &src))) { + return res; + } + + try { + ctx.sources.push_back(armorsrc); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + src_close(&armorsrc); + return RNP_ERROR_OUT_OF_MEMORY; + } + return init_packet_sequence(ctx, ctx.sources.back()); +} + +rnp_result_t +process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src) +{ + rnp_result_t res = RNP_ERROR_BAD_FORMAT; + rnp_result_t fres; + pgp_processing_ctx_t ctx = {}; + pgp_source_t * decsrc = NULL; + pgp_source_t datasrc = {0}; + pgp_dest_t * outdest = NULL; + bool closeout = true; + uint8_t * readbuf = NULL; + + ctx.handler = *handler; + /* Building readers sequence. Checking whether it is binary data */ + if (is_pgp_source(src)) { + res = init_packet_sequence(ctx, src); + } else { + /* Trying armored or cleartext data */ + if (is_cleartext_source(&src)) { + /* Initializing cleartext message */ + res = init_cleartext_sequence(ctx, src); + } else if (is_armored_source(&src)) { + /* Initializing armored message */ + res = init_armored_sequence(ctx, src); + } else { + RNP_LOG("not an OpenPGP data provided"); + res = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + + if (res != RNP_SUCCESS) { + goto finish; + } + + if ((readbuf = (uint8_t *) calloc(1, PGP_INPUT_CACHE_SIZE)) == NULL) { + RNP_LOG("allocation failure"); + res = RNP_ERROR_OUT_OF_MEMORY; + goto finish; + } + + if (ctx.msg_type == PGP_MESSAGE_DETACHED) { + /* detached signature case */ + if (!handler->ctx->detached) { + RNP_LOG("Unexpected detached signature input."); + res = RNP_ERROR_BAD_STATE; + goto finish; + } + if (!handler->src_provider || !handler->src_provider(handler, &datasrc)) { + RNP_LOG("no data source for detached signature verification"); + res = RNP_ERROR_READ; + goto finish; + } + + while (!datasrc.eof) { + size_t read = 0; + if (!src_read(&datasrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (read > 0) { + signed_src_update(ctx.signed_src, readbuf, read); + } + } + src_close(&datasrc); + } else { + if (handler->ctx->detached) { + RNP_LOG("Detached signature expected."); + res = RNP_ERROR_BAD_STATE; + goto finish; + } + /* file processing case */ + decsrc = &ctx.sources.back(); + char * filename = NULL; + uint32_t mtime = 0; + + if (ctx.literal_src) { + auto *param = static_cast<pgp_source_literal_param_t *>(ctx.literal_src->param); + filename = param->hdr.fname; + mtime = param->hdr.timestamp; + } + + if (!handler->dest_provider || + !handler->dest_provider(handler, &outdest, &closeout, filename, mtime)) { + res = RNP_ERROR_WRITE; + goto finish; + } + + /* reading the input */ + while (!decsrc->eof) { + size_t read = 0; + if (!src_read(decsrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (!read) { + continue; + } + if (ctx.signed_src) { + signed_src_update(ctx.signed_src, readbuf, read); + } + dst_write(outdest, readbuf, read); + if (outdest->werr != RNP_SUCCESS) { + RNP_LOG("failed to output data"); + res = RNP_ERROR_WRITE; + break; + } + } + } + + /* finalizing the input. Signatures are checked on this step */ + if (res == RNP_SUCCESS) { + for (auto &ctxsrc : ctx.sources) { + fres = src_finish(&ctxsrc); + if (fres) { + res = fres; + } + } + } + + if (closeout && (ctx.msg_type != PGP_MESSAGE_DETACHED)) { + dst_close(outdest, res != RNP_SUCCESS); + } + +finish: + free(readbuf); + return res; +} |