summaryrefslogtreecommitdiffstats
path: root/src/librepgp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:32:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:32:49 +0000
commit8053187731ae8e3eb368d8360989cf5fd6eed9f7 (patch)
tree32bada84ff5d7460cdf3934fcbdbe770d6afe4cd /src/librepgp
parentInitial commit. (diff)
downloadrnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.tar.xz
rnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.zip
Adding upstream version 0.17.0.upstream/0.17.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/librepgp/stream-armor.cpp1287
-rw-r--r--src/librepgp/stream-armor.h174
-rw-r--r--src/librepgp/stream-common.cpp1212
-rw-r--r--src/librepgp/stream-common.h556
-rw-r--r--src/librepgp/stream-ctx.cpp69
-rw-r--r--src/librepgp/stream-ctx.h123
-rw-r--r--src/librepgp/stream-def.h67
-rw-r--r--src/librepgp/stream-dump.cpp2533
-rw-r--r--src/librepgp/stream-dump.h53
-rw-r--r--src/librepgp/stream-key.cpp1469
-rw-r--r--src/librepgp/stream-key.h143
-rw-r--r--src/librepgp/stream-packet.cpp1228
-rw-r--r--src/librepgp/stream-packet.h323
-rw-r--r--src/librepgp/stream-parse.cpp2636
-rw-r--r--src/librepgp/stream-parse.h123
-rw-r--r--src/librepgp/stream-sig.cpp1557
-rw-r--r--src/librepgp/stream-sig.h437
-rw-r--r--src/librepgp/stream-write.cpp1973
-rw-r--r--src/librepgp/stream-write.h83
19 files changed, 16046 insertions, 0 deletions
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 <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <algorithm>
+#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<rnp::CRC24> 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<rnp::CRC24> 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, &param->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, &param->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, &param->eol[0], 1);
+ }
+ if (param->eol[1]) {
+ dst_write(param->writedst, &param->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(&param->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 <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#include <string.h>
+#else
+#include "uniwin.h"
+#endif
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <errno.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#include <rnp/rnp_def.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "types.h"
+#include "file-utils.h"
+#include "crypto/mem.h"
+#include <algorithm>
+#include <memory>
+
+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<pgp_dest_file_param_t> 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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#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<uint8_t> &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<uint8_t>
+ to_vector()
+ {
+ uint8_t *mem = (uint8_t *) memory();
+ return std::vector<uint8_t>(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 <string.h>
+#include <assert.h>
+#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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "types.h"
+#include <string>
+#include <list>
+#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<uint8_t, PGP_MAX_KEY_SIZE> 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<pgp_key_t *> recipients{}; /* recipients of the encrypted message */
+ std::list<rnp_symmetric_pass_info_t> passwords{}; /* passwords to encrypt message */
+ std::list<rnp_signer_info_t> 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 <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#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 <algorithm>
+
+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<uint8_t> 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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#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 <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <time.h>
+#include <inttypes.h>
+#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 <set>
+#include <algorithm>
+#include <cassert>
+
+/**
+ * @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<pgp_pkt_type_t> &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<uint8_t, PGP_MAX_KEY_SIZE> 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<uint8_t> 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<uint8_t, PGP_MAX_KEY_SIZE> 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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#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<pgp_transferable_userid_t> userids;
+ std::vector<pgp_transferable_subkey_t> 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<pgp_transferable_key_t> 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 <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <inttypes.h>
+#include <rnp/rnp_def.h>
+#include "types.h"
+#include "crypto.h"
+#include "crypto/mem.h"
+#include "stream-packet.h"
+#include "stream-key.h"
+#include <algorithm>
+
+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<pgp_key_id_t>::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<decltype(keyid)>::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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#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<uint8_t> 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<uint8_t> 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 <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <time.h>
+#include <cinttypes>
+#include <cassert>
+#include <rnp/rnp_def.h>
+#include "stream-ctx.h"
+#include "stream-def.h"
+#include "stream-parse.h"
+#include "stream-armor.h"
+#include "stream-packet.h"
+#include "stream-sig.h"
+#include "str-utils.h"
+#include "types.h"
+#include "crypto/s2k.h"
+#include "crypto.h"
+#include "crypto/signatures.h"
+#include "fingerprint.h"
+#include "pgp-key.h"
+
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif
+#ifdef HAVE_BZLIB_H
+#include <bzlib.h>
+#endif
+
+typedef enum pgp_message_t {
+ PGP_MESSAGE_UNKNOWN = 0,
+ PGP_MESSAGE_NORMAL,
+ PGP_MESSAGE_DETACHED,
+ PGP_MESSAGE_CLEARTEXT
+} pgp_message_t;
+
+typedef struct pgp_processing_ctx_t {
+ pgp_parse_handler_t handler;
+ pgp_source_t * signed_src;
+ pgp_source_t * literal_src;
+ pgp_message_t msg_type;
+ pgp_dest_t output;
+ std::list<pgp_source_t> sources;
+
+ ~pgp_processing_ctx_t();
+} pgp_processing_ctx_t;
+
+/* common fields for encrypted, compressed and literal data */
+typedef struct pgp_source_packet_param_t {
+ pgp_source_t * readsrc; /* source to read from, could be partial*/
+ pgp_source_t * origsrc; /* original source passed to init_*_src */
+ pgp_packet_hdr_t hdr; /* packet header info */
+} pgp_source_packet_param_t;
+
+typedef struct pgp_source_encrypted_param_t {
+ pgp_source_packet_param_t pkt{}; /* underlying packet-related params */
+ std::vector<pgp_sk_sesskey_t> symencs; /* array of sym-encrypted session keys */
+ std::vector<pgp_pk_sesskey_t> pubencs; /* array of pk-encrypted session keys */
+ rnp::AuthType auth_type; /* Authentication type */
+ bool auth_validated{}; /* Auth tag (MDC or AEAD) was already validated */
+ pgp_crypt_t decrypt{}; /* decrypting crypto */
+ std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */
+ size_t chunklen{}; /* size of AEAD chunk in bytes */
+ size_t chunkin{}; /* number of bytes read from the current chunk */
+ size_t chunkidx{}; /* index of the current chunk */
+ uint8_t cache[PGP_AEAD_CACHE_LEN]; /* read cache */
+ size_t cachelen{}; /* number of bytes in the cache */
+ size_t cachepos{}; /* index of first unread byte in the cache */
+ pgp_aead_hdr_t aead_hdr; /* AEAD encryption parameters */
+ uint8_t aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */
+ size_t aead_adlen{}; /* length of the additional data */
+ pgp_symm_alg_t salg; /* data encryption algorithm */
+ pgp_parse_handler_t * handler{}; /* parsing handler with callbacks */
+
+ pgp_source_encrypted_param_t() : auth_type(rnp::AuthType::None), salg(PGP_SA_UNKNOWN)
+ {
+ }
+
+ bool
+ use_cfb()
+ {
+ return auth_type != rnp::AuthType::AEADv1;
+ }
+} pgp_source_encrypted_param_t;
+
+typedef struct pgp_source_signed_param_t {
+ pgp_parse_handler_t *handler; /* parsing handler with callbacks */
+ pgp_source_t * readsrc; /* source to read from */
+ bool detached; /* detached signature */
+ bool cleartext; /* source is cleartext signed */
+ bool clr_eod; /* cleartext data is over */
+ bool clr_fline; /* first line of the cleartext */
+ bool clr_mline; /* in the middle of the very long line */
+ uint8_t out[CT_BUF_LEN]; /* cleartext output cache for easier parsing */
+ size_t outlen; /* total bytes in out */
+ size_t outpos; /* offset of first available byte in out */
+ bool max_line_warn; /* warning about too long line is already issued */
+ size_t text_line_len; /* length of a current line in a text document */
+ long stripped_crs; /* number of trailing CR characters stripped from the end of the last
+ processed chunk */
+
+ std::vector<pgp_one_pass_sig_t> onepasses; /* list of one-pass singatures */
+ std::list<pgp_signature_t> sigs; /* list of signatures */
+ std::vector<pgp_signature_info_t> siginfos; /* signature validation info */
+ rnp::HashList hashes; /* hash contexts */
+ rnp::HashList txt_hashes; /* hash contexts for text-mode sigs */
+
+ pgp_source_signed_param_t() = default;
+ ~pgp_source_signed_param_t() = default;
+} pgp_source_signed_param_t;
+
+typedef struct pgp_source_compressed_param_t {
+ pgp_source_packet_param_t pkt; /* underlying packet-related params */
+ pgp_compression_type_t alg;
+ union {
+ z_stream z;
+ bz_stream bz;
+ };
+ uint8_t in[PGP_INPUT_CACHE_SIZE / 2];
+ size_t inpos;
+ size_t inlen;
+ bool zend;
+} pgp_source_compressed_param_t;
+
+typedef struct pgp_source_literal_param_t {
+ pgp_source_packet_param_t pkt; /* underlying packet-related params */
+ pgp_literal_hdr_t hdr; /* literal packet fields */
+} pgp_source_literal_param_t;
+
+typedef struct pgp_source_partial_param_t {
+ pgp_source_t *readsrc; /* source to read from */
+ int type; /* type of the packet */
+ size_t psize; /* size of the current part */
+ size_t pleft; /* bytes left to read from the current part */
+ bool last; /* current part is last */
+} pgp_source_partial_param_t;
+
+static bool
+is_pgp_source(pgp_source_t &src)
+{
+ uint8_t buf;
+ if (!src_peek_eq(&src, &buf, 1)) {
+ return false;
+ }
+
+ switch (get_packet_type(buf)) {
+ case PGP_PKT_PK_SESSION_KEY:
+ case PGP_PKT_SK_SESSION_KEY:
+ case PGP_PKT_ONE_PASS_SIG:
+ case PGP_PKT_SIGNATURE:
+ case PGP_PKT_SE_DATA:
+ case PGP_PKT_SE_IP_DATA:
+ case PGP_PKT_COMPRESSED:
+ case PGP_PKT_LITDATA:
+ case PGP_PKT_MARKER:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+partial_pkt_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ if (src->eof) {
+ *readres = 0;
+ return true;
+ }
+
+ pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ size_t read;
+ size_t write = 0;
+ while (len > 0) {
+ if (!param->pleft && param->last) {
+ // we have the last chunk
+ *readres = write;
+ return true;
+ }
+ if (!param->pleft) {
+ // reading next chunk
+ if (!stream_read_partial_chunk_len(param->readsrc, &read, &param->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(&param->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(&param->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(&param->bz);
+ }
+#endif
+ if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) {
+ inflateEnd(&param->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(&param->decrypt);
+ }
+ STORE64BE(param->aead_ad + param->aead_adlen, total);
+ param->aead_adlen += 8;
+ }
+
+ if (!pgp_cipher_aead_set_ad(&param->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(&param->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(&param->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(&param->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(
+ &param->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(
+ &param->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(&param->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(&param->decrypt, mdcbuf, mdcbuf, MDC_V1_SIZE);
+ pgp_cipher_cfb_finish(&param->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(&param->decrypt);
+#endif
+ } else {
+ pgp_cipher_cfb_finish(&param->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 &param, 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 &param, 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 = &param->sigs.back();
+ if (sig) {
+ *sig = siginfo.sig;
+ }
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+}
+
+static rnp_result_t
+signed_read_cleartext_signatures(pgp_source_t &src, pgp_source_signed_param_t *param)
+{
+ try {
+ rnp::ArmoredSource armor(*param->readsrc);
+ while (!armor.eof()) {
+ auto ret = signed_read_single_signature(param, &armor.src(), NULL);
+ if (ret) {
+ return ret;
+ }
+ }
+ return RNP_SUCCESS;
+ } catch (const rnp::rnp_exception &e) {
+ RNP_LOG("%s", e.what());
+ return e.code();
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_FORMAT;
+ }
+}
+
+static rnp_result_t
+signed_read_signatures(pgp_source_t *src)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+
+ /* reading signatures */
+ for (auto op = param->onepasses.rbegin(); op != param->onepasses.rend(); op++) {
+ pgp_signature_t *sig = NULL;
+ rnp_result_t ret = signed_read_single_signature(param, src, &sig);
+ /* we have more onepasses then signatures */
+ if (ret == RNP_ERROR_READ) {
+ RNP_LOG("Warning: premature end of signatures");
+ return param->siginfos.size() ? RNP_SUCCESS : ret;
+ }
+ if (ret) {
+ return ret;
+ }
+ if (sig && !sig->matches_onepass(*op)) {
+ RNP_LOG("Warning: signature doesn't match one-pass");
+ }
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+signed_src_finish(pgp_source_t *src)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ if (param->cleartext) {
+ ret = signed_read_cleartext_signatures(*src, param);
+ } else {
+ ret = signed_read_signatures(src);
+ }
+
+ if (ret) {
+ return ret;
+ }
+
+ if (!src_eof(src)) {
+ RNP_LOG("warning: unexpected data on the stream end");
+ }
+
+ /* validating signatures */
+ for (auto &sinfo : param->siginfos) {
+ if (!sinfo.sig) {
+ continue;
+ }
+ signed_validate_signature(*param, sinfo);
+ }
+
+ /* checking the validation results */
+ ret = RNP_ERROR_SIGNATURE_INVALID;
+ for (auto &sinfo : param->siginfos) {
+ if (sinfo.valid) {
+ /* If we have at least one valid signature then data is safe to process */
+ ret = RNP_SUCCESS;
+ break;
+ }
+ }
+
+ /* call the callback with signature infos */
+ if (param->handler->on_signatures) {
+ param->handler->on_signatures(param->siginfos, param->handler->param);
+ }
+ return ret;
+}
+
+/*
+ * str is a string to tokenize.
+ * delims is a string containing a list of delimiter characters.
+ * result is a container<string_type> that supports push_back.
+ */
+template <typename T>
+static void
+tokenize(const typename T::value_type &str, const typename T::value_type &delims, T &result)
+{
+ typedef typename T::value_type::size_type string_size_t;
+ const string_size_t npos = T::value_type::npos;
+
+ result.clear();
+ string_size_t current;
+ string_size_t next = 0;
+ do {
+ next = str.find_first_not_of(delims, next);
+ if (next == npos) {
+ break;
+ }
+ current = next;
+ next = str.find_first_of(delims, current);
+ string_size_t count = (next == npos) ? npos : (next - current);
+ result.push_back(str.substr(current, count));
+ } while (next != npos);
+}
+
+static bool
+cleartext_parse_headers(pgp_source_t *src)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ char hdr[1024] = {0};
+ char * hval;
+ pgp_hash_alg_t halg;
+ size_t hdrlen;
+
+ do {
+ if (!src_peek_line(param->readsrc, hdr, sizeof(hdr), &hdrlen)) {
+ RNP_LOG("failed to peek line");
+ return false;
+ }
+
+ if (!hdrlen) {
+ break;
+ }
+
+ if (rnp::is_blank_line(hdr, hdrlen)) {
+ src_skip(param->readsrc, hdrlen);
+ break;
+ }
+
+ try {
+ if ((hdrlen >= 6) && !strncmp(hdr, ST_HEADER_HASH, 6)) {
+ hval = hdr + 6;
+
+ std::string remainder = hval;
+
+ const std::string delimiters = ", \t";
+ std::vector<std::string> tokens;
+
+ tokenize(remainder, delimiters, tokens);
+
+ for (const auto &token : tokens) {
+ if ((halg = rnp::Hash::alg(token.c_str())) == PGP_HASH_UNKNOWN) {
+ RNP_LOG("unknown halg: %s", token.c_str());
+ continue;
+ }
+ add_hash_for_sig(param, PGP_SIG_TEXT, halg);
+ }
+ } else {
+ RNP_LOG("unknown header '%s'", hdr);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+
+ src_skip(param->readsrc, hdrlen);
+
+ if (!src_skip_eol(param->readsrc)) {
+ return false;
+ }
+ } while (1);
+
+ /* we have exactly one empty line after the headers */
+ return src_skip_eol(param->readsrc);
+}
+
+static void
+cleartext_process_line(pgp_source_t *src, const uint8_t *buf, size_t len, bool eol)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ uint8_t * bufen = (uint8_t *) buf + len - 1;
+
+ /* check for dashes only if we are not in the middle */
+ if (!param->clr_mline && (len > 0) && (buf[0] == CH_DASH)) {
+ if ((len > 1) && (buf[1] == CH_SPACE)) {
+ buf += 2;
+ len -= 2;
+ } else if ((len > 5) && !memcmp(buf, ST_DASHES, 5)) {
+ param->clr_eod = true;
+ return;
+ } else {
+ RNP_LOG("dash at the line begin");
+ }
+ }
+
+ /* hash eol if it is not the first line and we are not in the middle */
+ if (!param->clr_fline && !param->clr_mline) {
+ /* we hash \r\n after the previous line to not hash the last eol before the sig */
+ signed_src_update(src, ST_CRLF, 2);
+ }
+
+ if (!len) {
+ return;
+ }
+
+ if (len + param->outlen > sizeof(param->out)) {
+ RNP_LOG("wrong state");
+ return;
+ }
+
+ /* if we have eol after this line then strip trailing spaces and tabs */
+ if (eol) {
+ for (; (bufen >= buf) &&
+ ((*bufen == CH_SPACE) || (*bufen == CH_TAB) || (*bufen == CH_CR));
+ bufen--)
+ ;
+ }
+
+ if ((len = bufen + 1 - buf)) {
+ memcpy(param->out + param->outlen, buf, len);
+ param->outlen += len;
+ signed_src_update(src, buf, len);
+ }
+}
+
+static bool
+cleartext_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ uint8_t srcb[CT_BUF_LEN];
+ uint8_t *cur, *en, *bg;
+ size_t read = 0;
+ size_t origlen = len;
+
+ read = param->outlen - param->outpos;
+ if (read >= len) {
+ memcpy(buf, param->out + param->outpos, len);
+ param->outpos += len;
+ if (param->outpos == param->outlen) {
+ param->outpos = param->outlen = 0;
+ }
+ *readres = len;
+ return true;
+ } else if (read > 0) {
+ memcpy(buf, param->out + param->outpos, read);
+ len -= read;
+ buf = (uint8_t *) buf + read;
+ param->outpos = param->outlen = 0;
+ }
+
+ if (param->clr_eod) {
+ *readres = origlen - len;
+ return true;
+ }
+
+ do {
+ if (!src_peek(param->readsrc, srcb, sizeof(srcb), &read)) {
+ return false;
+ } else if (!read) {
+ break;
+ }
+
+ /* processing data line by line, eol could be \n or \r\n */
+ for (cur = srcb, bg = srcb, en = cur + read; cur < en; cur++) {
+ if ((*cur == CH_LF) ||
+ ((*cur == CH_CR) && (cur + 1 < en) && (*(cur + 1) == CH_LF))) {
+ cleartext_process_line(src, bg, cur - bg, true);
+ /* processing eol */
+ if (param->clr_eod) {
+ break;
+ }
+
+ /* processing eol */
+ param->clr_fline = false;
+ param->clr_mline = false;
+ if (*cur == CH_CR) {
+ param->out[param->outlen++] = *cur++;
+ }
+ param->out[param->outlen++] = *cur;
+ bg = cur + 1;
+ }
+ }
+
+ /* if line is larger then 4k then just dump it out */
+ if ((bg == srcb) && !param->clr_eod) {
+ /* if last char is \r, and it's not the end of stream, then do not dump it */
+ if ((en > bg) && (*(en - 1) == CH_CR) && (read > 1)) {
+ en--;
+ }
+ cleartext_process_line(src, bg, en - bg, false);
+ param->clr_mline = true;
+ bg = en;
+ }
+ src_skip(param->readsrc, bg - srcb);
+
+ /* put data from the param->out to buf */
+ read = param->outlen > len ? len : param->outlen;
+ memcpy(buf, param->out, read);
+ buf = (uint8_t *) buf + read;
+ len -= read;
+
+ if (read == param->outlen) {
+ param->outlen = 0;
+ } else {
+ param->outpos = read;
+ }
+
+ /* we got to the signature marker */
+ if (param->clr_eod || !len) {
+ break;
+ }
+ } while (1);
+
+ *readres = origlen - len;
+ return true;
+}
+
+static bool
+encrypted_decrypt_cfb_header(pgp_source_encrypted_param_t *param,
+ pgp_symm_alg_t alg,
+ uint8_t * key)
+{
+ pgp_crypt_t crypt;
+ uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2];
+ uint8_t dechdr[PGP_MAX_BLOCK_SIZE + 2];
+ unsigned blsize;
+
+ if (!(blsize = pgp_block_size(alg))) {
+ return false;
+ }
+
+ /* reading encrypted header to check the password validity */
+ if (!src_peek_eq(param->pkt.readsrc, enchdr, blsize + 2)) {
+ RNP_LOG("failed to read encrypted header");
+ return false;
+ }
+
+ /* having symmetric key in keybuf let's decrypt blocksize + 2 bytes and check them */
+ if (!pgp_cipher_cfb_start(&crypt, alg, key, NULL)) {
+ RNP_LOG("failed to start cipher");
+ return false;
+ }
+
+ pgp_cipher_cfb_decrypt(&crypt, dechdr, enchdr, blsize + 2);
+
+ if ((dechdr[blsize] != dechdr[blsize - 2]) || (dechdr[blsize + 1] != dechdr[blsize - 1])) {
+ RNP_LOG("checksum check failed");
+ goto error;
+ }
+
+ src_skip(param->pkt.readsrc, blsize + 2);
+ param->decrypt = crypt;
+
+ /* init mdc if it is here */
+ /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB
+ * resynchronization is done after encrypting this prefix data. */
+ if (param->auth_type == rnp::AuthType::None) {
+ pgp_cipher_cfb_resync(&param->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(
+ &param->decrypt, param->aead_hdr.ealg, param->aead_hdr.aalg, key, true)) {
+ return false;
+ }
+
+ gran = pgp_cipher_aead_granularity(&param->decrypt);
+ if (gran > sizeof(param->cache)) {
+ RNP_LOG("wrong granularity");
+ return false;
+ }
+
+ return encrypted_start_aead_chunk(param, 0, false);
+#endif
+}
+
+static bool
+encrypted_try_key(pgp_source_encrypted_param_t *param,
+ pgp_pk_sesskey_t * sesskey,
+ pgp_key_pkt_t * seckey,
+ rnp::SecurityContext & ctx)
+{
+ pgp_encrypted_material_t encmaterial;
+ try {
+ if (!sesskey->parse_material(encmaterial)) {
+ return false;
+ }
+ seckey->material.validate(ctx, false);
+ if (!seckey->material.valid()) {
+ RNP_LOG("Attempt to decrypt using the key with invalid material.");
+ return false;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+
+ rnp::secure_array<uint8_t, PGP_MPINT_SIZE> decbuf;
+ /* Decrypting session key value */
+ rnp_result_t err;
+ bool res = false;
+ pgp_key_material_t *keymaterial = &seckey->material;
+ size_t declen = 0;
+ switch (sesskey->alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ err = rsa_decrypt_pkcs1(
+ &ctx.rng, decbuf.data(), &declen, &encmaterial.rsa, &keymaterial->rsa);
+ if (err) {
+ RNP_LOG("RSA decryption failure");
+ return false;
+ }
+ break;
+ case PGP_PKA_SM2:
+#if defined(ENABLE_SM2)
+ declen = decbuf.size();
+ err = sm2_decrypt(decbuf.data(), &declen, &encmaterial.sm2, &keymaterial->ec);
+ if (err != RNP_SUCCESS) {
+ RNP_LOG("SM2 decryption failure, error %x", (int) err);
+ return false;
+ }
+ break;
+#else
+ RNP_LOG("SM2 decryption is not available.");
+ return false;
+#endif
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: {
+ const rnp_result_t ret = elgamal_decrypt_pkcs1(
+ &ctx.rng, decbuf.data(), &declen, &encmaterial.eg, &keymaterial->eg);
+ if (ret) {
+ RNP_LOG("ElGamal decryption failure [%X]", ret);
+ return false;
+ }
+ break;
+ }
+ case PGP_PKA_ECDH: {
+ if (!curve_supported(keymaterial->ec.curve)) {
+ RNP_LOG("ECDH decrypt: curve %d is not supported.", (int) keymaterial->ec.curve);
+ return false;
+ }
+ pgp_fingerprint_t fingerprint;
+ if (pgp_fingerprint(fingerprint, *seckey)) {
+ RNP_LOG("ECDH fingerprint calculation failed");
+ return false;
+ }
+ if ((keymaterial->ec.curve == PGP_CURVE_25519) &&
+ !x25519_bits_tweaked(keymaterial->ec)) {
+ RNP_LOG("Warning: bits of 25519 secret key are not tweaked.");
+ }
+ declen = decbuf.size();
+ err = ecdh_decrypt_pkcs5(
+ decbuf.data(), &declen, &encmaterial.ecdh, &keymaterial->ec, fingerprint);
+ if (err != RNP_SUCCESS) {
+ RNP_LOG("ECDH decryption error %u", err);
+ return false;
+ }
+ break;
+ }
+ default:
+ RNP_LOG("unsupported public key algorithm %d\n", seckey->alg);
+ return false;
+ }
+
+ /* Check algorithm and key length */
+ if (!pgp_is_sa_supported(decbuf[0])) {
+ RNP_LOG("Unsupported symmetric algorithm %" PRIu8, decbuf[0]);
+ return false;
+ }
+
+ pgp_symm_alg_t salg = static_cast<pgp_symm_alg_t>(decbuf[0]);
+ size_t keylen = pgp_key_size(salg);
+ if (declen != keylen + 3) {
+ RNP_LOG("invalid symmetric key length");
+ return false;
+ }
+
+ /* Validate checksum */
+ rnp::secure_array<unsigned, 1> checksum;
+ for (unsigned i = 1; i <= keylen; i++) {
+ checksum[0] += decbuf[i];
+ }
+
+ if ((checksum[0] & 0xffff) !=
+ (decbuf[keylen + 2] | ((unsigned) decbuf[keylen + 1] << 8))) {
+ RNP_LOG("wrong checksum\n");
+ return false;
+ }
+
+ if (param->use_cfb()) {
+ /* Decrypt header */
+ res = encrypted_decrypt_cfb_header(param, salg, &decbuf[1]);
+ } else {
+ /* Start AEAD decrypting, assuming we have correct key */
+ res = encrypted_start_aead(param, salg, &decbuf[1]);
+ }
+ if (res) {
+ param->salg = salg;
+ }
+ return res;
+}
+
+#if defined(ENABLE_AEAD)
+static bool
+encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey)
+{
+ /* TODO: this method is exact duplicate as in stream-write.c. Not sure where to put it */
+ uint8_t ad_data[4];
+
+ ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT;
+ ad_data[1] = skey->version;
+ ad_data[2] = skey->alg;
+ ad_data[3] = skey->aalg;
+
+ return pgp_cipher_aead_set_ad(crypt, ad_data, 4);
+}
+#endif
+
+static int
+encrypted_try_password(pgp_source_encrypted_param_t *param, const char *password)
+{
+ bool keyavail = false; /* tried password at least once */
+
+ for (auto &skey : param->symencs) {
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 1> keybuf;
+ /* deriving symmetric key from password */
+ size_t keysize = pgp_key_size(skey.alg);
+ if (!keysize || !pgp_s2k_derive_key(&skey.s2k, password, keybuf.data(), keysize)) {
+ continue;
+ }
+ pgp_crypt_t crypt;
+ pgp_symm_alg_t alg;
+
+ if (skey.version == PGP_SKSK_V4) {
+ /* v4 symmetrically-encrypted session key */
+ if (skey.enckeylen > 0) {
+ /* decrypting session key */
+ if (!pgp_cipher_cfb_start(&crypt, skey.alg, keybuf.data(), NULL)) {
+ continue;
+ }
+
+ pgp_cipher_cfb_decrypt(&crypt, keybuf.data(), skey.enckey, skey.enckeylen);
+ pgp_cipher_cfb_finish(&crypt);
+
+ alg = (pgp_symm_alg_t) keybuf[0];
+ keysize = pgp_key_size(alg);
+ if (!keysize || (keysize + 1 != skey.enckeylen)) {
+ continue;
+ }
+ memmove(keybuf.data(), keybuf.data() + 1, keysize);
+ } else {
+ alg = (pgp_symm_alg_t) skey.alg;
+ }
+
+ if (!pgp_block_size(alg)) {
+ continue;
+ }
+ keyavail = true;
+ } else if (skey.version == PGP_SKSK_V5) {
+#if !defined(ENABLE_AEAD)
+ continue;
+#else
+ /* v5 AEAD-encrypted session key */
+ size_t taglen = pgp_cipher_aead_tag_len(skey.aalg);
+ size_t ceklen = pgp_key_size(param->aead_hdr.ealg);
+ if (!taglen || !ceklen || (ceklen + taglen != skey.enckeylen)) {
+ RNP_LOG("CEK len/alg mismatch");
+ continue;
+ }
+ alg = skey.alg;
+
+ /* initialize cipher */
+ if (!pgp_cipher_aead_init(&crypt, skey.alg, skey.aalg, keybuf.data(), true)) {
+ continue;
+ }
+
+ /* set additional data */
+ if (!encrypted_sesk_set_ad(&crypt, &skey)) {
+ RNP_LOG("failed to set ad");
+ continue;
+ }
+
+ /* calculate nonce */
+ uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN];
+ size_t noncelen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0);
+
+ /* start cipher, decrypt key and verify tag */
+ keyavail =
+ pgp_cipher_aead_start(&crypt, nonce, noncelen) &&
+ pgp_cipher_aead_finish(&crypt, keybuf.data(), skey.enckey, skey.enckeylen);
+ pgp_cipher_aead_destroy(&crypt);
+
+ /* we have decrypted key so let's start decryption */
+ if (!keyavail) {
+ continue;
+ }
+#endif
+ } else {
+ continue;
+ }
+
+ /* Decrypt header for CFB */
+ if (param->use_cfb() && !encrypted_decrypt_cfb_header(param, alg, keybuf.data())) {
+ continue;
+ }
+ if (!param->use_cfb() &&
+ !encrypted_start_aead(param, param->aead_hdr.ealg, keybuf.data())) {
+ continue;
+ }
+
+ param->salg = param->use_cfb() ? alg : param->aead_hdr.ealg;
+ /* inform handler that we used this symenc */
+ if (param->handler->on_decryption_start) {
+ param->handler->on_decryption_start(NULL, &skey, param->handler->param);
+ }
+ return 1;
+ }
+
+ if (!param->use_cfb() && pgp_block_size(param->aead_hdr.ealg)) {
+ /* we know aead symm alg even if we wasn't able to start decryption */
+ param->salg = param->aead_hdr.ealg;
+ }
+
+ if (!keyavail) {
+ RNP_LOG("no supported sk available");
+ return -1;
+ }
+ return 0;
+}
+
+/** @brief Initialize common to stream packets params, including partial data source */
+static rnp_result_t
+init_packet_params(pgp_source_packet_param_t &param)
+{
+ param.origsrc = NULL;
+
+ /* save packet header */
+ rnp_result_t ret = stream_peek_packet_hdr(param.readsrc, &param.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(&param->z, 0x0, sizeof(param->z));
+ zret =
+ alg == PGP_C_ZIP ? (int) inflateInit2(&param->z, -15) : (int) inflateInit(&param->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(&param->bz, 0x0, sizeof(param->bz));
+ zret = BZ2_bzDecompressInit(&param->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, &param->aead_hdr)) {
+ RNP_LOG("failed to read AEAD header");
+ return RNP_ERROR_READ;
+ }
+
+ /* check AEAD encrypted data packet header */
+ if (param->aead_hdr.version != 1) {
+ RNP_LOG("unknown aead ver: %d", param->aead_hdr.version);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if ((param->aead_hdr.aalg != PGP_AEAD_EAX) && (param->aead_hdr.aalg != PGP_AEAD_OCB)) {
+ RNP_LOG("unknown aead alg: %d", (int) param->aead_hdr.aalg);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (param->aead_hdr.csize > 56) {
+ RNP_LOG("too large chunk size: %d", param->aead_hdr.csize);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (param->aead_hdr.csize > 16) {
+ RNP_LOG("Warning: AEAD chunk bits > 16.");
+ }
+ param->chunklen = 1L << (param->aead_hdr.csize + 6);
+
+ /* build additional data */
+ param->aead_adlen = 13;
+ param->aead_ad[0] = param->pkt.hdr.hdr[0];
+ memcpy(param->aead_ad + 1, hdr, 4);
+ memset(param->aead_ad + 5, 0, 8);
+ } else if (ptype == PGP_PKT_SE_IP_DATA) {
+ uint8_t mdcver;
+ if (!src_read_eq(param->pkt.readsrc, &mdcver, 1)) {
+ return RNP_ERROR_READ;
+ }
+
+ if (mdcver != 1) {
+ RNP_LOG("unknown mdc ver: %d", (int) mdcver);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ param->auth_type = rnp::AuthType::MDC;
+ }
+ param->auth_validated = false;
+
+ return RNP_SUCCESS;
+}
+
+#define MAX_HIDDEN_TRIES 64
+
+static rnp_result_t
+init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc)
+{
+ if (!init_src_common(src, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ pgp_source_encrypted_param_t *param = new (std::nothrow) pgp_source_encrypted_param_t();
+ if (!param) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ src->param = param;
+ param->pkt.readsrc = readsrc;
+ param->handler = handler;
+
+ src->close = encrypted_src_close;
+ src->finish = encrypted_src_finish;
+ src->type = PGP_STREAM_ENCRYPTED;
+
+ /* Read the packet-related information */
+ rnp_result_t errcode = encrypted_read_packet_data(param);
+ if (errcode) {
+ goto finish;
+ }
+
+ src->read = !param->use_cfb() ? encrypted_src_read_aead : encrypted_src_read_cfb;
+
+ /* Obtaining the symmetric key */
+ if (!handler->password_provider) {
+ RNP_LOG("no password provider");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ /* informing handler about the available pubencs/symencs */
+ if (handler->on_recipients) {
+ handler->on_recipients(param->pubencs, param->symencs, handler->param);
+ }
+
+ bool have_key;
+ have_key = false;
+ /* Trying public-key decryption */
+ if (!param->pubencs.empty()) {
+ if (!handler->key_provider) {
+ RNP_LOG("no key provider");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ pgp_key_request_ctx_t keyctx(PGP_OP_DECRYPT, true, PGP_KEY_SEARCH_KEYID);
+
+ size_t pubidx = 0;
+ size_t hidden_tries = 0;
+ errcode = RNP_ERROR_NO_SUITABLE_KEY;
+ while (pubidx < param->pubencs.size()) {
+ auto &pubenc = param->pubencs[pubidx];
+ keyctx.search.by.keyid = pubenc.key_id;
+ /* Get the key if any */
+ pgp_key_t *seckey = pgp_request_key(handler->key_provider, &keyctx);
+ if (!seckey) {
+ pubidx++;
+ continue;
+ }
+ /* Check whether key fits our needs */
+ bool hidden = pubenc.key_id == pgp_key_id_t({});
+ if (!hidden || (++hidden_tries >= MAX_HIDDEN_TRIES)) {
+ pubidx++;
+ }
+ if (!seckey->has_secret() || !seckey->can_encrypt()) {
+ continue;
+ }
+ /* Check whether key is of required algorithm for hidden keyid */
+ if (hidden && seckey->alg() != pubenc.alg) {
+ continue;
+ }
+ /* Decrypt key */
+ rnp::KeyLocker seclock(*seckey);
+ if (!seckey->unlock(*handler->password_provider, PGP_OP_DECRYPT)) {
+ errcode = RNP_ERROR_BAD_PASSWORD;
+ continue;
+ }
+
+ /* Try to initialize the decryption */
+ rnp::LogStop logstop(hidden);
+ if (encrypted_try_key(param, &pubenc, &seckey->pkt(), *handler->ctx->ctx)) {
+ have_key = true;
+ /* inform handler that we used this pubenc */
+ if (handler->on_decryption_start) {
+ handler->on_decryption_start(&pubenc, NULL, handler->param);
+ }
+ break;
+ }
+ }
+ }
+
+ /* Trying password-based decryption */
+ if (!have_key && !param->symencs.empty()) {
+ rnp::secure_array<char, MAX_PASSWORD_LENGTH> password;
+ pgp_password_ctx_t pass_ctx(PGP_OP_DECRYPT_SYM);
+ if (!pgp_request_password(
+ handler->password_provider, &pass_ctx, password.data(), password.size())) {
+ errcode = RNP_ERROR_BAD_PASSWORD;
+ goto finish;
+ }
+
+ int intres = encrypted_try_password(param, password.data());
+ if (intres > 0) {
+ have_key = true;
+ } else if (intres < 0) {
+ errcode = RNP_ERROR_NOT_SUPPORTED;
+ } else {
+ errcode = RNP_ERROR_BAD_PASSWORD;
+ }
+ }
+
+ /* report decryption start to the handler */
+ if (handler->on_decryption_info) {
+ handler->on_decryption_info(param->auth_type == rnp::AuthType::MDC,
+ param->aead_hdr.aalg,
+ param->salg,
+ handler->param);
+ }
+
+ if (!have_key) {
+ RNP_LOG("failed to obtain decrypting key or password");
+ if (!errcode) {
+ errcode = RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ goto finish;
+ }
+ errcode = RNP_SUCCESS;
+finish:
+ if (errcode != RNP_SUCCESS) {
+ src_close(src);
+ }
+ return errcode;
+}
+
+static rnp_result_t
+init_cleartext_signed_src(pgp_source_t *src)
+{
+ char buf[64];
+ size_t hdrlen = strlen(ST_CLEAR_BEGIN);
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+
+ /* checking header line */
+ if (!src_read_eq(param->readsrc, buf, hdrlen)) {
+ RNP_LOG("failed to read header");
+ return RNP_ERROR_READ;
+ }
+
+ if (memcmp(ST_CLEAR_BEGIN, buf, hdrlen)) {
+ RNP_LOG("wrong header");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* eol */
+ if (!src_skip_eol(param->readsrc)) {
+ RNP_LOG("no eol after the cleartext header");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* parsing Hash headers */
+ if (!cleartext_parse_headers(src)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* now we are good to go */
+ param->clr_fline = true;
+ return RNP_SUCCESS;
+}
+
+#define MAX_SIG_ERRORS 65536
+
+static rnp_result_t
+init_signed_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc)
+{
+ rnp_result_t errcode = RNP_ERROR_GENERIC;
+ pgp_source_signed_param_t *param;
+ uint8_t ptag;
+ int ptype;
+ pgp_signature_t * sig = NULL;
+ bool cleartext;
+ size_t sigerrors = 0;
+
+ if (!init_src_common(src, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ try {
+ param = new pgp_source_signed_param_t();
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ src->param = param;
+
+ cleartext = is_cleartext_source(readsrc);
+ param->readsrc = readsrc;
+ param->handler = handler;
+ param->cleartext = cleartext;
+ param->stripped_crs = 0;
+ src->read = cleartext ? cleartext_src_read : signed_src_read;
+ src->close = signed_src_close;
+ src->finish = signed_src_finish;
+ src->type = cleartext ? PGP_STREAM_CLEARTEXT : PGP_STREAM_SIGNED;
+
+ /* we need key provider to validate signatures */
+ if (!handler->key_provider) {
+ RNP_LOG("no key provider");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ if (cleartext) {
+ errcode = init_cleartext_signed_src(src);
+ goto finish;
+ }
+
+ /* Reading one-pass and signature packets */
+ while (true) {
+ /* stop early if we are in zip-bomb with erroneous packets */
+ if (sigerrors >= MAX_SIG_ERRORS) {
+ RNP_LOG("Too many one-pass/signature errors. Stopping.");
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+
+ size_t readb = readsrc->readb;
+ if (!src_peek_eq(readsrc, &ptag, 1)) {
+ RNP_LOG("failed to read packet header");
+ errcode = RNP_ERROR_READ;
+ goto finish;
+ }
+
+ ptype = get_packet_type(ptag);
+
+ if (ptype == PGP_PKT_ONE_PASS_SIG) {
+ if (param->onepasses.size() >= MAX_SIGNATURES) {
+ RNP_LOG("Too many one-pass signatures.");
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ pgp_one_pass_sig_t onepass;
+ try {
+ errcode = onepass.parse(*readsrc);
+ } catch (const std::exception &e) {
+ errcode = RNP_ERROR_GENERIC;
+ }
+ if (errcode) {
+ if (errcode == RNP_ERROR_READ) {
+ goto finish;
+ }
+ if (readb == readsrc->readb) {
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ sigerrors++;
+ continue;
+ }
+
+ try {
+ param->onepasses.push_back(onepass);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ errcode = RNP_ERROR_OUT_OF_MEMORY;
+ goto finish;
+ }
+
+ /* adding hash context */
+ try {
+ add_hash_for_sig(param, onepass.type, onepass.halg);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to create hash %d for onepass %d : %s.",
+ (int) onepass.halg,
+ (int) onepass.type,
+ e.what());
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ if (onepass.nested) {
+ /* despite the name non-zero value means that it is the last one-pass */
+ break;
+ }
+ } else if (ptype == PGP_PKT_SIGNATURE) {
+ /* no need to check the error here - we already know tag */
+ if (signed_read_single_signature(param, readsrc, &sig)) {
+ sigerrors++;
+ }
+ /* adding hash context */
+ if (sig) {
+ try {
+ add_hash_for_sig(param, sig->type(), sig->halg);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to create hash %d for sig %d : %s.",
+ (int) sig->halg,
+ (int) sig->type(),
+ e.what());
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+ }
+ } else {
+ break;
+ }
+
+ /* check if we are not it endless loop */
+ if (readb == readsrc->readb) {
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ /* for detached signature we'll get eof */
+ if (src_eof(readsrc)) {
+ param->detached = true;
+ break;
+ }
+ }
+
+ /* checking what we have now */
+ if (param->onepasses.empty() && param->sigs.empty()) {
+ RNP_LOG("no signatures");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+ if (!param->onepasses.empty() && !param->sigs.empty()) {
+ RNP_LOG("warning: one-passes are mixed with signatures");
+ }
+
+ errcode = RNP_SUCCESS;
+finish:
+ if (errcode != RNP_SUCCESS) {
+ src_close(src);
+ }
+
+ return errcode;
+}
+
+pgp_processing_ctx_t::~pgp_processing_ctx_t()
+{
+ for (auto &src : sources) {
+ src_close(&src);
+ }
+}
+
+/** @brief build PGP source sequence down to the literal data packet
+ *
+ **/
+static rnp_result_t
+init_packet_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src)
+{
+ pgp_source_t *lsrc = &src;
+ size_t srcnum = ctx.sources.size();
+
+ while (1) {
+ uint8_t ptag = 0;
+ if (!src_peek_eq(lsrc, &ptag, 1)) {
+ RNP_LOG("cannot read packet tag");
+ return RNP_ERROR_READ;
+ }
+
+ int type = get_packet_type(ptag);
+ if (type < 0) {
+ RNP_LOG("wrong pkt tag %d", (int) ptag);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ if (ctx.sources.size() - srcnum == MAXIMUM_NESTING_LEVEL) {
+ RNP_LOG("Too many nested OpenPGP packets");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ pgp_source_t psrc = {};
+ rnp_result_t ret = RNP_ERROR_BAD_FORMAT;
+ switch (type) {
+ case PGP_PKT_PK_SESSION_KEY:
+ case PGP_PKT_SK_SESSION_KEY:
+ ret = init_encrypted_src(&ctx.handler, &psrc, lsrc);
+ break;
+ case PGP_PKT_ONE_PASS_SIG:
+ case PGP_PKT_SIGNATURE:
+ ret = init_signed_src(&ctx.handler, &psrc, lsrc);
+ break;
+ case PGP_PKT_COMPRESSED:
+ ret = init_compressed_src(&psrc, lsrc);
+ break;
+ case PGP_PKT_LITDATA:
+ if ((lsrc != &src) && (lsrc->type != PGP_STREAM_ENCRYPTED) &&
+ (lsrc->type != PGP_STREAM_SIGNED) && (lsrc->type != PGP_STREAM_COMPRESSED)) {
+ RNP_LOG("unexpected literal pkt");
+ ret = RNP_ERROR_BAD_FORMAT;
+ break;
+ }
+ ret = init_literal_src(&psrc, lsrc);
+ break;
+ case PGP_PKT_MARKER:
+ if (ctx.sources.size() != srcnum) {
+ RNP_LOG("Warning: marker packet wrapped in pgp stream.");
+ }
+ ret = stream_parse_marker(*lsrc);
+ if (ret) {
+ RNP_LOG("Invalid marker packet");
+ return ret;
+ }
+ continue;
+ default:
+ RNP_LOG("unexpected pkt %d", type);
+ ret = RNP_ERROR_BAD_FORMAT;
+ }
+
+ if (ret) {
+ return ret;
+ }
+
+ try {
+ ctx.sources.push_back(psrc);
+ lsrc = &ctx.sources.back();
+ } catch (const std::exception &e) {
+ src_close(&psrc);
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (lsrc->type == PGP_STREAM_LITERAL) {
+ ctx.literal_src = lsrc;
+ ctx.msg_type = PGP_MESSAGE_NORMAL;
+ return RNP_SUCCESS;
+ }
+ if (lsrc->type == PGP_STREAM_SIGNED) {
+ ctx.signed_src = lsrc;
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) lsrc->param;
+ if (param->detached) {
+ ctx.msg_type = PGP_MESSAGE_DETACHED;
+ return RNP_SUCCESS;
+ }
+ }
+ }
+}
+
+static rnp_result_t
+init_cleartext_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src)
+{
+ pgp_source_t clrsrc = {};
+ rnp_result_t res;
+
+ if ((res = init_signed_src(&ctx.handler, &clrsrc, &src))) {
+ return res;
+ }
+ try {
+ ctx.sources.push_back(clrsrc);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ src_close(&clrsrc);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+init_armored_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src)
+{
+ pgp_source_t armorsrc = {};
+ rnp_result_t res;
+
+ if ((res = init_armored_src(&armorsrc, &src))) {
+ return res;
+ }
+
+ try {
+ ctx.sources.push_back(armorsrc);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ src_close(&armorsrc);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return init_packet_sequence(ctx, ctx.sources.back());
+}
+
+rnp_result_t
+process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src)
+{
+ rnp_result_t res = RNP_ERROR_BAD_FORMAT;
+ rnp_result_t fres;
+ pgp_processing_ctx_t ctx = {};
+ pgp_source_t * decsrc = NULL;
+ pgp_source_t datasrc = {0};
+ pgp_dest_t * outdest = NULL;
+ bool closeout = true;
+ uint8_t * readbuf = NULL;
+
+ ctx.handler = *handler;
+ /* Building readers sequence. Checking whether it is binary data */
+ if (is_pgp_source(src)) {
+ res = init_packet_sequence(ctx, src);
+ } else {
+ /* Trying armored or cleartext data */
+ if (is_cleartext_source(&src)) {
+ /* Initializing cleartext message */
+ res = init_cleartext_sequence(ctx, src);
+ } else if (is_armored_source(&src)) {
+ /* Initializing armored message */
+ res = init_armored_sequence(ctx, src);
+ } else {
+ RNP_LOG("not an OpenPGP data provided");
+ res = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ }
+
+ if (res != RNP_SUCCESS) {
+ goto finish;
+ }
+
+ if ((readbuf = (uint8_t *) calloc(1, PGP_INPUT_CACHE_SIZE)) == NULL) {
+ RNP_LOG("allocation failure");
+ res = RNP_ERROR_OUT_OF_MEMORY;
+ goto finish;
+ }
+
+ if (ctx.msg_type == PGP_MESSAGE_DETACHED) {
+ /* detached signature case */
+ if (!handler->ctx->detached) {
+ RNP_LOG("Unexpected detached signature input.");
+ res = RNP_ERROR_BAD_STATE;
+ goto finish;
+ }
+ if (!handler->src_provider || !handler->src_provider(handler, &datasrc)) {
+ RNP_LOG("no data source for detached signature verification");
+ res = RNP_ERROR_READ;
+ goto finish;
+ }
+
+ while (!datasrc.eof) {
+ size_t read = 0;
+ if (!src_read(&datasrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (read > 0) {
+ signed_src_update(ctx.signed_src, readbuf, read);
+ }
+ }
+ src_close(&datasrc);
+ } else {
+ if (handler->ctx->detached) {
+ RNP_LOG("Detached signature expected.");
+ res = RNP_ERROR_BAD_STATE;
+ goto finish;
+ }
+ /* file processing case */
+ decsrc = &ctx.sources.back();
+ char * filename = NULL;
+ uint32_t mtime = 0;
+
+ if (ctx.literal_src) {
+ auto *param = static_cast<pgp_source_literal_param_t *>(ctx.literal_src->param);
+ filename = param->hdr.fname;
+ mtime = param->hdr.timestamp;
+ }
+
+ if (!handler->dest_provider ||
+ !handler->dest_provider(handler, &outdest, &closeout, filename, mtime)) {
+ res = RNP_ERROR_WRITE;
+ goto finish;
+ }
+
+ /* reading the input */
+ while (!decsrc->eof) {
+ size_t read = 0;
+ if (!src_read(decsrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (!read) {
+ continue;
+ }
+ if (ctx.signed_src) {
+ signed_src_update(ctx.signed_src, readbuf, read);
+ }
+ dst_write(outdest, readbuf, read);
+ if (outdest->werr != RNP_SUCCESS) {
+ RNP_LOG("failed to output data");
+ res = RNP_ERROR_WRITE;
+ break;
+ }
+ }
+ }
+
+ /* finalizing the input. Signatures are checked on this step */
+ if (res == RNP_SUCCESS) {
+ for (auto &ctxsrc : ctx.sources) {
+ fres = src_finish(&ctxsrc);
+ if (fres) {
+ res = fres;
+ }
+ }
+ }
+
+ if (closeout && (ctx.msg_type != PGP_MESSAGE_DETACHED)) {
+ dst_close(outdest, res != RNP_SUCCESS);
+ }
+
+finish:
+ free(readbuf);
+ return res;
+}
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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#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<pgp_signature_info_t> &sigs, void *param);
+
+typedef void pgp_on_recipients_func_t(const std::vector<pgp_pk_sesskey_t> &recipients,
+ const std::vector<pgp_sk_sesskey_t> &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 <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <type_traits>
+#include <stdexcept>
+#include <rnp/rnp_def.h>
+#include "types.h"
+#include "stream-sig.h"
+#include "stream-packet.h"
+#include "stream-armor.h"
+#include "pgp-key.h"
+#include "crypto/signatures.h"
+
+#include <time.h>
+
+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<rnp::Hash>
+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<rnp::Hash>
+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<rnp::Hash>
+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<decltype(res)>::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<decltype(res)>::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<std::remove_reference<decltype(id)>::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<uint8_t>
+pgp_signature_t::preferred(pgp_sig_subpacket_type_t type) const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(type);
+ return subpkt ? std::vector<uint8_t>(subpkt->fields.preferred.arr,
+ subpkt->fields.preferred.arr +
+ subpkt->fields.preferred.len) :
+ std::vector<uint8_t>();
+}
+
+void
+pgp_signature_t::set_preferred(const std::vector<uint8_t> &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<uint8_t>
+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<uint8_t> &algs)
+{
+ set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_SKA);
+}
+
+std::vector<uint8_t>
+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<uint8_t> &algs)
+{
+ set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_HASH);
+}
+
+std::vector<uint8_t>
+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<uint8_t> &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<uint8_t> &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<uint8_t>(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<decltype(signer)>::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<uint8_t> 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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "stream-packet.h"
+
+typedef struct pgp_signature_t {
+ private:
+ pgp_sig_type_t type_;
+ std::vector<uint8_t> preferred(pgp_sig_subpacket_type_t type) const;
+ void set_preferred(const std::vector<uint8_t> &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<pgp_sig_subpkt_t> 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<uint8_t> 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<uint8_t> &algs);
+
+ /** @brief Get preferred hash algorithms if any. If there are no ones then empty vector is
+ * returned.*/
+ std::vector<uint8_t> 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<uint8_t> &algs);
+
+ /** @brief Get preferred compression algorithms if any. If there are no ones then empty
+ * vector is returned.*/
+ std::vector<uint8_t> 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<uint8_t> &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<uint8_t> &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_t> 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<rnp::Hash> signature_hash_certification(const pgp_signature_t & sig,
+ const pgp_key_pkt_t & key,
+ const pgp_userid_pkt_t &userid);
+
+std::unique_ptr<rnp::Hash> signature_hash_binding(const pgp_signature_t &sig,
+ const pgp_key_pkt_t & key,
+ const pgp_key_pkt_t & subkey);
+
+std::unique_ptr<rnp::Hash> 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 <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <sys/param.h>
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif
+#ifdef HAVE_BZLIB_H
+#include <bzlib.h>
+#endif
+#include <rnp/rnp_def.h>
+#include "stream-def.h"
+#include "stream-ctx.h"
+#include "stream-write.h"
+#include "stream-packet.h"
+#include "stream-armor.h"
+#include "stream-sig.h"
+#include "pgp-key.h"
+#include "fingerprint.h"
+#include "types.h"
+#include "crypto/signatures.h"
+#include "defaults.h"
+#include <time.h>
+#include <algorithm>
+
+/* 8192 bytes, as GnuPG */
+#define PGP_PARTIAL_PKT_SIZE_BITS (13)
+#define PGP_PARTIAL_PKT_BLOCK_SIZE (1 << PGP_PARTIAL_PKT_SIZE_BITS)
+
+/* common fields for encrypted, compressed and literal data */
+typedef struct pgp_dest_packet_param_t {
+ pgp_dest_t *writedst; /* destination to write to, could be partial */
+ pgp_dest_t *origdst; /* original dest passed to init_*_dst */
+ bool partial; /* partial length packet */
+ bool indeterminate; /* indeterminate length packet */
+ int tag; /* packet tag */
+ uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* header, including length, as it was written */
+ size_t hdrlen; /* number of bytes in hdr */
+} pgp_dest_packet_param_t;
+
+typedef struct pgp_dest_compressed_param_t {
+ pgp_dest_packet_param_t pkt;
+ pgp_compression_type_t alg;
+ union {
+ z_stream z;
+ bz_stream bz;
+ };
+ bool zstarted; /* whether we initialize zlib/bzip2 */
+ uint8_t cache[PGP_INPUT_CACHE_SIZE / 2]; /* pre-allocated cache for compression */
+ size_t len; /* number of bytes cached */
+} pgp_dest_compressed_param_t;
+
+typedef struct pgp_dest_encrypted_param_t {
+ pgp_dest_packet_param_t pkt; /* underlying packet-related params */
+ rnp_ctx_t * ctx; /* rnp operation context with additional parameters */
+ rnp::AuthType auth_type; /* Authentication type: MDC, AEAD or none */
+ pgp_crypt_t encrypt; /* encrypting crypto */
+ std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */
+ pgp_aead_alg_t aalg; /* AEAD algorithm used */
+ uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* iv for AEAD mode */
+ uint8_t ad[PGP_AEAD_MAX_AD_LEN]; /* additional data for AEAD mode */
+ size_t adlen; /* length of additional data, including chunk idx */
+ size_t chunklen; /* length of the AEAD chunk in bytes */
+ size_t chunkout; /* how many bytes from the chunk were written out */
+ size_t chunkidx; /* index of the current AEAD chunk */
+ size_t cachelen; /* how many bytes are in cache, for AEAD */
+ uint8_t cache[PGP_AEAD_CACHE_LEN]; /* pre-allocated cache for encryption */
+} pgp_dest_encrypted_param_t;
+
+typedef struct pgp_dest_signer_info_t {
+ pgp_one_pass_sig_t onepass;
+ pgp_key_t * key;
+ pgp_hash_alg_t halg;
+ int64_t sigcreate;
+ uint64_t sigexpire;
+} pgp_dest_signer_info_t;
+
+typedef struct pgp_dest_signed_param_t {
+ pgp_dest_t * writedst; /* destination to write to */
+ rnp_ctx_t * ctx; /* rnp operation context with additional parameters */
+ pgp_password_provider_t *password_provider; /* password provider from write handler */
+ std::vector<pgp_dest_signer_info_t> siginfos; /* list of pgp_dest_signer_info_t */
+ rnp::HashList hashes; /* hashes to pass raw data through and then sign */
+ bool clr_start; /* we are on the start of the line */
+ uint8_t clr_buf[CT_BUF_LEN]; /* buffer to hold partial line data */
+ size_t clr_buflen; /* number of bytes in buffer */
+
+ pgp_dest_signed_param_t() = default;
+ ~pgp_dest_signed_param_t() = default;
+} pgp_dest_signed_param_t;
+
+typedef struct pgp_dest_partial_param_t {
+ pgp_dest_t *writedst;
+ uint8_t part[PGP_PARTIAL_PKT_BLOCK_SIZE];
+ uint8_t parthdr; /* header byte for the current part */
+ size_t partlen; /* length of the current part, up to PARTIAL_PKT_BLOCK_SIZE */
+ size_t len; /* bytes cached in part */
+} pgp_dest_partial_param_t;
+
+static rnp_result_t
+partial_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param;
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (len > param->partlen - param->len) {
+ /* we have full part - in block and in buf */
+ size_t wrlen = param->partlen - param->len;
+ dst_write(param->writedst, &param->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, &param->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(&param->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, &param->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, &param->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(&param->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(
+ &param->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(&param->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(&param->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(&param->encrypt, nonce, nlen);
+
+ /* write final authentication tag */
+ if (last) {
+ res = res && pgp_cipher_aead_finish(&param->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(&param->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(&param->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(&param->encrypt);
+#endif
+ if (res) {
+ finish_streamed_packet(&param->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(&param->encrypt, mdcbuf, mdcbuf, MDC_V1_SIZE);
+ dst_write(param->pkt.writedst, mdcbuf, MDC_V1_SIZE);
+ }
+
+ return finish_streamed_packet(&param->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(&param->encrypt);
+#endif
+ } else {
+ pgp_cipher_cfb_finish(&param->encrypt);
+ }
+ close_streamed_packet(&param->pkt, discard);
+ delete param;
+ dst->param = NULL;
+}
+
+static rnp_result_t
+encrypted_add_recipient(pgp_write_handler_t *handler,
+ pgp_dest_t * dst,
+ pgp_key_t * userkey,
+ const uint8_t * key,
+ const unsigned keylen)
+{
+ pgp_pk_sesskey_t pkey;
+ pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ /* Use primary key if good for encryption, otherwise look in subkey list */
+ userkey = find_suitable_key(PGP_OP_ENCRYPT, userkey, handler->key_provider);
+ if (!userkey) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+
+ /* Fill pkey */
+ pkey.version = PGP_PKSK_V3;
+ pkey.alg = userkey->alg();
+ pkey.key_id = userkey->keyid();
+
+ /* Encrypt the session key */
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 3> enckey;
+ enckey[0] = param->ctx->ealg;
+ memcpy(&enckey[1], key, keylen);
+
+ /* Calculate checksum */
+ rnp::secure_array<unsigned, 1> checksum;
+
+ for (unsigned i = 1; i <= keylen; i++) {
+ checksum[0] += enckey[i];
+ }
+ enckey[keylen + 1] = (checksum[0] >> 8) & 0xff;
+ enckey[keylen + 2] = checksum[0] & 0xff;
+
+ pgp_encrypted_material_t material;
+
+ switch (userkey->alg()) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY: {
+ ret = rsa_encrypt_pkcs1(&handler->ctx->ctx->rng,
+ &material.rsa,
+ enckey.data(),
+ keylen + 3,
+ &userkey->material().rsa);
+ if (ret) {
+ RNP_LOG("rsa_encrypt_pkcs1 failed");
+ return ret;
+ }
+ break;
+ }
+ case PGP_PKA_SM2: {
+#if defined(ENABLE_SM2)
+ ret = sm2_encrypt(&handler->ctx->ctx->rng,
+ &material.sm2,
+ enckey.data(),
+ keylen + 3,
+ PGP_HASH_SM3,
+ &userkey->material().ec);
+ if (ret) {
+ RNP_LOG("sm2_encrypt failed");
+ return ret;
+ }
+ break;
+#else
+ RNP_LOG("sm2_encrypt is not available");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+#endif
+ }
+ case PGP_PKA_ECDH: {
+ if (!curve_supported(userkey->material().ec.curve)) {
+ RNP_LOG("ECDH encrypt: curve %d is not supported.",
+ (int) userkey->material().ec.curve);
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ ret = ecdh_encrypt_pkcs5(&handler->ctx->ctx->rng,
+ &material.ecdh,
+ enckey.data(),
+ keylen + 3,
+ &userkey->material().ec,
+ userkey->fp());
+ if (ret) {
+ RNP_LOG("ECDH encryption failed %d", ret);
+ return ret;
+ }
+ break;
+ }
+ case PGP_PKA_ELGAMAL: {
+ ret = elgamal_encrypt_pkcs1(&handler->ctx->ctx->rng,
+ &material.eg,
+ enckey.data(),
+ keylen + 3,
+ &userkey->material().eg);
+ if (ret) {
+ RNP_LOG("pgp_elgamal_public_encrypt failed");
+ return ret;
+ }
+ break;
+ }
+ default:
+ RNP_LOG("unsupported alg: %d", (int) userkey->alg());
+ return ret;
+ }
+
+ /* Writing symmetric key encrypted session key packet */
+ try {
+ pkey.write_material(material);
+ pkey.write(*param->pkt.origdst);
+ return param->pkt.origdst->werr;
+ } catch (const std::exception &e) {
+ return RNP_ERROR_WRITE;
+ }
+}
+
+#if defined(ENABLE_AEAD)
+static bool
+encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey)
+{
+ uint8_t ad_data[4];
+
+ ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT;
+ ad_data[1] = skey->version;
+ ad_data[2] = skey->alg;
+ ad_data[3] = skey->aalg;
+
+ return pgp_cipher_aead_set_ad(crypt, ad_data, 4);
+}
+#endif
+
+static rnp_result_t
+encrypted_add_password(rnp_symmetric_pass_info_t * pass,
+ pgp_dest_encrypted_param_t *param,
+ uint8_t * key,
+ const unsigned keylen,
+ bool singlepass)
+{
+ pgp_sk_sesskey_t skey = {};
+ pgp_crypt_t kcrypt;
+
+ skey.s2k = pass->s2k;
+
+ if (param->auth_type != rnp::AuthType::AEADv1) {
+ skey.version = PGP_SKSK_V4;
+ if (singlepass) {
+ /* if there are no public keys then we do not encrypt session key in the packet */
+ skey.alg = param->ctx->ealg;
+ skey.enckeylen = 0;
+ memcpy(key, pass->key.data(), keylen);
+ } else {
+ /* We may use different algo for CEK and KEK */
+ skey.enckeylen = keylen + 1;
+ skey.enckey[0] = param->ctx->ealg;
+ memcpy(&skey.enckey[1], key, keylen);
+ skey.alg = pass->s2k_cipher;
+ if (!pgp_cipher_cfb_start(&kcrypt, skey.alg, pass->key.data(), NULL)) {
+ RNP_LOG("key encryption failed");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_cipher_cfb_encrypt(&kcrypt, skey.enckey, skey.enckey, skey.enckeylen);
+ pgp_cipher_cfb_finish(&kcrypt);
+ }
+ } else {
+#if !defined(ENABLE_AEAD)
+ RNP_LOG("AEAD support is not enabled.");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+#else
+ /* AEAD-encrypted v5 packet */
+ if ((param->ctx->aalg != PGP_AEAD_EAX) && (param->ctx->aalg != PGP_AEAD_OCB)) {
+ RNP_LOG("unsupported AEAD algorithm");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ skey.version = PGP_SKSK_V5;
+ skey.alg = pass->s2k_cipher;
+ skey.aalg = param->ctx->aalg;
+ skey.ivlen = pgp_cipher_aead_nonce_len(skey.aalg);
+ skey.enckeylen = keylen + pgp_cipher_aead_tag_len(skey.aalg);
+
+ try {
+ param->ctx->ctx->rng.get(skey.iv, skey.ivlen);
+ } catch (const std::exception &e) {
+ return RNP_ERROR_RNG;
+ }
+
+ /* initialize cipher */
+ if (!pgp_cipher_aead_init(&kcrypt, skey.alg, skey.aalg, pass->key.data(), false)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* set additional data */
+ if (!encrypted_sesk_set_ad(&kcrypt, &skey)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* calculate nonce */
+ uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN];
+ size_t nlen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0);
+
+ /* start cipher, encrypt key and get tag */
+ bool res = pgp_cipher_aead_start(&kcrypt, nonce, nlen) &&
+ pgp_cipher_aead_finish(&kcrypt, skey.enckey, key, keylen);
+
+ pgp_cipher_aead_destroy(&kcrypt);
+
+ if (!res) {
+ return RNP_ERROR_BAD_STATE;
+ }
+#endif
+ }
+
+ /* Writing symmetric key encrypted session key packet */
+ try {
+ skey.write(*param->pkt.origdst);
+ } catch (const std::exception &e) {
+ return RNP_ERROR_WRITE;
+ }
+ return param->pkt.origdst->werr;
+}
+
+static rnp_result_t
+encrypted_start_cfb(pgp_dest_encrypted_param_t *param, uint8_t *enckey)
+{
+ uint8_t mdcver = 1;
+ uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; /* encrypted header */
+ unsigned blsize;
+
+ if (param->auth_type == rnp::AuthType::MDC) {
+ /* initializing the mdc */
+ dst_write(param->pkt.writedst, &mdcver, 1);
+
+ try {
+ param->mdc = rnp::Hash::create(PGP_HASH_SHA1);
+ } catch (const std::exception &e) {
+ RNP_LOG("cannot create sha1 hash: %s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+ }
+
+ /* initializing the crypto */
+ if (!pgp_cipher_cfb_start(&param->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(&param->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(&param->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(
+ &param->encrypt, param->ctx->ealg, param->ctx->aalg, enckey, false)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ return encrypted_start_aead_chunk(param, 0, false);
+#endif
+}
+
+static rnp_result_t
+init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst)
+{
+ pgp_dest_encrypted_param_t *param;
+ bool singlepass = true;
+ unsigned pkeycount = 0;
+ unsigned skeycount = 0;
+ unsigned keylen;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ keylen = pgp_key_size(handler->ctx->ealg);
+ if (!keylen) {
+ RNP_LOG("unknown symmetric algorithm");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (handler->ctx->aalg) {
+ if ((handler->ctx->aalg != PGP_AEAD_EAX) && (handler->ctx->aalg != PGP_AEAD_OCB)) {
+ RNP_LOG("unknown AEAD algorithm: %d", (int) handler->ctx->aalg);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if ((pgp_block_size(handler->ctx->ealg) != 16)) {
+ RNP_LOG("wrong AEAD symmetric algorithm");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if ((handler->ctx->abits < 0) || (handler->ctx->abits > 16)) {
+ RNP_LOG("wrong AEAD chunk bits: %d", handler->ctx->abits);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+
+ if (!init_dst_common(dst, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ try {
+ param = new pgp_dest_encrypted_param_t();
+ dst->param = param;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ param->auth_type =
+ handler->ctx->aalg == PGP_AEAD_NONE ? rnp::AuthType::MDC : rnp::AuthType::AEADv1;
+ param->aalg = handler->ctx->aalg;
+ param->ctx = handler->ctx;
+ param->pkt.origdst = writedst;
+ dst->write = param->auth_type == rnp::AuthType::AEADv1 ? encrypted_dst_write_aead :
+ encrypted_dst_write_cfb;
+ dst->finish = encrypted_dst_finish;
+ dst->close = encrypted_dst_close;
+ dst->type = PGP_STREAM_ENCRYPTED;
+
+ pkeycount = handler->ctx->recipients.size();
+ skeycount = handler->ctx->passwords.size();
+
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> enckey; /* content encryption key */
+ if (!pkeycount && !skeycount) {
+ RNP_LOG("no recipients");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ if ((pkeycount > 0) || (skeycount > 1) || (param->auth_type == rnp::AuthType::AEADv1)) {
+ try {
+ handler->ctx->ctx->rng.get(enckey.data(), keylen);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_RNG;
+ goto finish;
+ }
+ singlepass = false;
+ }
+
+ /* Configuring and writing pk-encrypted session keys */
+ for (auto recipient : handler->ctx->recipients) {
+ ret = encrypted_add_recipient(handler, dst, recipient, enckey.data(), keylen);
+ if (ret) {
+ goto finish;
+ }
+ }
+
+ /* Configuring and writing sk-encrypted session key(s) */
+ for (auto &passinfo : handler->ctx->passwords) {
+ ret = encrypted_add_password(&passinfo, param, enckey.data(), keylen, singlepass);
+ if (ret != RNP_SUCCESS) {
+ goto finish;
+ }
+ }
+
+ /* Initializing partial packet writer */
+ param->pkt.partial = true;
+ param->pkt.indeterminate = false;
+ if (param->auth_type == rnp::AuthType::AEADv1) {
+ param->pkt.tag = PGP_PKT_AEAD_ENCRYPTED;
+ } else {
+ /* We do not generate PGP_PKT_SE_DATA, leaving this just in case */
+ param->pkt.tag =
+ param->auth_type == rnp::AuthType::MDC ? PGP_PKT_SE_IP_DATA : PGP_PKT_SE_DATA;
+ }
+
+ /* initializing partial data length writer */
+ /* we may use intederminate len packet here as well, for compatibility or so on */
+ if (!init_streamed_packet(&param->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 &param,
+ 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 == &param->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 != &param->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(&param->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(&param->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(&param->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(&param->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(&param->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(&param->z);
+ }
+#ifdef HAVE_BZLIB_H
+ if (param->alg == PGP_C_BZIP2) {
+ BZ2_bzCompressEnd(&param->bz);
+ }
+#endif
+ }
+
+ close_streamed_packet(&param->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(&param->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(&param->z, 0x0, sizeof(param->z));
+ if (param->alg == PGP_C_ZIP) {
+ zret = deflateInit2(
+ &param->z, handler->ctx->zlevel, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
+ } else {
+ zret = deflateInit(&param->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(&param->bz, 0x0, sizeof(param->bz));
+ zret = BZ2_bzCompressInit(&param->bz, handler->ctx->zlevel, 0, 0);
+ if (zret != BZ_OK) {
+ RNP_LOG("failed to init bz, error %d", zret);
+ ret = RNP_ERROR_NOT_SUPPORTED;
+ goto finish;
+ }
+ break;
+#endif
+ default:
+ RNP_LOG("unknown compression algorithm");
+ ret = RNP_ERROR_NOT_SUPPORTED;
+ goto finish;
+ }
+ param->zstarted = true;
+ ret = RNP_SUCCESS;
+finish:
+ if (ret != RNP_SUCCESS) {
+ compressed_dst_close(dst, true);
+ }
+
+ return ret;
+}
+
+static rnp_result_t
+literal_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param;
+
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ dst_write(param->writedst, buf, len);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+literal_dst_finish(pgp_dest_t *dst)
+{
+ return finish_streamed_packet((pgp_dest_packet_param_t *) dst->param);
+}
+
+static void
+literal_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param;
+
+ if (!param) {
+ return;
+ }
+
+ close_streamed_packet(param, discard);
+ free(param);
+ dst->param = NULL;
+}
+
+static rnp_result_t
+init_literal_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst)
+{
+ pgp_dest_packet_param_t *param;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ size_t flen = 0;
+ uint8_t buf[4];
+
+ if (!init_dst_common(dst, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_dest_packet_param_t *) dst->param;
+ dst->write = literal_dst_write;
+ dst->finish = literal_dst_finish;
+ dst->close = literal_dst_close;
+ dst->type = PGP_STREAM_LITERAL;
+ param->partial = true;
+ param->indeterminate = false;
+ param->tag = PGP_PKT_LITDATA;
+
+ /* initializing partial length or indeterminate packet, writing header */
+ if (!init_streamed_packet(param, writedst)) {
+ RNP_LOG("failed to init streamed packet");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+ /* content type - forcing binary now */
+ buf[0] = (uint8_t) 'b';
+ /* filename */
+ flen = handler->ctx->filename.size();
+ if (flen > 255) {
+ RNP_LOG("filename too long, truncating");
+ flen = 255;
+ }
+ buf[1] = (uint8_t) flen;
+ dst_write(param->writedst, buf, 2);
+ if (flen) {
+ dst_write(param->writedst, handler->ctx->filename.c_str(), flen);
+ }
+ /* timestamp */
+ STORE32BE(buf, handler->ctx->filemtime);
+ dst_write(param->writedst, buf, 4);
+ ret = RNP_SUCCESS;
+finish:
+ if (ret != RNP_SUCCESS) {
+ literal_dst_close(dst, true);
+ }
+
+ return ret;
+}
+
+static rnp_result_t
+process_stream_sequence(pgp_source_t *src,
+ pgp_dest_t * streams,
+ unsigned count,
+ pgp_dest_t * sstream,
+ pgp_dest_t * wstream)
+{
+ std::unique_ptr<uint8_t[]> readbuf(new (std::nothrow) uint8_t[PGP_INPUT_CACHE_SIZE]);
+ if (!readbuf) {
+ RNP_LOG("allocation failure");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ /* processing source stream */
+ while (!src->eof) {
+ size_t read = 0;
+ if (!src_read(src, readbuf.get(), PGP_INPUT_CACHE_SIZE, &read)) {
+ RNP_LOG("failed to read from source");
+ return RNP_ERROR_READ;
+ } else if (!read) {
+ continue;
+ }
+
+ if (sstream) {
+ signed_dst_update(sstream, readbuf.get(), read);
+ }
+
+ if (wstream) {
+ dst_write(wstream, readbuf.get(), read);
+
+ for (int i = count - 1; i >= 0; i--) {
+ if (streams[i].werr) {
+ RNP_LOG("failed to process data");
+ return RNP_ERROR_WRITE;
+ }
+ }
+ }
+ }
+
+ /* finalizing destinations */
+ for (int i = count - 1; i >= 0; i--) {
+ rnp_result_t ret = dst_finish(&streams[i]);
+ if (ret) {
+ RNP_LOG("failed to finish stream");
+ return ret;
+ }
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst)
+{
+ /* stack of the streams would be as following:
+ [armoring stream] - if armoring is enabled
+ [compressing stream, partial writing stream] - compression is enabled, and not detached
+ signing stream
+ literal data stream, partial writing stream - if not detached or cleartext signature
+ */
+ pgp_dest_t dests[4];
+ unsigned destc = 0;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ rnp_ctx_t & ctx = *handler->ctx;
+ pgp_dest_t * wstream = NULL;
+ pgp_dest_t * sstream = NULL;
+
+ /* pushing armoring stream, which will write to the output */
+ if (ctx.armor && !ctx.clearsign) {
+ pgp_armored_msg_t msgt = ctx.detached ? PGP_ARMORED_SIGNATURE : PGP_ARMORED_MESSAGE;
+ ret = init_armored_dst(&dests[destc], dst, msgt);
+ if (ret) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* if compression is enabled then pushing compressing stream */
+ if (!ctx.detached && !ctx.clearsign && (ctx.zlevel > 0)) {
+ if ((ret =
+ init_compressed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* pushing signing stream, which will use handler->ctx to distinguish between
+ * attached/detached/cleartext signature */
+ if ((ret = init_signed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) {
+ goto finish;
+ }
+ if (!ctx.clearsign) {
+ sstream = &dests[destc];
+ }
+ if (!ctx.detached) {
+ wstream = &dests[destc];
+ }
+ destc++;
+
+ /* pushing literal data stream, if not detached/cleartext signature */
+ if (!ctx.no_wrap && !ctx.detached && !ctx.clearsign) {
+ if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) {
+ goto finish;
+ }
+ wstream = &dests[destc];
+ destc++;
+ }
+
+ /* process source with streams stack */
+ ret = process_stream_sequence(src, dests, destc, sstream, wstream);
+finish:
+ for (int i = destc - 1; i >= 0; i--) {
+ dst_close(&dests[i], ret);
+ }
+ return ret;
+}
+
+rnp_result_t
+rnp_encrypt_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst)
+{
+ /* stack of the streams would be as following:
+ [armoring stream] - if armoring is enabled
+ [encrypting stream, partial writing stream]
+ [compressing stream, partial writing stream] - compression is enabled
+ signing stream
+ literal data stream, partial writing stream
+ */
+ pgp_dest_t dests[5];
+ size_t destc = 0;
+ rnp_result_t ret = RNP_SUCCESS;
+ rnp_ctx_t & ctx = *handler->ctx;
+ pgp_dest_t * sstream = NULL;
+
+ /* we may use only attached signatures here */
+ if (ctx.clearsign || ctx.detached) {
+ RNP_LOG("cannot clearsign or sign detached together with encryption");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* pushing armoring stream, which will write to the output */
+ if (ctx.armor) {
+ if ((ret = init_armored_dst(&dests[destc], dst, PGP_ARMORED_MESSAGE))) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* pushing encrypting stream, which will write to the output or armoring stream */
+ if ((ret = init_encrypted_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) {
+ goto finish;
+ }
+ destc++;
+
+ /* if compression is enabled then pushing compressing stream */
+ if (ctx.zlevel > 0) {
+ if ((ret = init_compressed_dst(handler, &dests[destc], &dests[destc - 1]))) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* pushing signing stream if we have signers */
+ if (!ctx.signers.empty()) {
+ if ((ret = init_signed_dst(handler, &dests[destc], &dests[destc - 1]))) {
+ goto finish;
+ }
+ sstream = &dests[destc];
+ destc++;
+ }
+
+ /* pushing literal data stream */
+ if (!ctx.no_wrap) {
+ if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* process source with streams stack */
+ ret = process_stream_sequence(src, dests, destc, sstream, &dests[destc - 1]);
+finish:
+ for (size_t i = destc; i > 0; i--) {
+ dst_close(&dests[i - 1], ret);
+ }
+ return ret;
+}
+
+rnp_result_t
+rnp_compress_src(pgp_source_t &src, pgp_dest_t &dst, pgp_compression_type_t zalg, int zlevel)
+{
+ pgp_write_handler_t handler = {};
+ rnp_ctx_t ctx;
+ ctx.zalg = zalg;
+ ctx.zlevel = zlevel;
+ handler.ctx = &ctx;
+
+ pgp_dest_t compressed = {};
+ rnp_result_t ret = init_compressed_dst(&handler, &compressed, &dst);
+ if (ret) {
+ goto done;
+ }
+ ret = dst_write_src(&src, &compressed);
+done:
+ dst_close(&compressed, ret);
+ return ret;
+}
+
+rnp_result_t
+rnp_wrap_src(pgp_source_t &src, pgp_dest_t &dst, const std::string &filename, uint32_t modtime)
+{
+ pgp_write_handler_t handler = {};
+ rnp_ctx_t ctx;
+ ctx.filename = filename;
+ ctx.filemtime = modtime;
+ handler.ctx = &ctx;
+
+ pgp_dest_t literal = {};
+ rnp_result_t ret = init_literal_dst(&handler, &literal, &dst);
+ if (ret) {
+ goto done;
+ }
+
+ ret = dst_write_src(&src, &literal);
+done:
+ dst_close(&literal, ret);
+ return ret;
+}
+
+rnp_result_t
+rnp_raw_encrypt_src(pgp_source_t & src,
+ pgp_dest_t & dst,
+ const std::string & password,
+ rnp::SecurityContext &secctx)
+{
+ pgp_write_handler_t handler = {};
+ rnp_ctx_t ctx;
+
+ ctx.ctx = &secctx;
+ ctx.ealg = DEFAULT_PGP_SYMM_ALG;
+ handler.ctx = &ctx;
+ pgp_dest_t encrypted = {};
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ try {
+ ret =
+ ctx.add_encryption_password(password, DEFAULT_PGP_HASH_ALG, DEFAULT_PGP_SYMM_ALG);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ goto done;
+ }
+ if (ret) {
+ goto done;
+ }
+
+ ret = init_encrypted_dst(&handler, &encrypted, &dst);
+ if (ret) {
+ goto done;
+ }
+
+ ret = dst_write_src(&src, &encrypted);
+done:
+ dst_close(&encrypted, ret);
+ return ret;
+}
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 <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#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