From 8053187731ae8e3eb368d8360989cf5fd6eed9f7 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 05:32:49 +0200 Subject: Adding upstream version 0.17.0. Signed-off-by: Daniel Baumann --- src/librepgp/stream-armor.cpp | 1287 ++++++++++++++++++++ src/librepgp/stream-armor.h | 174 +++ src/librepgp/stream-common.cpp | 1212 ++++++++++++++++++ src/librepgp/stream-common.h | 556 +++++++++ src/librepgp/stream-ctx.cpp | 69 ++ src/librepgp/stream-ctx.h | 123 ++ src/librepgp/stream-def.h | 67 + src/librepgp/stream-dump.cpp | 2533 ++++++++++++++++++++++++++++++++++++++ src/librepgp/stream-dump.h | 53 + src/librepgp/stream-key.cpp | 1469 ++++++++++++++++++++++ src/librepgp/stream-key.h | 143 +++ src/librepgp/stream-packet.cpp | 1228 +++++++++++++++++++ src/librepgp/stream-packet.h | 323 +++++ src/librepgp/stream-parse.cpp | 2636 ++++++++++++++++++++++++++++++++++++++++ src/librepgp/stream-parse.h | 123 ++ src/librepgp/stream-sig.cpp | 1557 ++++++++++++++++++++++++ src/librepgp/stream-sig.h | 437 +++++++ src/librepgp/stream-write.cpp | 1973 ++++++++++++++++++++++++++++++ src/librepgp/stream-write.h | 83 ++ 19 files changed, 16046 insertions(+) create mode 100644 src/librepgp/stream-armor.cpp create mode 100644 src/librepgp/stream-armor.h create mode 100644 src/librepgp/stream-common.cpp create mode 100644 src/librepgp/stream-common.h create mode 100644 src/librepgp/stream-ctx.cpp create mode 100644 src/librepgp/stream-ctx.h create mode 100644 src/librepgp/stream-def.h create mode 100644 src/librepgp/stream-dump.cpp create mode 100644 src/librepgp/stream-dump.h create mode 100644 src/librepgp/stream-key.cpp create mode 100644 src/librepgp/stream-key.h create mode 100644 src/librepgp/stream-packet.cpp create mode 100644 src/librepgp/stream-packet.h create mode 100644 src/librepgp/stream-parse.cpp create mode 100644 src/librepgp/stream-parse.h create mode 100644 src/librepgp/stream-sig.cpp create mode 100644 src/librepgp/stream-sig.h create mode 100644 src/librepgp/stream-write.cpp create mode 100644 src/librepgp/stream-write.h (limited to 'src/librepgp') diff --git a/src/librepgp/stream-armor.cpp b/src/librepgp/stream-armor.cpp new file mode 100644 index 0000000..669c305 --- /dev/null +++ b/src/librepgp/stream-armor.cpp @@ -0,0 +1,1287 @@ +/* + * Copyright (c) 2017-2022, [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 +#ifdef HAVE_UNISTD_H +#include +#else +#include "uniwin.h" +#endif +#include +#include +#include "stream-def.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "str-utils.h" +#include "crypto/hash.hpp" +#include "utils.h" + +#define ARMORED_BLOCK_SIZE (4096) +#define ARMORED_PEEK_BUF_SIZE 1024 +#define ARMORED_MIN_LINE_LENGTH (16) +#define ARMORED_MAX_LINE_LENGTH (76) + +typedef struct pgp_source_armored_param_t { + pgp_source_t * readsrc; /* source to read from */ + pgp_armored_msg_t type; /* type of the message */ + char * armorhdr; /* armor header */ + char * version; /* Version: header if any */ + char * comment; /* Comment: header if any */ + char * hash; /* Hash: header if any */ + char * charset; /* Charset: header if any */ + uint8_t rest[ARMORED_BLOCK_SIZE]; /* unread decoded bytes, makes implementation easier */ + unsigned restlen; /* number of bytes in rest */ + unsigned restpos; /* index of first unread byte in rest, restpos <= restlen */ + uint8_t brest[3]; /* decoded 6-bit tail bytes */ + unsigned brestlen; /* number of bytes in brest */ + bool eofb64; /* end of base64 stream reached */ + uint8_t readcrc[3]; /* crc-24 from the armored data */ + bool has_crc; /* message contains CRC line */ + std::unique_ptr crc_ctx; /* CTX used to calculate CRC */ + bool noheaders; /* only base64 data, no headers */ +} pgp_source_armored_param_t; + +typedef struct pgp_dest_armored_param_t { + pgp_dest_t * writedst; + pgp_armored_msg_t type; /* type of the message */ + char eol[2]; /* end of line, all non-zeroes are written */ + unsigned lout; /* chars written in current line */ + unsigned llen; /* length of the base64 line, defaults to 76 as per RFC */ + uint8_t tail[2]; /* bytes which didn't fit into 3-byte boundary */ + unsigned tailc; /* number of bytes in tail */ + std::unique_ptr crc_ctx; /* CTX used to calculate CRC */ +} pgp_dest_armored_param_t; + +/* + Table for base64 lookups: + 0xff - wrong character, + 0xfe - '=' + 0xfd - eol/whitespace, + 0..0x3f - represented 6-bit number +*/ +static const uint8_t B64DEC[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, + 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, + 0xff, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff}; + +static bool +armor_read_padding(pgp_source_armored_param_t *param, size_t *read) +{ + char st[64]; + size_t stlen = 0; + + if (!src_peek_line(param->readsrc, st, 64, &stlen)) { + return false; + } + + if ((stlen == 1) || (stlen == 2)) { + if ((st[0] != CH_EQ) || ((stlen == 2) && (st[1] != CH_EQ))) { + return false; + } + + *read = stlen; + src_skip(param->readsrc, stlen); + return src_skip_eol(param->readsrc); + } else if (stlen == 5) { + *read = 0; + return true; + } else if ((stlen > 5) && !memcmp(st, ST_DASHES, 5)) { + /* case with absent crc and 3-byte last chunk */ + *read = 0; + return true; + } + return false; +} + +static bool +base64_read_padding(pgp_source_armored_param_t *param, size_t *read) +{ + char pad[16]; + size_t padlen = sizeof(pad); + + /* we would allow arbitrary number of whitespaces/eols after the padding */ + if (!src_read(param->readsrc, pad, padlen, &padlen)) { + return false; + } + /* strip trailing whitespaces */ + while (padlen && (B64DEC[(int) pad[padlen - 1]] == 0xfd)) { + padlen--; + } + /* check for '=' */ + for (size_t i = 0; i < padlen; i++) { + if (pad[i] != CH_EQ) { + RNP_LOG("wrong base64 padding: %.*s", (int) padlen, pad); + return false; + } + } + if (padlen > 2) { + RNP_LOG("wrong base64 padding length %zu.", padlen); + return false; + } + if (!src_eof(param->readsrc)) { + RNP_LOG("warning: extra data after the base64 stream."); + } + *read = padlen; + return true; +} + +static bool +armor_read_crc(pgp_source_t *src) +{ + uint8_t dec[4] = {0}; + char crc[8] = {0}; + size_t clen = 0; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!src_peek_line(param->readsrc, crc, sizeof(crc), &clen)) { + return false; + } + + if ((clen != 5) || (crc[0] != CH_EQ)) { + return false; + } + + for (int i = 0; i < 4; i++) { + if ((dec[i] = B64DEC[(uint8_t) crc[i + 1]]) >= 64) { + return false; + } + } + + param->readcrc[0] = (dec[0] << 2) | ((dec[1] >> 4) & 0x0F); + param->readcrc[1] = (dec[1] << 4) | ((dec[2] >> 2) & 0x0F); + param->readcrc[2] = (dec[2] << 6) | dec[3]; + + param->has_crc = true; + + src_skip(param->readsrc, 5); + return src_skip_eol(param->readsrc); +} + +static bool +armor_skip_chars(pgp_source_t *src, const char *chars) +{ + uint8_t ch; + size_t read; + + do { + bool found = false; + if (!src_peek(src, &ch, 1, &read)) { + return false; + } + if (!read) { + /* return true only if there is no underlying read error */ + return true; + } + for (const char *chptr = chars; *chptr; chptr++) { + if (ch == *chptr) { + src_skip(src, 1); + found = true; + break; + } + } + if (!found) { + break; + } + } while (1); + + return true; +} + +static bool +armor_read_trailer(pgp_source_t *src) +{ + char st[64]; + char str[64]; + size_t stlen; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!armor_skip_chars(param->readsrc, "\r\n")) { + return false; + } + + stlen = strlen(param->armorhdr); + if ((stlen > 5) && (stlen + 8 + 1 <= sizeof(st))) { + memcpy(st, ST_ARMOR_END, 8); /* 8 here is mandatory */ + memcpy(st + 8, param->armorhdr + 5, stlen - 5); + memcpy(st + stlen + 3, ST_DASHES, 5); + stlen += 8; + } else { + RNP_LOG("Internal error"); + return false; + } + if (!src_peek_eq(param->readsrc, str, stlen) || strncmp(str, st, stlen)) { + return false; + } + src_skip(param->readsrc, stlen); + (void) armor_skip_chars(param->readsrc, "\t "); + (void) src_skip_eol(param->readsrc); + return true; +} + +static bool +armored_update_crc(pgp_source_armored_param_t *param, + const void * buf, + size_t len, + bool finish = false) +{ + if (param->noheaders) { + return true; + } + try { + param->crc_ctx->add(buf, len); + if (!finish) { + return true; + } + auto crc = param->crc_ctx->finish(); + if (param->has_crc && memcmp(param->readcrc, crc.data(), 3)) { + RNP_LOG("Warning: CRC mismatch"); + } + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +static bool +armored_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + uint8_t b64buf[ARMORED_BLOCK_SIZE]; /* input base64 data with spaces and so on */ + uint8_t decbuf[ARMORED_BLOCK_SIZE + 4]; /* decoded 6-bit values */ + uint8_t *bufptr = (uint8_t *) buf; /* for better readability below */ + uint8_t *bptr, *bend; /* pointer to input data in b64buf */ + uint8_t *dptr, *dend, *pend; /* pointers to decoded data in decbuf: working pointer, last + available byte, last byte to process */ + uint8_t bval; + uint32_t b24; + size_t read = 0; + size_t left = len; + size_t eqcount = 0; /* number of '=' at the end of base64 stream */ + + if (!param) { + return false; + } + + /* checking whether there are some decoded bytes */ + if (param->restpos < param->restlen) { + if (param->restlen - param->restpos >= len) { + memcpy(bufptr, ¶m->rest[param->restpos], len); + param->restpos += len; + try { + param->crc_ctx->add(bufptr, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + *readres = len; + return true; + } else { + left = len - (param->restlen - param->restpos); + memcpy(bufptr, ¶m->rest[param->restpos], len - left); + param->restpos = param->restlen = 0; + bufptr += len - left; + } + } + + if (param->eofb64) { + *readres = len - left; + return true; + } + + memcpy(decbuf, param->brest, param->brestlen); + dend = decbuf + param->brestlen; + + do { + if (!src_peek(param->readsrc, b64buf, sizeof(b64buf), &read)) { + return false; + } + if (!read) { + RNP_LOG("premature end of armored input"); + return false; + } + + dptr = dend; + bptr = b64buf; + bend = b64buf + read; + /* checking input data, stripping away whitespaces, checking for end of the b64 data */ + while (bptr < bend) { + if ((bval = B64DEC[*(bptr++)]) < 64) { + *(dptr++) = bval; + } else if (bval == 0xfe) { + /* '=' means the base64 padding or the beginning of checksum */ + param->eofb64 = true; + break; + } else if (bval == 0xff) { + auto ch = *(bptr - 1); + /* OpenPGP message headers without the crc and without trailing = */ + if ((ch == CH_DASH) && !param->noheaders) { + param->eofb64 = true; + break; + } + RNP_LOG("wrong base64 character 0x%02hhX", ch); + return false; + } + } + + dend = dptr; + dptr = decbuf; + /* Processing full 4s which will go directly to the buf. + After this left < 3 or decbuf has < 4 bytes */ + if ((size_t)(dend - dptr) / 4 * 3 < left) { + pend = decbuf + (dend - dptr) / 4 * 4; + left -= (dend - dptr) / 4 * 3; + } else { + pend = decbuf + (left / 3) * 4; + left -= left / 3 * 3; + } + + /* this one would the most performance-consuming part for large chunks */ + while (dptr < pend) { + b24 = *dptr++ << 18; + b24 |= *dptr++ << 12; + b24 |= *dptr++ << 6; + b24 |= *dptr++; + *bufptr++ = b24 >> 16; + *bufptr++ = b24 >> 8; + *bufptr++ = b24 & 0xff; + } + + /* moving rest to the beginning of decbuf */ + memmove(decbuf, dptr, dend - dptr); + dend = decbuf + (dend - dptr); + + /* skip already processed data */ + if (!param->eofb64) { + /* all input is base64 data or eol/spaces, so skipping it */ + src_skip(param->readsrc, read); + /* check for eof for base64-encoded data without headers */ + if (param->noheaders && src_eof(param->readsrc)) { + src_skip(param->readsrc, read); + param->eofb64 = true; + } else { + continue; + } + } else { + /* '=' reached, bptr points on it */ + src_skip(param->readsrc, bptr - b64buf - 1); + } + + /* end of base64 data */ + if (param->noheaders) { + if (!base64_read_padding(param, &eqcount)) { + return false; + } + break; + } + /* reading b64 padding if any */ + if (!armor_read_padding(param, &eqcount)) { + RNP_LOG("wrong padding"); + return false; + } + /* reading crc */ + if (!armor_read_crc(src)) { + RNP_LOG("Warning: missing or malformed CRC line"); + } + /* reading armor trailing line */ + if (!armor_read_trailer(src)) { + RNP_LOG("wrong armor trailer"); + return false; + } + break; + } while (left >= 3); + + /* process bytes left in decbuf */ + + dptr = decbuf; + pend = decbuf + (dend - decbuf) / 4 * 4; + bptr = param->rest; + while (dptr < pend) { + b24 = *dptr++ << 18; + b24 |= *dptr++ << 12; + b24 |= *dptr++ << 6; + b24 |= *dptr++; + *bptr++ = b24 >> 16; + *bptr++ = b24 >> 8; + *bptr++ = b24 & 0xff; + } + + if (!armored_update_crc(param, buf, bufptr - (uint8_t *) buf)) { + return false; + } + + if (param->eofb64) { + if ((dend - dptr + eqcount) % 4 != 0) { + RNP_LOG("wrong b64 padding"); + return false; + } + + if (eqcount == 1) { + b24 = (*dptr << 10) | (*(dptr + 1) << 4) | (*(dptr + 2) >> 2); + *bptr++ = b24 >> 8; + *bptr++ = b24 & 0xff; + } else if (eqcount == 2) { + *bptr++ = (*dptr << 2) | (*(dptr + 1) >> 4); + } + + /* Calculate CRC after reading whole input stream */ + if (!armored_update_crc(param, param->rest, bptr - param->rest, true)) { + return false; + } + } else { + /* few bytes which do not fit to 4 boundary */ + for (int i = 0; i < dend - dptr; i++) { + param->brest[i] = *(dptr + i); + } + param->brestlen = dend - dptr; + } + + param->restlen = bptr - param->rest; + + /* check whether we have some bytes to add */ + if ((left > 0) && (param->restlen > 0)) { + read = left > param->restlen ? param->restlen : left; + memcpy(bufptr, param->rest, read); + if (!param->eofb64 && !armored_update_crc(param, bufptr, read)) { + return false; + } + left -= read; + param->restpos += read; + } + + *readres = len - left; + return true; +} + +static void +armored_src_close(pgp_source_t *src) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (param) { + free(param->armorhdr); + free(param->version); + free(param->comment); + free(param->hash); + free(param->charset); + delete param; + src->param = NULL; + } +} + +/** @brief finds armor header position in the buffer, returning beginning of header or NULL. + * hdrlen will contain the length of the header + **/ +static const char * +find_armor_header(const char *buf, size_t len, size_t *hdrlen) +{ + int st = -1; + + for (unsigned i = 0; i < len - 10; i++) { + if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) { + st = i; + break; + } + } + + if (st < 0) { + return NULL; + } + + for (unsigned i = st + 5; i <= len - 5; i++) { + if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) { + *hdrlen = i + 5 - st; + return &buf[st]; + } + } + + return NULL; +} + +static bool +str_equals(const char *str, size_t len, const char *another) +{ + size_t alen = strlen(another); + return (len == alen) && !memcmp(str, another, alen); +} + +static pgp_armored_msg_t +armor_str_to_data_type(const char *str, size_t len) +{ + if (!str) { + return PGP_ARMORED_UNKNOWN; + } + if (str_equals(str, len, "BEGIN PGP MESSAGE")) { + return PGP_ARMORED_MESSAGE; + } + if (str_equals(str, len, "BEGIN PGP PUBLIC KEY BLOCK") || + str_equals(str, len, "BEGIN PGP PUBLIC KEY")) { + return PGP_ARMORED_PUBLIC_KEY; + } + if (str_equals(str, len, "BEGIN PGP SECRET KEY BLOCK") || + str_equals(str, len, "BEGIN PGP SECRET KEY") || + str_equals(str, len, "BEGIN PGP PRIVATE KEY BLOCK") || + str_equals(str, len, "BEGIN PGP PRIVATE KEY")) { + return PGP_ARMORED_SECRET_KEY; + } + if (str_equals(str, len, "BEGIN PGP SIGNATURE")) { + return PGP_ARMORED_SIGNATURE; + } + if (str_equals(str, len, "BEGIN PGP SIGNED MESSAGE")) { + return PGP_ARMORED_CLEARTEXT; + } + return PGP_ARMORED_UNKNOWN; +} + +pgp_armored_msg_t +rnp_armor_guess_type(pgp_source_t *src) +{ + uint8_t ptag; + + if (!src_peek_eq(src, &ptag, 1)) { + return PGP_ARMORED_UNKNOWN; + } + + switch (get_packet_type(ptag)) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_COMPRESSED: + case PGP_PKT_LITDATA: + case PGP_PKT_MARKER: + return PGP_ARMORED_MESSAGE; + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + return PGP_ARMORED_PUBLIC_KEY; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return PGP_ARMORED_SECRET_KEY; + case PGP_PKT_SIGNATURE: + return PGP_ARMORED_SIGNATURE; + default: + return PGP_ARMORED_UNKNOWN; + } +} + +static pgp_armored_msg_t +rnp_armored_guess_type_by_readahead(pgp_source_t *src) +{ + if (!src->cache) { + return PGP_ARMORED_UNKNOWN; + } + + pgp_source_t armorsrc = {0}; + pgp_source_t memsrc = {0}; + size_t read; + // peek as much as the cache can take + bool cache_res = src_peek(src, NULL, sizeof(src->cache->buf), &read); + if (!cache_res || !read || + init_mem_src(&memsrc, + src->cache->buf + src->cache->pos, + src->cache->len - src->cache->pos, + false)) { + return PGP_ARMORED_UNKNOWN; + } + rnp_result_t res = init_armored_src(&armorsrc, &memsrc); + if (res) { + src_close(&memsrc); + RNP_LOG("failed to parse armored data"); + return PGP_ARMORED_UNKNOWN; + } + pgp_armored_msg_t guessed = rnp_armor_guess_type(&armorsrc); + src_close(&armorsrc); + src_close(&memsrc); + return guessed; +} + +pgp_armored_msg_t +rnp_armored_get_type(pgp_source_t *src) +{ + pgp_armored_msg_t guessed = rnp_armored_guess_type_by_readahead(src); + if (guessed != PGP_ARMORED_UNKNOWN) { + return guessed; + } + + char hdr[ARMORED_PEEK_BUF_SIZE]; + const char *armhdr; + size_t armhdrlen; + size_t read; + + if (!src_peek(src, hdr, sizeof(hdr), &read) || (read < 20)) { + return PGP_ARMORED_UNKNOWN; + } + if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { + return PGP_ARMORED_UNKNOWN; + } + + return armor_str_to_data_type(armhdr + 5, armhdrlen - 10); +} + +static bool +armor_parse_header(pgp_source_t *src) +{ + char hdr[ARMORED_PEEK_BUF_SIZE]; + const char * armhdr; + size_t armhdrlen; + size_t read; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!src_peek(param->readsrc, hdr, sizeof(hdr), &read) || (read < 20)) { + return false; + } + + if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { + RNP_LOG("no armor header"); + return false; + } + + /* if there are non-whitespaces before the armor header then issue warning */ + for (char *ch = hdr; ch < armhdr; ch++) { + if (B64DEC[(uint8_t) *ch] != 0xfd) { + RNP_LOG("extra data before the header line"); + break; + } + } + + param->type = armor_str_to_data_type(armhdr + 5, armhdrlen - 10); + if (param->type == PGP_ARMORED_UNKNOWN) { + RNP_LOG("unknown armor header"); + return false; + } + + if ((param->armorhdr = (char *) malloc(armhdrlen - 9)) == NULL) { + RNP_LOG("allocation failed"); + return false; + } + + memcpy(param->armorhdr, armhdr + 5, armhdrlen - 10); + param->armorhdr[armhdrlen - 10] = '\0'; + src_skip(param->readsrc, armhdr - hdr + armhdrlen); + armor_skip_chars(param->readsrc, "\t "); + return true; +} + +static bool +armor_skip_line(pgp_source_t *src) +{ + char header[ARMORED_PEEK_BUF_SIZE] = {0}; + do { + size_t hdrlen = 0; + bool res = src_peek_line(src, header, sizeof(header), &hdrlen); + if (hdrlen) { + src_skip(src, hdrlen); + } + if (res || (hdrlen < sizeof(header) - 1)) { + return res; + } + } while (1); +} + +static bool +is_base64_line(const char *line, size_t len) +{ + for (size_t i = 0; i < len && line[i]; i++) { + if (B64DEC[(uint8_t) line[i]] == 0xff) + return false; + } + return true; +} + +static bool +armor_parse_headers(pgp_source_t *src) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + char header[ARMORED_PEEK_BUF_SIZE] = {0}; + + do { + size_t hdrlen = 0; + if (!src_peek_line(param->readsrc, header, sizeof(header), &hdrlen)) { + /* if line is too long let's cut it to the reasonable size */ + src_skip(param->readsrc, hdrlen); + if ((hdrlen != sizeof(header) - 1) || !armor_skip_line(param->readsrc)) { + RNP_LOG("failed to peek line: unexpected end of data"); + return false; + } + RNP_LOG("Too long armor header - truncated."); + header[hdrlen] = '\0'; + } else if (hdrlen) { + if (is_base64_line(header, hdrlen)) { + RNP_LOG("Warning: no empty line after the base64 headers"); + return true; + } + src_skip(param->readsrc, hdrlen); + if (rnp::is_blank_line(header, hdrlen)) { + return src_skip_eol(param->readsrc); + } + } else { + /* empty line - end of the headers */ + return src_skip_eol(param->readsrc); + } + + char *hdrval = (char *) malloc(hdrlen + 1); + if (!hdrval) { + RNP_LOG("malloc failed"); + return false; + } + + if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_VERSION, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->version); + param->version = hdrval; + } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_COMMENT, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->comment); + param->comment = hdrval; + } else if ((hdrlen >= 5) && !strncmp(header, ST_HEADER_HASH, 6)) { + memcpy(hdrval, header + 6, hdrlen - 5); + free(param->hash); + param->hash = hdrval; + } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_CHARSET, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->charset); + param->charset = hdrval; + } else { + RNP_LOG("unknown header '%s'", header); + free(hdrval); + } + + if (!src_skip_eol(param->readsrc)) { + return false; + } + } while (1); +} + +rnp_result_t +init_armored_src(pgp_source_t *src, pgp_source_t *readsrc, bool noheaders) +{ + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_source_armored_param_t *param = new (std::nothrow) pgp_source_armored_param_t(); + if (!param) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + param->readsrc = readsrc; + param->noheaders = noheaders; + src->param = param; + src->read = armored_src_read; + src->close = armored_src_close; + src->type = PGP_STREAM_ARMORED; + + /* base64 data only */ + if (noheaders) { + return RNP_SUCCESS; + } + + /* initialize crc context */ + param->crc_ctx = rnp::CRC24::create(); + /* parsing armored header */ + rnp_result_t errcode = RNP_ERROR_GENERIC; + if (!armor_parse_header(src)) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* eol */ + if (!src_skip_eol(param->readsrc)) { + RNP_LOG("no eol after the armor header"); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* parsing headers */ + if (!armor_parse_headers(src)) { + RNP_LOG("failed to parse headers"); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + + /* now we are good to go with base64-encoded data */ + errcode = RNP_SUCCESS; +finish: + if (errcode) { + src_close(src); + } + return errcode; +} + +/** @brief Write message header to the dst. */ +static bool +armor_write_message_header(pgp_dest_armored_param_t *param, bool finish) +{ + const char *str = finish ? ST_ARMOR_END : ST_ARMOR_BEGIN; + dst_write(param->writedst, str, strlen(str)); + switch (param->type) { + case PGP_ARMORED_MESSAGE: + str = "MESSAGE"; + break; + case PGP_ARMORED_PUBLIC_KEY: + str = "PUBLIC KEY BLOCK"; + break; + case PGP_ARMORED_SECRET_KEY: + str = "PRIVATE KEY BLOCK"; + break; + case PGP_ARMORED_SIGNATURE: + str = "SIGNATURE"; + break; + case PGP_ARMORED_CLEARTEXT: + str = "SIGNED MESSAGE"; + break; + default: + return false; + } + dst_write(param->writedst, str, strlen(str)); + dst_write(param->writedst, ST_DASHES, strlen(ST_DASHES)); + return true; +} + +static void +armor_write_eol(pgp_dest_armored_param_t *param) +{ + if (param->eol[0]) { + dst_write(param->writedst, ¶m->eol[0], 1); + } + if (param->eol[1]) { + dst_write(param->writedst, ¶m->eol[1], 1); + } +} + +static void +armor_append_eol(pgp_dest_armored_param_t *param, uint8_t *&ptr) +{ + if (param->eol[0]) { + *ptr++ = param->eol[0]; + } + if (param->eol[1]) { + *ptr++ = param->eol[1]; + } +} + +/* Base 64 encoded table, quadruplicated to save cycles on use & 0x3f operation */ +static const uint8_t B64ENC[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', + '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/'}; + +static void +armored_encode3(uint8_t *out, uint8_t *in) +{ + out[0] = B64ENC[in[0] >> 2]; + out[1] = B64ENC[((in[0] << 4) | (in[1] >> 4)) & 0xff]; + out[2] = B64ENC[((in[1] << 2) | (in[2] >> 6)) & 0xff]; + out[3] = B64ENC[in[2] & 0xff]; +} + +static rnp_result_t +armored_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* update crc */ + bool base64 = param->type == PGP_ARMORED_BASE64; + if (!base64) { + try { + param->crc_ctx->add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + uint8_t encbuf[PGP_INPUT_CACHE_SIZE / 2]; + uint8_t *bufptr = (uint8_t *) buf; + uint8_t *bufend = bufptr + len; + uint8_t *encptr = encbuf; + /* processing tail if any */ + if (len + param->tailc < 3) { + memcpy(¶m->tail[param->tailc], buf, len); + param->tailc += len; + return RNP_SUCCESS; + } else if (param->tailc > 0) { + uint8_t dec3[3] = {0}; + memcpy(dec3, param->tail, param->tailc); + memcpy(&dec3[param->tailc], bufptr, 3 - param->tailc); + bufptr += 3 - param->tailc; + param->tailc = 0; + armored_encode3(encptr, dec3); + encptr += 4; + param->lout += 4; + if (param->lout == param->llen) { + armor_append_eol(param, encptr); + param->lout = 0; + } + } + + /* this version prints whole chunks, so rounding down to the closest 4 */ + auto adjusted_llen = param->llen & ~3; + /* number of input bytes to form a whole line of output, param->llen / 4 * 3 */ + auto inllen = (adjusted_llen >> 2) + (adjusted_llen >> 1); + /* pointer to the last full line space in encbuf */ + auto enclast = encbuf + sizeof(encbuf) - adjusted_llen - 2; + + /* processing line chunks, this is the main performance-hitting cycle */ + while (bufptr + 3 <= bufend) { + /* checking whether we have enough space in encbuf */ + if (encptr > enclast) { + dst_write(param->writedst, encbuf, encptr - encbuf); + encptr = encbuf; + } + /* setup length of the input to process in this iteration */ + uint8_t *inlend = + !param->lout ? bufptr + inllen : bufptr + ((adjusted_llen - param->lout) >> 2) * 3; + if (inlend > bufend) { + /* no enough input for the full line */ + inlend = bufptr + (bufend - bufptr) / 3 * 3; + param->lout += (inlend - bufptr) / 3 * 4; + } else { + /* we have full line of input */ + param->lout = 0; + } + + /* processing one line */ + while (bufptr < inlend) { + uint32_t t = (bufptr[0] << 16) | (bufptr[1] << 8) | (bufptr[2]); + bufptr += 3; + *encptr++ = B64ENC[(t >> 18) & 0xff]; + *encptr++ = B64ENC[(t >> 12) & 0xff]; + *encptr++ = B64ENC[(t >> 6) & 0xff]; + *encptr++ = B64ENC[t & 0xff]; + } + + /* adding line ending */ + if (!param->lout) { + armor_append_eol(param, encptr); + } + } + + dst_write(param->writedst, encbuf, encptr - encbuf); + + /* saving tail */ + param->tailc = bufend - bufptr; + memcpy(param->tail, bufptr, param->tailc); + + return RNP_SUCCESS; +} + +static rnp_result_t +armored_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + + /* writing tail */ + uint8_t buf[5]; + if (param->tailc == 1) { + buf[0] = B64ENC[param->tail[0] >> 2]; + buf[1] = B64ENC[(param->tail[0] << 4) & 0xff]; + buf[2] = CH_EQ; + buf[3] = CH_EQ; + dst_write(param->writedst, buf, 4); + } else if (param->tailc == 2) { + buf[0] = B64ENC[(param->tail[0] >> 2)]; + buf[1] = B64ENC[((param->tail[0] << 4) | (param->tail[1] >> 4)) & 0xff]; + buf[2] = B64ENC[(param->tail[1] << 2) & 0xff]; + buf[3] = CH_EQ; + dst_write(param->writedst, buf, 4); + } + /* Check for base64 */ + if (param->type == PGP_ARMORED_BASE64) { + return param->writedst->werr; + } + + /* writing EOL if needed */ + if ((param->tailc > 0) || (param->lout > 0)) { + armor_write_eol(param); + } + + /* writing CRC and EOL */ + // At this point crc_ctx is initialized, so call can't fail + buf[0] = CH_EQ; + try { + auto crc = param->crc_ctx->finish(); + armored_encode3(&buf[1], crc.data()); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + dst_write(param->writedst, buf, 5); + armor_write_eol(param); + + /* writing armor header */ + if (!armor_write_message_header(param, true)) { + return RNP_ERROR_BAD_PARAMETERS; + } + armor_write_eol(param); + return param->writedst->werr; +} + +static void +armored_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + + if (!param) { + return; + } + /* dst_close may be called without dst_finish on error */ + delete param; + dst->param = NULL; +} + +rnp_result_t +init_armored_dst(pgp_dest_t *dst, pgp_dest_t *writedst, pgp_armored_msg_t msgtype) +{ + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_dest_armored_param_t *param = new (std::nothrow) pgp_dest_armored_param_t(); + if (!param) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->param = param; + dst->write = armored_dst_write; + dst->finish = armored_dst_finish; + dst->close = armored_dst_close; + dst->type = PGP_STREAM_ARMORED; + dst->writeb = 0; + dst->clen = 0; + + param->writedst = writedst; + param->type = msgtype; + /* Base64 message */ + if (msgtype == PGP_ARMORED_BASE64) { + /* Base64 encoding will not output EOLs but we need this to not duplicate code for a + * separate base64_dst_write function */ + param->eol[0] = 0; + param->eol[1] = 0; + param->llen = 256; + return RNP_SUCCESS; + } + /* create crc context */ + param->crc_ctx = rnp::CRC24::create(); + param->eol[0] = CH_CR; + param->eol[1] = CH_LF; + param->llen = 76; /* must be multiple of 4 */ + /* armor header */ + if (!armor_write_message_header(param, false)) { + RNP_LOG("unknown data type"); + armored_dst_close(dst, true); + return RNP_ERROR_BAD_PARAMETERS; + } + armor_write_eol(param); + /* empty line */ + armor_write_eol(param); + return RNP_SUCCESS; +} + +bool +is_armored_dest(pgp_dest_t *dst) +{ + return dst->type == PGP_STREAM_ARMORED; +} + +rnp_result_t +armored_dst_set_line_length(pgp_dest_t *dst, size_t llen) +{ + if (!dst || (llen < ARMORED_MIN_LINE_LENGTH) || (llen > ARMORED_MAX_LINE_LENGTH) || + !dst->param || !is_armored_dest(dst)) { + return RNP_ERROR_BAD_PARAMETERS; + } + auto param = (pgp_dest_armored_param_t *) dst->param; + param->llen = llen; + return RNP_SUCCESS; +} + +bool +is_armored_source(pgp_source_t *src) +{ + uint8_t buf[ARMORED_PEEK_BUF_SIZE]; + size_t read = 0; + + if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_ARMOR_BEGIN) + 1)) { + return false; + } + buf[read - 1] = 0; + return !!strstr((char *) buf, ST_ARMOR_BEGIN); +} + +bool +is_cleartext_source(pgp_source_t *src) +{ + uint8_t buf[ARMORED_PEEK_BUF_SIZE]; + size_t read = 0; + + if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_CLEAR_BEGIN))) { + return false; + } + buf[read - 1] = 0; + return !!strstr((char *) buf, ST_CLEAR_BEGIN); +} + +bool +is_base64_source(pgp_source_t &src) +{ + char buf[128]; + size_t read = 0; + + if (!src_peek(&src, buf, sizeof(buf), &read) || (read < 4)) { + return false; + } + return is_base64_line(buf, read); +} + +rnp_result_t +rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst) +{ + rnp_result_t res = RNP_ERROR_BAD_FORMAT; + pgp_source_t armorsrc = {0}; + + /* initializing armored message */ + res = init_armored_src(&armorsrc, src); + if (res) { + return res; + } + /* Reading data from armored source and writing it to the output */ + res = dst_write_src(&armorsrc, dst); + if (res) { + RNP_LOG("dearmoring failed"); + } + + src_close(&armorsrc); + return res; +} + +rnp_result_t +rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype) +{ + pgp_dest_t armordst = {0}; + rnp_result_t res = init_armored_dst(&armordst, dst, msgtype); + if (res) { + return res; + } + + res = dst_write_src(src, &armordst); + if (res) { + RNP_LOG("armoring failed"); + } + + dst_close(&armordst, res != RNP_SUCCESS); + return res; +} + +namespace rnp { + +const uint32_t ArmoredSource::AllowBinary = 0x01; +const uint32_t ArmoredSource::AllowBase64 = 0x02; +const uint32_t ArmoredSource::AllowMultiple = 0x04; + +ArmoredSource::ArmoredSource(pgp_source_t &readsrc, uint32_t flags) + : Source(), readsrc_(readsrc), multiple_(false) +{ + /* Do not dearmor already armored stream */ + bool already = readsrc_.type == PGP_STREAM_ARMORED; + /* Check for base64 source: no multiple streams allowed */ + if (!already && (flags & AllowBase64) && (is_base64_source(readsrc))) { + auto res = init_armored_src(&src_, &readsrc_, true); + if (res) { + RNP_LOG("Failed to parse base64 data."); + throw rnp::rnp_exception(res); + } + armored_ = true; + return; + } + /* Check for armored source */ + if (!already && is_armored_source(&readsrc)) { + auto res = init_armored_src(&src_, &readsrc_); + if (res) { + RNP_LOG("Failed to parse armored data."); + throw rnp::rnp_exception(res); + } + armored_ = true; + multiple_ = flags & AllowMultiple; + return; + } + /* Use binary source if allowed */ + if (!(flags & AllowBinary)) { + RNP_LOG("Non-armored data is not allowed here."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + armored_ = false; +} + +void +ArmoredSource::restart() +{ + if (!armored_ || src_eof(&readsrc_) || src_error(&readsrc_)) { + return; + } + src_close(&src_); + auto res = init_armored_src(&src_, &readsrc_); + if (res) { + throw rnp::rnp_exception(res); + } +} + +pgp_source_t & +ArmoredSource::src() +{ + return armored_ ? src_ : readsrc_; +} +} // namespace rnp diff --git a/src/librepgp/stream-armor.h b/src/librepgp/stream-armor.h new file mode 100644 index 0000000..4c91fd2 --- /dev/null +++ b/src/librepgp/stream-armor.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2017-2020, [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. + */ + +#ifndef STREAM_ARMOUR_H_ +#define STREAM_ARMOUR_H_ + +#include "stream-common.h" + +typedef enum { + PGP_ARMORED_UNKNOWN, + PGP_ARMORED_MESSAGE, + PGP_ARMORED_PUBLIC_KEY, + PGP_ARMORED_SECRET_KEY, + PGP_ARMORED_SIGNATURE, + PGP_ARMORED_CLEARTEXT, + PGP_ARMORED_BASE64 +} pgp_armored_msg_t; + +/* @brief Init dearmoring stream + * @param src allocated pgp_source_t structure + * @param readsrc source to read data from + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t init_armored_src(pgp_source_t *src, + pgp_source_t *readsrc, + bool noheaders = false); + +/* @brief Init armoring stream + * @param dst allocated pgp_dest_t structure + * @param writedst destination to write armored data to + * @param msgtype type of the message (see pgp_armored_msg_t) + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t init_armored_dst(pgp_dest_t * dst, + pgp_dest_t * writedst, + pgp_armored_msg_t msgtype); + +/* @brief Dearmor the source, outputting binary data + * @param src initialized source with armored data + * @param dst initialized dest to write binary data to + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst); + +/* @brief Armor the source, outputting base64-encoded data with headers + * @param src initialized source with binary data + * @param dst destination to write armored data + * @msgtype type of the message, to write correct armor headers + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype); + +/* @brief Guess the corresponding armored message type by first byte(s) of PGP message + * @param src initialized source with binary PGP message data + * @return corresponding enum element or PGP_ARMORED_UNKNOWN + **/ +pgp_armored_msg_t rnp_armor_guess_type(pgp_source_t *src); + +/* @brief Get type of the armored message by peeking header. + * @param src initialized source with armored message data. + * @return corresponding enum element or PGP_ARMORED_UNKNOWN + **/ +pgp_armored_msg_t rnp_armored_get_type(pgp_source_t *src); + +/* @brief Check whether source could be an armored source + * @param src initialized source with some data + * @return true if source could be an armored data or false otherwise + **/ +bool is_armored_source(pgp_source_t *src); + +/* @brief Check whether destination is armored + * @param dest initialized destination + * @return true if destination is armored or false otherwise + **/ +bool is_armored_dest(pgp_dest_t *dst); + +/* @brief Check whether source is cleartext signed + * @param src initialized source with some data + * @return true if source could be a cleartext signed data or false otherwise + **/ +bool is_cleartext_source(pgp_source_t *src); + +/** @brief Check whether source is base64-encoded + * @param src initialized source with some data + * @return true if source could be a base64-encoded data or false otherwise + **/ +bool is_base64_source(pgp_source_t &src); + +/** Set line length for armoring + * + * @param dst initialized dest to write armored data to + * @param llen line length in characters + * @return RNP_SUCCESS on success, or any other value on error + */ +rnp_result_t armored_dst_set_line_length(pgp_dest_t *dst, size_t llen); + +namespace rnp { + +class ArmoredSource : public Source { + pgp_source_t &readsrc_; + bool armored_; + bool multiple_; + + public: + static const uint32_t AllowBinary; + static const uint32_t AllowBase64; + static const uint32_t AllowMultiple; + + ArmoredSource(const ArmoredSource &) = delete; + ArmoredSource(ArmoredSource &&) = delete; + + ArmoredSource(pgp_source_t &readsrc, uint32_t flags = 0); + + pgp_source_t &src(); + + bool + multiple() + { + return multiple_; + } + + /* Restart dearmoring in case of multiple armored messages in a single stream */ + void restart(); +}; + +class ArmoredDest : public Dest { + pgp_dest_t &writedst_; + + public: + ArmoredDest(const ArmoredDest &) = delete; + ArmoredDest(ArmoredDest &&) = delete; + + ArmoredDest(pgp_dest_t &writedst, pgp_armored_msg_t msgtype) : Dest(), writedst_(writedst) + { + auto ret = init_armored_dst(&dst_, &writedst_, msgtype); + if (ret) { + throw rnp::rnp_exception(ret); + } + }; + + ~ArmoredDest() + { + if (!discard_) { + dst_finish(&dst_); + } + } +}; + +} // namespace rnp + +#endif diff --git a/src/librepgp/stream-common.cpp b/src/librepgp/stream-common.cpp new file mode 100644 index 0000000..334f93b --- /dev/null +++ b/src/librepgp/stream-common.cpp @@ -0,0 +1,1212 @@ +/* + * Copyright (c) 2017-2020 [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 +#ifdef HAVE_UNISTD_H +#include +#include +#else +#include "uniwin.h" +#endif +#include +#include +#include +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#include +#include "rnp.h" +#include "stream-common.h" +#include "types.h" +#include "file-utils.h" +#include "crypto/mem.h" +#include +#include + +bool +src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + size_t left = len; + size_t read; + pgp_source_cache_t *cache = src->cache; + bool readahead = cache ? cache->readahead : false; + + if (src->error) { + return false; + } + + if (src->eof || (len == 0)) { + *readres = 0; + return true; + } + + // Do not read more then available if source size is known + if (src->knownsize && (src->readb + len > src->size)) { + len = src->size - src->readb; + left = len; + readahead = false; + } + + // Check whether we have cache and there is data inside + if (cache && (cache->len > cache->pos)) { + read = cache->len - cache->pos; + if (read >= len) { + memcpy(buf, &cache->buf[cache->pos], len); + cache->pos += len; + goto finish; + } else { + memcpy(buf, &cache->buf[cache->pos], read); + cache->pos += read; + buf = (uint8_t *) buf + read; + left = len - read; + } + } + + // If we got here then we have empty cache or no cache at all + while (left > 0) { + if (left > sizeof(cache->buf) || !readahead || !cache) { + // If there is no cache or chunk is larger then read directly + if (!src->read(src, buf, left, &read)) { + src->error = 1; + return false; + } + if (!read) { + src->eof = 1; + len = len - left; + goto finish; + } + left -= read; + buf = (uint8_t *) buf + read; + } else { + // Try to fill the cache to avoid small reads + if (!src->read(src, &cache->buf[0], sizeof(cache->buf), &read)) { + src->error = 1; + return false; + } + if (!read) { + src->eof = 1; + len = len - left; + goto finish; + } else if (read < left) { + memcpy(buf, &cache->buf[0], read); + left -= read; + buf = (uint8_t *) buf + read; + } else { + memcpy(buf, &cache->buf[0], left); + cache->pos = left; + cache->len = read; + goto finish; + } + } + } + +finish: + src->readb += len; + if (src->knownsize && (src->readb == src->size)) { + src->eof = 1; + } + *readres = len; + return true; +} + +bool +src_read_eq(pgp_source_t *src, void *buf, size_t len) +{ + size_t res = 0; + return src_read(src, buf, len, &res) && (res == len); +} + +bool +src_peek(pgp_source_t *src, void *buf, size_t len, size_t *peeked) +{ + pgp_source_cache_t *cache = src->cache; + if (src->error) { + return false; + } + if (!cache || (len > sizeof(cache->buf))) { + return false; + } + if (src->eof) { + *peeked = 0; + return true; + } + + size_t read = 0; + bool readahead = cache->readahead; + // Do not read more then available if source size is known + if (src->knownsize && (src->readb + len > src->size)) { + len = src->size - src->readb; + readahead = false; + } + + if (cache->len - cache->pos >= len) { + if (buf) { + memcpy(buf, &cache->buf[cache->pos], len); + } + *peeked = len; + return true; + } + + if (cache->pos > 0) { + memmove(&cache->buf[0], &cache->buf[cache->pos], cache->len - cache->pos); + cache->len -= cache->pos; + cache->pos = 0; + } + + while (cache->len < len) { + read = readahead ? sizeof(cache->buf) - cache->len : len - cache->len; + if (src->knownsize && (src->readb + read > src->size)) { + read = src->size - src->readb; + } + if (!src->read(src, &cache->buf[cache->len], read, &read)) { + src->error = 1; + return false; + } + if (!read) { + if (buf) { + memcpy(buf, &cache->buf[0], cache->len); + } + *peeked = cache->len; + return true; + } + cache->len += read; + if (cache->len >= len) { + if (buf) { + memcpy(buf, cache->buf, len); + } + *peeked = len; + return true; + } + } + return false; +} + +bool +src_peek_eq(pgp_source_t *src, void *buf, size_t len) +{ + size_t res = 0; + return src_peek(src, buf, len, &res) && (res == len); +} + +void +src_skip(pgp_source_t *src, size_t len) +{ + if (src->cache && (src->cache->len - src->cache->pos >= len)) { + src->readb += len; + src->cache->pos += len; + return; + } + + size_t res = 0; + uint8_t sbuf[16]; + if (len < sizeof(sbuf)) { + (void) src_read(src, sbuf, len, &res); + return; + } + if (src_eof(src)) { + return; + } + + void *buf = calloc(1, std::min((size_t) PGP_INPUT_CACHE_SIZE, len)); + if (!buf) { + src->error = 1; + return; + } + + while (len && !src_eof(src)) { + if (!src_read(src, buf, std::min((size_t) PGP_INPUT_CACHE_SIZE, len), &res)) { + break; + } + len -= res; + } + free(buf); +} + +rnp_result_t +src_finish(pgp_source_t *src) +{ + rnp_result_t res = RNP_SUCCESS; + if (src->finish) { + res = src->finish(src); + } + + return res; +} + +bool +src_error(const pgp_source_t *src) +{ + return src->error; +} + +bool +src_eof(pgp_source_t *src) +{ + if (src->eof) { + return true; + } + /* Error on stream read is NOT considered as eof. See src_error(). */ + uint8_t check; + size_t read = 0; + return src_peek(src, &check, 1, &read) && (read == 0); +} + +void +src_close(pgp_source_t *src) +{ + if (src->close) { + src->close(src); + } + + if (src->cache) { + free(src->cache); + src->cache = NULL; + } +} + +bool +src_skip_eol(pgp_source_t *src) +{ + uint8_t eol[2]; + size_t read; + + if (!src_peek(src, eol, 2, &read) || !read) { + return false; + } + if (eol[0] == '\n') { + src_skip(src, 1); + return true; + } + if ((read == 2) && (eol[0] == '\r') && (eol[1] == '\n')) { + src_skip(src, 2); + return true; + } + return false; +} + +bool +src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *readres) +{ + size_t scan_pos = 0; + size_t inc = 64; + len = len - 1; + + do { + size_t to_peek = scan_pos + inc; + to_peek = to_peek > len ? len : to_peek; + inc = inc * 2; + + /* inefficient, each time we again read from the beginning */ + if (!src_peek(src, buf, to_peek, readres)) { + return false; + } + + /* we continue scanning where we stopped previously */ + for (; scan_pos < *readres; scan_pos++) { + if (buf[scan_pos] == '\n') { + if ((scan_pos > 0) && (buf[scan_pos - 1] == '\r')) { + scan_pos--; + } + buf[scan_pos] = '\0'; + *readres = scan_pos; + return true; + } + } + if (*readres < to_peek) { + return false; + } + } while (scan_pos < len); + return false; +} + +bool +init_src_common(pgp_source_t *src, size_t paramsize) +{ + memset(src, 0, sizeof(*src)); + src->cache = (pgp_source_cache_t *) calloc(1, sizeof(*src->cache)); + if (!src->cache) { + RNP_LOG("cache allocation failed"); + return false; + } + src->cache->readahead = true; + if (!paramsize) { + return true; + } + src->param = calloc(1, paramsize); + if (!src->param) { + RNP_LOG("param allocation failed"); + free(src->cache); + src->cache = NULL; + return false; + } + return true; +} + +typedef struct pgp_source_file_param_t { + int fd; +} pgp_source_file_param_t; + +static bool +file_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + if (!param) { + return false; + } + + int64_t rres = read(param->fd, buf, len); + if (rres < 0) { + return false; + } + *readres = rres; + return true; +} + +static void +file_src_close(pgp_source_t *src) +{ + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + if (param) { + if (src->type == PGP_STREAM_FILE) { + close(param->fd); + } + free(src->param); + src->param = NULL; + } +} + +static rnp_result_t +init_fd_src(pgp_source_t *src, int fd, uint64_t *size) +{ + if (!init_src_common(src, sizeof(pgp_source_file_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + param->fd = fd; + src->read = file_src_read; + src->close = file_src_close; + src->type = PGP_STREAM_FILE; + src->size = size ? *size : 0; + src->knownsize = !!size; + + return RNP_SUCCESS; +} + +rnp_result_t +init_file_src(pgp_source_t *src, const char *path) +{ + int fd; + struct stat st; + + if (rnp_stat(path, &st) != 0) { + RNP_LOG("can't stat '%s'", path); + return RNP_ERROR_READ; + } + + /* read call may succeed on directory depending on OS type */ + if (S_ISDIR(st.st_mode)) { + RNP_LOG("source is directory"); + return RNP_ERROR_BAD_PARAMETERS; + } + + int flags = O_RDONLY; +#ifdef HAVE_O_BINARY + flags |= O_BINARY; +#else +#ifdef HAVE__O_BINARY + flags |= _O_BINARY; +#endif +#endif + fd = rnp_open(path, flags, 0); + + if (fd < 0) { + RNP_LOG("can't open '%s'", path); + return RNP_ERROR_READ; + } + uint64_t size = st.st_size; + rnp_result_t ret = init_fd_src(src, fd, &size); + if (ret) { + close(fd); + } + return ret; +} + +rnp_result_t +init_stdin_src(pgp_source_t *src) +{ + pgp_source_file_param_t *param; + + if (!init_src_common(src, sizeof(pgp_source_file_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_file_param_t *) src->param; + param->fd = 0; + src->read = file_src_read; + src->close = file_src_close; + src->type = PGP_STREAM_STDIN; + + return RNP_SUCCESS; +} + +typedef struct pgp_source_mem_param_t { + const void *memory; + bool free; + size_t len; + size_t pos; +} pgp_source_mem_param_t; + +typedef struct pgp_dest_mem_param_t { + unsigned maxalloc; + unsigned allocated; + void * memory; + bool free; + bool discard_overflow; + bool secure; +} pgp_dest_mem_param_t; + +static bool +mem_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (!param) { + return false; + } + + if (len > param->len - param->pos) { + len = param->len - param->pos; + } + memcpy(buf, (uint8_t *) param->memory + param->pos, len); + param->pos += len; + *read = len; + return true; +} + +static void +mem_src_close(pgp_source_t *src) +{ + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (param) { + if (param->free) { + free((void *) param->memory); + } + free(src->param); + src->param = NULL; + } +} + +rnp_result_t +init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free) +{ + if (!mem && len) { + return RNP_ERROR_NULL_POINTER; + } + /* this is actually double buffering, but then src_peek will fail */ + if (!init_src_common(src, sizeof(pgp_source_mem_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + param->memory = mem; + param->len = len; + param->pos = 0; + param->free = free; + src->read = mem_src_read; + src->close = mem_src_close; + src->finish = NULL; + src->size = len; + src->knownsize = 1; + src->type = PGP_STREAM_MEMORY; + + return RNP_SUCCESS; +} + +static bool +null_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + return false; +} + +rnp_result_t +init_null_src(pgp_source_t *src) +{ + memset(src, 0, sizeof(*src)); + src->read = null_src_read; + src->type = PGP_STREAM_NULL; + src->error = true; + return RNP_SUCCESS; +} + +rnp_result_t +read_mem_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + pgp_dest_t dst; + rnp_result_t ret; + + if ((ret = init_mem_dest(&dst, NULL, 0))) { + return ret; + } + + if ((ret = dst_write_src(readsrc, &dst))) { + goto done; + } + + if ((ret = init_mem_src(src, mem_dest_own_memory(&dst), dst.writeb, true))) { + goto done; + } + + ret = RNP_SUCCESS; +done: + dst_close(&dst, true); + return ret; +} + +rnp_result_t +file_to_mem_src(pgp_source_t *src, const char *filename) +{ + pgp_source_t fsrc = {}; + rnp_result_t res = RNP_ERROR_GENERIC; + + if ((res = init_file_src(&fsrc, filename))) { + return res; + } + + res = read_mem_src(src, &fsrc); + src_close(&fsrc); + + return res; +} + +const void * +mem_src_get_memory(pgp_source_t *src, bool own) +{ + if (src->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + if (!src->param) { + return NULL; + } + + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (own) { + param->free = false; + } + return param->memory; +} + +bool +init_dst_common(pgp_dest_t *dst, size_t paramsize) +{ + memset(dst, 0, sizeof(*dst)); + dst->werr = RNP_SUCCESS; + if (!paramsize) { + return true; + } + /* allocate param */ + dst->param = calloc(1, paramsize); + if (!dst->param) { + RNP_LOG("allocation failed"); + } + return dst->param; +} + +void +dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + /* we call write function only if all previous calls succeeded */ + if ((len > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) { + /* if cache non-empty and len will overflow it then fill it and write out */ + if ((dst->clen > 0) && (dst->clen + len > sizeof(dst->cache))) { + memcpy(dst->cache + dst->clen, buf, sizeof(dst->cache) - dst->clen); + buf = (uint8_t *) buf + sizeof(dst->cache) - dst->clen; + len -= sizeof(dst->cache) - dst->clen; + dst->werr = dst->write(dst, dst->cache, sizeof(dst->cache)); + dst->writeb += sizeof(dst->cache); + dst->clen = 0; + if (dst->werr != RNP_SUCCESS) { + return; + } + } + + /* here everything will fit into the cache or cache is empty */ + if (dst->no_cache || (len > sizeof(dst->cache))) { + dst->werr = dst->write(dst, buf, len); + if (!dst->werr) { + dst->writeb += len; + } + } else { + memcpy(dst->cache + dst->clen, buf, len); + dst->clen += len; + } + } +} + +void +dst_printf(pgp_dest_t *dst, const char *format, ...) +{ + char buf[2048]; + size_t len; + va_list ap; + + va_start(ap, format); + len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + + if (len >= sizeof(buf)) { + RNP_LOG("too long dst_printf"); + len = sizeof(buf) - 1; + } + dst_write(dst, buf, len); +} + +void +dst_flush(pgp_dest_t *dst) +{ + if ((dst->clen > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) { + dst->werr = dst->write(dst, dst->cache, dst->clen); + dst->writeb += dst->clen; + dst->clen = 0; + } +} + +rnp_result_t +dst_finish(pgp_dest_t *dst) +{ + rnp_result_t res = RNP_SUCCESS; + + if (!dst->finished) { + /* flush write cache in the dst */ + dst_flush(dst); + if (dst->finish) { + res = dst->finish(dst); + } + dst->finished = true; + } + + return res; +} + +void +dst_close(pgp_dest_t *dst, bool discard) +{ + if (!discard && !dst->finished) { + dst_finish(dst); + } + + if (dst->close) { + dst->close(dst, discard); + } +} + +typedef struct pgp_dest_file_param_t { + int fd; + int errcode; + bool overwrite; + std::string path; +} pgp_dest_file_param_t; + +static rnp_result_t +file_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* we assyme that blocking I/O is used so everything is written or error received */ + ssize_t ret = write(param->fd, buf, len); + if (ret < 0) { + param->errcode = errno; + RNP_LOG("write failed, error %d", param->errcode); + return RNP_ERROR_WRITE; + } else { + param->errcode = 0; + return RNP_SUCCESS; + } +} + +static void +file_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return; + } + + if (dst->type == PGP_STREAM_FILE) { + close(param->fd); + if (discard) { + rnp_unlink(param->path.c_str()); + } + } + + delete param; + dst->param = NULL; +} + +static rnp_result_t +init_fd_dest(pgp_dest_t *dst, int fd, const char *path) +{ + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + try { + std::unique_ptr param(new pgp_dest_file_param_t()); + param->path = path; + param->fd = fd; + dst->param = param.release(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->write = file_dst_write; + dst->close = file_dst_close; + dst->type = PGP_STREAM_FILE; + return RNP_SUCCESS; +} + +rnp_result_t +init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite) +{ + /* check whether file/dir already exists */ + struct stat st; + if (!rnp_stat(path, &st)) { + if (!overwrite) { + RNP_LOG("file already exists: '%s'", path); + return RNP_ERROR_WRITE; + } + + /* if we are overwriting empty directory then should first remove it */ + if (S_ISDIR(st.st_mode)) { + if (rmdir(path) == -1) { + RNP_LOG("failed to remove directory: error %d", errno); + return RNP_ERROR_BAD_PARAMETERS; + } + } + } + + int flags = O_WRONLY | O_CREAT; + flags |= overwrite ? O_TRUNC : O_EXCL; +#ifdef HAVE_O_BINARY + flags |= O_BINARY; +#else +#ifdef HAVE__O_BINARY + flags |= _O_BINARY; +#endif +#endif + int fd = rnp_open(path, flags, S_IRUSR | S_IWUSR); + if (fd < 0) { + RNP_LOG("failed to create file '%s'. Error %d.", path, errno); + return RNP_ERROR_WRITE; + } + + rnp_result_t res = init_fd_dest(dst, fd, path); + if (res) { + close(fd); + } + return res; +} + +#define TMPDST_SUFFIX ".rnp-tmp.XXXXXX" + +static rnp_result_t +file_tmpdst_finish(pgp_dest_t *dst) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* close the file */ + close(param->fd); + param->fd = -1; + + /* rename the temporary file */ + if (param->path.size() < strlen(TMPDST_SUFFIX)) { + return RNP_ERROR_BAD_PARAMETERS; + } + try { + /* remove suffix so we have required path */ + std::string origpath(param->path.begin(), param->path.end() - strlen(TMPDST_SUFFIX)); + /* check if file already exists */ + struct stat st; + if (!rnp_stat(origpath.c_str(), &st)) { + if (!param->overwrite) { + RNP_LOG("target path already exists"); + return RNP_ERROR_BAD_STATE; + } +#ifdef _WIN32 + /* rename() call on Windows fails if destination exists */ + else { + rnp_unlink(origpath.c_str()); + } +#endif + + /* we should remove dir if overwriting, file will be unlinked in rename call */ + if (S_ISDIR(st.st_mode) && rmdir(origpath.c_str())) { + RNP_LOG("failed to remove directory"); + return RNP_ERROR_BAD_STATE; + } + } + + if (rnp_rename(param->path.c_str(), origpath.c_str())) { + RNP_LOG("failed to rename temporary path to target file: %s", strerror(errno)); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } +} + +static void +file_tmpdst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return; + } + + /* we close file in finish function, except the case when some error occurred */ + if (!dst->finished && (dst->type == PGP_STREAM_FILE)) { + close(param->fd); + if (discard) { + rnp_unlink(param->path.c_str()); + } + } + + delete param; + dst->param = NULL; +} + +rnp_result_t +init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite) +{ + try { + std::string tmp = std::string(path) + std::string(TMPDST_SUFFIX); + /* make sure tmp.data() is zero-terminated */ + tmp.push_back('\0'); +#if defined(HAVE_MKSTEMP) && !defined(_WIN32) + int fd = mkstemp(&tmp[0]); +#else + int fd = rnp_mkstemp(&tmp[0]); +#endif + if (fd < 0) { + RNP_LOG("failed to create temporary file with template '%s'. Error %d.", + tmp.c_str(), + errno); + return RNP_ERROR_WRITE; + } + rnp_result_t res = init_fd_dest(dst, fd, tmp.c_str()); + if (res) { + close(fd); + return res; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + + /* now let's change some parameters to handle temporary file correctly */ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + param->overwrite = overwrite; + dst->finish = file_tmpdst_finish; + dst->close = file_tmpdst_close; + return RNP_SUCCESS; +} + +rnp_result_t +init_stdout_dest(pgp_dest_t *dst) +{ + rnp_result_t res = init_fd_dest(dst, STDOUT_FILENO, ""); + if (res) { + return res; + } + dst->type = PGP_STREAM_STDOUT; + return RNP_SUCCESS; +} + +static rnp_result_t +mem_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (!param) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* checking whether we need to realloc or discard extra bytes */ + if (param->discard_overflow && (dst->writeb >= param->allocated)) { + return RNP_SUCCESS; + } + if (param->discard_overflow && (dst->writeb + len > param->allocated)) { + len = param->allocated - dst->writeb; + } + + if (dst->writeb + len > param->allocated) { + if ((param->maxalloc > 0) && (dst->writeb + len > param->maxalloc)) { + RNP_LOG("attempt to alloc more then allowed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + /* round up to the page boundary and do it exponentially */ + size_t alloc = ((dst->writeb + len) * 2 + 4095) / 4096 * 4096; + if ((param->maxalloc > 0) && (alloc > param->maxalloc)) { + alloc = param->maxalloc; + } + + void *newalloc = param->secure ? calloc(1, alloc) : realloc(param->memory, alloc); + if (!newalloc) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (param->secure && param->memory) { + memcpy(newalloc, param->memory, dst->writeb); + secure_clear(param->memory, dst->writeb); + free(param->memory); + } + param->memory = newalloc; + param->allocated = alloc; + } + + memcpy((uint8_t *) param->memory + dst->writeb, buf, len); + return RNP_SUCCESS; +} + +static void +mem_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (!param) { + return; + } + + if (param->free) { + if (param->secure) { + secure_clear(param->memory, param->allocated); + } + free(param->memory); + } + free(param); + dst->param = NULL; +} + +rnp_result_t +init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len) +{ + pgp_dest_mem_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_mem_param_t *) dst->param; + + param->maxalloc = len; + param->allocated = mem ? len : 0; + param->memory = mem; + param->free = !mem; + param->secure = false; + + dst->write = mem_dst_write; + dst->close = mem_dst_close; + dst->type = PGP_STREAM_MEMORY; + dst->werr = RNP_SUCCESS; + dst->no_cache = true; + + return RNP_SUCCESS; +} + +void +mem_dest_discard_overflow(pgp_dest_t *dst, bool discard) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (param) { + param->discard_overflow = discard; + } +} + +void * +mem_dest_get_memory(pgp_dest_t *dst) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + + if (param) { + return param->memory; + } + + return NULL; +} + +void * +mem_dest_own_memory(pgp_dest_t *dst) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + + if (!param) { + RNP_LOG("null param"); + return NULL; + } + + dst_finish(dst); + + if (param->free) { + if (!dst->writeb) { + free(param->memory); + param->memory = NULL; + return param->memory; + } + /* it may be larger then required - let's truncate */ + void *newalloc = realloc(param->memory, dst->writeb); + if (!newalloc) { + return NULL; + } + param->memory = newalloc; + param->allocated = dst->writeb; + param->free = false; + return param->memory; + } + + /* in this case we should copy the memory */ + void *res = malloc(dst->writeb); + if (res) { + memcpy(res, param->memory, dst->writeb); + } + return res; +} + +void +mem_dest_secure_memory(pgp_dest_t *dst, bool secure) +{ + if (!dst || (dst->type != PGP_STREAM_MEMORY)) { + RNP_LOG("wrong function call"); + return; + } + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (param) { + param->secure = secure; + } +} + +static rnp_result_t +null_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + return RNP_SUCCESS; +} + +static void +null_dst_close(pgp_dest_t *dst, bool discard) +{ + ; +} + +rnp_result_t +init_null_dest(pgp_dest_t *dst) +{ + dst->param = NULL; + dst->write = null_dst_write; + dst->close = null_dst_close; + dst->type = PGP_STREAM_NULL; + dst->writeb = 0; + dst->clen = 0; + dst->werr = RNP_SUCCESS; + dst->no_cache = true; + + return RNP_SUCCESS; +} + +rnp_result_t +dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit) +{ + const size_t bufsize = PGP_INPUT_CACHE_SIZE; + uint8_t * readbuf = (uint8_t *) malloc(bufsize); + if (!readbuf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t res = RNP_SUCCESS; + try { + size_t read; + uint64_t totalread = 0; + + while (!src->eof) { + if (!src_read(src, readbuf, bufsize, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (!read) { + continue; + } + totalread += read; + if (limit && totalread > limit) { + res = RNP_ERROR_GENERIC; + break; + } + if (dst) { + dst_write(dst, readbuf, read); + if (dst->werr) { + RNP_LOG("failed to output data"); + res = RNP_ERROR_WRITE; + break; + } + } + } + } catch (...) { + free(readbuf); + throw; + } + free(readbuf); + if (res || !dst) { + return res; + } + dst_flush(dst); + return dst->werr; +} diff --git a/src/librepgp/stream-common.h b/src/librepgp/stream-common.h new file mode 100644 index 0000000..02279d3 --- /dev/null +++ b/src/librepgp/stream-common.h @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2017-2020, [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. + */ + +#ifndef STREAM_COMMON_H_ +#define STREAM_COMMON_H_ + +#include +#include +#include +#include "types.h" + +#define PGP_INPUT_CACHE_SIZE 32768 +#define PGP_OUTPUT_CACHE_SIZE 32768 + +#define PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE 512 + +typedef enum { + PGP_STREAM_NULL, + PGP_STREAM_FILE, + PGP_STREAM_MEMORY, + PGP_STREAM_STDIN, + PGP_STREAM_STDOUT, + PGP_STREAM_PACKET, + PGP_STREAM_PARLEN_PACKET, + PGP_STREAM_LITERAL, + PGP_STREAM_COMPRESSED, + PGP_STREAM_ENCRYPTED, + PGP_STREAM_SIGNED, + PGP_STREAM_ARMORED, + PGP_STREAM_CLEARTEXT +} pgp_stream_type_t; + +typedef struct pgp_source_t pgp_source_t; +typedef struct pgp_dest_t pgp_dest_t; + +typedef bool pgp_source_read_func_t(pgp_source_t *src, void *buf, size_t len, size_t *read); +typedef rnp_result_t pgp_source_finish_func_t(pgp_source_t *src); +typedef void pgp_source_close_func_t(pgp_source_t *src); + +typedef rnp_result_t pgp_dest_write_func_t(pgp_dest_t *dst, const void *buf, size_t len); +typedef rnp_result_t pgp_dest_finish_func_t(pgp_dest_t *src); +typedef void pgp_dest_close_func_t(pgp_dest_t *dst, bool discard); + +/* statically preallocated cache for sources */ +typedef struct pgp_source_cache_t { + uint8_t buf[PGP_INPUT_CACHE_SIZE]; + unsigned pos; /* current position in cache */ + unsigned len; /* number of bytes available in cache */ + bool readahead; /* whether read-ahead with larger chunks allowed */ +} pgp_source_cache_t; + +typedef struct pgp_source_t { + pgp_source_read_func_t * read; + pgp_source_finish_func_t *finish; + pgp_source_close_func_t * close; + pgp_stream_type_t type; + + uint64_t size; /* size of the data if available, see knownsize */ + uint64_t readb; /* number of bytes read from the stream via src_read. Do not confuse with + number of bytes as returned via the read since data may be cached */ + pgp_source_cache_t *cache; /* cache if used */ + void * param; /* source-specific additional data */ + + unsigned eof : 1; /* end of data as reported by read and empty cache */ + unsigned knownsize : 1; /* whether size of the data is known */ + unsigned error : 1; /* there were reading error */ +} pgp_source_t; + +/** @brief helper function to allocate memory for source's cache and param + * Also fills src and param with zeroes + * @param src pointer to the source structure + * @param paramsize number of bytes required for src->param + * @return true on success or false if memory allocation failed. + **/ +bool init_src_common(pgp_source_t *src, size_t paramsize); + +/** @brief read up to len bytes from the source + * While this function tries to read as much bytes as possible however it may return + * less then len bytes. Then src->eof can be checked if it's end of data. + * + * @param src source structure + * @param buf preallocated buffer which can store up to len bytes + * @param len number of bytes to read + * @param read number of read bytes will be stored here. Cannot be NULL. + * @return true on success or false otherwise + **/ +bool src_read(pgp_source_t *src, void *buf, size_t len, size_t *read); + +/** @brief shortcut to read exactly len bytes from source. See src_read for parameters. + * @return true if len bytes were read or false otherwise (i.e. less then len were read or + * read error occurred) */ +bool src_read_eq(pgp_source_t *src, void *buf, size_t len); + +/** @brief read up to len bytes and keep them in the cache/do not process + * Works only for streams with cache + * @param src source structure + * @param buf preallocated buffer which can store up to len bytes, or NULL if data should be + * discarded, just making sure that needed input is available in source + * @param len number of bytes to read. Must be less then PGP_INPUT_CACHE_SIZE. + * @param read number of bytes read will be stored here. Cannot be NULL. + * @return true on success or false otherwise + **/ +bool src_peek(pgp_source_t *src, void *buf, size_t len, size_t *read); + +/** @brief shortcut to read exactly len bytes and keep them in the cache/do not process + * Works only for streams with cache + * @return true if len bytes were read or false otherwise (i.e. less then len were read or + * read error occurred) */ +bool src_peek_eq(pgp_source_t *src, void *buf, size_t len); + +/** @brief skip up to len bytes. + * Note: use src_read() if you want to check error condition/get number of bytes + *skipped. + * @param src source structure + * @param len number of bytes to skip + **/ +void src_skip(pgp_source_t *src, size_t len); + +/** @brief notify source that all reading is done, so final data processing may be started, + * i.e. signature reading and verification and so on. Do not misuse with src_close. + * @param src allocated and initialized source structure + * @return RNP_SUCCESS or error code. If source doesn't have finish handler then also + * RNP_SUCCESS is returned + */ +rnp_result_t src_finish(pgp_source_t *src); + +/** @brief check whether there were reading error on source + * @param allocated and initialized source structure + * @return true if there were reading error or false otherwise + */ +bool src_error(const pgp_source_t *src); + +/** @brief check whether there is no more input on source + * @param src allocated and initialized source structure + * @return true if there is no more input or false otherwise. + * On read error false will be returned. + */ +bool src_eof(pgp_source_t *src); + +/** @brief close the source and deallocate all internal resources if any + */ +void src_close(pgp_source_t *src); + +/** @brief skip end of line on the source (\r\n or \n, depending on input) + * @param src allocated and initialized source + * @return true if eol was found and skipped or false otherwise + */ +bool src_skip_eol(pgp_source_t *src); + +/** @brief peek the line on the source + * @param src allocated and initialized source with data + * @param buf preallocated buffer to store the result. Result include NULL character and + * doesn't include the end of line sequence. + * @param len maximum length of data to store in buf, including terminating NULL + * @param read on success here will be stored number of bytes in the string, without the NULL + * character. + * @return true on success + * false is returned if there were eof, read error or eol was not found within the + * len. Supported eol sequences are \r\n and \n + */ +bool src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *read); + +/** @brief init file source + * @param src pre-allocated source structure + * @param path path to the file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_file_src(pgp_source_t *src, const char *path); + +/** @brief init stdin source + * @param src pre-allocated source structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_stdin_src(pgp_source_t *src); + +/** @brief init memory source + * @param src pre-allocated source structure + * @param mem memory to read from + * @param len number of bytes in input + * @param free free the memory pointer on stream close or not + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free); + +/** @brief init NULL source, which doesn't allow to read anything and always returns an error. + * @param src pre-allocated source structure + * @return always RNP_SUCCESS + **/ +rnp_result_t init_null_src(pgp_source_t *src); + +/** @brief init memory source with contents of other source + * @param src pre-allocated source structure + * @param readsrc opened source with data + * @return RNP_SUCCESS or error code + **/ +rnp_result_t read_mem_src(pgp_source_t *src, pgp_source_t *readsrc); + +/** @brief init memory source with contents of the specified file + * @param src pre-allocated source structure + * @param filename name of the file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t file_to_mem_src(pgp_source_t *src, const char *filename); + +/** @brief get memory from the memory source + * @param src initialized memory source + * @param own transfer ownership of the memory + * @return pointer to the memory or NULL if it is not a memory source + **/ +const void *mem_src_get_memory(pgp_source_t *src, bool own = false); + +typedef struct pgp_dest_t { + pgp_dest_write_func_t * write; + pgp_dest_finish_func_t *finish; + pgp_dest_close_func_t * close; + pgp_stream_type_t type; + rnp_result_t werr; /* write function may set this to some error code */ + + size_t writeb; /* number of bytes written */ + void * param; /* source-specific additional data */ + bool no_cache; /* disable write caching */ + uint8_t cache[PGP_OUTPUT_CACHE_SIZE]; + unsigned clen; /* number of bytes in cache */ + bool finished; /* whether dst_finish was called on dest or not */ +} pgp_dest_t; + +/** @brief helper function to allocate memory for dest's param. + * Initializes dst and param with zeroes as well. + * @param dst dest structure + * @param paramsize number of bytes required for dst->param + * @return true on success, or false if memory allocation failed + **/ +bool init_dst_common(pgp_dest_t *dst, size_t paramsize); + +/** @brief write buffer to the destination + * + * @param dst destination structure + * @param buf buffer with data + * @param len number of bytes to write + * @return true on success or false otherwise + **/ +void dst_write(pgp_dest_t *dst, const void *buf, size_t len); + +/** @brief printf formatted string to the destination + * + * @param dst destination structure + * @param format format string, which is the same as printf() uses + * @param ... additional arguments + */ +void dst_printf(pgp_dest_t *dst, const char *format, ...); + +/** @brief do all finalization tasks after all writing is done, i.e. calculate and write + * mdc, signatures and so on. Do not misuse with dst_close. If was not called then will be + * called from the dst_close + * + * @param dst destination structure + * @return RNP_SUCCESS or error code if something went wrong + **/ +rnp_result_t dst_finish(pgp_dest_t *dst); + +/** @brief close the destination + * + * @param dst destination structure to be closed + * @param discard if this is true then all produced output should be discarded + * @return void + **/ +void dst_close(pgp_dest_t *dst, bool discard); + +/** @brief flush cached data if any. dst_write caches small writes, so data does not + * immediately go to stream write function. + * + * @param dst destination structure + * @return void + **/ +void dst_flush(pgp_dest_t *dst); + +/** @brief init file destination + * @param dst pre-allocated dest structure + * @param path path to the file + * @param overwrite overwrite existing file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite); + +/** @brief init file destination, using the temporary file name, based on path. + * Once writing is over, dst_finish() will attempt to rename to the desired name. + * @param dst pre-allocated dest structure + * @param path path to the file + * @param overwrite overwrite existing file on rename + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite); + +/** @brief init stdout destination + * @param dst pre-allocated dest structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_stdout_dest(pgp_dest_t *dst); + +/** @brief init memory destination + * @param dst pre-allocated dest structure + * @param mem pointer to the pre-allocated memory buffer, or NULL if it should be allocated + * @param len number of bytes which mem can keep, or maximum amount of memory to allocate if + * mem is NULL. If len is zero in later case then allocation is not limited. + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len); + +/** @brief set whether to silently discard bytes which overflow memory of the dst. + * @param dst pre-allocated and initialized memory dest + * @param discard true to discard or false to return an error on overflow. + **/ +void mem_dest_discard_overflow(pgp_dest_t *dst, bool discard); + +/** @brief get the pointer to the memory where data is written. + * Do not retain the result, it may change between calls due to realloc + * @param dst pre-allocated and initialized memory dest + * @return pointer to the memory area or NULL if memory was not allocated + **/ +void *mem_dest_get_memory(pgp_dest_t *dst); + +/** @brief get ownership on the memory dest's contents. This must be called only before + * closing the dest + * @param dst pre-allocated and initialized memory dest + * @return pointer to the memory area or NULL if memory was not allocated (i.e. nothing was + * written to the destination). Also NULL will be returned on possible (re-)allocation + * failure, this case can be identified by non-zero dst->writeb. + **/ +void *mem_dest_own_memory(pgp_dest_t *dst); + +/** @brief mark memory dest as secure, so it will be deallocated securely + * @param dst pre-allocated and initialized memory dest + * @param secure whether memory should be considered as secure or not + * @return void + **/ +void mem_dest_secure_memory(pgp_dest_t *dst, bool secure); + +/** @brief init null destination which silently discards all the output + * @param dst pre-allocated dest structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_null_dest(pgp_dest_t *dst); + +/** @brief reads from source and writes to destination + * @param src initialized source + * @param dst initialized destination + * @param limit sets the maximum amount of bytes to be read, + * returning an error if the source hasn't come to eof after that amount + * if 0, no limit is imposed + * @return RNP_SUCCESS or error code + **/ +rnp_result_t dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit = 0); + +namespace rnp { +/* Temporary wrapper to destruct stack-based pgp_source_t */ +class Source { + protected: + pgp_source_t src_; + + public: + Source(const Source &) = delete; + Source(Source &&) = delete; + + Source() : src_({}) + { + } + + virtual ~Source() + { + src_close(&src_); + } + + virtual pgp_source_t & + src() + { + return src_; + } + + size_t + size() + { + return src().size; + } + + size_t + readb() + { + return src().readb; + } + + bool + eof() + { + return src_eof(&src()); + } + + bool + error() + { + return src_error(&src()); + } +}; + +class MemorySource : public Source { + public: + MemorySource(const MemorySource &) = delete; + MemorySource(MemorySource &&) = delete; + + /** + * @brief Construct memory source object. + * + * @param mem source memory. Must be valid for the whole lifetime of the object. + * @param len size of the memory. + * @param free free memory once processing is finished. + */ + MemorySource(const void *mem, size_t len, bool free) : Source() + { + auto res = init_mem_src(&src_, mem, len, free); + if (res) { + throw std::bad_alloc(); + } + } + + /** + * @brief Construct memory source object + * + * @param vec vector with data. Must be valid for the whole lifetime of the object. + */ + MemorySource(const std::vector &vec) : MemorySource(vec.data(), vec.size(), false) + { + } + + MemorySource(pgp_source_t &src) : Source() + { + auto res = read_mem_src(&src_, &src); + if (res) { + throw rnp::rnp_exception(res); + } + } + + const void * + memory(bool own = false) + { + return mem_src_get_memory(&src_, own); + } +}; + +/* Temporary wrapper to destruct stack-based pgp_dest_t */ +class Dest { + protected: + pgp_dest_t dst_; + bool discard_; + + public: + Dest(const Dest &) = delete; + Dest(Dest &&) = delete; + + Dest() : dst_({}), discard_(false) + { + } + + virtual ~Dest() + { + dst_close(&dst_, discard_); + } + + void + write(const void *buf, size_t len) + { + dst_write(&dst_, buf, len); + } + + void + set_discard(bool discard) + { + discard_ = discard; + } + + pgp_dest_t & + dst() + { + return dst_; + } + + size_t + writeb() + { + return dst_.writeb; + } + + rnp_result_t + werr() + { + return dst_.werr; + } +}; + +class MemoryDest : public Dest { + public: + MemoryDest(const MemoryDest &) = delete; + MemoryDest(MemoryDest &&) = delete; + + MemoryDest(void *mem = NULL, size_t len = 0) : Dest() + { + auto res = init_mem_dest(&dst_, mem, len); + if (res) { + throw std::bad_alloc(); + } + discard_ = true; + } + + void * + memory() + { + return mem_dest_get_memory(&dst_); + } + + void + set_secure(bool secure) + { + mem_dest_secure_memory(&dst_, secure); + } + + std::vector + to_vector() + { + uint8_t *mem = (uint8_t *) memory(); + return std::vector(mem, mem + writeb()); + } +}; +} // namespace rnp + +#endif diff --git a/src/librepgp/stream-ctx.cpp b/src/librepgp/stream-ctx.cpp new file mode 100644 index 0000000..28b5444 --- /dev/null +++ b/src/librepgp/stream-ctx.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020, [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 +#include +#include "defaults.h" +#include "utils.h" +#include "stream-ctx.h" + +rnp_result_t +rnp_ctx_t::add_encryption_password(const std::string &password, + pgp_hash_alg_t halg, + pgp_symm_alg_t ealg, + size_t iterations) +{ + rnp_symmetric_pass_info_t info = {}; + + info.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + info.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + info.s2k.hash_alg = halg; + ctx->rng.get(info.s2k.salt, sizeof(info.s2k.salt)); + if (!iterations) { + iterations = ctx->s2k_iterations(halg); + } + if (!iterations) { + return RNP_ERROR_BAD_PARAMETERS; + } + info.s2k.iterations = pgp_s2k_encode_iterations(iterations); + info.s2k_cipher = ealg; + /* Note: we're relying on the fact that a longer-than-needed key length + * here does not change the entire derived key (it just generates unused + * extra bytes at the end). We derive a key of our maximum supported length, + * which is a bit wasteful. + * + * This is done because we do not yet know what cipher this key will actually + * end up being used with until later. + * + * An alternative would be to keep a list of actual passwords and s2k params, + * and save the key derivation for later. + */ + if (!pgp_s2k_derive_key(&info.s2k, password.c_str(), info.key.data(), info.key.size())) { + return RNP_ERROR_GENERIC; + } + passwords.push_back(info); + return RNP_SUCCESS; +} diff --git a/src/librepgp/stream-ctx.h b/src/librepgp/stream-ctx.h new file mode 100644 index 0000000..b9e0c10 --- /dev/null +++ b/src/librepgp/stream-ctx.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019-2020, [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. + */ + +#ifndef STREAM_CTX_H_ +#define STREAM_CTX_H_ + +#include +#include +#include +#include "types.h" +#include +#include +#include "pgp-key.h" +#include "crypto/mem.h" +#include "sec_profile.hpp" + +/* signature info structure */ +typedef struct rnp_signer_info_t { + pgp_key_t * key{}; + pgp_hash_alg_t halg{}; + int64_t sigcreate{}; + uint64_t sigexpire{}; +} rnp_signer_info_t; + +typedef struct rnp_symmetric_pass_info_t { + pgp_s2k_t s2k{}; + pgp_symm_alg_t s2k_cipher{}; + + rnp::secure_array key; +} rnp_symmetric_pass_info_t; + +/** rnp operation context : contains configuration data about the currently ongoing operation. + * + * Common fields which make sense for every operation: + * - overwrite : silently overwrite output file if exists + * - armor : except cleartext signing, which outputs text in clear and always armor signature, + * this controls whether output is armored (base64-encoded). For armor/dearmor operation it + * controls the direction of the conversion (true means enarmor, false - dearmor), + * - rng : random number generator + * - operation : current operation type + * + * For operations with OpenPGP embedded data (i.e. encrypted data and attached signatures): + * - filename, filemtime : to specify information about the contents of literal data packet + * - zalg, zlevel : compression algorithm and level, zlevel = 0 to disable compression + * + * For encryption operation (including encrypt-and-sign): + * - halg : hash algorithm used during key derivation for password-based encryption + * - ealg, aalg, abits : symmetric encryption algorithm and AEAD parameters if used + * - recipients : list of key ids used to encrypt data to + * - passwords : list of passwords used for password-based encryption + * - filename, filemtime, zalg, zlevel : see previous + * + * For signing of any kind (attached, detached, cleartext): + * - clearsign, detached : controls kind of the signed data. Both are mutually-exclusive. + * If both are false then attached signing is used. + * - halg : hash algorithm used to calculate signature(s) + * - signers : list of rnp_signer_info_t structures describing signing key and parameters + * - sigcreate, sigexpire : default signature(s) creation and expiration times + * - filename, filemtime, zalg, zlevel : only for attached signatures, see previous + * + * For data decryption and/or verification there is not much of fields: + * - discard: discard the output data (i.e. just decrypt and/or verify signatures) + * + */ + +typedef struct rnp_ctx_t { + std::string filename{}; /* name of the input file to store in literal data packet */ + int64_t filemtime{}; /* file modification time to store in literal data packet */ + int64_t sigcreate{}; /* signature creation time */ + uint64_t sigexpire{}; /* signature expiration time */ + bool clearsign{}; /* cleartext signature */ + bool detached{}; /* detached signature */ + pgp_hash_alg_t halg{}; /* hash algorithm */ + pgp_symm_alg_t ealg{}; /* encryption algorithm */ + int zalg{}; /* compression algorithm used */ + int zlevel{}; /* compression level */ + pgp_aead_alg_t aalg{}; /* non-zero to use AEAD */ + int abits{}; /* AEAD chunk bits */ + bool overwrite{}; /* allow to overwrite output file if exists */ + bool armor{}; /* whether to use ASCII armor on output */ + bool no_wrap{}; /* do not wrap source in literal data packet */ + std::list recipients{}; /* recipients of the encrypted message */ + std::list passwords{}; /* passwords to encrypt message */ + std::list signers{}; /* keys to which sign message */ + rnp::SecurityContext * ctx{}; /* pointer to rnp::RNG */ + + rnp_ctx_t() = default; + rnp_ctx_t(const rnp_ctx_t &) = delete; + rnp_ctx_t(rnp_ctx_t &&) = delete; + + rnp_ctx_t &operator=(const rnp_ctx_t &) = delete; + rnp_ctx_t &operator=(rnp_ctx_t &&) = delete; + + rnp_result_t add_encryption_password(const std::string &password, + pgp_hash_alg_t halg, + pgp_symm_alg_t ealg, + size_t iterations = 0); +} rnp_ctx_t; + +#endif diff --git a/src/librepgp/stream-def.h b/src/librepgp/stream-def.h new file mode 100644 index 0000000..7a1108f --- /dev/null +++ b/src/librepgp/stream-def.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017-2020, [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. + */ + +#ifndef STREAM_DEF_H_ +#define STREAM_DEF_H_ + +#define CT_BUF_LEN 4096 +#define CH_CR ('\r') +#define CH_LF ('\n') +#define CH_EQ ('=') +#define CH_DASH ('-') +#define CH_SPACE (' ') +#define CH_TAB ('\t') +#define CH_COMMA (',') +#define ST_CR ("\r") +#define ST_LF ("\n") +#define ST_CRLF ("\r\n") +#define ST_CRLFCRLF ("\r\n\r\n") +#define ST_DASHSP ("- ") +#define ST_COMMA (",") + +#define ST_DASHES ("-----") +#define ST_ARMOR_BEGIN ("-----BEGIN PGP ") +#define ST_ARMOR_END ("-----END PGP ") +#define ST_CLEAR_BEGIN ("-----BEGIN PGP SIGNED MESSAGE-----") +#define ST_SIG_BEGIN ("\n-----BEGIN PGP SIGNATURE-----") +#define ST_HEADER_VERSION ("Version: ") +#define ST_HEADER_COMMENT ("Comment: ") +#define ST_HEADER_HASH ("Hash: ") +#define ST_HEADER_CHARSET ("Charset: ") +#define ST_FROM ("From") + +/* Preallocated cache length for AEAD encryption/decryption */ +#define PGP_AEAD_CACHE_LEN (PGP_INPUT_CACHE_SIZE + PGP_AEAD_MAX_TAG_LEN) + +/* Maximum OpenPGP packet nesting level */ +#define MAXIMUM_NESTING_LEVEL 32 +#define MAXIMUM_STREAM_PKTS 16 +#define MAXIMUM_ERROR_PKTS 64 + +/* Maximum text line length supported by GnuPG */ +#define MAXIMUM_GNUPG_LINELEN 19995 + +#endif /* !STREAM_DEF_H_ */ diff --git a/src/librepgp/stream-dump.cpp b/src/librepgp/stream-dump.cpp new file mode 100644 index 0000000..416f9ae --- /dev/null +++ b/src/librepgp/stream-dump.cpp @@ -0,0 +1,2533 @@ +/* + * Copyright (c) 2018-2020, [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 +#else +#include "uniwin.h" +#endif +#include +#include "time-utils.h" +#include "stream-def.h" +#include "stream-dump.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-parse.h" +#include "types.h" +#include "ctype.h" +#include "crypto/symmetric.h" +#include "crypto/s2k.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "crypto.h" +#include "json-utils.h" +#include + +static const id_str_pair packet_tag_map[] = { + {PGP_PKT_RESERVED, "Reserved"}, + {PGP_PKT_PK_SESSION_KEY, "Public-Key Encrypted Session Key"}, + {PGP_PKT_SIGNATURE, "Signature"}, + {PGP_PKT_SK_SESSION_KEY, "Symmetric-Key Encrypted Session Key"}, + {PGP_PKT_ONE_PASS_SIG, "One-Pass Signature"}, + {PGP_PKT_SECRET_KEY, "Secret Key"}, + {PGP_PKT_PUBLIC_KEY, "Public Key"}, + {PGP_PKT_SECRET_SUBKEY, "Secret Subkey"}, + {PGP_PKT_COMPRESSED, "Compressed Data"}, + {PGP_PKT_SE_DATA, "Symmetrically Encrypted Data"}, + {PGP_PKT_MARKER, "Marker"}, + {PGP_PKT_LITDATA, "Literal Data"}, + {PGP_PKT_TRUST, "Trust"}, + {PGP_PKT_USER_ID, "User ID"}, + {PGP_PKT_PUBLIC_SUBKEY, "Public Subkey"}, + {PGP_PKT_RESERVED2, "reserved2"}, + {PGP_PKT_RESERVED3, "reserved3"}, + {PGP_PKT_USER_ATTR, "User Attribute"}, + {PGP_PKT_SE_IP_DATA, "Symmetric Encrypted and Integrity Protected Data"}, + {PGP_PKT_MDC, "Modification Detection Code"}, + {PGP_PKT_AEAD_ENCRYPTED, "AEAD Encrypted Data Packet"}, + {0x00, NULL}, +}; + +static const id_str_pair sig_type_map[] = { + {PGP_SIG_BINARY, "Signature of a binary document"}, + {PGP_SIG_TEXT, "Signature of a canonical text document"}, + {PGP_SIG_STANDALONE, "Standalone signature"}, + {PGP_CERT_GENERIC, "Generic User ID certification"}, + {PGP_CERT_PERSONA, "Personal User ID certification"}, + {PGP_CERT_CASUAL, "Casual User ID certification"}, + {PGP_CERT_POSITIVE, "Positive User ID certification"}, + {PGP_SIG_SUBKEY, "Subkey Binding Signature"}, + {PGP_SIG_PRIMARY, "Primary Key Binding Signature"}, + {PGP_SIG_DIRECT, "Direct-key signature"}, + {PGP_SIG_REV_KEY, "Key revocation signature"}, + {PGP_SIG_REV_SUBKEY, "Subkey revocation signature"}, + {PGP_SIG_REV_CERT, "Certification revocation signature"}, + {PGP_SIG_TIMESTAMP, "Timestamp signature"}, + {PGP_SIG_3RD_PARTY, "Third-Party Confirmation signature"}, + {0x00, NULL}, +}; + +static const id_str_pair sig_subpkt_type_map[] = { + {PGP_SIG_SUBPKT_CREATION_TIME, "signature creation time"}, + {PGP_SIG_SUBPKT_EXPIRATION_TIME, "signature expiration time"}, + {PGP_SIG_SUBPKT_EXPORT_CERT, "exportable certification"}, + {PGP_SIG_SUBPKT_TRUST, "trust signature"}, + {PGP_SIG_SUBPKT_REGEXP, "regular expression"}, + {PGP_SIG_SUBPKT_REVOCABLE, "revocable"}, + {PGP_SIG_SUBPKT_KEY_EXPIRY, "key expiration time"}, + {PGP_SIG_SUBPKT_PREFERRED_SKA, "preferred symmetric algorithms"}, + {PGP_SIG_SUBPKT_REVOCATION_KEY, "revocation key"}, + {PGP_SIG_SUBPKT_ISSUER_KEY_ID, "issuer key ID"}, + {PGP_SIG_SUBPKT_NOTATION_DATA, "notation data"}, + {PGP_SIG_SUBPKT_PREFERRED_HASH, "preferred hash algorithms"}, + {PGP_SIG_SUBPKT_PREF_COMPRESS, "preferred compression algorithms"}, + {PGP_SIG_SUBPKT_KEYSERV_PREFS, "key server preferences"}, + {PGP_SIG_SUBPKT_PREF_KEYSERV, "preferred key server"}, + {PGP_SIG_SUBPKT_PRIMARY_USER_ID, "primary user ID"}, + {PGP_SIG_SUBPKT_POLICY_URI, "policy URI"}, + {PGP_SIG_SUBPKT_KEY_FLAGS, "key flags"}, + {PGP_SIG_SUBPKT_SIGNERS_USER_ID, "signer's user ID"}, + {PGP_SIG_SUBPKT_REVOCATION_REASON, "reason for revocation"}, + {PGP_SIG_SUBPKT_FEATURES, "features"}, + {PGP_SIG_SUBPKT_SIGNATURE_TARGET, "signature target"}, + {PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, "embedded signature"}, + {PGP_SIG_SUBPKT_ISSUER_FPR, "issuer fingerprint"}, + {PGP_SIG_SUBPKT_PREFERRED_AEAD, "preferred AEAD algorithms"}, + {0x00, NULL}, +}; + +static const id_str_pair key_type_map[] = { + {PGP_PKT_SECRET_KEY, "Secret key"}, + {PGP_PKT_PUBLIC_KEY, "Public key"}, + {PGP_PKT_SECRET_SUBKEY, "Secret subkey"}, + {PGP_PKT_PUBLIC_SUBKEY, "Public subkey"}, + {0x00, NULL}, +}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, "RSA (Encrypt or Sign)"}, + {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA (Encrypt-Only)"}, + {PGP_PKA_RSA_SIGN_ONLY, "RSA (Sign-Only)"}, + {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"}, + {PGP_PKA_DSA, "DSA"}, + {PGP_PKA_ECDH, "ECDH"}, + {PGP_PKA_ECDSA, "ECDSA"}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Elgamal"}, + {PGP_PKA_RESERVED_DH, "Reserved for DH (X9.42)"}, + {PGP_PKA_EDDSA, "EdDSA"}, + {PGP_PKA_SM2, "SM2"}, + {0x00, NULL}, +}; + +static const id_str_pair symm_alg_map[] = { + {PGP_SA_PLAINTEXT, "Plaintext"}, + {PGP_SA_IDEA, "IDEA"}, + {PGP_SA_TRIPLEDES, "TripleDES"}, + {PGP_SA_CAST5, "CAST5"}, + {PGP_SA_BLOWFISH, "Blowfish"}, + {PGP_SA_AES_128, "AES-128"}, + {PGP_SA_AES_192, "AES-192"}, + {PGP_SA_AES_256, "AES-256"}, + {PGP_SA_TWOFISH, "Twofish"}, + {PGP_SA_CAMELLIA_128, "Camellia-128"}, + {PGP_SA_CAMELLIA_192, "Camellia-192"}, + {PGP_SA_CAMELLIA_256, "Camellia-256"}, + {PGP_SA_SM4, "SM4"}, + {0x00, NULL}, +}; + +static const id_str_pair hash_alg_map[] = { + {PGP_HASH_MD5, "MD5"}, + {PGP_HASH_SHA1, "SHA1"}, + {PGP_HASH_RIPEMD, "RIPEMD160"}, + {PGP_HASH_SHA256, "SHA256"}, + {PGP_HASH_SHA384, "SHA384"}, + {PGP_HASH_SHA512, "SHA512"}, + {PGP_HASH_SHA224, "SHA224"}, + {PGP_HASH_SM3, "SM3"}, + {PGP_HASH_SHA3_256, "SHA3-256"}, + {PGP_HASH_SHA3_512, "SHA3-512"}, + {0x00, NULL}, +}; + +static const id_str_pair z_alg_map[] = { + {PGP_C_NONE, "Uncompressed"}, + {PGP_C_ZIP, "ZIP"}, + {PGP_C_ZLIB, "ZLib"}, + {PGP_C_BZIP2, "BZip2"}, + {0x00, NULL}, +}; + +static const id_str_pair aead_alg_map[] = { + {PGP_AEAD_NONE, "None"}, + {PGP_AEAD_EAX, "EAX"}, + {PGP_AEAD_OCB, "OCB"}, + {0x00, NULL}, +}; + +static const id_str_pair revoc_reason_map[] = { + {PGP_REVOCATION_NO_REASON, "No reason"}, + {PGP_REVOCATION_SUPERSEDED, "Superseded"}, + {PGP_REVOCATION_COMPROMISED, "Compromised"}, + {PGP_REVOCATION_RETIRED, "Retired"}, + {PGP_REVOCATION_NO_LONGER_VALID, "No longer valid"}, + {0x00, NULL}, +}; + +typedef struct pgp_dest_indent_param_t { + int level; + bool lstart; + pgp_dest_t *writedst; +} pgp_dest_indent_param_t; + +static rnp_result_t +indent_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + const char * line = (const char *) buf; + char indent[4] = {' ', ' ', ' ', ' '}; + + if (!len) { + return RNP_SUCCESS; + } + + do { + if (param->lstart) { + for (int i = 0; i < param->level; i++) { + dst_write(param->writedst, indent, sizeof(indent)); + } + param->lstart = false; + } + + for (size_t i = 0; i < len; i++) { + if ((line[i] == '\n') || (i == len - 1)) { + dst_write(param->writedst, line, i + 1); + param->lstart = line[i] == '\n'; + line += i + 1; + len -= i + 1; + break; + } + } + } while (len > 0); + + return RNP_SUCCESS; +} + +static void +indent_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + if (!param) { + return; + } + + free(param); +} + +static rnp_result_t +init_indent_dest(pgp_dest_t *dst, pgp_dest_t *origdst) +{ + pgp_dest_indent_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->write = indent_dst_write; + dst->close = indent_dst_close; + dst->finish = NULL; + dst->no_cache = true; + param = (pgp_dest_indent_param_t *) dst->param; + param->writedst = origdst; + param->lstart = true; + + return RNP_SUCCESS; +} + +static void +indent_dest_increase(pgp_dest_t *dst) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + param->level++; +} + +static void +indent_dest_decrease(pgp_dest_t *dst) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + if (param->level > 0) { + param->level--; + } +} + +static void +indent_dest_set(pgp_dest_t *dst, int level) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + param->level = level; +} + +static size_t +vsnprinthex(char *str, size_t slen, const uint8_t *buf, size_t buflen) +{ + static const char *hexes = "0123456789abcdef"; + size_t idx = 0; + + for (size_t i = 0; (i < buflen) && (i < (slen - 1) / 2); i++) { + str[idx++] = hexes[buf[i] >> 4]; + str[idx++] = hexes[buf[i] & 0xf]; + } + str[idx] = '\0'; + return buflen * 2; +} + +static void +dst_print_mpi(pgp_dest_t *dst, const char *name, pgp_mpi_t *mpi, bool dumpbin) +{ + char hex[5000]; + if (!dumpbin) { + dst_printf(dst, "%s: %d bits\n", name, (int) mpi_bits(mpi)); + } else { + vsnprinthex(hex, sizeof(hex), mpi->mpi, mpi->len); + dst_printf(dst, "%s: %d bits, %s\n", name, (int) mpi_bits(mpi), hex); + } +} + +static void +dst_print_palg(pgp_dest_t *dst, const char *name, pgp_pubkey_alg_t palg) +{ + const char *palg_name = id_str_pair::lookup(pubkey_alg_map, palg, "Unknown"); + if (!name) { + name = "public key algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) palg, palg_name); +} + +static void +dst_print_halg(pgp_dest_t *dst, const char *name, pgp_hash_alg_t halg) +{ + const char *halg_name = id_str_pair::lookup(hash_alg_map, halg, "Unknown"); + if (!name) { + name = "hash algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) halg, halg_name); +} + +static void +dst_print_salg(pgp_dest_t *dst, const char *name, pgp_symm_alg_t salg) +{ + const char *salg_name = id_str_pair::lookup(symm_alg_map, salg, "Unknown"); + if (!name) { + name = "symmetric algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) salg, salg_name); +} + +static void +dst_print_aalg(pgp_dest_t *dst, const char *name, pgp_aead_alg_t aalg) +{ + const char *aalg_name = id_str_pair::lookup(aead_alg_map, aalg, "Unknown"); + if (!name) { + name = "aead algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) aalg, aalg_name); +} + +static void +dst_print_zalg(pgp_dest_t *dst, const char *name, pgp_compression_type_t zalg) +{ + const char *zalg_name = id_str_pair::lookup(z_alg_map, zalg, "Unknown"); + if (!name) { + name = "compression algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) zalg, zalg_name); +} + +static void +dst_print_raw(pgp_dest_t *dst, const char *name, const void *data, size_t len) +{ + dst_printf(dst, "%s: ", name); + dst_write(dst, data, len); + dst_printf(dst, "\n"); +} + +static void +dst_print_algs( + pgp_dest_t *dst, const char *name, uint8_t *algs, size_t algc, const id_str_pair map[]) +{ + if (!name) { + name = "algorithms"; + } + + dst_printf(dst, "%s: ", name); + for (size_t i = 0; i < algc; i++) { + dst_printf( + dst, "%s%s", id_str_pair::lookup(map, algs[i], "Unknown"), i + 1 < algc ? ", " : ""); + } + dst_printf(dst, " ("); + for (size_t i = 0; i < algc; i++) { + dst_printf(dst, "%d%s", (int) algs[i], i + 1 < algc ? ", " : ""); + } + dst_printf(dst, ")\n"); +} + +static void +dst_print_sig_type(pgp_dest_t *dst, const char *name, pgp_sig_type_t sigtype) +{ + const char *sig_name = id_str_pair::lookup(sig_type_map, sigtype, "Unknown"); + if (!name) { + name = "signature type"; + } + dst_printf(dst, "%s: %d (%s)\n", name, (int) sigtype, sig_name); +} + +static void +dst_print_hex(pgp_dest_t *dst, const char *name, const uint8_t *data, size_t len, bool bytes) +{ + char hex[512]; + vsnprinthex(hex, sizeof(hex), data, len); + if (bytes) { + dst_printf(dst, "%s: 0x%s (%d bytes)\n", name, hex, (int) len); + } else { + dst_printf(dst, "%s: 0x%s\n", name, hex); + } +} + +static void +dst_print_keyid(pgp_dest_t *dst, const char *name, const pgp_key_id_t &keyid) +{ + if (!name) { + name = "key id"; + } + dst_print_hex(dst, name, keyid.data(), keyid.size(), false); +} + +static void +dst_print_s2k(pgp_dest_t *dst, pgp_s2k_t *s2k) +{ + dst_printf(dst, "s2k specifier: %d\n", (int) s2k->specifier); + if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) { + dst_printf(dst, "GPG extension num: %d\n", (int) s2k->gpg_ext_num); + if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + static_assert(sizeof(s2k->gpg_serial) == 16, "invalid s2k->gpg_serial size"); + size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len; + dst_print_hex(dst, "card serial number", s2k->gpg_serial, slen, true); + } + return; + } + if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) { + dst_print_hex(dst, + "Unknown experimental s2k", + s2k->experimental.data(), + s2k->experimental.size(), + true); + return; + } + dst_print_halg(dst, "s2k hash algorithm", s2k->hash_alg); + if ((s2k->specifier == PGP_S2KS_SALTED) || + (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) { + dst_print_hex(dst, "s2k salt", s2k->salt, PGP_SALT_SIZE, false); + } + if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) { + size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations); + dst_printf(dst, "s2k iterations: %zu (encoded as %u)\n", real_iter, s2k->iterations); + } +} + +static void +dst_print_time(pgp_dest_t *dst, const char *name, uint32_t time) +{ + if (!name) { + name = "time"; + } + auto str = rnp_ctime(time).substr(0, 24); + dst_printf(dst, + "%s: %zu (%s%s)\n", + name, + (size_t) time, + rnp_y2k38_warning(time) ? ">=" : "", + str.c_str()); +} + +static void +dst_print_expiration(pgp_dest_t *dst, const char *name, uint32_t seconds) +{ + if (!name) { + name = "expiration"; + } + if (seconds) { + int days = seconds / (24 * 60 * 60); + dst_printf(dst, "%s: %zu seconds (%d days)\n", name, (size_t) seconds, days); + } else { + dst_printf(dst, "%s: 0 (never)\n", name); + } +} + +#define LINELEN 16 + +static void +dst_hexdump(pgp_dest_t *dst, const uint8_t *src, size_t length) +{ + size_t i; + char line[LINELEN + 1]; + + for (i = 0; i < length; i++) { + if (i % LINELEN == 0) { + dst_printf(dst, "%.5zu | ", i); + } + dst_printf(dst, "%.02x ", (uint8_t) src[i]); + line[i % LINELEN] = (isprint(src[i])) ? src[i] : '.'; + if (i % LINELEN == LINELEN - 1) { + line[LINELEN] = 0x0; + dst_printf(dst, " | %s\n", line); + } + } + if (i % LINELEN != 0) { + for (; i % LINELEN != 0; i++) { + dst_printf(dst, " "); + line[i % LINELEN] = ' '; + } + line[LINELEN] = 0x0; + dst_printf(dst, " | %s\n", line); + } +} + +static rnp_result_t stream_dump_packets_raw(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + pgp_dest_t * dst); +static void stream_dump_signature_pkt(rnp_dump_ctx_t * ctx, + pgp_signature_t *sig, + pgp_dest_t * dst); + +static void +signature_dump_subpacket(rnp_dump_ctx_t *ctx, pgp_dest_t *dst, const pgp_sig_subpkt_t &subpkt) +{ + const char *sname = id_str_pair::lookup(sig_subpkt_type_map, subpkt.type, "Unknown"); + + switch (subpkt.type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + dst_print_time(dst, sname, subpkt.fields.create); + break; + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + dst_print_expiration(dst, sname, subpkt.fields.expiry); + break; + case PGP_SIG_SUBPKT_EXPORT_CERT: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.exportable); + break; + case PGP_SIG_SUBPKT_TRUST: + dst_printf(dst, + "%s: amount %d, level %d\n", + sname, + (int) subpkt.fields.trust.amount, + (int) subpkt.fields.trust.level); + break; + case PGP_SIG_SUBPKT_REGEXP: + dst_print_raw(dst, sname, subpkt.fields.regexp.str, subpkt.fields.regexp.len); + break; + case PGP_SIG_SUBPKT_REVOCABLE: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.revocable); + break; + case PGP_SIG_SUBPKT_KEY_EXPIRY: + dst_print_expiration(dst, sname, subpkt.fields.expiry); + break; + case PGP_SIG_SUBPKT_PREFERRED_SKA: + dst_print_algs(dst, + "preferred symmetric algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + symm_alg_map); + break; + case PGP_SIG_SUBPKT_REVOCATION_KEY: + dst_printf(dst, "%s\n", sname); + dst_printf(dst, "class: %d\n", (int) subpkt.fields.revocation_key.klass); + dst_print_palg(dst, NULL, subpkt.fields.revocation_key.pkalg); + dst_print_hex( + dst, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE, true); + break; + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + dst_print_hex(dst, sname, subpkt.fields.issuer, PGP_KEY_ID_SIZE, false); + break; + case PGP_SIG_SUBPKT_NOTATION_DATA: { + std::string name(subpkt.fields.notation.name, + subpkt.fields.notation.name + subpkt.fields.notation.nlen); + std::vector value(subpkt.fields.notation.value, + subpkt.fields.notation.value + subpkt.fields.notation.vlen); + if (subpkt.fields.notation.human) { + dst_printf(dst, "%s: %s = ", sname, name.c_str()); + dst_printf(dst, "%.*s\n", (int) value.size(), (char *) value.data()); + } else { + char hex[64]; + vsnprinthex(hex, sizeof(hex), value.data(), value.size()); + dst_printf(dst, "%s: %s = ", sname, name.c_str()); + dst_printf(dst, "0x%s (%zu bytes)\n", hex, value.size()); + } + break; + } + case PGP_SIG_SUBPKT_PREFERRED_HASH: + dst_print_algs(dst, + "preferred hash algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + hash_alg_map); + break; + case PGP_SIG_SUBPKT_PREF_COMPRESS: + dst_print_algs(dst, + "preferred compression algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + z_alg_map); + break; + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + dst_printf(dst, "%s\n", sname); + dst_printf(dst, "no-modify: %d\n", (int) subpkt.fields.ks_prefs.no_modify); + break; + case PGP_SIG_SUBPKT_PREF_KEYSERV: + dst_print_raw( + dst, sname, subpkt.fields.preferred_ks.uri, subpkt.fields.preferred_ks.len); + break; + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.primary_uid); + break; + case PGP_SIG_SUBPKT_POLICY_URI: + dst_print_raw(dst, sname, subpkt.fields.policy.uri, subpkt.fields.policy.len); + break; + case PGP_SIG_SUBPKT_KEY_FLAGS: { + uint8_t flg = subpkt.fields.key_flags; + dst_printf(dst, "%s: 0x%02x ( ", sname, flg); + dst_printf(dst, "%s", flg ? "" : "none"); + dst_printf(dst, "%s", flg & PGP_KF_CERTIFY ? "certify " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SIGN ? "sign " : ""); + dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_COMMS ? "encrypt_comm " : ""); + dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_STORAGE ? "encrypt_storage " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SPLIT ? "split " : ""); + dst_printf(dst, "%s", flg & PGP_KF_AUTH ? "auth " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SHARED ? "shared " : ""); + dst_printf(dst, ")\n"); + break; + } + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + dst_print_raw(dst, sname, subpkt.fields.signer.uid, subpkt.fields.signer.len); + break; + case PGP_SIG_SUBPKT_REVOCATION_REASON: { + int code = subpkt.fields.revocation_reason.code; + const char *reason = id_str_pair::lookup(revoc_reason_map, code, "Unknown"); + dst_printf(dst, "%s: %d (%s)\n", sname, code, reason); + dst_print_raw(dst, + "message", + subpkt.fields.revocation_reason.str, + subpkt.fields.revocation_reason.len); + break; + } + case PGP_SIG_SUBPKT_FEATURES: + dst_printf(dst, "%s: 0x%02x ( ", sname, subpkt.data[0]); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_MDC ? "mdc " : ""); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_AEAD ? "aead " : ""); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_V5 ? "v5 keys " : ""); + dst_printf(dst, ")\n"); + break; + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: + dst_printf(dst, "%s:\n", sname); + stream_dump_signature_pkt(ctx, subpkt.fields.sig, dst); + break; + case PGP_SIG_SUBPKT_ISSUER_FPR: + dst_print_hex( + dst, sname, subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len, true); + break; + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + dst_print_algs(dst, + "preferred aead algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + aead_alg_map); + break; + default: + if (!ctx->dump_packets) { + indent_dest_increase(dst); + dst_hexdump(dst, subpkt.data, subpkt.len); + indent_dest_decrease(dst); + } + } +} + +static void +signature_dump_subpackets(rnp_dump_ctx_t * ctx, + pgp_dest_t * dst, + pgp_signature_t *sig, + bool hashed) +{ + bool empty = true; + + for (auto &subpkt : sig->subpkts) { + if (subpkt.hashed != hashed) { + continue; + } + empty = false; + dst_printf(dst, ":type %d, len %d", (int) subpkt.type, (int) subpkt.len); + dst_printf(dst, "%s\n", subpkt.critical ? ", critical" : ""); + if (ctx->dump_packets) { + dst_printf(dst, ":subpacket contents:\n"); + indent_dest_increase(dst); + dst_hexdump(dst, subpkt.data, subpkt.len); + indent_dest_decrease(dst); + } + signature_dump_subpacket(ctx, dst, subpkt); + } + + if (empty) { + dst_printf(dst, "none\n"); + } +} + +static void +stream_dump_signature_pkt(rnp_dump_ctx_t *ctx, pgp_signature_t *sig, pgp_dest_t *dst) +{ + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) sig->version); + dst_print_sig_type(dst, "type", sig->type()); + if (sig->version < PGP_V4) { + dst_print_time(dst, "creation time", sig->creation_time); + dst_print_keyid(dst, "signing key id", sig->signer); + } + dst_print_palg(dst, NULL, sig->palg); + dst_print_halg(dst, NULL, sig->halg); + + if (sig->version >= PGP_V4) { + dst_printf(dst, "hashed subpackets:\n"); + indent_dest_increase(dst); + signature_dump_subpackets(ctx, dst, sig, true); + indent_dest_decrease(dst); + + dst_printf(dst, "unhashed subpackets:\n"); + indent_dest_increase(dst); + signature_dump_subpackets(ctx, dst, sig, false); + indent_dest_decrease(dst); + } + + dst_print_hex(dst, "lbits", sig->lbits, sizeof(sig->lbits), false); + dst_printf(dst, "signature material:\n"); + indent_dest_increase(dst); + + pgp_signature_material_t material = {}; + try { + sig->parse_material(material); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa s", &material.rsa.s, ctx->dump_mpi); + break; + case PGP_PKA_DSA: + dst_print_mpi(dst, "dsa r", &material.dsa.r, ctx->dump_mpi); + dst_print_mpi(dst, "dsa s", &material.dsa.s, ctx->dump_mpi); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + dst_print_mpi(dst, "ecc r", &material.ecc.r, ctx->dump_mpi); + dst_print_mpi(dst, "ecc s", &material.ecc.s, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg r", &material.eg.r, ctx->dump_mpi); + dst_print_mpi(dst, "eg s", &material.eg.s, ctx->dump_mpi); + break; + default: + dst_printf(dst, "unknown algorithm\n"); + } + indent_dest_decrease(dst); + indent_dest_decrease(dst); +} + +static rnp_result_t +stream_dump_signature(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_signature_t sig; + rnp_result_t ret; + + dst_printf(dst, "Signature packet\n"); + try { + ret = sig.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + indent_dest_increase(dst); + dst_printf(dst, "failed to parse\n"); + indent_dest_decrease(dst); + return ret; + } + stream_dump_signature_pkt(ctx, &sig, dst); + return ret; +} + +static rnp_result_t +stream_dump_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_key_pkt_t key; + rnp_result_t ret; + pgp_fingerprint_t keyfp = {}; + + try { + ret = key.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "%s packet\n", id_str_pair::lookup(key_type_map, key.tag, "Unknown")); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) key.version); + dst_print_time(dst, "creation time", key.creation_time); + if (key.version < PGP_V4) { + dst_printf(dst, "v3 validity days: %d\n", (int) key.v3_days); + } + dst_print_palg(dst, NULL, key.alg); + dst_printf(dst, "public key material:\n"); + indent_dest_increase(dst); + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa n", &key.material.rsa.n, ctx->dump_mpi); + dst_print_mpi(dst, "rsa e", &key.material.rsa.e, ctx->dump_mpi); + break; + case PGP_PKA_DSA: + dst_print_mpi(dst, "dsa p", &key.material.dsa.p, ctx->dump_mpi); + dst_print_mpi(dst, "dsa q", &key.material.dsa.q, ctx->dump_mpi); + dst_print_mpi(dst, "dsa g", &key.material.dsa.g, ctx->dump_mpi); + dst_print_mpi(dst, "dsa y", &key.material.dsa.y, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg p", &key.material.eg.p, ctx->dump_mpi); + dst_print_mpi(dst, "eg g", &key.material.eg.g, ctx->dump_mpi); + dst_print_mpi(dst, "eg y", &key.material.eg.y, ctx->dump_mpi); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + dst_print_mpi(dst, "ecc p", &key.material.ec.p, ctx->dump_mpi); + dst_printf(dst, "ecc curve: %s\n", cdesc ? cdesc->pgp_name : "unknown"); + break; + } + case PGP_PKA_ECDH: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + dst_print_mpi(dst, "ecdh p", &key.material.ec.p, ctx->dump_mpi); + dst_printf(dst, "ecdh curve: %s\n", cdesc ? cdesc->pgp_name : "unknown"); + dst_print_halg(dst, "ecdh hash algorithm", key.material.ec.kdf_hash_alg); + dst_printf(dst, "ecdh key wrap algorithm: %d\n", (int) key.material.ec.key_wrap_alg); + break; + } + default: + dst_printf(dst, "unknown public key algorithm\n"); + } + indent_dest_decrease(dst); + + if (is_secret_key_pkt(key.tag)) { + dst_printf(dst, "secret key material:\n"); + indent_dest_increase(dst); + + dst_printf(dst, "s2k usage: %d\n", (int) key.sec_protection.s2k.usage); + if ((key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED) || + (key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED)) { + dst_print_salg(dst, NULL, key.sec_protection.symm_alg); + dst_print_s2k(dst, &key.sec_protection.s2k); + if (key.sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t bl_size = pgp_block_size(key.sec_protection.symm_alg); + if (bl_size) { + dst_print_hex(dst, "cipher iv", key.sec_protection.iv, bl_size, true); + } else { + dst_printf(dst, "cipher iv: unknown algorithm\n"); + } + } + dst_printf(dst, "encrypted secret key data: %d bytes\n", (int) key.sec_len); + } + + if (!key.sec_protection.s2k.usage) { + dst_printf(dst, "cleartext secret key data: %d bytes\n", (int) key.sec_len); + } + indent_dest_decrease(dst); + } + + pgp_key_id_t keyid = {}; + if (!pgp_keyid(keyid, key)) { + dst_print_hex(dst, "keyid", keyid.data(), keyid.size(), false); + } else { + dst_printf(dst, "keyid: failed to calculate"); + } + + if ((key.version > PGP_V3) && (ctx->dump_grips)) { + if (!pgp_fingerprint(keyfp, key)) { + dst_print_hex(dst, "fingerprint", keyfp.fingerprint, keyfp.length, false); + } else { + dst_printf(dst, "fingerprint: failed to calculate"); + } + } + + if (ctx->dump_grips) { + pgp_key_grip_t grip; + if (rnp_key_store_get_key_grip(&key.material, grip)) { + dst_print_hex(dst, "grip", grip.data(), grip.size(), false); + } else { + dst_printf(dst, "grip: failed to calculate"); + } + } + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_userid(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_userid_pkt_t uid; + rnp_result_t ret; + const char * utype; + + try { + ret = uid.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + switch (uid.tag) { + case PGP_PKT_USER_ID: + utype = "UserID"; + break; + case PGP_PKT_USER_ATTR: + utype = "UserAttr"; + break; + default: + utype = "Unknown user id"; + } + + dst_printf(dst, "%s packet\n", utype); + indent_dest_increase(dst); + + switch (uid.tag) { + case PGP_PKT_USER_ID: + dst_printf(dst, "id: "); + dst_write(dst, uid.uid, uid.uid_len); + dst_printf(dst, "\n"); + break; + case PGP_PKT_USER_ATTR: + dst_printf(dst, "id: (%d bytes of data)\n", (int) uid.uid_len); + break; + default:; + } + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_pk_session_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_pk_sesskey_t pkey; + pgp_encrypted_material_t material; + rnp_result_t ret; + + try { + ret = pkey.parse(*src); + if (!pkey.parse_material(material)) { + ret = RNP_ERROR_BAD_FORMAT; + } + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "Public-key encrypted session key packet\n"); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) pkey.version); + dst_print_keyid(dst, NULL, pkey.key_id); + dst_print_palg(dst, NULL, pkey.alg); + dst_printf(dst, "encrypted material:\n"); + indent_dest_increase(dst); + + switch (pkey.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa m", &material.rsa.m, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg g", &material.eg.g, ctx->dump_mpi); + dst_print_mpi(dst, "eg m", &material.eg.m, ctx->dump_mpi); + break; + case PGP_PKA_SM2: + dst_print_mpi(dst, "sm2 m", &material.sm2.m, ctx->dump_mpi); + break; + case PGP_PKA_ECDH: + dst_print_mpi(dst, "ecdh p", &material.ecdh.p, ctx->dump_mpi); + if (ctx->dump_mpi) { + dst_print_hex(dst, "ecdh m", material.ecdh.m, material.ecdh.mlen, true); + } else { + dst_printf(dst, "ecdh m: %d bytes\n", (int) material.ecdh.mlen); + } + break; + default: + dst_printf(dst, "unknown public key algorithm\n"); + } + + indent_dest_decrease(dst); + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_sk_session_key(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_sk_sesskey_t skey; + rnp_result_t ret; + + try { + ret = skey.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "Symmetric-key encrypted session key packet\n"); + indent_dest_increase(dst); + dst_printf(dst, "version: %d\n", (int) skey.version); + dst_print_salg(dst, NULL, skey.alg); + if (skey.version == PGP_SKSK_V5) { + dst_print_aalg(dst, NULL, skey.aalg); + } + dst_print_s2k(dst, &skey.s2k); + if (skey.version == PGP_SKSK_V5) { + dst_print_hex(dst, "aead iv", skey.iv, skey.ivlen, true); + } + dst_print_hex(dst, "encrypted key", skey.enckey, skey.enckeylen, true); + indent_dest_decrease(dst); + + return RNP_SUCCESS; +} + +static bool +stream_dump_get_aead_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr) +{ + pgp_dest_t encdst = {}; + uint8_t encpkt[64] = {}; + + if (init_mem_dest(&encdst, &encpkt, sizeof(encpkt))) { + return false; + } + mem_dest_discard_overflow(&encdst, true); + + if (stream_read_packet(src, &encdst)) { + dst_close(&encdst, false); + return false; + } + size_t len = std::min(encdst.writeb, sizeof(encpkt)); + dst_close(&encdst, false); + + pgp_source_t memsrc = {}; + if (init_mem_src(&memsrc, encpkt, len, false)) { + return false; + } + bool res = get_aead_src_hdr(&memsrc, hdr); + src_close(&memsrc); + return res; +} + +static rnp_result_t +stream_dump_aead_encrypted(pgp_source_t *src, pgp_dest_t *dst) +{ + dst_printf(dst, "AEAD-encrypted data packet\n"); + + pgp_aead_hdr_t aead = {}; + if (!stream_dump_get_aead_hdr(src, &aead)) { + dst_printf(dst, "ERROR: failed to read AEAD header\n"); + return RNP_ERROR_READ; + } + + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) aead.version); + dst_print_salg(dst, NULL, aead.ealg); + dst_print_aalg(dst, NULL, aead.aalg); + dst_printf(dst, "chunk size: %d\n", (int) aead.csize); + dst_print_hex(dst, "initialization vector", aead.iv, aead.ivlen, true); + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_encrypted(pgp_source_t *src, pgp_dest_t *dst, int tag) +{ + switch (tag) { + case PGP_PKT_SE_DATA: + dst_printf(dst, "Symmetrically-encrypted data packet\n\n"); + break; + case PGP_PKT_SE_IP_DATA: + dst_printf(dst, "Symmetrically-encrypted integrity protected data packet\n\n"); + break; + case PGP_PKT_AEAD_ENCRYPTED: + return stream_dump_aead_encrypted(src, dst); + default: + dst_printf(dst, "Unknown encrypted data packet\n\n"); + break; + } + + return stream_skip_packet(src); +} + +static rnp_result_t +stream_dump_one_pass(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_one_pass_sig_t onepass; + rnp_result_t ret; + + try { + ret = onepass.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "One-pass signature packet\n"); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) onepass.version); + dst_print_sig_type(dst, NULL, onepass.type); + dst_print_halg(dst, NULL, onepass.halg); + dst_print_palg(dst, NULL, onepass.palg); + dst_print_keyid(dst, "signing key id", onepass.keyid); + dst_printf(dst, "nested: %d\n", (int) onepass.nested); + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_compressed(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t zsrc = {0}; + uint8_t zalg; + rnp_result_t ret; + + if ((ret = init_compressed_src(&zsrc, src))) { + return ret; + } + + dst_printf(dst, "Compressed data packet\n"); + indent_dest_increase(dst); + + get_compressed_src_alg(&zsrc, &zalg); + dst_print_zalg(dst, NULL, (pgp_compression_type_t) zalg); + dst_printf(dst, "Decompressed contents:\n"); + ret = stream_dump_packets_raw(ctx, &zsrc, dst); + + src_close(&zsrc); + indent_dest_decrease(dst); + return ret; +} + +static rnp_result_t +stream_dump_literal(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t lsrc = {0}; + pgp_literal_hdr_t lhdr = {0}; + rnp_result_t ret; + uint8_t readbuf[16384]; + + if ((ret = init_literal_src(&lsrc, src))) { + return ret; + } + + dst_printf(dst, "Literal data packet\n"); + indent_dest_increase(dst); + + get_literal_src_hdr(&lsrc, &lhdr); + dst_printf(dst, "data format: '%c'\n", lhdr.format); + dst_printf(dst, "filename: %s (len %d)\n", lhdr.fname, (int) lhdr.fname_len); + dst_print_time(dst, "timestamp", lhdr.timestamp); + + ret = RNP_SUCCESS; + while (!src_eof(&lsrc)) { + size_t read = 0; + if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) { + ret = RNP_ERROR_READ; + break; + } + } + + dst_printf(dst, "data bytes: %lu\n", (unsigned long) lsrc.readb); + src_close(&lsrc); + indent_dest_decrease(dst); + return ret; +} + +static rnp_result_t +stream_dump_marker(pgp_source_t &src, pgp_dest_t &dst) +{ + dst_printf(&dst, "Marker packet\n"); + indent_dest_increase(&dst); + rnp_result_t ret = stream_parse_marker(src); + dst_printf(&dst, "contents: %s\n", ret ? "invalid" : PGP_MARKER_CONTENTS); + indent_dest_decrease(&dst); + return ret; +} + +static rnp_result_t +stream_dump_packets_raw(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + char msg[1024 + PGP_MAX_HEADER_SIZE] = {0}; + char smsg[128] = {0}; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (src_eof(src)) { + return RNP_SUCCESS; + } + + /* do not allow endless recursion */ + if (++ctx->layers > MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many OpenPGP nested layers during the dump."); + dst_printf(dst, ":too many OpenPGP packet layers, stopping.\n"); + ret = RNP_SUCCESS; + goto finish; + } + + while (!src_eof(src)) { + pgp_packet_hdr_t hdr = {}; + size_t off = src->readb; + rnp_result_t hdrret = stream_peek_packet_hdr(src, &hdr); + if (hdrret) { + ret = hdrret; + goto finish; + } + + if (hdr.partial) { + snprintf(msg, sizeof(msg), "partial len"); + } else if (hdr.indeterminate) { + snprintf(msg, sizeof(msg), "indeterminate len"); + } else { + snprintf(msg, sizeof(msg), "len %zu", hdr.pkt_len); + } + vsnprinthex(smsg, sizeof(smsg), hdr.hdr, hdr.hdr_len); + dst_printf( + dst, ":off %zu: packet header 0x%s (tag %d, %s)\n", off, smsg, hdr.tag, msg); + + if (ctx->dump_packets) { + size_t rlen = hdr.pkt_len + hdr.hdr_len; + bool part = false; + + if (!hdr.pkt_len || (rlen > 1024 + hdr.hdr_len)) { + rlen = 1024 + hdr.hdr_len; + part = true; + } + + dst_printf(dst, ":off %zu: packet contents ", off + hdr.hdr_len); + if (!src_peek(src, msg, rlen, &rlen)) { + dst_printf(dst, "- failed to read\n"); + } else { + rlen -= hdr.hdr_len; + if (part || (rlen < hdr.pkt_len)) { + dst_printf(dst, "(first %d bytes)\n", (int) rlen); + } else { + dst_printf(dst, "(%d bytes)\n", (int) rlen); + } + indent_dest_increase(dst); + dst_hexdump(dst, (uint8_t *) msg + hdr.hdr_len, rlen); + indent_dest_decrease(dst); + } + dst_printf(dst, "\n"); + } + + switch (hdr.tag) { + case PGP_PKT_SIGNATURE: + ret = stream_dump_signature(ctx, src, dst); + break; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: + ret = stream_dump_key(ctx, src, dst); + break; + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + ret = stream_dump_userid(src, dst); + break; + case PGP_PKT_PK_SESSION_KEY: + ret = stream_dump_pk_session_key(ctx, src, dst); + break; + case PGP_PKT_SK_SESSION_KEY: + ret = stream_dump_sk_session_key(src, dst); + break; + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + ctx->stream_pkts++; + ret = stream_dump_encrypted(src, dst, hdr.tag); + break; + case PGP_PKT_ONE_PASS_SIG: + ret = stream_dump_one_pass(src, dst); + break; + case PGP_PKT_COMPRESSED: + ctx->stream_pkts++; + ret = stream_dump_compressed(ctx, src, dst); + break; + case PGP_PKT_LITDATA: + ctx->stream_pkts++; + ret = stream_dump_literal(src, dst); + break; + case PGP_PKT_MARKER: + ret = stream_dump_marker(*src, *dst); + break; + case PGP_PKT_TRUST: + case PGP_PKT_MDC: + dst_printf(dst, "Skipping unhandled pkt: %d\n\n", (int) hdr.tag); + ret = stream_skip_packet(src); + break; + default: + dst_printf(dst, "Skipping Unknown pkt: %d\n\n", (int) hdr.tag); + ret = stream_skip_packet(src); + if (ret) { + goto finish; + } + } + + if (ret) { + RNP_LOG("failed to process packet"); + if (++ctx->failures > MAXIMUM_ERROR_PKTS) { + RNP_LOG("too many packet dump errors."); + goto finish; + } + } + + if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) { + RNP_LOG("Too many OpenPGP stream packets during the dump."); + dst_printf(dst, ":too many OpenPGP stream packets, stopping.\n"); + ret = RNP_SUCCESS; + goto finish; + } + } + + ret = RNP_SUCCESS; +finish: + return ret; +} + +static bool +stream_skip_cleartext(pgp_source_t *src) +{ + char buf[4096]; + size_t read = 0; + size_t siglen = strlen(ST_SIG_BEGIN); + char * hdrpos; + + while (!src_eof(src)) { + if (!src_peek(src, buf, sizeof(buf) - 1, &read) || (read <= siglen)) { + return false; + } + buf[read] = '\0'; + + if ((hdrpos = strstr(buf, ST_SIG_BEGIN))) { + /* +1 here is to skip \n on the beginning of ST_SIG_BEGIN */ + src_skip(src, hdrpos - buf + 1); + return true; + } + src_skip(src, read - siglen + 1); + } + return false; +} + +rnp_result_t +stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t armorsrc = {0}; + pgp_dest_t wrdst = {0}; + bool armored = false; + bool indent = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; + ctx->stream_pkts = 0; + ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + dst_printf(dst, ":cleartext signed data\n"); + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + /* check whether source is armored */ + if (is_armored_source(src)) { + if ((ret = init_armored_src(&armorsrc, src))) { + RNP_LOG("failed to parse armored data"); + goto finish; + } + armored = true; + src = &armorsrc; + dst_printf(dst, ":armored input\n"); + } + + if (src_eof(src)) { + dst_printf(dst, ":empty input\n"); + ret = RNP_SUCCESS; + goto finish; + } + + if ((ret = init_indent_dest(&wrdst, dst))) { + RNP_LOG("failed to init indent dest"); + goto finish; + } + indent = true; + indent_dest_set(&wrdst, 0); + + ret = stream_dump_packets_raw(ctx, src, &wrdst); +finish: + if (armored) { + src_close(&armorsrc); + } + if (indent) { + dst_close(&wrdst, false); + } + return ret; +} + +static bool +obj_add_intstr_json(json_object *obj, const char *name, int val, const id_str_pair map[]) +{ + if (!obj_add_field_json(obj, name, json_object_new_int(val))) { + return false; + } + if (!map) { + return true; + } + char namestr[64] = {0}; + const char *str = id_str_pair::lookup(map, val, "Unknown"); + snprintf(namestr, sizeof(namestr), "%s.str", name); + return obj_add_field_json(obj, namestr, json_object_new_string(str)); +} + +static bool +obj_add_mpi_json(json_object *obj, const char *name, const pgp_mpi_t *mpi, bool contents) +{ + char strname[64] = {0}; + snprintf(strname, sizeof(strname), "%s.bits", name); + if (!obj_add_field_json(obj, strname, json_object_new_int(mpi_bits(mpi)))) { + return false; + } + if (!contents) { + return true; + } + snprintf(strname, sizeof(strname), "%s.raw", name); + return obj_add_hex_json(obj, strname, mpi->mpi, mpi->len); +} + +static bool +subpacket_obj_add_algs( + json_object *obj, const char *name, uint8_t *algs, size_t len, const id_str_pair map[]) +{ + json_object *jso_algs = json_object_new_array(); + if (!jso_algs || !obj_add_field_json(obj, name, jso_algs)) { + return false; + } + for (size_t i = 0; i < len; i++) { + if (!array_add_element_json(jso_algs, json_object_new_int(algs[i]))) { + return false; + } + } + if (!map) { + return true; + } + + char strname[64] = {0}; + snprintf(strname, sizeof(strname), "%s.str", name); + + jso_algs = json_object_new_array(); + if (!jso_algs || !obj_add_field_json(obj, strname, jso_algs)) { + return false; + } + for (size_t i = 0; i < len; i++) { + if (!array_add_element_json( + jso_algs, + json_object_new_string(id_str_pair::lookup(map, algs[i], "Unknown")))) { + return false; + } + } + return true; +} + +static bool +obj_add_s2k_json(json_object *obj, pgp_s2k_t *s2k) +{ + json_object *s2k_obj = json_object_new_object(); + if (!obj_add_field_json(obj, "s2k", s2k_obj)) { + return false; + } + if (!obj_add_field_json(s2k_obj, "specifier", json_object_new_int(s2k->specifier))) { + return false; + } + if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) { + if (!obj_add_field_json( + s2k_obj, "gpg extension", json_object_new_int(s2k->gpg_ext_num))) { + return false; + } + if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len; + if (!obj_add_hex_json(s2k_obj, "card serial number", s2k->gpg_serial, slen)) { + return false; + } + } + } + if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) { + return obj_add_hex_json( + s2k_obj, "unknown experimental", s2k->experimental.data(), s2k->experimental.size()); + } + if (!obj_add_intstr_json(s2k_obj, "hash algorithm", s2k->hash_alg, hash_alg_map)) { + return false; + } + if (((s2k->specifier == PGP_S2KS_SALTED) || + (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) && + !obj_add_hex_json(s2k_obj, "salt", s2k->salt, PGP_SALT_SIZE)) { + return false; + } + if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) { + size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations); + if (!obj_add_field_json(s2k_obj, "iterations", json_object_new_int(real_iter))) { + return false; + } + } + return true; +} + +static rnp_result_t stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, + const pgp_signature_t *sig, + json_object * pkt); + +static bool +signature_dump_subpacket_json(rnp_dump_ctx_t * ctx, + const pgp_sig_subpkt_t &subpkt, + json_object * obj) +{ + switch (subpkt.type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + return obj_add_field_json( + obj, "creation time", json_object_new_int64(subpkt.fields.create)); + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + return obj_add_field_json( + obj, "expiration time", json_object_new_int64(subpkt.fields.expiry)); + case PGP_SIG_SUBPKT_EXPORT_CERT: + return obj_add_field_json( + obj, "exportable", json_object_new_boolean(subpkt.fields.exportable)); + case PGP_SIG_SUBPKT_TRUST: + return obj_add_field_json( + obj, "amount", json_object_new_int(subpkt.fields.trust.amount)) && + obj_add_field_json( + obj, "level", json_object_new_int(subpkt.fields.trust.level)); + case PGP_SIG_SUBPKT_REGEXP: + return obj_add_field_json( + obj, + "regexp", + json_object_new_string_len(subpkt.fields.regexp.str, subpkt.fields.regexp.len)); + case PGP_SIG_SUBPKT_REVOCABLE: + return obj_add_field_json( + obj, "revocable", json_object_new_boolean(subpkt.fields.revocable)); + case PGP_SIG_SUBPKT_KEY_EXPIRY: + return obj_add_field_json( + obj, "key expiration", json_object_new_int64(subpkt.fields.expiry)); + case PGP_SIG_SUBPKT_PREFERRED_SKA: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + symm_alg_map); + case PGP_SIG_SUBPKT_PREFERRED_HASH: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + hash_alg_map); + case PGP_SIG_SUBPKT_PREF_COMPRESS: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + z_alg_map); + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + aead_alg_map); + case PGP_SIG_SUBPKT_REVOCATION_KEY: + return obj_add_field_json( + obj, "class", json_object_new_int(subpkt.fields.revocation_key.klass)) && + obj_add_field_json( + obj, "algorithm", json_object_new_int(subpkt.fields.revocation_key.pkalg)) && + obj_add_hex_json( + obj, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE); + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + return obj_add_hex_json(obj, "issuer keyid", subpkt.fields.issuer, PGP_KEY_ID_SIZE); + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + return obj_add_field_json( + obj, "no-modify", json_object_new_boolean(subpkt.fields.ks_prefs.no_modify)); + case PGP_SIG_SUBPKT_PREF_KEYSERV: + return obj_add_field_json(obj, + "uri", + json_object_new_string_len(subpkt.fields.preferred_ks.uri, + subpkt.fields.preferred_ks.len)); + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + return obj_add_field_json( + obj, "primary", json_object_new_boolean(subpkt.fields.primary_uid)); + case PGP_SIG_SUBPKT_POLICY_URI: + return obj_add_field_json( + obj, + "uri", + json_object_new_string_len(subpkt.fields.policy.uri, subpkt.fields.policy.len)); + case PGP_SIG_SUBPKT_KEY_FLAGS: { + uint8_t flg = subpkt.fields.key_flags; + if (!obj_add_field_json(obj, "flags", json_object_new_int(flg))) { + return false; + } + json_object *jso_flg = json_object_new_array(); + if (!jso_flg || !obj_add_field_json(obj, "flags.str", jso_flg)) { + return false; + } + if ((flg & PGP_KF_CERTIFY) && + !array_add_element_json(jso_flg, json_object_new_string("certify"))) { + return false; + } + if ((flg & PGP_KF_SIGN) && + !array_add_element_json(jso_flg, json_object_new_string("sign"))) { + return false; + } + if ((flg & PGP_KF_ENCRYPT_COMMS) && + !array_add_element_json(jso_flg, json_object_new_string("encrypt_comm"))) { + return false; + } + if ((flg & PGP_KF_ENCRYPT_STORAGE) && + !array_add_element_json(jso_flg, json_object_new_string("encrypt_storage"))) { + return false; + } + if ((flg & PGP_KF_SPLIT) && + !array_add_element_json(jso_flg, json_object_new_string("split"))) { + return false; + } + if ((flg & PGP_KF_AUTH) && + !array_add_element_json(jso_flg, json_object_new_string("auth"))) { + return false; + } + if ((flg & PGP_KF_SHARED) && + !array_add_element_json(jso_flg, json_object_new_string("shared"))) { + return false; + } + return true; + } + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + return obj_add_field_json( + obj, + "uid", + json_object_new_string_len(subpkt.fields.signer.uid, subpkt.fields.signer.len)); + case PGP_SIG_SUBPKT_REVOCATION_REASON: { + if (!obj_add_intstr_json( + obj, "code", subpkt.fields.revocation_reason.code, revoc_reason_map)) { + return false; + } + return obj_add_field_json( + obj, + "message", + json_object_new_string_len(subpkt.fields.revocation_reason.str, + subpkt.fields.revocation_reason.len)); + } + case PGP_SIG_SUBPKT_FEATURES: + return obj_add_field_json( + obj, + "mdc", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_MDC)) && + obj_add_field_json( + obj, + "aead", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_AEAD)) && + obj_add_field_json( + obj, + "v5 keys", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_V5)); + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: { + json_object *sig = json_object_new_object(); + if (!sig || !obj_add_field_json(obj, "signature", sig)) { + return false; + } + return !stream_dump_signature_pkt_json(ctx, subpkt.fields.sig, sig); + } + case PGP_SIG_SUBPKT_ISSUER_FPR: + return obj_add_hex_json( + obj, "fingerprint", subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len); + case PGP_SIG_SUBPKT_NOTATION_DATA: { + bool human = subpkt.fields.notation.human; + if (!json_add(obj, "human", human) || !json_add(obj, + "name", + (char *) subpkt.fields.notation.name, + subpkt.fields.notation.nlen)) { + return false; + } + if (human) { + return json_add(obj, + "value", + (char *) subpkt.fields.notation.value, + subpkt.fields.notation.vlen); + } + return obj_add_hex_json( + obj, "value", subpkt.fields.notation.value, subpkt.fields.notation.vlen); + } + default: + if (!ctx->dump_packets) { + return obj_add_hex_json(obj, "raw", subpkt.data, subpkt.len); + } + return true; + } + return true; +} + +static json_object * +signature_dump_subpackets_json(rnp_dump_ctx_t *ctx, const pgp_signature_t *sig) +{ + json_object *res = json_object_new_array(); + + for (auto &subpkt : sig->subpkts) { + json_object *jso_subpkt = json_object_new_object(); + if (json_object_array_add(res, jso_subpkt)) { + json_object_put(jso_subpkt); + goto error; + } + + if (!obj_add_intstr_json(jso_subpkt, "type", subpkt.type, sig_subpkt_type_map)) { + goto error; + } + if (!obj_add_field_json(jso_subpkt, "length", json_object_new_int(subpkt.len))) { + goto error; + } + if (!obj_add_field_json( + jso_subpkt, "hashed", json_object_new_boolean(subpkt.hashed))) { + goto error; + } + if (!obj_add_field_json( + jso_subpkt, "critical", json_object_new_boolean(subpkt.critical))) { + goto error; + } + + if (ctx->dump_packets && + !obj_add_hex_json(jso_subpkt, "raw", subpkt.data, subpkt.len)) { + goto error; + } + + if (!signature_dump_subpacket_json(ctx, subpkt, jso_subpkt)) { + goto error; + } + } + + return res; +error: + json_object_put(res); + return NULL; +} + +static rnp_result_t +stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, + const pgp_signature_t *sig, + json_object * pkt) +{ + json_object * material = NULL; + pgp_signature_material_t sigmaterial = {}; + rnp_result_t ret = RNP_ERROR_OUT_OF_MEMORY; + + if (!obj_add_field_json(pkt, "version", json_object_new_int(sig->version))) { + goto done; + } + if (!obj_add_intstr_json(pkt, "type", sig->type(), sig_type_map)) { + goto done; + } + + if (sig->version < PGP_V4) { + if (!obj_add_field_json( + pkt, "creation time", json_object_new_int(sig->creation_time))) { + goto done; + } + if (!obj_add_hex_json(pkt, "signer", sig->signer.data(), sig->signer.size())) { + goto done; + } + } + if (!obj_add_intstr_json(pkt, "algorithm", sig->palg, pubkey_alg_map)) { + goto done; + } + if (!obj_add_intstr_json(pkt, "hash algorithm", sig->halg, hash_alg_map)) { + goto done; + } + + if (sig->version >= PGP_V4) { + json_object *subpkts = signature_dump_subpackets_json(ctx, sig); + if (!subpkts) { + goto done; + } + if (!obj_add_field_json(pkt, "subpackets", subpkts)) { + goto done; + } + } + + if (!obj_add_hex_json(pkt, "lbits", sig->lbits, sizeof(sig->lbits))) { + goto done; + } + + material = json_object_new_object(); + if (!material || !obj_add_field_json(pkt, "material", material)) { + goto done; + } + + try { + sig->parse_material(sigmaterial); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "s", &sigmaterial.rsa.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_DSA: + if (!obj_add_mpi_json(material, "r", &sigmaterial.dsa.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.dsa.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!obj_add_mpi_json(material, "r", &sigmaterial.ecc.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.ecc.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "r", &sigmaterial.eg.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.eg.s, ctx->dump_mpi)) { + goto done; + } + break; + default: + break; + } + ret = RNP_SUCCESS; +done: + return ret; +} + +static rnp_result_t +stream_dump_signature_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_signature_t sig; + rnp_result_t ret; + try { + ret = sig.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + return stream_dump_signature_pkt_json(ctx, &sig, pkt); +} + +static rnp_result_t +stream_dump_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_key_pkt_t key; + rnp_result_t ret; + pgp_key_id_t keyid = {}; + pgp_fingerprint_t keyfp = {}; + json_object * material = NULL; + + try { + ret = key.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + ret = RNP_ERROR_OUT_OF_MEMORY; + + if (!obj_add_field_json(pkt, "version", json_object_new_int(key.version))) { + goto done; + } + if (!obj_add_field_json(pkt, "creation time", json_object_new_int64(key.creation_time))) { + goto done; + } + if ((key.version < PGP_V4) && + !obj_add_field_json(pkt, "v3 days", json_object_new_int(key.v3_days))) { + goto done; + } + if (!obj_add_intstr_json(pkt, "algorithm", key.alg, pubkey_alg_map)) { + goto done; + } + + material = json_object_new_object(); + if (!material || !obj_add_field_json(pkt, "material", material)) { + goto done; + } + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "n", &key.material.rsa.n, ctx->dump_mpi) || + !obj_add_mpi_json(material, "e", &key.material.rsa.e, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_DSA: + if (!obj_add_mpi_json(material, "p", &key.material.dsa.p, ctx->dump_mpi) || + !obj_add_mpi_json(material, "q", &key.material.dsa.q, ctx->dump_mpi) || + !obj_add_mpi_json(material, "g", &key.material.dsa.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "y", &key.material.dsa.y, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "p", &key.material.eg.p, ctx->dump_mpi) || + !obj_add_mpi_json(material, "g", &key.material.eg.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "y", &key.material.eg.y, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) { + goto done; + } + if (!obj_add_field_json(material, + "curve", + json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) { + goto done; + } + break; + } + case PGP_PKA_ECDH: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) { + goto done; + } + if (!obj_add_field_json(material, + "curve", + json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) { + goto done; + } + if (!obj_add_intstr_json( + material, "hash algorithm", key.material.ec.kdf_hash_alg, hash_alg_map)) { + goto done; + } + if (!obj_add_intstr_json( + material, "key wrap algorithm", key.material.ec.key_wrap_alg, symm_alg_map)) { + goto done; + } + break; + } + default: + break; + } + + if (is_secret_key_pkt(key.tag)) { + if (!obj_add_field_json( + material, "s2k usage", json_object_new_int(key.sec_protection.s2k.usage))) { + goto done; + } + if (!obj_add_s2k_json(material, &key.sec_protection.s2k)) { + goto done; + } + if (key.sec_protection.s2k.usage && + !obj_add_intstr_json( + material, "symmetric algorithm", key.sec_protection.symm_alg, symm_alg_map)) { + goto done; + } + } + + if (pgp_keyid(keyid, key) || !obj_add_hex_json(pkt, "keyid", keyid.data(), keyid.size())) { + goto done; + } + + if (ctx->dump_grips) { + if (pgp_fingerprint(keyfp, key) || + !obj_add_hex_json(pkt, "fingerprint", keyfp.fingerprint, keyfp.length)) { + goto done; + } + + pgp_key_grip_t grip; + if (!rnp_key_store_get_key_grip(&key.material, grip) || + !obj_add_hex_json(pkt, "grip", grip.data(), grip.size())) { + goto done; + } + } + ret = RNP_SUCCESS; +done: + return ret; +} + +static rnp_result_t +stream_dump_userid_json(pgp_source_t *src, json_object *pkt) +{ + pgp_userid_pkt_t uid; + rnp_result_t ret; + + try { + ret = uid.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + switch (uid.tag) { + case PGP_PKT_USER_ID: + if (!obj_add_field_json( + pkt, "userid", json_object_new_string_len((char *) uid.uid, uid.uid_len))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKT_USER_ATTR: + if (!obj_add_hex_json(pkt, "userattr", uid.uid, uid.uid_len)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + default:; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_pk_session_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_pk_sesskey_t pkey; + pgp_encrypted_material_t pkmaterial; + rnp_result_t ret; + + try { + ret = pkey.parse(*src); + if (!pkey.parse_material(pkmaterial)) { + ret = RNP_ERROR_BAD_FORMAT; + } + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(pkey.version)) || + !obj_add_hex_json(pkt, "keyid", pkey.key_id.data(), pkey.key_id.size()) || + !obj_add_intstr_json(pkt, "algorithm", pkey.alg, pubkey_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + json_object *material = json_object_new_object(); + if (!obj_add_field_json(pkt, "material", material)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + switch (pkey.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "m", &pkmaterial.rsa.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "g", &pkmaterial.eg.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "m", &pkmaterial.eg.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_SM2: + if (!obj_add_mpi_json(material, "m", &pkmaterial.sm2.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_ECDH: + if (!obj_add_mpi_json(material, "p", &pkmaterial.ecdh.p, ctx->dump_mpi) || + !obj_add_field_json( + material, "m.bytes", json_object_new_int(pkmaterial.ecdh.mlen))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (ctx->dump_mpi && + !obj_add_hex_json(material, "m", pkmaterial.ecdh.m, pkmaterial.ecdh.mlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + default:; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_sk_session_key_json(pgp_source_t *src, json_object *pkt) +{ + pgp_sk_sesskey_t skey; + rnp_result_t ret; + + try { + ret = skey.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(skey.version)) || + !obj_add_intstr_json(pkt, "algorithm", skey.alg, symm_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if ((skey.version == PGP_SKSK_V5) && + !obj_add_intstr_json(pkt, "aead algorithm", skey.aalg, aead_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_s2k_json(pkt, &skey.s2k)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if ((skey.version == PGP_SKSK_V5) && + !obj_add_hex_json(pkt, "aead iv", skey.iv, skey.ivlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_hex_json(pkt, "encrypted key", skey.enckey, skey.enckeylen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_encrypted_json(pgp_source_t *src, json_object *pkt, pgp_pkt_type_t tag) +{ + if (tag != PGP_PKT_AEAD_ENCRYPTED) { + /* packet header with tag is already in pkt */ + return stream_skip_packet(src); + } + + /* dumping AEAD data */ + pgp_aead_hdr_t aead = {}; + if (!stream_dump_get_aead_hdr(src, &aead)) { + return RNP_ERROR_READ; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(aead.version)) || + !obj_add_intstr_json(pkt, "algorithm", aead.ealg, symm_alg_map) || + !obj_add_intstr_json(pkt, "aead algorithm", aead.aalg, aead_alg_map) || + !obj_add_field_json(pkt, "chunk size", json_object_new_int(aead.csize)) || + !obj_add_hex_json(pkt, "aead iv", aead.iv, aead.ivlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_one_pass_json(pgp_source_t *src, json_object *pkt) +{ + pgp_one_pass_sig_t onepass; + rnp_result_t ret; + + try { + ret = onepass.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(onepass.version))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "type", onepass.type, sig_type_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "hash algorithm", onepass.halg, hash_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "public key algorithm", onepass.palg, pubkey_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_hex_json(pkt, "signer", onepass.keyid.data(), onepass.keyid.size())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_field_json(pkt, "nested", json_object_new_boolean(onepass.nested))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_marker_json(pgp_source_t &src, json_object *pkt) +{ + rnp_result_t ret = stream_parse_marker(src); + + if (!obj_add_field_json( + pkt, "contents", json_object_new_string(ret ? "invalid" : PGP_MARKER_CONTENTS))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return ret; +} + +static rnp_result_t stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + json_object ** jso); + +static rnp_result_t +stream_dump_compressed_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_source_t zsrc = {0}; + uint8_t zalg; + rnp_result_t ret; + json_object *contents = NULL; + + if ((ret = init_compressed_src(&zsrc, src))) { + return ret; + } + + get_compressed_src_alg(&zsrc, &zalg); + if (!obj_add_intstr_json(pkt, "algorithm", zalg, z_alg_map)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = stream_dump_raw_packets_json(ctx, &zsrc, &contents); + if (!ret && !obj_add_field_json(pkt, "contents", contents)) { + json_object_put(contents); + ret = RNP_ERROR_OUT_OF_MEMORY; + } +done: + src_close(&zsrc); + return ret; +} + +static rnp_result_t +stream_dump_literal_json(pgp_source_t *src, json_object *pkt) +{ + pgp_source_t lsrc = {0}; + pgp_literal_hdr_t lhdr = {0}; + rnp_result_t ret; + uint8_t readbuf[16384]; + + if ((ret = init_literal_src(&lsrc, src))) { + return ret; + } + ret = RNP_ERROR_OUT_OF_MEMORY; + get_literal_src_hdr(&lsrc, &lhdr); + if (!obj_add_field_json( + pkt, "format", json_object_new_string_len((char *) &lhdr.format, 1))) { + goto done; + } + if (!obj_add_field_json( + pkt, "filename", json_object_new_string_len(lhdr.fname, lhdr.fname_len))) { + goto done; + } + if (!obj_add_field_json(pkt, "timestamp", json_object_new_int64(lhdr.timestamp))) { + goto done; + } + + while (!src_eof(&lsrc)) { + size_t read = 0; + if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) { + ret = RNP_ERROR_READ; + goto done; + } + } + + if (!obj_add_field_json(pkt, "datalen", json_object_new_int64(lsrc.readb))) { + goto done; + } + ret = RNP_SUCCESS; +done: + src_close(&lsrc); + return ret; +} + +static bool +stream_dump_hdr_json(pgp_source_t *src, pgp_packet_hdr_t *hdr, json_object *pkt) +{ + rnp_result_t hdrret = stream_peek_packet_hdr(src, hdr); + if (hdrret) { + return false; + } + + json_object *jso_hdr = json_object_new_object(); + if (!jso_hdr) { + return false; + } + + if (!obj_add_field_json(jso_hdr, "offset", json_object_new_int64(src->readb))) { + goto error; + } + if (!obj_add_intstr_json(jso_hdr, "tag", hdr->tag, packet_tag_map)) { + goto error; + } + if (!obj_add_hex_json(jso_hdr, "raw", hdr->hdr, hdr->hdr_len)) { + goto error; + } + if (!hdr->partial && !hdr->indeterminate && + !obj_add_field_json(jso_hdr, "length", json_object_new_int64(hdr->pkt_len))) { + goto error; + } + if (!obj_add_field_json(jso_hdr, "partial", json_object_new_boolean(hdr->partial))) { + goto error; + } + if (!obj_add_field_json( + jso_hdr, "indeterminate", json_object_new_boolean(hdr->indeterminate))) { + goto error; + } + return obj_add_field_json(pkt, "header", jso_hdr); +error: + json_object_put(jso_hdr); + return false; +} + +static rnp_result_t +stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso) +{ + json_object *pkts = NULL; + json_object *pkt = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + pkts = json_object_new_array(); + if (!pkts) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (src_eof(src)) { + ret = RNP_SUCCESS; + goto done; + } + + /* do not allow endless recursion */ + if (++ctx->layers > MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many OpenPGP nested layers during the dump."); + ret = RNP_SUCCESS; + goto done; + } + + while (!src_eof(src)) { + pgp_packet_hdr_t hdr = {}; + + pkt = json_object_new_object(); + if (!pkt) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (!stream_dump_hdr_json(src, &hdr, pkt)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (ctx->dump_packets) { + size_t rlen = hdr.pkt_len + hdr.hdr_len; + uint8_t buf[2048 + sizeof(hdr.hdr)] = {0}; + + if (!hdr.pkt_len || (rlen > 2048 + hdr.hdr_len)) { + rlen = 2048 + hdr.hdr_len; + } + if (!src_peek(src, buf, rlen, &rlen) || (rlen < hdr.hdr_len)) { + ret = RNP_ERROR_READ; + goto done; + } + if (!obj_add_hex_json(pkt, "raw", buf + hdr.hdr_len, rlen - hdr.hdr_len)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + + switch (hdr.tag) { + case PGP_PKT_SIGNATURE: + ret = stream_dump_signature_json(ctx, src, pkt); + break; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: + ret = stream_dump_key_json(ctx, src, pkt); + break; + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + ret = stream_dump_userid_json(src, pkt); + break; + case PGP_PKT_PK_SESSION_KEY: + ret = stream_dump_pk_session_key_json(ctx, src, pkt); + break; + case PGP_PKT_SK_SESSION_KEY: + ret = stream_dump_sk_session_key_json(src, pkt); + break; + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + ctx->stream_pkts++; + ret = stream_dump_encrypted_json(src, pkt, hdr.tag); + break; + case PGP_PKT_ONE_PASS_SIG: + ret = stream_dump_one_pass_json(src, pkt); + break; + case PGP_PKT_COMPRESSED: + ctx->stream_pkts++; + ret = stream_dump_compressed_json(ctx, src, pkt); + break; + case PGP_PKT_LITDATA: + ctx->stream_pkts++; + ret = stream_dump_literal_json(src, pkt); + break; + case PGP_PKT_MARKER: + ret = stream_dump_marker_json(*src, pkt); + break; + case PGP_PKT_TRUST: + case PGP_PKT_MDC: + ret = stream_skip_packet(src); + break; + default: + ret = stream_skip_packet(src); + } + + if (ret) { + RNP_LOG("failed to process packet"); + if (++ctx->failures > MAXIMUM_ERROR_PKTS) { + RNP_LOG("too many packet dump errors."); + goto done; + } + ret = RNP_SUCCESS; + } + + if (json_object_array_add(pkts, pkt)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) { + RNP_LOG("Too many OpenPGP stream packets during the dump."); + ret = RNP_SUCCESS; + goto done; + } + + pkt = NULL; + } +done: + if (ret) { + json_object_put(pkts); + json_object_put(pkt); + pkts = NULL; + } + *jso = pkts; + return ret; +} + +rnp_result_t +stream_dump_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso) +{ + pgp_source_t armorsrc = {0}; + bool armored = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; + ctx->stream_pkts = 0; + ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + /* check whether source is armored */ + if (is_armored_source(src)) { + if ((ret = init_armored_src(&armorsrc, src))) { + RNP_LOG("failed to parse armored data"); + goto finish; + } + armored = true; + src = &armorsrc; + } + + if (src_eof(src)) { + ret = RNP_ERROR_NOT_ENOUGH_DATA; + goto finish; + } + + ret = stream_dump_raw_packets_json(ctx, src, jso); +finish: + if (armored) { + src_close(&armorsrc); + } + return ret; +} diff --git a/src/librepgp/stream-dump.h b/src/librepgp/stream-dump.h new file mode 100644 index 0000000..6c2fcf1 --- /dev/null +++ b/src/librepgp/stream-dump.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, [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. + */ + +#ifndef STREAM_DUMP_H_ +#define STREAM_DUMP_H_ + +#include +#include +#include +#include "json_object.h" +#include "json.h" +#include "rnp.h" +#include "stream-common.h" + +typedef struct rnp_dump_ctx_t { + bool dump_mpi; + bool dump_packets; + bool dump_grips; + size_t layers; + size_t stream_pkts; + size_t failures; +} rnp_dump_ctx_t; + +rnp_result_t stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst); + +rnp_result_t stream_dump_packets_json(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + json_object ** jso); + +#endif diff --git a/src/librepgp/stream-key.cpp b/src/librepgp/stream-key.cpp new file mode 100644 index 0000000..8090ff7 --- /dev/null +++ b/src/librepgp/stream-key.cpp @@ -0,0 +1,1469 @@ +/* + * Copyright (c) 2018-2022, [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 +#else +#include "uniwin.h" +#endif +#include +#include +#include +#include "stream-def.h" +#include "stream-key.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-sig.h" +#include "types.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "crypto.h" +#include "crypto/signatures.h" +#include "crypto/mem.h" +#include "../librekey/key_store_pgp.h" +#include +#include +#include + +/** + * @brief Add signatures from src to dst, skipping the duplicates. + * + * @param dst Vector which will contain all distinct signatures from src and dst + * @param src Vector to merge signatures from + * @return true on success or false otherwise. On failure dst may have some sigs appended. + */ +static rnp_result_t +merge_signatures(pgp_signature_list_t &dst, const pgp_signature_list_t &src) +{ + for (auto &sig : src) { + try { + if (std::find(dst.begin(), dst.end(), sig) != dst.end()) { + continue; + } + dst.emplace_back(sig); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static rnp_result_t +transferable_userid_merge(pgp_transferable_userid_t &dst, const pgp_transferable_userid_t &src) +{ + if (dst.uid != src.uid) { + RNP_LOG("wrong userid merge attempt"); + return RNP_ERROR_BAD_PARAMETERS; + } + return merge_signatures(dst.signatures, src.signatures); +} + +rnp_result_t +transferable_subkey_from_key(pgp_transferable_subkey_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_subkey(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +transferable_subkey_merge(pgp_transferable_subkey_t &dst, const pgp_transferable_subkey_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.subkey.equals(src.subkey, true)) { + RNP_LOG("wrong subkey merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + } + return ret; +} + +rnp_result_t +transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_key(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static pgp_transferable_userid_t * +transferable_key_has_userid(pgp_transferable_key_t &src, const pgp_userid_pkt_t &userid) +{ + for (auto &uid : src.userids) { + if (uid.uid == userid) { + return &uid; + } + } + return NULL; +} + +static pgp_transferable_subkey_t * +transferable_key_has_subkey(pgp_transferable_key_t &src, const pgp_key_pkt_t &subkey) +{ + for (auto &srcsub : src.subkeys) { + if (srcsub.subkey.equals(subkey, true)) { + return &srcsub; + } + } + return NULL; +} + +rnp_result_t +transferable_key_merge(pgp_transferable_key_t &dst, const pgp_transferable_key_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.key.equals(src.key, true)) { + RNP_LOG("wrong key merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* direct-key signatures */ + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + return ret; + } + /* userids */ + for (auto &srcuid : src.userids) { + pgp_transferable_userid_t *dstuid = transferable_key_has_userid(dst, srcuid.uid); + if (dstuid) { + if ((ret = transferable_userid_merge(*dstuid, srcuid))) { + RNP_LOG("failed to merge userid"); + return ret; + } + continue; + } + /* add userid */ + try { + dst.userids.emplace_back(srcuid); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + /* subkeys */ + for (auto &srcsub : src.subkeys) { + pgp_transferable_subkey_t *dstsub = transferable_key_has_subkey(dst, srcsub.subkey); + if (dstsub) { + if ((ret = transferable_subkey_merge(*dstsub, srcsub))) { + RNP_LOG("failed to merge subkey"); + return ret; + } + continue; + } + /* add subkey */ + if (is_public_key_pkt(dst.key.tag) != is_public_key_pkt(srcsub.subkey.tag)) { + RNP_LOG("warning: adding public/secret subkey to secret/public key"); + } + try { + dst.subkeys.emplace_back(srcsub); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static bool +skip_pgp_packets(pgp_source_t &src, const std::set &pkts) +{ + do { + int pkt = stream_pkt_type(src); + if (!pkt) { + break; + } + if (pkt < 0) { + return false; + } + if (pkts.find((pgp_pkt_type_t) pkt) == pkts.end()) { + return true; + } + uint64_t ppos = src.readb; + if (stream_skip_packet(&src)) { + RNP_LOG("failed to skip packet at %" PRIu64, ppos); + return false; + } + } while (1); + + return true; +} + +static rnp_result_t +process_pgp_key_signatures(pgp_source_t &src, pgp_signature_list_t &sigs, bool skiperrors) +{ + int ptag; + while ((ptag = stream_pkt_type(src)) == PGP_PKT_SIGNATURE) { + uint64_t sigpos = src.readb; + try { + pgp_signature_t sig; + rnp_result_t ret = sig.parse(src); + if (ret) { + RNP_LOG("failed to parse signature at %" PRIu64, sigpos); + if (!skiperrors) { + return ret; + } + } else { + sigs.emplace_back(std::move(sig)); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + } + return ptag < 0 ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; +} + +static rnp_result_t +process_pgp_userid(pgp_source_t &src, pgp_transferable_userid_t &uid, bool skiperrors) +{ + rnp_result_t ret; + uint64_t uidpos = src.readb; + try { + ret = uid.uid.parse(src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse userid at %" PRIu64, uidpos); + return ret; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + return process_pgp_key_signatures(src, uid.signatures, skiperrors); +} + +rnp_result_t +process_pgp_subkey(pgp_source_t &src, pgp_transferable_subkey_t &subkey, bool skiperrors) +{ + int ptag; + subkey = pgp_transferable_subkey_t(); + uint64_t keypos = src.readb; + if (!is_subkey_pkt(ptag = stream_pkt_type(src))) { + RNP_LOG("wrong subkey ptag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + try { + ret = subkey.subkey.parse(src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse subkey at %" PRIu64, keypos); + subkey.subkey = {}; + return ret; + } + + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + return process_pgp_key_signatures(src, subkey.signatures, skiperrors); +} + +rnp_result_t +process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors) +{ + key = {}; + uint64_t srcpos = src.readb; + int ptag = stream_pkt_type(src); + if (is_subkey_pkt(ptag) && allowsub) { + pgp_transferable_subkey_t subkey; + rnp_result_t ret = process_pgp_subkey(src, subkey, skiperrors); + if (subkey.subkey.tag != PGP_PKT_RESERVED) { + try { + key.subkeys.push_back(std::move(subkey)); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_OUT_OF_MEMORY; + } + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + if (!is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key tag: %d at pos %" PRIu64, ptag, src.readb); + } else { + try { + ret = process_pgp_key(src, key, skiperrors); + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s", e.what()); + ret = e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + } + if (skiperrors && (ret == RNP_ERROR_BAD_FORMAT) && + !skip_pgp_packets(src, + {PGP_PKT_TRUST, + PGP_PKT_SIGNATURE, + PGP_PKT_USER_ID, + PGP_PKT_USER_ATTR, + PGP_PKT_PUBLIC_SUBKEY, + PGP_PKT_SECRET_SUBKEY})) { + ret = RNP_ERROR_READ; + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; +} + +rnp_result_t +process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors) +{ + bool has_secret = false; + bool has_public = false; + + keys.keys.clear(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* read sequence of transferable OpenPGP keys as described in RFC 4880, 11.1 - 11.2 */ + while (!armor.error()) { + /* Allow multiple armored messages in a single stream */ + if (armor.eof() && armor.multiple()) { + armor.restart(); + } + if (armor.eof()) { + break; + } + /* Attempt to read the next key */ + pgp_transferable_key_t curkey; + rnp_result_t ret = process_pgp_key_auto(armor.src(), curkey, false, skiperrors); + if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) { + keys.keys.clear(); + return ret; + } + /* check whether we actually read any key or just skipped erroneous packets */ + if (curkey.key.tag == PGP_PKT_RESERVED) { + continue; + } + has_secret |= (curkey.key.tag == PGP_PKT_SECRET_KEY); + has_public |= (curkey.key.tag == PGP_PKT_PUBLIC_KEY); + + keys.keys.emplace_back(std::move(curkey)); + } + + if (has_secret && has_public) { + RNP_LOG("warning! public keys are mixed together with secret ones!"); + } + + if (armor.error()) { + keys.keys.clear(); + return RNP_ERROR_READ; + } + return RNP_SUCCESS; +} + +rnp_result_t +process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors) +{ + key = pgp_transferable_key_t(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* main key packet */ + uint64_t keypos = armor.readb(); + int ptag = stream_pkt_type(armor.src()); + if ((ptag <= 0) || !is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key packet tag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = key.key.parse(armor.src()); + if (ret) { + RNP_LOG("failed to parse key pkt at %" PRIu64, keypos); + key.key = {}; + return ret; + } + + if (!skip_pgp_packets(armor.src(), {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + /* direct-key signatures */ + if ((ret = process_pgp_key_signatures(armor.src(), key.signatures, skiperrors))) { + return ret; + } + + /* user ids/attrs with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if ((ptag != PGP_PKT_USER_ID) && (ptag != PGP_PKT_USER_ATTR)) { + break; + } + + pgp_transferable_userid_t uid; + ret = process_pgp_userid(armor.src(), uid, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed uid */ + continue; + } + if (ret) { + return ret; + } + key.userids.push_back(std::move(uid)); + } + + /* subkeys with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if (!is_subkey_pkt(ptag)) { + break; + } + + pgp_transferable_subkey_t subkey; + ret = process_pgp_subkey(armor.src(), subkey, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed subkey */ + continue; + } + if (ret) { + return ret; + } + key.subkeys.emplace_back(std::move(subkey)); + } + return ptag >= 0 ? RNP_SUCCESS : RNP_ERROR_BAD_FORMAT; +} + +static rnp_result_t +decrypt_secret_key_v3(pgp_crypt_t *crypt, uint8_t *dec, const uint8_t *enc, size_t len) +{ + size_t idx; + size_t pos = 0; + size_t mpilen; + size_t blsize; + + if (!(blsize = pgp_cipher_block_size(crypt))) { + RNP_LOG("wrong crypto"); + return RNP_ERROR_BAD_STATE; + } + + /* 4 RSA secret mpis with cleartext header */ + for (idx = 0; idx < 4; idx++) { + if (pos + 2 > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + mpilen = (read_uint16(enc + pos) + 7) >> 3; + memcpy(dec + pos, enc + pos, 2); + pos += 2; + if (pos + mpilen > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_decrypt(crypt, dec + pos, enc + pos, mpilen); + pos += mpilen; + if (mpilen < blsize) { + RNP_LOG("bad rsa v3 mpi len"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_resync(crypt, enc + pos - blsize); + } + + /* sum16 */ + if (pos + 2 != len) { + return RNP_ERROR_BAD_FORMAT; + } + memcpy(dec + pos, enc + pos, 2); + return RNP_SUCCESS; +} + +static rnp_result_t +parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) +{ + if (!mpis) { + return RNP_ERROR_NULL_POINTER; + } + + /* check the cleartext data */ + switch (key.sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + case PGP_S2KU_ENCRYPTED: { + /* calculate and check sum16 of the cleartext */ + if (len < 2) { + RNP_LOG("No space for checksum."); + return RNP_ERROR_BAD_FORMAT; + } + uint16_t sum = 0; + len -= 2; + for (size_t idx = 0; idx < len; idx++) { + sum += mpis[idx]; + } + uint16_t expsum = read_uint16(mpis + len); + if (sum != expsum) { + RNP_LOG("Wrong key checksum, got 0x%X instead of 0x%X.", (int) sum, (int) expsum); + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + if (len < PGP_SHA1_HASH_SIZE) { + RNP_LOG("No space for hash"); + return RNP_ERROR_BAD_FORMAT; + } + /* calculate and check sha1 hash of the cleartext */ + uint8_t hval[PGP_SHA1_HASH_SIZE]; + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + assert(hash->size() == sizeof(hval)); + len -= PGP_SHA1_HASH_SIZE; + hash->add(mpis, len); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + return RNP_ERROR_BAD_STATE; + } + } catch (const std::exception &e) { + RNP_LOG("hash calculation failed: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + if (memcmp(hval, mpis + len, PGP_SHA1_HASH_SIZE)) { + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + default: + RNP_LOG("unknown s2k usage: %d", (int) key.sec_protection.s2k.usage); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* parse mpis depending on algorithm */ + pgp_packet_body_t body(mpis, len); + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!body.get(key.material.rsa.d) || !body.get(key.material.rsa.p) || + !body.get(key.material.rsa.q) || !body.get(key.material.rsa.u)) { + RNP_LOG("failed to parse rsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!body.get(key.material.dsa.x)) { + RNP_LOG("failed to parse dsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!body.get(key.material.ec.x)) { + RNP_LOG("failed to parse ecc secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!body.get(key.material.eg.x)) { + RNP_LOG("failed to parse eg secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (body.left()) { + RNP_LOG("extra data in sec key"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.secret = true; + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +decrypt_secret_key(pgp_key_pkt_t *key, const char *password) +{ + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + if (!is_secret_key_pkt(key->tag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* mark material as not validated as it may be valid for public part */ + key->material.validity.reset(); + + /* check whether data is not encrypted */ + if (!key->sec_protection.s2k.usage) { + return parse_secret_key_mpis(*key, key->sec_data, key->sec_len); + } + + /* check whether secret key data present */ + if (!key->sec_len) { + RNP_LOG("No secret key data"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + if (!password) { + return RNP_ERROR_NULL_POINTER; + } + + if (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp::secure_array keybuf; + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + if (!keysize || + !pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + rnp::secure_vector decdata(key->sec_len); + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb decryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + switch (key->version) { + case PGP_V3: + if (!is_rsa_key_alg(key->alg)) { + RNP_LOG("non-RSA v3 key"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + ret = decrypt_secret_key_v3(&crypt, decdata.data(), key->sec_data, key->sec_len); + break; + case PGP_V4: + pgp_cipher_cfb_decrypt(&crypt, decdata.data(), key->sec_data, key->sec_len); + ret = RNP_SUCCESS; + break; + default: + ret = RNP_ERROR_BAD_PARAMETERS; + } + + pgp_cipher_cfb_finish(&crypt); + if (ret) { + return ret; + } + + return parse_secret_key_mpis(*key, decdata.data(), key->sec_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static void +write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key) +{ + /* add mpis */ + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + body.add(key.material.rsa.d); + body.add(key.material.rsa.p); + body.add(key.material.rsa.q); + body.add(key.material.rsa.u); + break; + case PGP_PKA_DSA: + body.add(key.material.dsa.x); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + body.add(key.material.ec.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + body.add(key.material.eg.x); + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* add sum16 if sha1 is not used */ + if (key.sec_protection.s2k.usage != PGP_S2KU_ENCRYPTED_AND_HASHED) { + uint16_t sum = 0; + for (size_t i = 0; i < body.size(); i++) { + sum += body.data()[i]; + } + body.add_uint16(sum); + return; + } + + /* add sha1 hash */ + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(body.data(), body.size()); + uint8_t hval[PGP_SHA1_HASH_SIZE]; + assert(sizeof(hval) == hash->size()); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + RNP_LOG("failed to finish hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + body.add(hval, PGP_SHA1_HASH_SIZE); +} + +rnp_result_t +encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng) +{ + if (!is_secret_key_pkt(key->tag) || !key->material.secret) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (key->sec_protection.s2k.usage && + (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB)) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* build secret key data */ + pgp_packet_body_t body(PGP_PKT_RESERVED); + body.mark_secure(); + write_secret_key_mpis(body, *key); + + /* check whether data is not encrypted */ + if (key->sec_protection.s2k.usage == PGP_S2KU_NONE) { + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + return RNP_SUCCESS; + } + if (key->version < PGP_V4) { + RNP_LOG("encryption of v3 keys is not supported"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + size_t blsize = pgp_block_size(key->sec_protection.symm_alg); + if (!keysize || !blsize) { + RNP_LOG("wrong symm alg"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* generate iv and s2k salt */ + rng.get(key->sec_protection.iv, blsize); + if ((key->sec_protection.s2k.specifier != PGP_S2KS_SIMPLE)) { + rng.get(key->sec_protection.s2k.salt, PGP_SALT_SIZE); + } + /* derive key */ + rnp::secure_array keybuf; + if (!pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* encrypt sec data */ + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb encryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + pgp_cipher_cfb_encrypt(&crypt, body.data(), body.data(), body.size()); + pgp_cipher_cfb_finish(&crypt); + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + /* cleanup cleartext fields */ + forget_secret_key_fields(&key->material); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +void +forget_secret_key_fields(pgp_key_material_t *key) +{ + if (!key || !key->secret) { + return; + } + + switch (key->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + mpi_forget(&key->rsa.d); + mpi_forget(&key->rsa.p); + mpi_forget(&key->rsa.q); + mpi_forget(&key->rsa.u); + break; + case PGP_PKA_DSA: + mpi_forget(&key->dsa.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + mpi_forget(&key->eg.x); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + mpi_forget(&key->ec.x); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) key->alg); + } + + key->secret = false; +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(const pgp_userid_pkt_t &src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(pgp_userid_pkt_t &&src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = src.uid; + src.uid = NULL; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(pgp_userid_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = src.uid; + src.uid = NULL; + return *this; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(const pgp_userid_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } + return *this; +} + +bool +pgp_userid_pkt_t::operator==(const pgp_userid_pkt_t &src) const +{ + return (tag == src.tag) && (uid_len == src.uid_len) && !memcmp(uid, src.uid, uid_len); +} + +bool +pgp_userid_pkt_t::operator!=(const pgp_userid_pkt_t &src) const +{ + return !(*this == src); +} + +pgp_userid_pkt_t::~pgp_userid_pkt_t() +{ + free(uid); +} + +void +pgp_userid_pkt_t::write(pgp_dest_t &dst) const +{ + if ((tag != PGP_PKT_USER_ID) && (tag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (uid_len && !uid) { + RNP_LOG("null but non-empty userid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t pktbody(tag); + if (uid) { + pktbody.add(uid, uid_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_userid_pkt_t::parse(pgp_source_t &src) +{ + /* check the tag */ + int stag = stream_pkt_type(src); + if ((stag != PGP_PKT_USER_ID) && (stag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag: %d", stag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt(PGP_PKT_RESERVED); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + /* userid type, i.e. tag */ + tag = (pgp_pkt_type_t) stag; + free(uid); + uid = (uint8_t *) malloc(pkt.size()); + if (!uid) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(uid, pkt.data(), pkt.size()); + uid_len = pkt.size(); + return RNP_SUCCESS; +} + +pgp_key_pkt_t::pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly) +{ + if (pubonly && is_secret_key_pkt(src.tag)) { + tag = (src.tag == PGP_PKT_SECRET_KEY) ? PGP_PKT_PUBLIC_KEY : PGP_PKT_PUBLIC_SUBKEY; + } else { + tag = src.tag; + } + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + if (pubonly) { + forget_secret_key_fields(&material); + sec_len = 0; + sec_data = NULL; + sec_protection = {}; + return; + } + sec_len = src.sec_len; + sec_data = NULL; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t::pgp_key_pkt_t(pgp_key_pkt_t &&src) +{ + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(pgp_key_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + secure_clear(sec_data, sec_len); + free(sec_data); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + src.sec_len = 0; + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(const pgp_key_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + secure_clear(sec_data, sec_len); + free(sec_data); + sec_data = NULL; + sec_len = src.sec_len; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t::~pgp_key_pkt_t() +{ + forget_secret_key_fields(&material); + free(hashed_data); + secure_clear(sec_data, sec_len); + free(sec_data); +} + +void +pgp_key_pkt_t::write(pgp_dest_t &dst) +{ + if (!is_key_pkt(tag)) { + RNP_LOG("wrong key tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (!hashed_data) { + fill_hashed_data(); + } + + pgp_packet_body_t pktbody(tag); + /* all public key data is written in hashed_data */ + pktbody.add(hashed_data, hashed_len); + /* if we have public key then we do not need further processing */ + if (!is_secret_key_pkt(tag)) { + pktbody.write(dst); + return; + } + + /* secret key fields should be pre-populated in sec_data field */ + if ((sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) && (!sec_data || !sec_len)) { + RNP_LOG("secret key data is not populated"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add_byte(sec_protection.s2k.usage); + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED_AND_HASHED: + case PGP_S2KU_ENCRYPTED: { + pktbody.add_byte(sec_protection.symm_alg); + pktbody.add(sec_protection.s2k); + if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t blsize = pgp_block_size(sec_protection.symm_alg); + if (!blsize) { + RNP_LOG("wrong block size"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add(sec_protection.iv, blsize); + } + break; + } + default: + RNP_LOG("wrong s2k usage"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (sec_len) { + /* if key is stored on card, or exported via gpg --export-secret-subkeys, then + * sec_data is empty */ + pktbody.add(sec_data, sec_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_key_pkt_t::parse(pgp_source_t &src) +{ + /* check the key tag */ + int atag = stream_pkt_type(src); + if (!is_key_pkt(atag)) { + RNP_LOG("wrong key packet tag: %d", atag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt((pgp_pkt_type_t) atag); + /* Read the packet into memory */ + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + /* key type, i.e. tag */ + tag = (pgp_pkt_type_t) atag; + /* version */ + uint8_t ver = 0; + if (!pkt.get(ver) || (ver < PGP_V2) || (ver > PGP_V4)) { + RNP_LOG("wrong key packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = (pgp_version_t) ver; + /* creation time */ + if (!pkt.get(creation_time)) { + return RNP_ERROR_BAD_FORMAT; + } + /* v3: validity days */ + if ((version < PGP_V4) && !pkt.get(v3_days)) { + return RNP_ERROR_BAD_FORMAT; + } + /* key algorithm */ + uint8_t analg = 0; + if (!pkt.get(analg)) { + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_pubkey_alg_t) analg; + material.alg = (pgp_pubkey_alg_t) analg; + /* v3 keys must be RSA-only */ + if ((version < PGP_V4) && !is_rsa_key_alg(alg)) { + RNP_LOG("wrong v3 pk algorithm"); + return RNP_ERROR_BAD_FORMAT; + } + /* algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!pkt.get(material.rsa.n) || !pkt.get(material.rsa.e)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!pkt.get(material.dsa.p) || !pkt.get(material.dsa.q) || !pkt.get(material.dsa.g) || + !pkt.get(material.dsa.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!pkt.get(material.eg.p) || !pkt.get(material.eg.g) || !pkt.get(material.eg.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDH: { + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + /* read KDF parameters. At the moment should be 0x03 0x01 halg ealg */ + uint8_t len = 0, halg = 0, walg = 0; + if (!pkt.get(len) || (len != 3)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(len) || (len != 1)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(halg) || !pkt.get(walg)) { + return RNP_ERROR_BAD_FORMAT; + } + material.ec.kdf_hash_alg = (pgp_hash_alg_t) halg; + material.ec.key_wrap_alg = (pgp_symm_alg_t) walg; + break; + } + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + return RNP_ERROR_BAD_FORMAT; + } + /* fill hashed data used for signatures */ + if (!(hashed_data = (uint8_t *) malloc(pkt.size() - pkt.left()))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(hashed_data, pkt.data(), pkt.size() - pkt.left()); + hashed_len = pkt.size() - pkt.left(); + + /* secret key fields if any */ + if (is_secret_key_pkt(tag)) { + uint8_t usage = 0; + if (!pkt.get(usage)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.s2k.usage = (pgp_s2k_usage_t) usage; + sec_protection.cipher_mode = PGP_CIPHER_MODE_CFB; + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED: + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + /* we have s2k */ + uint8_t salg = 0; + if (!pkt.get(salg) || !pkt.get(sec_protection.s2k)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.symm_alg = (pgp_symm_alg_t) salg; + break; + } + default: + /* old-style: usage is symmetric algorithm identifier */ + sec_protection.symm_alg = (pgp_symm_alg_t) usage; + sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED; + sec_protection.s2k.specifier = PGP_S2KS_SIMPLE; + sec_protection.s2k.hash_alg = PGP_HASH_MD5; + break; + } + + /* iv */ + if (sec_protection.s2k.usage && + (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + size_t bl_size = pgp_block_size(sec_protection.symm_alg); + if (!bl_size || !pkt.get(sec_protection.iv, bl_size)) { + RNP_LOG("failed to read iv"); + return RNP_ERROR_BAD_FORMAT; + } + } + + /* encrypted/cleartext secret MPIs are left */ + size_t asec_len = pkt.left(); + if (!asec_len) { + sec_data = NULL; + } else { + if (!(sec_data = (uint8_t *) calloc(1, asec_len))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!pkt.get(sec_data, asec_len)) { + return RNP_ERROR_BAD_STATE; + } + } + sec_len = asec_len; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in key packet", (int) pkt.left()); + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +void +pgp_key_pkt_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + if (version != PGP_V4) { + RNP_LOG("unknown key version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + hbody.add_byte(version); + hbody.add_uint32(creation_time); + hbody.add_byte(alg); + /* Algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + hbody.add(material.rsa.n); + hbody.add(material.rsa.e); + break; + case PGP_PKA_DSA: + hbody.add(material.dsa.p); + hbody.add(material.dsa.q); + hbody.add(material.dsa.g); + hbody.add(material.dsa.y); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + hbody.add(material.eg.p); + hbody.add(material.eg.g); + hbody.add(material.eg.y); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + break; + case PGP_PKA_ECDH: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + hbody.add_byte(3); + hbody.add_byte(1); + hbody.add_byte(material.ec.kdf_hash_alg); + hbody.add_byte(material.ec.key_wrap_alg); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + hashed_data = (uint8_t *) malloc(hbody.size()); + if (!hashed_data) { + RNP_LOG("allocation failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(hashed_data, hbody.data(), hbody.size()); + hashed_len = hbody.size(); +} + +bool +pgp_key_pkt_t::equals(const pgp_key_pkt_t &key, bool pubonly) const noexcept +{ + /* check tag. We allow public/secret key comparison here */ + if (pubonly) { + if (is_subkey_pkt(tag) && !is_subkey_pkt(key.tag)) { + return false; + } + if (is_key_pkt(tag) && !is_key_pkt(key.tag)) { + return false; + } + } else if (tag != key.tag) { + return false; + } + /* check basic fields */ + if ((version != key.version) || (alg != key.alg) || (creation_time != key.creation_time)) { + return false; + } + /* check key material */ + return key_material_equal(&material, &key.material); +} + +pgp_transferable_subkey_t::pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, + bool pubonly) +{ + subkey = pgp_key_pkt_t(src.subkey, pubonly); + signatures = src.signatures; +} + +pgp_transferable_key_t::pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly) +{ + key = pgp_key_pkt_t(src.key, pubonly); + userids = src.userids; + subkeys = src.subkeys; + signatures = src.signatures; +} diff --git a/src/librepgp/stream-key.h b/src/librepgp/stream-key.h new file mode 100644 index 0000000..a19a986 --- /dev/null +++ b/src/librepgp/stream-key.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018, [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. + */ + +#ifndef STREAM_KEY_H_ +#define STREAM_KEY_H_ + +#include +#include +#include +#include "rnp.h" +#include "stream-common.h" +#include "stream-sig.h" +#include "stream-packet.h" + +/** Struct to hold a key packet. May contain public or private key/subkey */ +typedef struct pgp_key_pkt_t { + pgp_pkt_type_t tag; /* packet tag: public key/subkey or private key/subkey */ + pgp_version_t version; /* Key packet version */ + uint32_t creation_time; /* Key creation time */ + pgp_pubkey_alg_t alg; + uint16_t v3_days; /* v2/v3 validity time */ + + uint8_t *hashed_data; /* key's hashed data used for signature calculation */ + size_t hashed_len; + + pgp_key_material_t material; + + /* secret key data, if available. sec_len == 0, sec_data == NULL for public key/subkey */ + pgp_key_protection_t sec_protection; + uint8_t * sec_data; + size_t sec_len; + + pgp_key_pkt_t() + : tag(PGP_PKT_RESERVED), version(PGP_VUNKNOWN), creation_time(0), alg(PGP_PKA_NOTHING), + v3_days(0), hashed_data(NULL), hashed_len(0), material({}), sec_protection({}), + sec_data(NULL), sec_len(0){}; + pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly = false); + pgp_key_pkt_t(pgp_key_pkt_t &&src); + pgp_key_pkt_t &operator=(pgp_key_pkt_t &&src); + pgp_key_pkt_t &operator=(const pgp_key_pkt_t &src); + ~pgp_key_pkt_t(); + + void write(pgp_dest_t &dst); + rnp_result_t parse(pgp_source_t &src); + /** @brief Fills the hashed (signed) data part of the key packet. Must be called before + * pgp_key_pkt_t::write() on the newly generated key */ + void fill_hashed_data(); + bool equals(const pgp_key_pkt_t &key, bool pubonly = false) const noexcept; +} pgp_key_pkt_t; + +/* userid/userattr with all the corresponding signatures */ +typedef struct pgp_transferable_userid_t { + pgp_userid_pkt_t uid; + pgp_signature_list_t signatures; +} pgp_transferable_userid_t; + +/* subkey with all corresponding signatures */ +typedef struct pgp_transferable_subkey_t { + pgp_key_pkt_t subkey; + pgp_signature_list_t signatures; + + pgp_transferable_subkey_t() = default; + pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, bool pubonly = false); + pgp_transferable_subkey_t &operator=(const pgp_transferable_subkey_t &) = default; +} pgp_transferable_subkey_t; + +/* transferable key with userids, subkeys and revocation signatures */ +typedef struct pgp_transferable_key_t { + pgp_key_pkt_t key; /* main key packet */ + std::vector userids; + std::vector subkeys; + pgp_signature_list_t signatures; + + pgp_transferable_key_t() = default; + pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly = false); + pgp_transferable_key_t &operator=(const pgp_transferable_key_t &) = default; +} pgp_transferable_key_t; + +/* sequence of OpenPGP transferable keys */ +typedef struct pgp_key_sequence_t { + std::vector keys; +} pgp_key_sequence_t; + +rnp_result_t transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key); + +rnp_result_t transferable_key_merge(pgp_transferable_key_t & dst, + const pgp_transferable_key_t &src); + +rnp_result_t transferable_subkey_from_key(pgp_transferable_subkey_t &dst, + const pgp_key_t & key); + +rnp_result_t transferable_subkey_merge(pgp_transferable_subkey_t & dst, + const pgp_transferable_subkey_t &src); + +/* Process single primary key or subkey, skipping all key-related packets on error. + If key.key.tag is zero, then (on success) result is subkey and it is stored in + key.subkeys[0]. + If returns RNP_ERROR_BAD_FORMAT then some packets failed parsing, but still key may contain + successfully read key or subkey. +*/ +rnp_result_t process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors); + +rnp_result_t process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors); + +rnp_result_t process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors); + +rnp_result_t process_pgp_subkey(pgp_source_t & src, + pgp_transferable_subkey_t &subkey, + bool skiperrors); + +rnp_result_t decrypt_secret_key(pgp_key_pkt_t *key, const char *password); + +rnp_result_t encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng); + +void forget_secret_key_fields(pgp_key_material_t *key); + +#endif diff --git a/src/librepgp/stream-packet.cpp b/src/librepgp/stream-packet.cpp new file mode 100644 index 0000000..49dd63d --- /dev/null +++ b/src/librepgp/stream-packet.cpp @@ -0,0 +1,1228 @@ +/* + * Copyright (c) 2017-2020, [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 +#else +#include "uniwin.h" +#endif +#include +#include +#include +#include "types.h" +#include "crypto.h" +#include "crypto/mem.h" +#include "stream-packet.h" +#include "stream-key.h" +#include + +uint32_t +read_uint32(const uint8_t *buf) +{ + return ((uint32_t) buf[0] << 24) | ((uint32_t) buf[1] << 16) | ((uint32_t) buf[2] << 8) | + (uint32_t) buf[3]; +} + +uint16_t +read_uint16(const uint8_t *buf) +{ + return ((uint16_t) buf[0] << 8) | buf[1]; +} + +void +write_uint16(uint8_t *buf, uint16_t val) +{ + buf[0] = val >> 8; + buf[1] = val & 0xff; +} + +size_t +write_packet_len(uint8_t *buf, size_t len) +{ + if (len < 192) { + buf[0] = len; + return 1; + } else if (len < 8192 + 192) { + buf[0] = ((len - 192) >> 8) + 192; + buf[1] = (len - 192) & 0xff; + return 2; + } else { + buf[0] = 0xff; + STORE32BE(&buf[1], len); + return 5; + } +} + +int +get_packet_type(uint8_t ptag) +{ + if (!(ptag & PGP_PTAG_ALWAYS_SET)) { + return -1; + } + + if (ptag & PGP_PTAG_NEW_FORMAT) { + return (int) (ptag & PGP_PTAG_NF_CONTENT_TAG_MASK); + } else { + return (int) ((ptag & PGP_PTAG_OF_CONTENT_TAG_MASK) >> PGP_PTAG_OF_CONTENT_TAG_SHIFT); + } +} + +int +stream_pkt_type(pgp_source_t &src) +{ + if (src_eof(&src)) { + return 0; + } + size_t hdrneed = 0; + if (!stream_pkt_hdr_len(src, hdrneed)) { + return -1; + } + uint8_t hdr[PGP_MAX_HEADER_SIZE]; + if (!src_peek_eq(&src, hdr, hdrneed)) { + return -1; + } + return get_packet_type(hdr[0]); +} + +bool +stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen) +{ + uint8_t buf[2]; + + if (!src_peek_eq(&src, buf, 2) || !(buf[0] & PGP_PTAG_ALWAYS_SET)) { + return false; + } + + if (buf[0] & PGP_PTAG_NEW_FORMAT) { + if (buf[1] < 192) { + hdrlen = 2; + } else if (buf[1] < 224) { + hdrlen = 3; + } else if (buf[1] < 255) { + hdrlen = 2; + } else { + hdrlen = 6; + } + return true; + } + + switch (buf[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) { + case PGP_PTAG_OLD_LEN_1: + hdrlen = 2; + return true; + case PGP_PTAG_OLD_LEN_2: + hdrlen = 3; + return true; + case PGP_PTAG_OLD_LEN_4: + hdrlen = 5; + return true; + case PGP_PTAG_OLD_LEN_INDETERMINATE: + hdrlen = 1; + return true; + default: + return false; + } +} + +static bool +get_pkt_len(uint8_t *hdr, size_t *pktlen) +{ + if (hdr[0] & PGP_PTAG_NEW_FORMAT) { + // 1-byte length + if (hdr[1] < 192) { + *pktlen = hdr[1]; + return true; + } + // 2-byte length + if (hdr[1] < 224) { + *pktlen = ((size_t)(hdr[1] - 192) << 8) + (size_t) hdr[2] + 192; + return true; + } + // partial length - we do not allow it here + if (hdr[1] < 255) { + return false; + } + // 4-byte length + *pktlen = read_uint32(&hdr[2]); + return true; + } + + switch (hdr[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) { + case PGP_PTAG_OLD_LEN_1: + *pktlen = hdr[1]; + return true; + case PGP_PTAG_OLD_LEN_2: + *pktlen = read_uint16(&hdr[1]); + return true; + case PGP_PTAG_OLD_LEN_4: + *pktlen = read_uint32(&hdr[1]); + return true; + default: + return false; + } +} + +bool +stream_read_pkt_len(pgp_source_t *src, size_t *pktlen) +{ + uint8_t buf[6] = {}; + size_t read = 0; + + if (!stream_pkt_hdr_len(*src, read)) { + return false; + } + + if (!src_read_eq(src, buf, read)) { + return false; + } + + return get_pkt_len(buf, pktlen); +} + +bool +stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last) +{ + uint8_t hdr[5] = {}; + size_t read = 0; + + if (!src_read(src, hdr, 1, &read)) { + RNP_LOG("failed to read header"); + return false; + } + if (read < 1) { + RNP_LOG("wrong eof"); + return false; + } + + *last = true; + // partial length + if ((hdr[0] >= 224) && (hdr[0] < 255)) { + *last = false; + *clen = get_partial_pkt_len(hdr[0]); + return true; + } + // 1-byte length + if (hdr[0] < 192) { + *clen = hdr[0]; + return true; + } + // 2-byte length + if (hdr[0] < 224) { + if (!src_read_eq(src, &hdr[1], 1)) { + RNP_LOG("wrong 2-byte length"); + return false; + } + *clen = ((size_t)(hdr[0] - 192) << 8) + (size_t) hdr[1] + 192; + return true; + } + // 4-byte length + if (!src_read_eq(src, &hdr[1], 4)) { + RNP_LOG("wrong 4-byte length"); + return false; + } + *clen = ((size_t) hdr[1] << 24) | ((size_t) hdr[2] << 16) | ((size_t) hdr[3] << 8) | + (size_t) hdr[4]; + return true; +} + +bool +stream_old_indeterminate_pkt_len(pgp_source_t *src) +{ + uint8_t ptag = 0; + if (!src_peek_eq(src, &ptag, 1)) { + return false; + } + return !(ptag & PGP_PTAG_NEW_FORMAT) && + ((ptag & PGP_PTAG_OF_LENGTH_TYPE_MASK) == PGP_PTAG_OLD_LEN_INDETERMINATE); +} + +bool +stream_partial_pkt_len(pgp_source_t *src) +{ + uint8_t hdr[2] = {}; + if (!src_peek_eq(src, hdr, 2)) { + return false; + } + return (hdr[0] & PGP_PTAG_NEW_FORMAT) && (hdr[1] >= 224) && (hdr[1] < 255); +} + +size_t +get_partial_pkt_len(uint8_t blen) +{ + return 1 << (blen & 0x1f); +} + +rnp_result_t +stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr) +{ + size_t hlen = 0; + memset(hdr, 0, sizeof(*hdr)); + if (!stream_pkt_hdr_len(*src, hlen)) { + uint8_t hdr2[2] = {0}; + if (!src_peek_eq(src, hdr2, 2)) { + RNP_LOG("pkt header read failed"); + return RNP_ERROR_READ; + } + + RNP_LOG("bad packet header: 0x%02x%02x", hdr2[0], hdr2[1]); + return RNP_ERROR_BAD_FORMAT; + } + + if (!src_peek_eq(src, hdr->hdr, hlen)) { + RNP_LOG("failed to read pkt header"); + return RNP_ERROR_READ; + } + + hdr->hdr_len = hlen; + hdr->tag = (pgp_pkt_type_t) get_packet_type(hdr->hdr[0]); + + if (stream_partial_pkt_len(src)) { + hdr->partial = true; + } else if (stream_old_indeterminate_pkt_len(src)) { + hdr->indeterminate = true; + } else { + (void) get_pkt_len(hdr->hdr, &hdr->pkt_len); + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_read_packet_partial(pgp_source_t *src, pgp_dest_t *dst) +{ + uint8_t hdr = 0; + if (!src_read_eq(src, &hdr, 1)) { + return RNP_ERROR_READ; + } + + bool last = false; + size_t partlen = 0; + if (!stream_read_partial_chunk_len(src, &partlen, &last)) { + return RNP_ERROR_BAD_FORMAT; + } + + uint8_t *buf = (uint8_t *) malloc(PGP_INPUT_CACHE_SIZE); + if (!buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + while (partlen > 0) { + size_t read = std::min(partlen, (size_t) PGP_INPUT_CACHE_SIZE); + if (!src_read_eq(src, buf, read)) { + free(buf); + return RNP_ERROR_READ; + } + if (dst) { + dst_write(dst, buf, read); + } + partlen -= read; + if (partlen > 0) { + continue; + } + if (last) { + break; + } + if (!stream_read_partial_chunk_len(src, &partlen, &last)) { + free(buf); + return RNP_ERROR_BAD_FORMAT; + } + } + free(buf); + return RNP_SUCCESS; +} + +rnp_result_t +stream_read_packet(pgp_source_t *src, pgp_dest_t *dst) +{ + if (stream_old_indeterminate_pkt_len(src)) { + return dst_write_src(src, dst, PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE); + } + + if (stream_partial_pkt_len(src)) { + return stream_read_packet_partial(src, dst); + } + + try { + pgp_packet_body_t body(PGP_PKT_RESERVED); + rnp_result_t ret = body.read(*src); + if (dst) { + body.write(*dst, false); + } + return ret; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +stream_skip_packet(pgp_source_t *src) +{ + return stream_read_packet(src, NULL); +} + +rnp_result_t +stream_parse_marker(pgp_source_t &src) +{ + try { + pgp_packet_body_t pkt(PGP_PKT_MARKER); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + if ((pkt.size() != PGP_MARKER_LEN) || + memcmp(pkt.data(), PGP_MARKER_CONTENTS, PGP_MARKER_LEN)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } +} + +bool +is_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_subkey_pkt(int tag) +{ + return (tag == PGP_PKT_PUBLIC_SUBKEY) || (tag == PGP_PKT_SECRET_SUBKEY); +} + +bool +is_primary_key_pkt(int tag) +{ + return (tag == PGP_PKT_PUBLIC_KEY) || (tag == PGP_PKT_SECRET_KEY); +} + +bool +is_public_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_secret_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_rsa_key_alg(pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return true; + default: + return false; + } +} + +pgp_packet_body_t::pgp_packet_body_t(pgp_pkt_type_t tag) +{ + data_.reserve(16); + tag_ = tag; + secure_ = is_secret_key_pkt(tag); +} + +pgp_packet_body_t::pgp_packet_body_t(const uint8_t *data, size_t len) +{ + data_.assign(data, data + len); + tag_ = PGP_PKT_RESERVED; + secure_ = false; +} + +pgp_packet_body_t::~pgp_packet_body_t() +{ + if (secure_) { + secure_clear(data_.data(), data_.size()); + } +} + +uint8_t * +pgp_packet_body_t::data() noexcept +{ + return data_.data(); +} + +size_t +pgp_packet_body_t::size() const noexcept +{ + return data_.size(); +} + +size_t +pgp_packet_body_t::left() const noexcept +{ + return data_.size() - pos_; +} + +bool +pgp_packet_body_t::get(uint8_t &val) noexcept +{ + if (pos_ >= data_.size()) { + return false; + } + val = data_[pos_++]; + return true; +} + +bool +pgp_packet_body_t::get(uint16_t &val) noexcept +{ + if (pos_ + 2 > data_.size()) { + return false; + } + val = read_uint16(data_.data() + pos_); + pos_ += 2; + return true; +} + +bool +pgp_packet_body_t::get(uint32_t &val) noexcept +{ + if (pos_ + 4 > data_.size()) { + return false; + } + val = read_uint32(data_.data() + pos_); + pos_ += 4; + return true; +} + +bool +pgp_packet_body_t::get(uint8_t *val, size_t len) noexcept +{ + if (pos_ + len > data_.size()) { + return false; + } + memcpy(val, data_.data() + pos_, len); + pos_ += len; + return true; +} + +bool +pgp_packet_body_t::get(pgp_key_id_t &val) noexcept +{ + static_assert(std::tuple_size::value == PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + return get(val.data(), val.size()); +} + +bool +pgp_packet_body_t::get(pgp_mpi_t &val) noexcept +{ + uint16_t bits = 0; + if (!get(bits)) { + return false; + } + size_t len = (bits + 7) >> 3; + if (len > PGP_MPINT_SIZE) { + RNP_LOG("too large mpi"); + return false; + } + if (!len) { + RNP_LOG("0 mpi"); + return false; + } + if (!get(val.mpi, len)) { + RNP_LOG("failed to read mpi body"); + return false; + } + /* check the mpi bit count */ + val.len = len; + size_t mbits = mpi_bits(&val); + if (mbits != bits) { + RNP_LOG( + "Warning! Wrong mpi bit count: got %" PRIu16 ", but actual is %zu", bits, mbits); + } + return true; +} + +bool +pgp_packet_body_t::get(pgp_curve_t &val) noexcept +{ + uint8_t oidlen = 0; + if (!get(oidlen)) { + return false; + } + uint8_t oid[MAX_CURVE_OID_HEX_LEN] = {0}; + if (!oidlen || (oidlen == 0xff) || (oidlen > sizeof(oid))) { + RNP_LOG("unsupported curve oid len: %" PRIu8, oidlen); + return false; + } + if (!get(oid, oidlen)) { + return false; + } + pgp_curve_t res = find_curve_by_OID(oid, oidlen); + if (res == PGP_CURVE_MAX) { + RNP_LOG("unsupported curve"); + return false; + } + val = res; + return true; +} + +bool +pgp_packet_body_t::get(pgp_s2k_t &s2k) noexcept +{ + uint8_t spec = 0, halg = 0; + if (!get(spec) || !get(halg)) { + return false; + } + s2k.specifier = (pgp_s2k_specifier_t) spec; + s2k.hash_alg = (pgp_hash_alg_t) halg; + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + return true; + case PGP_S2KS_SALTED: + return get(s2k.salt, PGP_SALT_SIZE); + case PGP_S2KS_ITERATED_AND_SALTED: { + uint8_t iter = 0; + if (!get(s2k.salt, PGP_SALT_SIZE) || !get(iter)) { + return false; + } + s2k.iterations = iter; + return true; + } + case PGP_S2KS_EXPERIMENTAL: { + try { + s2k.experimental = {data_.begin() + pos_, data_.end()}; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + uint8_t gnu[3] = {0}; + if (!get(gnu, 3) || memcmp(gnu, "GNU", 3)) { + RNP_LOG("Unknown experimental s2k. Skipping."); + pos_ = data_.size(); + s2k.gpg_ext_num = PGP_S2K_GPG_NONE; + return true; + } + uint8_t ext_num = 0; + if (!get(ext_num)) { + return false; + } + if ((ext_num != PGP_S2K_GPG_NO_SECRET) && (ext_num != PGP_S2K_GPG_SMARTCARD)) { + RNP_LOG("Unsupported gpg extension num: %" PRIu8 ", skipping", ext_num); + pos_ = data_.size(); + s2k.gpg_ext_num = PGP_S2K_GPG_NONE; + return true; + } + s2k.gpg_ext_num = (pgp_s2k_gpg_extension_t) ext_num; + if (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET) { + return true; + } + if (!get(s2k.gpg_serial_len)) { + RNP_LOG("Failed to get GPG serial len"); + return false; + } + size_t len = s2k.gpg_serial_len; + if (s2k.gpg_serial_len > 16) { + RNP_LOG("Warning: gpg_serial_len is %d", (int) len); + len = 16; + } + if (!get(s2k.gpg_serial, len)) { + RNP_LOG("Failed to get GPG serial"); + return false; + } + return true; + } + default: + RNP_LOG("unknown s2k specifier: %d", (int) s2k.specifier); + return false; + } +} + +void +pgp_packet_body_t::add(const void *data, size_t len) +{ + data_.insert(data_.end(), (uint8_t *) data, (uint8_t *) data + len); +} + +void +pgp_packet_body_t::add_byte(uint8_t bt) +{ + data_.push_back(bt); +} + +void +pgp_packet_body_t::add_uint16(uint16_t val) +{ + uint8_t bytes[2]; + write_uint16(bytes, val); + add(bytes, 2); +} + +void +pgp_packet_body_t::add_uint32(uint32_t val) +{ + uint8_t bytes[4]; + STORE32BE(bytes, val); + add(bytes, 4); +} + +void +pgp_packet_body_t::add(const pgp_key_id_t &val) +{ + add(val.data(), val.size()); +} + +void +pgp_packet_body_t::add(const pgp_mpi_t &val) +{ + if (!val.len) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + unsigned idx = 0; + while ((idx < val.len - 1) && (!val.mpi[idx])) { + idx++; + } + + unsigned bits = (val.len - idx - 1) << 3; + unsigned hibyte = val.mpi[idx]; + while (hibyte) { + bits++; + hibyte = hibyte >> 1; + } + + uint8_t hdr[2] = {(uint8_t)(bits >> 8), (uint8_t)(bits & 0xff)}; + add(hdr, 2); + add(val.mpi + idx, val.len - idx); +} + +void +pgp_packet_body_t::add_subpackets(const pgp_signature_t &sig, bool hashed) +{ + pgp_packet_body_t spbody(PGP_PKT_RESERVED); + + for (auto &subpkt : sig.subpkts) { + if (subpkt.hashed != hashed) { + continue; + } + + uint8_t splen[6]; + size_t lenlen = write_packet_len(splen, subpkt.len + 1); + spbody.add(splen, lenlen); + spbody.add_byte(subpkt.type | (subpkt.critical << 7)); + spbody.add(subpkt.data, subpkt.len); + } + + if (spbody.data_.size() > 0xffff) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + add_uint16(spbody.data_.size()); + add(spbody.data_.data(), spbody.data_.size()); +} + +void +pgp_packet_body_t::add(const pgp_curve_t curve) +{ + const ec_curve_desc_t *desc = get_curve_desc(curve); + if (!desc) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + add_byte((uint8_t) desc->OIDhex_len); + add(desc->OIDhex, (uint8_t) desc->OIDhex_len); +} + +void +pgp_packet_body_t::add(const pgp_s2k_t &s2k) +{ + add_byte(s2k.specifier); + add_byte(s2k.hash_alg); + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + return; + case PGP_S2KS_SALTED: + add(s2k.salt, PGP_SALT_SIZE); + return; + case PGP_S2KS_ITERATED_AND_SALTED: { + unsigned iter = s2k.iterations; + if (iter > 255) { + iter = pgp_s2k_encode_iterations(iter); + } + add(s2k.salt, PGP_SALT_SIZE); + add_byte(iter); + return; + } + case PGP_S2KS_EXPERIMENTAL: { + if ((s2k.gpg_ext_num != PGP_S2K_GPG_NO_SECRET) && + (s2k.gpg_ext_num != PGP_S2K_GPG_SMARTCARD)) { + RNP_LOG("Unknown experimental s2k."); + add(s2k.experimental.data(), s2k.experimental.size()); + return; + } + add("GNU", 3); + add_byte(s2k.gpg_ext_num); + if (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + static_assert(sizeof(s2k.gpg_serial) == 16, "invalid gpg serial length"); + size_t slen = s2k.gpg_serial_len > 16 ? 16 : s2k.gpg_serial_len; + add_byte(s2k.gpg_serial_len); + add(s2k.gpg_serial, slen); + } + return; + } + default: + RNP_LOG("unknown s2k specifier"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +rnp_result_t +pgp_packet_body_t::read(pgp_source_t &src) noexcept +{ + /* Make sure we have enough data for packet header */ + if (!src_peek_eq(&src, hdr_, 2)) { + return RNP_ERROR_READ; + } + + /* Read the packet header and length */ + size_t len = 0; + if (!stream_pkt_hdr_len(src, len)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!src_peek_eq(&src, hdr_, len)) { + return RNP_ERROR_READ; + } + hdr_len_ = len; + + int ptag = get_packet_type(hdr_[0]); + if ((ptag < 0) || ((tag_ != PGP_PKT_RESERVED) && (tag_ != ptag))) { + RNP_LOG("tag mismatch: %d vs %d", (int) tag_, ptag); + return RNP_ERROR_BAD_FORMAT; + } + tag_ = (pgp_pkt_type_t) ptag; + + if (!stream_read_pkt_len(&src, &len)) { + return RNP_ERROR_READ; + } + + /* early exit for the empty packet */ + if (!len) { + return RNP_SUCCESS; + } + + if (len > PGP_MAX_PKT_SIZE) { + RNP_LOG("too large packet"); + return RNP_ERROR_BAD_FORMAT; + } + + /* Read the packet contents */ + try { + data_.resize(len); + } catch (const std::exception &e) { + RNP_LOG("malloc of %d bytes failed, %s", (int) len, e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + size_t read = 0; + if (!src_read(&src, data_.data(), len, &read) || (read != len)) { + RNP_LOG("read %d instead of %d", (int) read, (int) len); + return RNP_ERROR_READ; + } + pos_ = 0; + return RNP_SUCCESS; +} + +void +pgp_packet_body_t::write(pgp_dest_t &dst, bool hdr) noexcept +{ + if (hdr) { + uint8_t hdrbt[6] = { + (uint8_t)(tag_ | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT), 0, 0, 0, 0, 0}; + size_t hlen = 1 + write_packet_len(&hdrbt[1], data_.size()); + dst_write(&dst, hdrbt, hlen); + } + dst_write(&dst, data_.data(), data_.size()); +} + +void +pgp_packet_body_t::mark_secure(bool secure) noexcept +{ + secure_ = secure; +} + +void +pgp_sk_sesskey_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_SK_SESSION_KEY); + /* version and algorithm fields */ + pktbody.add_byte(version); + pktbody.add_byte(alg); + if (version == PGP_SKSK_V5) { + pktbody.add_byte(aalg); + } + /* S2K specifier */ + pktbody.add_byte(s2k.specifier); + pktbody.add_byte(s2k.hash_alg); + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + break; + case PGP_S2KS_SALTED: + pktbody.add(s2k.salt, sizeof(s2k.salt)); + break; + case PGP_S2KS_ITERATED_AND_SALTED: + pktbody.add(s2k.salt, sizeof(s2k.salt)); + pktbody.add_byte(s2k.iterations); + break; + default: + RNP_LOG("Unexpected s2k specifier: %d", (int) s2k.specifier); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + /* v5 : iv */ + if (version == PGP_SKSK_V5) { + pktbody.add(iv, ivlen); + } + /* encrypted key and auth tag for v5 */ + if (enckeylen) { + pktbody.add(enckey, enckeylen); + } + /* write packet */ + pktbody.write(dst); +} + +rnp_result_t +pgp_sk_sesskey_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_SK_SESSION_KEY); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + /* version */ + uint8_t bt; + if (!pkt.get(bt) || ((bt != PGP_SKSK_V4) && (bt != PGP_SKSK_V5))) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = bt; + /* symmetric algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get symm alg"); + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_symm_alg_t) bt; + + if (version == PGP_SKSK_V5) { + /* aead algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get aead alg"); + return RNP_ERROR_BAD_FORMAT; + } + aalg = (pgp_aead_alg_t) bt; + if ((aalg != PGP_AEAD_EAX) && (aalg != PGP_AEAD_OCB)) { + RNP_LOG("unsupported AEAD algorithm : %d", (int) aalg); + return RNP_ERROR_BAD_PARAMETERS; + } + } + + /* s2k */ + if (!pkt.get(s2k)) { + RNP_LOG("failed to parse s2k"); + return RNP_ERROR_BAD_FORMAT; + } + + /* v4 key */ + if (version == PGP_SKSK_V4) { + /* encrypted session key if present */ + size_t keylen = pkt.left(); + if (keylen) { + if (keylen > PGP_MAX_KEY_SIZE + 1) { + RNP_LOG("too long esk"); + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(enckey, keylen)) { + RNP_LOG("failed to get key"); + return RNP_ERROR_BAD_FORMAT; + } + } + enckeylen = keylen; + return RNP_SUCCESS; + } + + /* v5: iv + esk + tag. For both EAX and OCB ivlen and taglen are 16 octets */ + size_t noncelen = pgp_cipher_aead_nonce_len(aalg); + size_t taglen = pgp_cipher_aead_tag_len(aalg); + size_t keylen = 0; + + if (pkt.left() > noncelen + taglen + PGP_MAX_KEY_SIZE) { + RNP_LOG("too long esk"); + return RNP_ERROR_BAD_FORMAT; + } + if (pkt.left() < noncelen + taglen + 8) { + RNP_LOG("too short esk"); + return RNP_ERROR_BAD_FORMAT; + } + /* iv */ + if (!pkt.get(iv, noncelen)) { + RNP_LOG("failed to get iv"); + return RNP_ERROR_BAD_FORMAT; + } + ivlen = noncelen; + + /* key */ + keylen = pkt.left(); + if (!pkt.get(enckey, keylen)) { + RNP_LOG("failed to get key"); + return RNP_ERROR_BAD_FORMAT; + } + enckeylen = keylen; + return RNP_SUCCESS; +} + +void +pgp_pk_sesskey_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY); + pktbody.add_byte(version); + pktbody.add(key_id); + pktbody.add_byte(alg); + pktbody.add(material_buf.data(), material_buf.size()); + pktbody.write(dst); +} + +rnp_result_t +pgp_pk_sesskey_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_PK_SESSION_KEY); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + /* version */ + uint8_t bt = 0; + if (!pkt.get(bt) || (bt != PGP_PKSK_V3)) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = bt; + /* key id */ + if (!pkt.get(key_id)) { + RNP_LOG("failed to get key id"); + return RNP_ERROR_BAD_FORMAT; + } + /* public key algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get palg"); + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_pubkey_alg_t) bt; + + /* raw signature material */ + if (!pkt.left()) { + RNP_LOG("No encrypted material"); + return RNP_ERROR_BAD_FORMAT; + } + try { + material_buf.resize(pkt.left()); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + /* we cannot fail here */ + pkt.get(material_buf.data(), material_buf.size()); + /* check whether it can be parsed */ + pgp_encrypted_material_t material = {}; + if (!parse_material(material)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +bool +pgp_pk_sesskey_t::parse_material(pgp_encrypted_material_t &material) const +{ + pgp_packet_body_t pkt(material_buf.data(), material_buf.size()); + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + /* RSA m */ + if (!pkt.get(material.rsa.m)) { + RNP_LOG("failed to get rsa m"); + return false; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + /* ElGamal g, m */ + if (!pkt.get(material.eg.g) || !pkt.get(material.eg.m)) { + RNP_LOG("failed to get elgamal mpis"); + return false; + } + break; + case PGP_PKA_SM2: + /* SM2 m */ + if (!pkt.get(material.sm2.m)) { + RNP_LOG("failed to get sm2 m"); + return false; + } + break; + case PGP_PKA_ECDH: { + /* ECDH ephemeral point */ + if (!pkt.get(material.ecdh.p)) { + RNP_LOG("failed to get ecdh p"); + return false; + } + /* ECDH m */ + uint8_t bt = 0; + if (!pkt.get(bt)) { + RNP_LOG("failed to get ecdh m len"); + return false; + } + if (bt > ECDH_WRAPPED_KEY_SIZE) { + RNP_LOG("wrong ecdh m len"); + return false; + } + material.ecdh.mlen = bt; + if (!pkt.get(material.ecdh.m, bt)) { + RNP_LOG("failed to get ecdh m len"); + return false; + } + break; + } + default: + RNP_LOG("unknown pk alg %d", (int) alg); + return false; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in pk packet", (int) pkt.left()); + return false; + } + return true; +} + +void +pgp_pk_sesskey_t::write_material(const pgp_encrypted_material_t &material) +{ + pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY); + + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + pktbody.add(material.rsa.m); + break; + case PGP_PKA_SM2: + pktbody.add(material.sm2.m); + break; + case PGP_PKA_ECDH: + pktbody.add(material.ecdh.p); + pktbody.add_byte(material.ecdh.mlen); + pktbody.add(material.ecdh.m, material.ecdh.mlen); + break; + case PGP_PKA_ELGAMAL: + pktbody.add(material.eg.g); + pktbody.add(material.eg.m); + break; + default: + RNP_LOG("Unknown pk alg: %d", (int) alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + material_buf = {pktbody.data(), pktbody.data() + pktbody.size()}; +} + +void +pgp_one_pass_sig_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_ONE_PASS_SIG); + pktbody.add_byte(version); + pktbody.add_byte(type); + pktbody.add_byte(halg); + pktbody.add_byte(palg); + pktbody.add(keyid); + pktbody.add_byte(nested); + pktbody.write(dst); +} + +rnp_result_t +pgp_one_pass_sig_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_ONE_PASS_SIG); + /* Read the packet into memory */ + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + uint8_t buf[13] = {0}; + if ((pkt.size() != 13) || !pkt.get(buf, 13)) { + return RNP_ERROR_BAD_FORMAT; + } + /* version */ + if (buf[0] != 3) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = buf[0]; + /* signature type */ + type = (pgp_sig_type_t) buf[1]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[2]; + /* pk algorithm */ + palg = (pgp_pubkey_alg_t) buf[3]; + /* key id */ + static_assert(std::tuple_size::value == PGP_KEY_ID_SIZE, + "pgp_one_pass_sig_t.keyid size mismatch"); + memcpy(keyid.data(), &buf[4], PGP_KEY_ID_SIZE); + /* nested flag */ + nested = buf[12]; + return RNP_SUCCESS; +} diff --git a/src/librepgp/stream-packet.h b/src/librepgp/stream-packet.h new file mode 100644 index 0000000..f88c96f --- /dev/null +++ b/src/librepgp/stream-packet.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2017-2020 [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. + */ + +#ifndef STREAM_PACKET_H_ +#define STREAM_PACKET_H_ + +#include +#include +#include +#include "types.h" +#include "stream-common.h" + +/* maximum size of the 'small' packet */ +#define PGP_MAX_PKT_SIZE 0x100000 + +/* maximum size of indeterminate-size packet allowed with old length format */ +#define PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE 0x40000000 + +typedef struct pgp_packet_hdr_t { + pgp_pkt_type_t tag; /* packet tag */ + uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* PGP packet header, needed for AEAD */ + size_t hdr_len; /* length of the header */ + size_t pkt_len; /* packet body length if non-partial and non-indeterminate */ + bool partial; /* partial length packet */ + bool indeterminate; /* indeterminate length packet */ +} pgp_packet_hdr_t; + +/* structure for convenient writing or parsing of non-stream packets */ +typedef struct pgp_packet_body_t { + private: + pgp_pkt_type_t tag_; /* packet tag */ + std::vector data_; /* packet bytes */ + /* fields below are filled only for parsed packet */ + uint8_t hdr_[PGP_MAX_HEADER_SIZE]{}; /* packet header bytes */ + size_t hdr_len_{}; /* number of bytes in hdr */ + size_t pos_{}; /* current read position in packet data */ + bool secure_{}; /* contents of the packet are secure so must be wiped in the destructor */ + public: + /** @brief initialize writing of packet body + * @param tag tag of the packet + **/ + pgp_packet_body_t(pgp_pkt_type_t tag); + /** @brief init packet body (without headers) with memory. Used for easier data parsing. + * @param data buffer with packet body part + * @param len number of available bytes in mem + */ + pgp_packet_body_t(const uint8_t *data, size_t len); + + pgp_packet_body_t(const pgp_packet_body_t &src) = delete; + pgp_packet_body_t(pgp_packet_body_t &&src) = delete; + pgp_packet_body_t &operator=(const pgp_packet_body_t &) = delete; + pgp_packet_body_t &operator=(pgp_packet_body_t &&) = delete; + ~pgp_packet_body_t(); + + /** @brief pointer to the data, kept in the packet */ + uint8_t *data() noexcept; + /** @brief number of bytes, kept in the packet (without the header) */ + size_t size() const noexcept; + /** @brief number of bytes left to read */ + size_t left() const noexcept; + /** @brief get next byte from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint8_t &val) noexcept; + /** @brief get next big-endian uint16 from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint16_t &val) noexcept; + /** @brief get next big-endian uint32 from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint32_t &val) noexcept; + /** @brief get some bytes from the packet body, populated with read() call. + * @param val packet body bytes will be stored here. Must be capable of storing len bytes. + * @param len number of bytes to read + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint8_t *val, size_t len) noexcept; + /** @brief get next keyid from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(pgp_key_id_t &val) noexcept; + /** @brief get next mpi from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached + * or mpi is ill-formed) + **/ + bool get(pgp_mpi_t &val) noexcept; + /** @brief Read ECC key curve and convert it to pgp_curve_t */ + bool get(pgp_curve_t &val) noexcept; + /** @brief read s2k from the packet */ + bool get(pgp_s2k_t &s2k) noexcept; + /** @brief append some bytes to the packet body */ + void add(const void *data, size_t len); + /** @brief append single byte to the packet body */ + void add_byte(uint8_t bt); + /** @brief append big endian 16-bit value to the packet body */ + void add_uint16(uint16_t val); + /** @brief append big endian 32-bit value to the packet body */ + void add_uint32(uint32_t val); + /** @brief append keyid to the packet body */ + void add(const pgp_key_id_t &val); + /** @brief add pgp mpi (including header) to the packet body */ + void add(const pgp_mpi_t &val); + /** + * @brief add pgp signature subpackets (including their length) to the packet body + * @param sig signature, containing subpackets + * @param hashed whether write hashed or not hashed subpackets + */ + void add_subpackets(const pgp_signature_t &sig, bool hashed); + /** @brief add ec curve description to the packet body */ + void add(const pgp_curve_t curve); + /** @brief add s2k description to the packet body */ + void add(const pgp_s2k_t &s2k); + /** @brief read 'short-length' packet body (including tag and length bytes) from the source + * @param src source to read from + * @return RNP_SUCCESS or error code if operation failed + **/ + rnp_result_t read(pgp_source_t &src) noexcept; + /** @brief write packet header, length and body to the dst + * @param dst destination to write to. + * @param hdr write packet's header or not + **/ + void write(pgp_dest_t &dst, bool hdr = true) noexcept; + /** @brief mark contents as secure, so secure_clear() must be called in the destructor */ + void mark_secure(bool secure = true) noexcept; +} pgp_packet_body_t; + +/** public-key encrypted session key packet */ +typedef struct pgp_pk_sesskey_t { + unsigned version{}; + pgp_key_id_t key_id{}; + pgp_pubkey_alg_t alg{}; + std::vector material_buf{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); + /** + * @brief Parse encrypted material which is stored in packet in raw. + * @param material on success parsed material will be stored here. + * @return true on success or false otherwise. May also throw an exception. + */ + bool parse_material(pgp_encrypted_material_t &material) const; + /** + * @brief Write encrypted material to the material_buf. + * @param material populated encrypted material. + */ + void write_material(const pgp_encrypted_material_t &material); +} pgp_pk_sesskey_t; + +/** pkp_sk_sesskey_t */ +typedef struct pgp_sk_sesskey_t { + unsigned version{}; + pgp_symm_alg_t alg{}; + pgp_s2k_t s2k{}; + uint8_t enckey[PGP_MAX_KEY_SIZE + PGP_AEAD_MAX_TAG_LEN + 1]{}; + unsigned enckeylen{}; + /* v5 specific fields */ + pgp_aead_alg_t aalg{}; + uint8_t iv[PGP_MAX_BLOCK_SIZE]{}; + unsigned ivlen{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_sk_sesskey_t; + +/** pgp_one_pass_sig_t */ +typedef struct pgp_one_pass_sig_t { + uint8_t version{}; + pgp_sig_type_t type{}; + pgp_hash_alg_t halg{}; + pgp_pubkey_alg_t palg{}; + pgp_key_id_t keyid{}; + unsigned nested{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_one_pass_sig_t; + +/** Struct to hold userid or userattr packet. We don't parse userattr now, just storing the + * binary blob as it is. It may be distinguished by tag field. + */ +typedef struct pgp_userid_pkt_t { + pgp_pkt_type_t tag; + uint8_t * uid; + size_t uid_len; + + pgp_userid_pkt_t() : tag(PGP_PKT_RESERVED), uid(NULL), uid_len(0){}; + pgp_userid_pkt_t(const pgp_userid_pkt_t &src); + pgp_userid_pkt_t(pgp_userid_pkt_t &&src); + pgp_userid_pkt_t &operator=(pgp_userid_pkt_t &&src); + pgp_userid_pkt_t &operator=(const pgp_userid_pkt_t &src); + bool operator==(const pgp_userid_pkt_t &src) const; + bool operator!=(const pgp_userid_pkt_t &src) const; + ~pgp_userid_pkt_t(); + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_userid_pkt_t; + +uint16_t read_uint16(const uint8_t *buf); + +uint32_t read_uint32(const uint8_t *buf); + +void write_uint16(uint8_t *buf, uint16_t val); + +/** @brief write new packet length + * @param buf pre-allocated buffer, must have 5 bytes + * @param len packet length + * @return number of bytes, saved in buf + **/ +size_t write_packet_len(uint8_t *buf, size_t len); + +/** @brief get packet type from the packet header byte + * @param ptag first byte of the packet header + * @return packet type or -1 if ptag is wrong + **/ +int get_packet_type(uint8_t ptag); + +/** @brief peek the packet type from the stream + * @param src source to peek from + * @return packet tag or -1 if read failed or packet header is malformed + */ +int stream_pkt_type(pgp_source_t &src); + +/** @brief Peek length of the packet header. Returns false on error. + * @param src source to read length from + * @param hdrlen header length will be put here on success. Cannot be NULL. + * @return true on success or false if there is a read error or packet length + * is ill-formed + **/ +bool stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen); + +bool stream_old_indeterminate_pkt_len(pgp_source_t *src); + +bool stream_partial_pkt_len(pgp_source_t *src); + +size_t get_partial_pkt_len(uint8_t blen); + +/** @brief Read packet length for fixed-size (say, small) packet. Returns false on error. + * Will also read packet tag byte. We do not allow partial length here as well as large + * packets (so ignoring possible size_t overflow) + * + * @param src source to read length from + * @param pktlen packet length will be stored here on success. Cannot be NULL. + * @return true on success or false if there is read error or packet length is ill-formed + **/ +bool stream_read_pkt_len(pgp_source_t *src, size_t *pktlen); + +/** @brief Read partial packet chunk length. + * + * @param src source to read length from + * @param clen chunk length will be stored here on success. Cannot be NULL. + * @param last will be set to true if chunk is last (i.e. has non-partial length) + * @return true on success or false if there is read error or packet length is ill-formed + **/ +bool stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last); + +/** @brief get and parse OpenPGP packet header to the structure. + * Note: this will not read but just peek required bytes. + * + * @param src source to read from + * @param hdr header structure + * @return RNP_SUCCESS or error code if operation failed + **/ +rnp_result_t stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr); + +/* Packet handling functions */ + +/** @brief read OpenPGP packet from the stream, and write its contents to another stream. + * @param src source with packet data + * @param dst destination to write packet contents. All write failures on dst + * will be ignored. Can be NULL if you need just to skip packet. + * @return RNP_SUCCESS or error code if operation failed. + */ +rnp_result_t stream_read_packet(pgp_source_t *src, pgp_dest_t *dst); + +rnp_result_t stream_skip_packet(pgp_source_t *src); + +rnp_result_t stream_parse_marker(pgp_source_t &src); + +/* Public/Private key or Subkey */ + +bool is_key_pkt(int tag); + +bool is_subkey_pkt(int tag); + +bool is_primary_key_pkt(int tag); + +bool is_public_key_pkt(int tag); + +bool is_secret_key_pkt(int tag); + +bool is_rsa_key_alg(pgp_pubkey_alg_t alg); + +#endif 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#endif +#ifdef HAVE_BZLIB_H +#include +#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 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 symencs; /* array of sym-encrypted session keys */ + std::vector 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 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 onepasses; /* list of one-pass singatures */ + std::list sigs; /* list of signatures */ + std::vector 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 that supports push_back. + */ +template +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 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 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(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 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 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 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(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; +} diff --git a/src/librepgp/stream-parse.h b/src/librepgp/stream-parse.h new file mode 100644 index 0000000..4f22b9a --- /dev/null +++ b/src/librepgp/stream-parse.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017, [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. + */ + +#ifndef STREAM_PARSE_H_ +#define STREAM_PARSE_H_ + +#include +#include +#include +#include "rnp.h" +#include "stream-common.h" +#include "stream-ctx.h" +#include "stream-packet.h" + +typedef struct pgp_parse_handler_t pgp_parse_handler_t; +typedef struct pgp_signature_info_t pgp_signature_info_t; +typedef bool pgp_destination_func_t(pgp_parse_handler_t *handler, + pgp_dest_t ** dst, + bool * closedst, + const char * filename, + uint32_t mtime); +typedef bool pgp_source_func_t(pgp_parse_handler_t *handler, pgp_source_t *src); +typedef void pgp_signatures_func_t(const std::vector &sigs, void *param); + +typedef void pgp_on_recipients_func_t(const std::vector &recipients, + const std::vector &passwords, + void * param); +typedef void pgp_decryption_start_func_t(pgp_pk_sesskey_t *pubenc, + pgp_sk_sesskey_t *symenc, + void * param); +typedef void pgp_decryption_info_func_t(bool mdc, + pgp_aead_alg_t aead, + pgp_symm_alg_t salg, + void * param); +typedef void pgp_decryption_done_func_t(bool validated, void *param); + +/* handler used to return needed information during pgp source processing */ +typedef struct pgp_parse_handler_t { + pgp_password_provider_t *password_provider; /* if NULL then default will be used */ + pgp_key_provider_t * key_provider; /* must be set when key is required, i.e. during + signing/verification/public key encryption and + deryption */ + pgp_destination_func_t *dest_provider; /* called when destination stream is required */ + pgp_source_func_t * src_provider; /* required to provider source during the detached + signature verification */ + pgp_on_recipients_func_t * on_recipients; /* called before decryption start */ + pgp_decryption_start_func_t *on_decryption_start; /* called when decryption key obtained */ + pgp_decryption_info_func_t * on_decryption_info; /* called when decryption is started */ + pgp_decryption_done_func_t * on_decryption_done; /* called when decryption is finished */ + pgp_signatures_func_t * on_signatures; /* for signature verification results */ + + rnp_ctx_t *ctx; /* operation context */ + void * param; /* additional parameters */ +} pgp_parse_handler_t; + +/* @brief Process the OpenPGP source: file, memory, stdin + * Function will parse input data, provided by any source conforming to pgp_source_t, + * autodetecting whether it is armored, cleartext or binary. + * @param handler handler to respond on stream reader callbacks + * @param src initialized source with cache + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src); + +/* @brief Init source with OpenPGP compressed data packet + * @param src allocated pgp_source_t structure + * @param readsrc source to read compressed data from + * @return RNP_SUCCESS on success or error code otherwise + */ +rnp_result_t init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc); + +/* @brief Get compression algorithm used in compressed source + * @param src compressed source, initialized with init_compressed_src + * @param alg algorithm will be written here. Cannot be NULL. + * @return true if operation succeeded and alg is populate or false otherwise + */ +bool get_compressed_src_alg(pgp_source_t *src, uint8_t *alg); + +/* @brief Init source with OpenPGP literal data packet + * @param src allocated pgp_source_t structure + * @param readsrc source to read literal data from + * @return RNP_SUCCESS on success or error code otherwise + */ +rnp_result_t init_literal_src(pgp_source_t *src, pgp_source_t *readsrc); + +/* @brief Get the literal data packet information fields (not the OpenPGP packet header) + * @param src literal data source, initialized with init_literal_src + * @param hdr pointer to header structure, where result will be stored + * @return true on success or false otherwise + */ +bool get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr); + +/* @brief Get the AEAD-encrypted packet information fields (not the OpenPGP packet header) + * @param src AEAD-encrypted data source (starting from packet data itself, not the header) + * @param hdr pointer to header structure, where result will be stored + * @return true on success or false otherwise + */ +bool get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr); + +#endif diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp new file mode 100644 index 0000000..6f3bc81 --- /dev/null +++ b/src/librepgp/stream-sig.cpp @@ -0,0 +1,1557 @@ +/* + * Copyright (c) 2018-2022, [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 +#ifdef HAVE_UNISTD_H +#include +#else +#include "uniwin.h" +#endif +#include +#include +#include +#include +#include "types.h" +#include "stream-sig.h" +#include "stream-packet.h" +#include "stream-armor.h" +#include "pgp-key.h" +#include "crypto/signatures.h" + +#include + +void +signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash) +{ + uint8_t hdr[3] = {0x99, 0x00, 0x00}; + if (key.hashed_data) { + write_uint16(hdr + 1, key.hashed_len); + hash.add(hdr, 3); + hash.add(key.hashed_data, key.hashed_len); + return; + } + + /* call self recursively if hashed data is not filled, to overcome const restriction */ + pgp_key_pkt_t keycp(key, true); + keycp.fill_hashed_data(); + signature_hash_key(keycp, hash); +} + +void +signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver) +{ + if (sigver < PGP_V4) { + hash.add(uid.uid, uid.uid_len); + return; + } + + uint8_t hdr[5] = {0}; + switch (uid.tag) { + case PGP_PKT_USER_ID: + hdr[0] = 0xB4; + break; + case PGP_PKT_USER_ATTR: + hdr[0] = 0xD1; + break; + default: + RNP_LOG("wrong uid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + STORE32BE(hdr + 1, uid.uid_len); + hash.add(hdr, 5); + hash.add(uid.uid, uid.uid_len); +} + +std::unique_ptr +signature_hash_certification(const pgp_signature_t & sig, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &userid) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + signature_hash_userid(userid, *hash, sig.version); + return hash; +} + +std::unique_ptr +signature_hash_binding(const pgp_signature_t &sig, + const pgp_key_pkt_t & key, + const pgp_key_pkt_t & subkey) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + signature_hash_key(subkey, *hash); + return hash; +} + +std::unique_ptr +signature_hash_direct(const pgp_signature_t &sig, const pgp_key_pkt_t &key) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + return hash; +} + +rnp_result_t +process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs) +{ + sigs.clear(); + /* Allow binary or armored input, including multiple armored messages */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + /* read sequence of OpenPGP signatures */ + while (!armor.error()) { + if (armor.eof() && armor.multiple()) { + armor.restart(); + } + if (armor.eof()) { + break; + } + int ptag = stream_pkt_type(armor.src()); + if (ptag != PGP_PKT_SIGNATURE) { + RNP_LOG("wrong signature tag: %d", ptag); + sigs.clear(); + return RNP_ERROR_BAD_FORMAT; + } + + sigs.emplace_back(); + rnp_result_t ret = sigs.back().parse(armor.src()); + if (ret) { + sigs.clear(); + return ret; + } + } + if (armor.error()) { + sigs.clear(); + return RNP_ERROR_READ; + } + return RNP_SUCCESS; +} + +pgp_sig_subpkt_t::pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src) +{ + type = src.type; + len = src.len; + data = (uint8_t *) malloc(len); + if (!data) { + throw std::bad_alloc(); + } + memcpy(data, src.data, len); + critical = src.critical; + hashed = src.hashed; + parsed = false; + parse(); +} + +pgp_sig_subpkt_t::pgp_sig_subpkt_t(pgp_sig_subpkt_t &&src) +{ + type = src.type; + len = src.len; + data = src.data; + src.data = NULL; + critical = src.critical; + hashed = src.hashed; + parsed = src.parsed; + memcpy(&fields, &src.fields, sizeof(fields)); + src.fields = {}; +} + +pgp_sig_subpkt_t & +pgp_sig_subpkt_t::operator=(pgp_sig_subpkt_t &&src) +{ + if (&src == this) { + return *this; + } + + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + type = src.type; + len = src.len; + free(data); + data = src.data; + src.data = NULL; + critical = src.critical; + hashed = src.hashed; + parsed = src.parsed; + fields = src.fields; + src.fields = {}; + return *this; +} + +pgp_sig_subpkt_t & +pgp_sig_subpkt_t::operator=(const pgp_sig_subpkt_t &src) +{ + if (&src == this) { + return *this; + } + + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + type = src.type; + len = src.len; + free(data); + data = (uint8_t *) malloc(len); + if (!data) { + throw std::bad_alloc(); + } + memcpy(data, src.data, len); + critical = src.critical; + hashed = src.hashed; + parsed = false; + fields = {}; + parse(); + return *this; +} + +bool +pgp_sig_subpkt_t::parse() +{ + bool oklen = true; + bool checked = true; + + switch (type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + if (!hashed) { + RNP_LOG("creation time subpacket must be hashed"); + checked = false; + } + if ((oklen = len == 4)) { + fields.create = read_uint32(data); + } + break; + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + case PGP_SIG_SUBPKT_KEY_EXPIRY: + if ((oklen = len == 4)) { + fields.expiry = read_uint32(data); + } + break; + case PGP_SIG_SUBPKT_EXPORT_CERT: + if ((oklen = len == 1)) { + fields.exportable = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_TRUST: + if ((oklen = len == 2)) { + fields.trust.level = data[0]; + fields.trust.amount = data[1]; + } + break; + case PGP_SIG_SUBPKT_REGEXP: + fields.regexp.str = (const char *) data; + fields.regexp.len = len; + break; + case PGP_SIG_SUBPKT_REVOCABLE: + if ((oklen = len == 1)) { + fields.revocable = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_PREFERRED_SKA: + case PGP_SIG_SUBPKT_PREFERRED_HASH: + case PGP_SIG_SUBPKT_PREF_COMPRESS: + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + fields.preferred.arr = data; + fields.preferred.len = len; + break; + case PGP_SIG_SUBPKT_REVOCATION_KEY: + if ((oklen = len == 22)) { + fields.revocation_key.klass = data[0]; + fields.revocation_key.pkalg = (pgp_pubkey_alg_t) data[1]; + fields.revocation_key.fp = &data[2]; + } + break; + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + if ((oklen = len == 8)) { + fields.issuer = data; + } + break; + case PGP_SIG_SUBPKT_NOTATION_DATA: + if ((oklen = len >= 8)) { + memcpy(fields.notation.flags, data, 4); + fields.notation.human = fields.notation.flags[0] & 0x80; + fields.notation.nlen = read_uint16(&data[4]); + fields.notation.vlen = read_uint16(&data[6]); + if (len != 8 + fields.notation.nlen + fields.notation.vlen) { + oklen = false; + } else { + fields.notation.name = data + 8; + fields.notation.value = fields.notation.name + fields.notation.nlen; + } + } + break; + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + if ((oklen = len >= 1)) { + fields.ks_prefs.no_modify = (data[0] & 0x80) != 0; + } + break; + case PGP_SIG_SUBPKT_PREF_KEYSERV: + fields.preferred_ks.uri = (const char *) data; + fields.preferred_ks.len = len; + break; + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + if ((oklen = len == 1)) { + fields.primary_uid = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_POLICY_URI: + fields.policy.uri = (const char *) data; + fields.policy.len = len; + break; + case PGP_SIG_SUBPKT_KEY_FLAGS: + if ((oklen = len >= 1)) { + fields.key_flags = data[0]; + } + break; + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + fields.signer.uid = (const char *) data; + fields.signer.len = len; + break; + case PGP_SIG_SUBPKT_REVOCATION_REASON: + if ((oklen = len >= 1)) { + fields.revocation_reason.code = (pgp_revocation_type_t) data[0]; + fields.revocation_reason.str = (const char *) &data[1]; + fields.revocation_reason.len = len - 1; + } + break; + case PGP_SIG_SUBPKT_FEATURES: + if ((oklen = len >= 1)) { + fields.features = data[0]; + } + break; + case PGP_SIG_SUBPKT_SIGNATURE_TARGET: + if ((oklen = len >= 18)) { + fields.sig_target.pkalg = (pgp_pubkey_alg_t) data[0]; + fields.sig_target.halg = (pgp_hash_alg_t) data[1]; + fields.sig_target.hash = &data[2]; + fields.sig_target.hlen = len - 2; + } + break; + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: + try { + /* parse signature */ + pgp_packet_body_t pkt(data, len); + pgp_signature_t sig; + oklen = checked = !sig.parse(pkt); + if (checked) { + fields.sig = new pgp_signature_t(std::move(sig)); + } + break; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + case PGP_SIG_SUBPKT_ISSUER_FPR: + if ((oklen = len >= 21)) { + fields.issuer_fp.version = data[0]; + fields.issuer_fp.fp = &data[1]; + fields.issuer_fp.len = len - 1; + } + break; + case PGP_SIG_SUBPKT_PRIVATE_100: + case PGP_SIG_SUBPKT_PRIVATE_101: + case PGP_SIG_SUBPKT_PRIVATE_102: + case PGP_SIG_SUBPKT_PRIVATE_103: + case PGP_SIG_SUBPKT_PRIVATE_104: + case PGP_SIG_SUBPKT_PRIVATE_105: + case PGP_SIG_SUBPKT_PRIVATE_106: + case PGP_SIG_SUBPKT_PRIVATE_107: + case PGP_SIG_SUBPKT_PRIVATE_108: + case PGP_SIG_SUBPKT_PRIVATE_109: + case PGP_SIG_SUBPKT_PRIVATE_110: + oklen = true; + checked = !critical; + if (!checked) { + RNP_LOG("unknown critical private subpacket %d", (int) type); + } + break; + case PGP_SIG_SUBPKT_RESERVED_1: + case PGP_SIG_SUBPKT_RESERVED_8: + case PGP_SIG_SUBPKT_PLACEHOLDER: + case PGP_SIG_SUBPKT_RESERVED_13: + case PGP_SIG_SUBPKT_RESERVED_14: + case PGP_SIG_SUBPKT_RESERVED_15: + case PGP_SIG_SUBPKT_RESERVED_17: + case PGP_SIG_SUBPKT_RESERVED_18: + case PGP_SIG_SUBPKT_RESERVED_19: + /* do not report reserved/placeholder subpacket */ + return !critical; + default: + RNP_LOG("unknown subpacket : %d", (int) type); + return !critical; + } + + if (!oklen) { + RNP_LOG("wrong len %d of subpacket type %d", (int) len, (int) type); + } else { + parsed = 1; + } + return oklen && checked; +} + +pgp_sig_subpkt_t::~pgp_sig_subpkt_t() +{ + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + free(data); +} + +pgp_signature_t::pgp_signature_t(const pgp_signature_t &src) +{ + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + + hashed_len = src.hashed_len; + hashed_data = NULL; + if (src.hashed_data) { + if (!(hashed_data = (uint8_t *) malloc(hashed_len))) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material_len = src.material_len; + material_buf = NULL; + if (src.material_buf) { + if (!(material_buf = (uint8_t *) malloc(material_len))) { + throw std::bad_alloc(); + } + memcpy(material_buf, src.material_buf, material_len); + } + subpkts = src.subpkts; +} + +pgp_signature_t::pgp_signature_t(pgp_signature_t &&src) +{ + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + hashed_len = src.hashed_len; + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material_len = src.material_len; + material_buf = src.material_buf; + src.material_buf = NULL; + subpkts = std::move(src.subpkts); +} + +pgp_signature_t & +pgp_signature_t::operator=(pgp_signature_t &&src) +{ + if (this == &src) { + return *this; + } + + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material_len = src.material_len; + free(material_buf); + material_buf = src.material_buf; + src.material_buf = NULL; + subpkts = std::move(src.subpkts); + + return *this; +} + +pgp_signature_t & +pgp_signature_t::operator=(const pgp_signature_t &src) +{ + if (this == &src) { + return *this; + } + + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = NULL; + if (src.hashed_data) { + if (!(hashed_data = (uint8_t *) malloc(hashed_len))) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material_len = src.material_len; + free(material_buf); + material_buf = NULL; + if (src.material_buf) { + if (!(material_buf = (uint8_t *) malloc(material_len))) { + throw std::bad_alloc(); + } + memcpy(material_buf, src.material_buf, material_len); + } + subpkts = src.subpkts; + + return *this; +} + +bool +pgp_signature_t::operator==(const pgp_signature_t &src) const +{ + if ((lbits[0] != src.lbits[0]) || (lbits[1] != src.lbits[1])) { + return false; + } + if ((hashed_len != src.hashed_len) || memcmp(hashed_data, src.hashed_data, hashed_len)) { + return false; + } + return (material_len == src.material_len) && + !memcmp(material_buf, src.material_buf, material_len); +} + +bool +pgp_signature_t::operator!=(const pgp_signature_t &src) const +{ + return !(*this == src); +} + +pgp_signature_t::~pgp_signature_t() +{ + free(hashed_data); + free(material_buf); +} + +pgp_sig_id_t +pgp_signature_t::get_id() const +{ + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(hashed_data, hashed_len); + hash->add(material_buf, material_len); + pgp_sig_id_t res = {0}; + static_assert(std::tuple_size::value == PGP_SHA1_HASH_SIZE, + "pgp_sig_id_t size mismatch"); + hash->finish(res.data()); + return res; +} + +pgp_sig_subpkt_t * +pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) +{ + if (version < PGP_V4) { + return NULL; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return &subpkt; + } + } + return NULL; +} + +const pgp_sig_subpkt_t * +pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const +{ + if (version < PGP_V4) { + return NULL; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return &subpkt; + } + } + return NULL; +} + +bool +pgp_signature_t::has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const +{ + if (version < PGP_V4) { + return false; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return true; + } + } + return false; +} + +bool +pgp_signature_t::has_keyid() const +{ + return (version < PGP_V4) || has_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false) || + has_keyfp(); +} + +pgp_key_id_t +pgp_signature_t::keyid() const noexcept +{ + /* version 3 uses signature field */ + if (version < PGP_V4) { + return signer; + } + + /* version 4 and up use subpackets */ + pgp_key_id_t res{}; + static_assert(std::tuple_size::value == PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false); + if (subpkt) { + memcpy(res.data(), subpkt->fields.issuer, PGP_KEY_ID_SIZE); + return res; + } + if ((subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR))) { + memcpy(res.data(), + subpkt->fields.issuer_fp.fp + subpkt->fields.issuer_fp.len - PGP_KEY_ID_SIZE, + PGP_KEY_ID_SIZE); + return res; + } + return res; +} + +void +pgp_signature_t::set_keyid(const pgp_key_id_t &id) +{ + if (version < PGP_V4) { + signer = id; + return; + } + + static_assert(std::tuple_size::type>::value == + PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, PGP_KEY_ID_SIZE, true); + subpkt.parsed = true; + subpkt.hashed = false; + memcpy(subpkt.data, id.data(), PGP_KEY_ID_SIZE); + subpkt.fields.issuer = subpkt.data; +} + +bool +pgp_signature_t::has_keyfp() const +{ + if (version < PGP_V4) { + return false; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); + return subpkt && (subpkt->fields.issuer_fp.len <= PGP_FINGERPRINT_SIZE); +} + +pgp_fingerprint_t +pgp_signature_t::keyfp() const noexcept +{ + pgp_fingerprint_t res{}; + if (version < PGP_V4) { + return res; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); + if (!subpkt || (subpkt->fields.issuer_fp.len > sizeof(res.fingerprint))) { + return res; + } + res.length = subpkt->fields.issuer_fp.len; + memcpy(res.fingerprint, subpkt->fields.issuer_fp.fp, subpkt->fields.issuer_fp.len); + return res; +} + +void +pgp_signature_t::set_keyfp(const pgp_fingerprint_t &fp) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR, 1 + fp.length, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = 4; + memcpy(subpkt.data + 1, fp.fingerprint, fp.length); + subpkt.fields.issuer_fp.len = fp.length; + subpkt.fields.issuer_fp.version = subpkt.data[0]; + subpkt.fields.issuer_fp.fp = subpkt.data + 1; +} + +uint32_t +pgp_signature_t::creation() const +{ + if (version < PGP_V4) { + return creation_time; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME); + return subpkt ? subpkt->fields.create : 0; +} + +void +pgp_signature_t::set_creation(uint32_t ctime) +{ + if (version < PGP_V4) { + creation_time = ctime; + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_CREATION_TIME, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, ctime); + subpkt.fields.create = ctime; +} + +uint32_t +pgp_signature_t::expiration() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME); + return subpkt ? subpkt->fields.expiry : 0; +} + +void +pgp_signature_t::set_expiration(uint32_t etime) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, etime); + subpkt.fields.expiry = etime; +} + +uint32_t +pgp_signature_t::key_expiration() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY); + return subpkt ? subpkt->fields.expiry : 0; +} + +void +pgp_signature_t::set_key_expiration(uint32_t etime) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, etime); + subpkt.fields.expiry = etime; +} + +uint8_t +pgp_signature_t::key_flags() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS); + return subpkt ? subpkt->fields.key_flags : 0; +} + +void +pgp_signature_t::set_key_flags(uint8_t flags) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = flags; + subpkt.fields.key_flags = flags; +} + +bool +pgp_signature_t::primary_uid() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID); + return subpkt ? subpkt->fields.primary_uid : false; +} + +void +pgp_signature_t::set_primary_uid(bool primary) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = primary; + subpkt.fields.primary_uid = primary; +} + +std::vector +pgp_signature_t::preferred(pgp_sig_subpacket_type_t type) const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(type); + return subpkt ? std::vector(subpkt->fields.preferred.arr, + subpkt->fields.preferred.arr + + subpkt->fields.preferred.len) : + std::vector(); +} + +void +pgp_signature_t::set_preferred(const std::vector &data, pgp_sig_subpacket_type_t type) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + if (data.empty()) { + pgp_sig_subpkt_t *subpkt = get_subpkt(type); + if (subpkt) { + remove_subpkt(subpkt); + } + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(type, data.size(), true); + subpkt.parsed = true; + subpkt.hashed = true; + memcpy(subpkt.data, data.data(), data.size()); + subpkt.fields.preferred.arr = subpkt.data; + subpkt.fields.preferred.len = data.size(); +} + +std::vector +pgp_signature_t::preferred_symm_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREFERRED_SKA); +} + +void +pgp_signature_t::set_preferred_symm_algs(const std::vector &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_SKA); +} + +std::vector +pgp_signature_t::preferred_hash_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREFERRED_HASH); +} + +void +pgp_signature_t::set_preferred_hash_algs(const std::vector &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_HASH); +} + +std::vector +pgp_signature_t::preferred_z_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREF_COMPRESS); +} + +void +pgp_signature_t::set_preferred_z_algs(const std::vector &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREF_COMPRESS); +} + +uint8_t +pgp_signature_t::key_server_prefs() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS); + return subpkt ? subpkt->data[0] : 0; +} + +void +pgp_signature_t::set_key_server_prefs(uint8_t prefs) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = prefs; + subpkt.fields.ks_prefs.no_modify = prefs & 0x80; +} + +std::string +pgp_signature_t::key_server() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV); + return subpkt ? std::string((char *) subpkt->data, subpkt->len) : ""; +} + +void +pgp_signature_t::set_key_server(const std::string &uri) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + if (uri.empty()) { + pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV); + if (subpkt) { + remove_subpkt(subpkt); + } + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV, uri.size(), true); + subpkt.parsed = true; + subpkt.hashed = true; + memcpy(subpkt.data, uri.data(), uri.size()); + subpkt.fields.preferred_ks.uri = (char *) subpkt.data; + subpkt.fields.preferred_ks.len = uri.size(); +} + +uint8_t +pgp_signature_t::trust_level() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST); + return subpkt ? subpkt->fields.trust.level : 0; +} + +uint8_t +pgp_signature_t::trust_amount() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST); + return subpkt ? subpkt->fields.trust.amount : 0; +} + +void +pgp_signature_t::set_trust(uint8_t level, uint8_t amount) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_TRUST, 2, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = level; + subpkt.data[1] = amount; + subpkt.fields.trust.level = level; + subpkt.fields.trust.amount = amount; +} + +bool +pgp_signature_t::revocable() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCABLE); + return subpkt ? subpkt->fields.revocable : true; +} + +void +pgp_signature_t::set_revocable(bool status) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCABLE, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = status; + subpkt.fields.revocable = status; +} + +std::string +pgp_signature_t::revocation_reason() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON); + return subpkt ? std::string(subpkt->fields.revocation_reason.str, + subpkt->fields.revocation_reason.len) : + ""; +} + +pgp_revocation_type_t +pgp_signature_t::revocation_code() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON); + return subpkt ? subpkt->fields.revocation_reason.code : PGP_REVOCATION_NO_REASON; +} + +void +pgp_signature_t::set_revocation_reason(pgp_revocation_type_t code, const std::string &reason) +{ + size_t datalen = 1 + reason.size(); + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON, datalen, true); + subpkt.hashed = true; + subpkt.data[0] = code; + memcpy(subpkt.data + 1, reason.data(), reason.size()); + + if (!subpkt.parse()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +bool +pgp_signature_t::key_has_features(pgp_key_feature_t flags) const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_FEATURES); + return subpkt ? subpkt->data[0] & flags : false; +} + +void +pgp_signature_t::set_key_features(pgp_key_feature_t flags) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_FEATURES, 1, true); + subpkt.hashed = true; + subpkt.data[0] = flags; + subpkt.fields.features = flags; + subpkt.parsed = true; +} + +std::string +pgp_signature_t::signer_uid() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID); + return subpkt ? std::string(subpkt->fields.signer.uid, subpkt->fields.signer.len) : ""; +} + +void +pgp_signature_t::set_signer_uid(const std::string &uid) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID, uid.size(), true); + subpkt.hashed = true; + memcpy(subpkt.data, uid.data(), uid.size()); + subpkt.fields.signer.uid = (const char *) subpkt.data; + subpkt.fields.signer.len = subpkt.len; + subpkt.parsed = true; +} + +void +pgp_signature_t::add_notation(const std::string & name, + const std::vector &value, + bool human, + bool critical) +{ + auto nlen = name.size(); + auto vlen = value.size(); + if ((nlen > 0xffff) || (vlen > 0xffff)) { + RNP_LOG("wrong length"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + auto &subpkt = add_subpkt(PGP_SIG_SUBPKT_NOTATION_DATA, 8 + nlen + vlen, false); + subpkt.hashed = true; + subpkt.critical = critical; + if (human) { + subpkt.data[0] = 0x80; + } + write_uint16(subpkt.data + 4, nlen); + write_uint16(subpkt.data + 6, vlen); + memcpy(subpkt.data + 8, name.data(), nlen); + memcpy(subpkt.data + 8 + nlen, value.data(), vlen); + if (!subpkt.parse()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +void +pgp_signature_t::add_notation(const std::string &name, const std::string &value, bool critical) +{ + add_notation(name, std::vector(value.begin(), value.end()), true, critical); +} + +void +pgp_signature_t::set_embedded_sig(const pgp_signature_t &esig) +{ + pgp_rawpacket_t esigpkt(esig); + rnp::MemorySource mem(esigpkt.raw); + size_t len = 0; + stream_read_pkt_len(&mem.src(), &len); + if (!len || (len > 0xffff) || (len >= esigpkt.raw.size())) { + RNP_LOG("wrong pkt len"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, len, true); + subpkt.hashed = false; + size_t skip = esigpkt.raw.size() - len; + memcpy(subpkt.data, esigpkt.raw.data() + skip, len); + subpkt.fields.sig = new pgp_signature_t(esig); + subpkt.parsed = true; +} + +pgp_sig_subpkt_t & +pgp_signature_t::add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse) +{ + if (version < PGP_V4) { + RNP_LOG("wrong signature version"); + throw std::invalid_argument("version"); + } + + uint8_t *newdata = (uint8_t *) calloc(1, datalen); + if (!newdata) { + RNP_LOG("Allocation failed"); + throw std::bad_alloc(); + } + + pgp_sig_subpkt_t *subpkt = NULL; + if (reuse && (subpkt = get_subpkt(type))) { + *subpkt = {}; + } else { + subpkts.push_back({}); + subpkt = &subpkts.back(); + } + + subpkt->data = newdata; + subpkt->type = type; + subpkt->len = datalen; + return *subpkt; +} + +void +pgp_signature_t::remove_subpkt(pgp_sig_subpkt_t *subpkt) +{ + for (auto it = subpkts.begin(); it < subpkts.end(); it++) { + if (&*it == subpkt) { + subpkts.erase(it); + return; + } + } +} + +bool +pgp_signature_t::matches_onepass(const pgp_one_pass_sig_t &onepass) const +{ + if (!has_keyid()) { + return false; + } + return (halg == onepass.halg) && (palg == onepass.palg) && (type_ == onepass.type) && + (onepass.keyid == keyid()); +} + +rnp_result_t +pgp_signature_t::parse_v3(pgp_packet_body_t &pkt) +{ + /* parse v3-specific fields, not the whole signature */ + uint8_t buf[16] = {}; + if (!pkt.get(buf, 16)) { + RNP_LOG("cannot get enough bytes"); + return RNP_ERROR_BAD_FORMAT; + } + /* length of hashed data, 5 */ + if (buf[0] != 5) { + RNP_LOG("wrong length of hashed data"); + return RNP_ERROR_BAD_FORMAT; + } + /* hashed data */ + free(hashed_data); + if (!(hashed_data = (uint8_t *) malloc(5))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(hashed_data, &buf[1], 5); + hashed_len = 5; + /* signature type */ + type_ = (pgp_sig_type_t) buf[1]; + /* creation time */ + creation_time = read_uint32(&buf[2]); + /* signer's key id */ + static_assert(std::tuple_size::value == PGP_KEY_ID_SIZE, + "v3 signer field size mismatch"); + memcpy(signer.data(), &buf[6], PGP_KEY_ID_SIZE); + /* public key algorithm */ + palg = (pgp_pubkey_alg_t) buf[14]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[15]; + return RNP_SUCCESS; +} + +#define MAX_SUBPACKETS 64 + +bool +pgp_signature_t::parse_subpackets(uint8_t *buf, size_t len, bool hashed) +{ + bool res = true; + + while (len > 0) { + if (subpkts.size() >= MAX_SUBPACKETS) { + RNP_LOG("too many signature subpackets"); + return false; + } + if (len < 2) { + RNP_LOG("got single byte %d", (int) *buf); + return false; + } + + /* subpacket length */ + size_t splen; + if (*buf < 192) { + splen = *buf; + buf++; + len--; + } else if (*buf < 255) { + splen = ((buf[0] - 192) << 8) + buf[1] + 192; + buf += 2; + len -= 2; + } else { + if (len < 5) { + RNP_LOG("got 4-byte len but only %d bytes in buffer", (int) len); + return false; + } + splen = read_uint32(&buf[1]); + buf += 5; + len -= 5; + } + + if (splen < 1) { + RNP_LOG("got subpacket with 0 length"); + return false; + } + + /* subpacket data */ + if (len < splen) { + RNP_LOG("got subpacket len %d, while only %d bytes left", (int) splen, (int) len); + return false; + } + + pgp_sig_subpkt_t subpkt; + if (!(subpkt.data = (uint8_t *) malloc(splen - 1))) { + RNP_LOG("subpacket data allocation failed"); + return false; + } + + subpkt.type = (pgp_sig_subpacket_type_t)(*buf & 0x7f); + subpkt.critical = !!(*buf & 0x80); + subpkt.hashed = hashed; + subpkt.parsed = 0; + memcpy(subpkt.data, buf + 1, splen - 1); + subpkt.len = splen - 1; + + res = res && subpkt.parse(); + subpkts.push_back(std::move(subpkt)); + len -= splen; + buf += splen; + } + return res; +} + +rnp_result_t +pgp_signature_t::parse_v4(pgp_packet_body_t &pkt) +{ + /* parse v4-specific fields, not the whole signature */ + uint8_t buf[5]; + if (!pkt.get(buf, 5)) { + RNP_LOG("cannot get first 5 bytes"); + return RNP_ERROR_BAD_FORMAT; + } + + /* signature type */ + type_ = (pgp_sig_type_t) buf[0]; + /* public key algorithm */ + palg = (pgp_pubkey_alg_t) buf[1]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[2]; + /* hashed subpackets length */ + uint16_t splen = read_uint16(&buf[3]); + /* hashed subpackets length + 2 bytes of length of unhashed subpackets */ + if (pkt.left() < (size_t)(splen + 2)) { + RNP_LOG("wrong packet or hashed subpackets length"); + return RNP_ERROR_BAD_FORMAT; + } + /* building hashed data */ + free(hashed_data); + if (!(hashed_data = (uint8_t *) malloc(splen + 6))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + hashed_data[0] = version; + memcpy(hashed_data + 1, buf, 5); + + if (!pkt.get(hashed_data + 6, splen)) { + RNP_LOG("cannot get hashed subpackets data"); + return RNP_ERROR_BAD_FORMAT; + } + hashed_len = splen + 6; + /* parsing hashed subpackets */ + if (!parse_subpackets(hashed_data + 6, splen, true)) { + RNP_LOG("failed to parse hashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + /* reading unhashed subpackets */ + if (!pkt.get(splen)) { + RNP_LOG("cannot get unhashed len"); + return RNP_ERROR_BAD_FORMAT; + } + if (pkt.left() < splen) { + RNP_LOG("not enough data for unhashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + std::vector spbuf(splen); + if (!pkt.get(spbuf.data(), splen)) { + RNP_LOG("read of unhashed subpackets failed"); + return RNP_ERROR_READ; + } + if (!parse_subpackets(spbuf.data(), splen, false)) { + RNP_LOG("failed to parse unhashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +rnp_result_t +pgp_signature_t::parse(pgp_packet_body_t &pkt) +{ + uint8_t ver = 0; + if (!pkt.get(ver)) { + return RNP_ERROR_BAD_FORMAT; + } + version = (pgp_version_t) ver; + + /* v3 or v4 signature body */ + rnp_result_t res; + if ((ver == PGP_V2) || (ver == PGP_V3)) { + res = parse_v3(pkt); + } else if (ver == PGP_V4) { + res = parse_v4(pkt); + } else { + RNP_LOG("unknown signature version: %d", (int) ver); + res = RNP_ERROR_BAD_FORMAT; + } + + if (res) { + return res; + } + + /* left 16 bits of the hash */ + if (!pkt.get(lbits, 2)) { + RNP_LOG("not enough data for hash left bits"); + return RNP_ERROR_BAD_FORMAT; + } + /* raw signature material */ + material_len = pkt.left(); + if (!material_len) { + RNP_LOG("No signature material"); + return RNP_ERROR_BAD_FORMAT; + } + material_buf = (uint8_t *) malloc(material_len); + if (!material_buf) { + RNP_LOG("Allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + /* we cannot fail here */ + pkt.get(material_buf, material_len); + /* check whether it can be parsed */ + pgp_signature_material_t material = {}; + if (!parse_material(material)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +rnp_result_t +pgp_signature_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_SIGNATURE); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + return parse(pkt); +} + +bool +pgp_signature_t::parse_material(pgp_signature_material_t &material) const +{ + pgp_packet_body_t pkt(material_buf, material_len); + + switch (palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + if (!pkt.get(material.rsa.s)) { + return false; + } + break; + case PGP_PKA_DSA: + if (!pkt.get(material.dsa.r) || !pkt.get(material.dsa.s)) { + return false; + } + break; + case PGP_PKA_EDDSA: + if (version < PGP_V4) { + RNP_LOG("Warning! v3 EdDSA signature."); + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!pkt.get(material.ecc.r) || !pkt.get(material.ecc.s)) { + return false; + } + break; + case PGP_PKA_ELGAMAL: /* we support reading it but will not validate */ + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!pkt.get(material.eg.r) || !pkt.get(material.eg.s)) { + return false; + } + break; + default: + RNP_LOG("Unknown pk algorithm : %d", (int) palg); + return false; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in signature packet", (int) pkt.left()); + return false; + } + return true; +} + +void +pgp_signature_t::write(pgp_dest_t &dst) const +{ + if ((version < PGP_V2) || (version > PGP_V4)) { + RNP_LOG("don't know version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE); + + if (version < PGP_V4) { + /* for v3 signatures hashed data includes only type + creation_time */ + pktbody.add_byte(version); + pktbody.add_byte(hashed_len); + pktbody.add(hashed_data, hashed_len); + pktbody.add(signer); + pktbody.add_byte(palg); + pktbody.add_byte(halg); + } else { + /* for v4 sig->hashed_data must contain most of signature fields */ + pktbody.add(hashed_data, hashed_len); + pktbody.add_subpackets(*this, false); + } + pktbody.add(lbits, 2); + /* write mpis */ + pktbody.add(material_buf, material_len); + pktbody.write(dst); +} + +void +pgp_signature_t::write_material(const pgp_signature_material_t &material) +{ + pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE); + switch (palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + pktbody.add(material.rsa.s); + break; + case PGP_PKA_DSA: + pktbody.add(material.dsa.r); + pktbody.add(material.dsa.s); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + pktbody.add(material.ecc.r); + pktbody.add(material.ecc.s); + break; + case PGP_PKA_ELGAMAL: /* we support writing it but will not generate */ + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + pktbody.add(material.eg.r); + pktbody.add(material.eg.s); + break; + default: + RNP_LOG("Unknown pk algorithm : %d", (int) palg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + free(material_buf); + material_buf = (uint8_t *) malloc(pktbody.size()); + if (!material_buf) { + RNP_LOG("allocation failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(material_buf, pktbody.data(), pktbody.size()); + material_len = pktbody.size(); +} + +void +pgp_signature_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + if ((version < PGP_V2) || (version > PGP_V4)) { + RNP_LOG("don't know version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + if (version < PGP_V4) { + hbody.add_byte(type()); + hbody.add_uint32(creation_time); + } else { + hbody.add_byte(version); + hbody.add_byte(type()); + hbody.add_byte(palg); + hbody.add_byte(halg); + hbody.add_subpackets(*this, true); + } + + free(hashed_data); + hashed_data = (uint8_t *) malloc(hbody.size()); + if (!hashed_data) { + RNP_LOG("allocation failed"); + throw std::bad_alloc(); + } + memcpy(hashed_data, hbody.data(), hbody.size()); + hashed_len = hbody.size(); +} + +void +rnp_selfsig_cert_info_t::populate(pgp_userid_pkt_t &uid, pgp_signature_t &sig) +{ + /* populate signature */ + sig.set_type(PGP_CERT_POSITIVE); + if (key_expiration) { + sig.set_key_expiration(key_expiration); + } + if (key_flags) { + sig.set_key_flags(key_flags); + } + if (primary) { + sig.set_primary_uid(true); + } + if (!prefs.symm_algs.empty()) { + sig.set_preferred_symm_algs(prefs.symm_algs); + } + if (!prefs.hash_algs.empty()) { + sig.set_preferred_hash_algs(prefs.hash_algs); + } + if (!prefs.z_algs.empty()) { + sig.set_preferred_z_algs(prefs.z_algs); + } + if (!prefs.ks_prefs.empty()) { + sig.set_key_server_prefs(prefs.ks_prefs[0]); + } + if (!prefs.key_server.empty()) { + sig.set_key_server(prefs.key_server); + } + /* populate uid */ + uid.tag = PGP_PKT_USER_ID; + uid.uid_len = userid.size(); + if (!(uid.uid = (uint8_t *) malloc(uid.uid_len))) { + RNP_LOG("alloc failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(uid.uid, userid.data(), uid.uid_len); +} diff --git a/src/librepgp/stream-sig.h b/src/librepgp/stream-sig.h new file mode 100644 index 0000000..4f36c38 --- /dev/null +++ b/src/librepgp/stream-sig.h @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2018-2022, [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. + */ + +#ifndef STREAM_SIG_H_ +#define STREAM_SIG_H_ + +#include +#include +#include +#include "rnp.h" +#include "stream-common.h" +#include "stream-packet.h" + +typedef struct pgp_signature_t { + private: + pgp_sig_type_t type_; + std::vector preferred(pgp_sig_subpacket_type_t type) const; + void set_preferred(const std::vector &data, pgp_sig_subpacket_type_t type); + rnp_result_t parse_v3(pgp_packet_body_t &pkt); + rnp_result_t parse_v4(pgp_packet_body_t &pkt); + bool parse_subpackets(uint8_t *buf, size_t len, bool hashed); + + public: + pgp_version_t version; + /* common v3 and v4 fields */ + pgp_pubkey_alg_t palg; + pgp_hash_alg_t halg; + uint8_t lbits[2]; + uint8_t * hashed_data; + size_t hashed_len; + uint8_t * material_buf; /* raw signature material */ + size_t material_len; /* raw signature material length */ + + /* v3 - only fields */ + uint32_t creation_time; + pgp_key_id_t signer; + + /* v4 - only fields */ + std::vector subpkts; + + pgp_signature_t() + : type_(PGP_SIG_BINARY), version(PGP_VUNKNOWN), palg(PGP_PKA_NOTHING), + halg(PGP_HASH_UNKNOWN), hashed_data(NULL), hashed_len(0), material_buf(NULL), + material_len(0), creation_time(0){}; + pgp_signature_t(const pgp_signature_t &src); + pgp_signature_t(pgp_signature_t &&src); + pgp_signature_t &operator=(pgp_signature_t &&src); + pgp_signature_t &operator=(const pgp_signature_t &src); + bool operator==(const pgp_signature_t &src) const; + bool operator!=(const pgp_signature_t &src) const; + ~pgp_signature_t(); + + /* @brief Get signature's type */ + pgp_sig_type_t + type() const + { + return type_; + }; + void + set_type(pgp_sig_type_t atype) + { + type_ = atype; + }; + + bool + is_document() const + { + return (type_ == PGP_SIG_BINARY) || (type_ == PGP_SIG_TEXT); + }; + + /** @brief Calculate the unique signature identifier by hashing signature's fields. */ + pgp_sig_id_t get_id() const; + + /** + * @brief Get v4 signature's subpacket of the specified type and hashedness. + * @param stype subpacket type. + * @param hashed If true (default), then will search for subpacket only in hashed (i.e. + * covered by signature) area, otherwise will search in both hashed and non-hashed areas. + * @return pointer to the subpacket, or NULL if subpacket was not found. + */ + pgp_sig_subpkt_t * get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true); + const pgp_sig_subpkt_t *get_subpkt(pgp_sig_subpacket_type_t stype, + bool hashed = true) const; + /* @brief Check whether v4 signature has subpacket of the specified type/hashedness */ + bool has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true) const; + /* @brief Check whether signature has signing key id (via v3 field, or v4 key id/key fp + * subpacket) */ + bool has_keyid() const; + /** + * @brief Get signer's key id if available. Availability may be checked via has_keyid(). + * @return signer's key id if available, or empty (zero-filled) keyid otherwise. + */ + pgp_key_id_t keyid() const noexcept; + /** @brief Set the signer's key id for the signature being populated. Version should be set + * prior of setting key id. */ + void set_keyid(const pgp_key_id_t &id); + /** + * @brief Check whether signature has valid issuer fingerprint subpacket. + * @return true if there is one, and it can be safely returned via keyfp() method or false + * otherwise. + */ + bool has_keyfp() const; + /** + * @brief Get signing key's fingerprint if it is available. Availability may be checked via + * has_keyfp() method. + * @return fingerprint (or empty zero-size fp in case it is unavailable) + */ + pgp_fingerprint_t keyfp() const noexcept; + + /** @brief Set signing key's fingerprint. Works only for signatures with version 4 and up, + * so version should be set prior to fingerprint. */ + void set_keyfp(const pgp_fingerprint_t &fp); + + /** + * @brief Get signature's creation time + * @return time in seconds since the Jan 1, 1970 UTC. 0 is the default value and returned + * even if creation time is not available + */ + uint32_t creation() const; + + /** + * @brief Set signature's creation time + * @param ctime creation time in seconds since the Jan 1, 1970 UTC. + */ + void set_creation(uint32_t ctime); + + /** + * @brief Get the signature's expiration time + * @return expiration time in seconds since the creation time. 0 if signature never + * expires. + */ + uint32_t expiration() const; + + /** + * @brief Set the signature's expiration time + * @param etime expiration time + */ + void set_expiration(uint32_t etime); + + /** + * @brief Get the key expiration time + * @return expiration time in seconds since the creation time. 0 if key never expires. + */ + uint32_t key_expiration() const; + + /** + * @brief Set the key expiration time + * @param etime expiration time + */ + void set_key_expiration(uint32_t etime); + + /** + * @brief Get the key flags + * @return byte of key flags. If there is no corresponding subpackets then 0 is returned. + */ + uint8_t key_flags() const; + + /** + * @brief Set the key flags + * @param flags byte of key flags + */ + void set_key_flags(uint8_t flags); + + /** + * @brief Get the primary user id flag + * @return true if user id is marked as primary or false otherwise + */ + bool primary_uid() const; + + /** + * @brief Set the primary user id flag + * @param primary true if user id should be marked as primary + */ + void set_primary_uid(bool primary); + + /** @brief Get preferred symmetric algorithms if any. If there are no ones then empty + * vector is returned. */ + std::vector preferred_symm_algs() const; + + /** @brief Set the preferred symmetric algorithms. If empty vector is passed then + * corresponding subpacket is deleted. */ + void set_preferred_symm_algs(const std::vector &algs); + + /** @brief Get preferred hash algorithms if any. If there are no ones then empty vector is + * returned.*/ + std::vector preferred_hash_algs() const; + + /** @brief Set the preferred hash algorithms. If empty vector is passed then corresponding + * subpacket is deleted. */ + void set_preferred_hash_algs(const std::vector &algs); + + /** @brief Get preferred compression algorithms if any. If there are no ones then empty + * vector is returned.*/ + std::vector preferred_z_algs() const; + + /** @brief Set the preferred compression algorithms. If empty vector is passed then + * corresponding subpacket is deleted. */ + void set_preferred_z_algs(const std::vector &algs); + + /** @brief Get key server preferences flags. If subpacket is not available then 0 is + * returned. */ + uint8_t key_server_prefs() const; + + /** @brief Set key server preferences flags. */ + void set_key_server_prefs(uint8_t prefs); + + /** @brief Get preferred key server URI, if available. Otherwise empty string is returned. + */ + std::string key_server() const; + + /** @brief Set preferred key server URI. If it is empty string then subpacket is deleted if + * it is available. */ + void set_key_server(const std::string &uri); + + /** @brief Get trust level, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + uint8_t trust_level() const; + + /** @brief Get trust amount, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + uint8_t trust_amount() const; + + /** @brief Set the trust level and amount. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + void set_trust(uint8_t level, uint8_t amount); + + /** @brief check whether signature is revocable. True by default. + */ + bool revocable() const; + + /** @brief Set the signature's revocability status. + */ + void set_revocable(bool status); + + /** @brief Get the key/subkey revocation reason in humand-readable form. If there is no + * revocation reason subpacket, then empty string will be returned. + */ + std::string revocation_reason() const; + + /** @brief Get the key/subkey revocation code. If there is no revocation reason subpacket, + * then PGP_REVOCATION_NO_REASON will be rerturned. See the RFC 4880, 5.2.3.24 for + * the detailed explanation. + */ + pgp_revocation_type_t revocation_code() const; + + /** @brief Set the revocation reason and code for key/subkey revocation signature. See the + * RFC 4880, 5.2.3.24 for the detailed explanation. + */ + void set_revocation_reason(pgp_revocation_type_t code, const std::string &reason); + + /** + * @brief Check whether signer's key supports certain feature(s). Makes sense only for + * self-signature, for more details see the RFC 4880bis, 5.2.3.25. If there is + * no corresponding subpacket then false will be returned. + * @param flags one or more flags, combined via bitwise OR operation. + * @return true if key is claimed to support all of the features listed in flags, or false + * otherwise + */ + bool key_has_features(pgp_key_feature_t flags) const; + + /** + * @brief Set the features supported by the signer's key, makes sense only for + * self-signature. For more details see the RFC 4880bis, 5.2.3.25. + * @param flags one or more flags, combined via bitwise OR operation. + */ + void set_key_features(pgp_key_feature_t flags); + + /** @brief Get signer's user id, if available. Otherwise empty string is returned. See the + * RFC 4880bis, 5.2.3.23 for details. + */ + std::string signer_uid() const; + + /** + * @brief Set the signer's uid, responcible for the signature creation. See the RFC + * 4880bis, 5.2.3.23 for details. + */ + void set_signer_uid(const std::string &uid); + + /** + * @brief Add notation. + */ + void add_notation(const std::string & name, + const std::vector &value, + bool human = true, + bool critical = false); + + /** + * @brief Add human-readable notation. + */ + void add_notation(const std::string &name, + const std::string &value, + bool critical = false); + + /** + * @brief Set the embedded signature. + * @param esig populated and calculated embedded signature. + */ + void set_embedded_sig(const pgp_signature_t &esig); + + /** + * @brief Add subpacket of the specified type to v4 signature + * @param type type of the subpacket + * @param datalen length of the subpacket body + * @param reuse replace already existing subpacket of the specified type if any + * @return reference to the subpacket structure or throws an exception + */ + pgp_sig_subpkt_t &add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse); + + /** + * @brief Remove signature's subpacket + * @param subpkt subpacket to remove. If not in the subpackets list then no action is + * taken. + */ + void remove_subpkt(pgp_sig_subpkt_t *subpkt); + + /** + * @brief Check whether signature packet matches one-pass signature packet. + * @param onepass reference to the read one-pass signature packet + * @return true if sig corresponds to onepass or false otherwise + */ + bool matches_onepass(const pgp_one_pass_sig_t &onepass) const; + + /** + * @brief Parse signature body (i.e. without checking the packet header). + * + * @param pkt packet body with data. + * @return RNP_SUCCESS or error code if failed. May also throw an exception. + */ + rnp_result_t parse(pgp_packet_body_t &pkt); + + /** + * @brief Parse signature packet from source. + * + * @param src source with data. + * @return RNP_SUCCESS or error code if failed. May also throw an exception. + */ + rnp_result_t parse(pgp_source_t &src); + + /** + * @brief Parse signature material, stored in the signature in raw. + * + * @param material on success parsed material will be stored here. + * @return true on success or false otherwise. May also throw an exception. + */ + bool parse_material(pgp_signature_material_t &material) const; + + /** + * @brief Write signature to the destination. May throw an exception. + */ + void write(pgp_dest_t &dst) const; + + /** + * @brief Write the signature material's raw representation. May throw an exception. + * + * @param material populated signature material. + */ + void write_material(const pgp_signature_material_t &material); + + /** + * @brief Fill signature's hashed data. This includes all the fields from signature which + * are hashed after the previous document or key fields. + */ + void fill_hashed_data(); +} pgp_signature_t; + +typedef std::vector pgp_signature_list_t; + +/* information about the validated signature */ +typedef struct pgp_signature_info_t { + pgp_signature_t *sig{}; /* signature, or NULL if there were parsing error */ + bool valid{}; /* signature is cryptographically valid (but may be expired) */ + bool unknown{}; /* signature is unknown - parsing error, wrong version, etc */ + bool no_signer{}; /* no signer's public key available */ + bool expired{}; /* signature is expired */ + bool signer_valid{}; /* assume that signing key is valid */ + bool ignore_expiry{}; /* ignore signer's key expiration time */ +} pgp_signature_info_t; + +/** + * @brief Hash key packet. Used in signatures and v4 fingerprint calculation. + * Throws exception on error. + * @param key key packet, must be populated + * @param hash initialized hash context + */ +void signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash); + +void signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver); + +std::unique_ptr signature_hash_certification(const pgp_signature_t & sig, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &userid); + +std::unique_ptr signature_hash_binding(const pgp_signature_t &sig, + const pgp_key_pkt_t & key, + const pgp_key_pkt_t & subkey); + +std::unique_ptr signature_hash_direct(const pgp_signature_t &sig, + const pgp_key_pkt_t & key); + +/** + * @brief Parse stream with signatures to the signatures list. + * Can handle binary or armored stream with signatures, including stream with multiple + * armored signatures. + * + * @param src signatures stream, cannot be NULL. + * @param sigs on success parsed signature structures will be put here. + * @return RNP_SUCCESS or error code otherwise. + */ +rnp_result_t process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs); + +#endif diff --git a/src/librepgp/stream-write.cpp b/src/librepgp/stream-write.cpp new file mode 100644 index 0000000..60d867a --- /dev/null +++ b/src/librepgp/stream-write.cpp @@ -0,0 +1,1973 @@ +/* + * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include +#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; +} diff --git a/src/librepgp/stream-write.h b/src/librepgp/stream-write.h new file mode 100644 index 0000000..49431f9 --- /dev/null +++ b/src/librepgp/stream-write.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, [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. + */ + +#ifndef STREAM_WRITE_H_ +#define STREAM_WRITE_H_ + +#include +#include +#include +#include "rnp.h" +#include "stream-common.h" +#include "stream-ctx.h" + +typedef struct pgp_write_handler_t { + pgp_password_provider_t *password_provider; + pgp_key_provider_t * key_provider; + rnp_ctx_t * ctx; + + void *param; +} pgp_write_handler_t; + +/** @brief sign the input data, producing attached, detached or cleartext signature. + * Type of the signature is controlled by clearsign and detached fields of the + * rnp_ctx_t structure + * @param handler handler to respond on stream processor callbacks, and additional processing + * parameters, including rnp_ctx_t + * @param src input source: file, stdin, memory, whatever else conforming to pgp_source_t + * @param dst output destination: file, stdout, memory, whatever else conforming to pgp_dest_t + **/ +rnp_result_t rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst); + +/** @brief encrypt and sign the input data. Signatures will be enrypted together with data. + * @param handler handler handler to respond on stream processor callbacks, and additional + * processing parameters, including rnp_ctx_t + * @param src input source: file, stdin, memory, whatever else conforming to pgp_source_t + * @param dst output destination: file, stdout, memory, whatever else conforming to pgp_dest_t + **/ +rnp_result_t rnp_encrypt_sign_src(pgp_write_handler_t *handler, + pgp_source_t * src, + pgp_dest_t * dst); + +/* Following functions are used only in tests currently. Later could be used in CLI for debug + * commands like --wrap-literal, --encrypt-raw, --compress-raw, etc. */ + +rnp_result_t rnp_compress_src(pgp_source_t & src, + pgp_dest_t & dst, + pgp_compression_type_t zalg, + int zlevel); + +rnp_result_t rnp_wrap_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string &filename, + uint32_t modtime); + +rnp_result_t rnp_raw_encrypt_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string & password, + rnp::SecurityContext &secctx); + +#endif -- cgit v1.2.3