diff --git a/src/librepgp/stream-armor.cpp b/src/librepgp/stream-armor.cpp index faf5e1af56..c6f444d798 100644 --- a/src/librepgp/stream-armor.cpp +++ b/src/librepgp/stream-armor.cpp @@ -32,8 +32,9 @@ #else #include "uniwin.h" #endif -#include +#include #include +#include #include "stream-def.h" #include "stream-armor.h" #include "stream-packet.h" @@ -47,13 +48,9 @@ #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 */ + pgp_source_t * readsrc; /* source to read from */ + pgp_armored_msg_t type; /* type of the message */ + std::string armorhdr; /* minimized header, i.e. PUBLIC KEY FILE, MESSAGE, etc */ 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 */ @@ -199,29 +196,17 @@ armor_read_crc(pgp_source_armored_param_t *param) static bool armor_read_trailer(pgp_source_armored_param_t *param) { - char st[64]; - char str[64]; - size_t stlen; - /* Space or tab could get between armor and trailer, see issue #2199 */ if (!param->readsrc->skip_chars("\r\n \t")) { 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 (!param->readsrc->peek_eq(str, stlen) || strncmp(str, st, stlen)) { + std::string st = ST_ARMOR_END + param->armorhdr + ST_DASHES; + std::string str(st.size(), 0); + if (!param->readsrc->peek_eq(&str.front(), str.size()) || (st != str)) { return false; } - param->readsrc->skip(stlen); + param->readsrc->skip(st.size()); (void) param->readsrc->skip_chars("\t "); (void) param->readsrc->skip_eol(); return true; @@ -465,75 +450,46 @@ armored_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) 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; - } + auto param = (pgp_source_armored_param_t *) src->param; + 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 +/** @brief finds armor header position in the buffer, returning position of header or + *std::string::npos. hdrlen will contain the length of the header. + type will contain content type, i.e. MESSAGE, PUBLIC KEY, whatever. **/ -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) +static size_t +find_armor_header(const std::string &str, size_t &hdrlen, std::string &type) { - size_t alen = strlen(another); - return (len == alen) && !memcmp(str, another, alen); + size_t bg_len = strlen(ST_ARMOR_BEGIN); + size_t pos = str.find(ST_ARMOR_BEGIN); + if (pos == std::string::npos) { + return pos; + } + size_t end = str.find(ST_DASHES, pos + bg_len); + if (end == std::string::npos) { + return end; + } + hdrlen = end + 5; + type.assign(str.begin() + pos + bg_len, str.begin() + end); + return pos; } static pgp_armored_msg_t -armor_str_to_data_type(const char *str, size_t len) +armor_str_to_data_type(const std::string &str) { - if (!str) { - return PGP_ARMORED_UNKNOWN; - } - if (str_equals(str, len, "BEGIN PGP MESSAGE")) { + if ((str == "MESSAGE") || (str == "ARMORED FILE")) { return PGP_ARMORED_MESSAGE; } - if (str_equals(str, len, "BEGIN PGP PUBLIC KEY BLOCK") || - str_equals(str, len, "BEGIN PGP PUBLIC KEY")) { + if ((str == "PUBLIC KEY BLOCK") || (str == "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")) { + if ((str == "SECRET KEY BLOCK") || (str == "SECRET KEY") || (str == "PRIVATE KEY BLOCK") || + (str == "PRIVATE KEY")) { return PGP_ARMORED_SECRET_KEY; } - if (str_equals(str, len, "BEGIN PGP SIGNATURE")) { + if (str == "SIGNATURE") { return PGP_ARMORED_SIGNATURE; } return PGP_ARMORED_UNKNOWN; @@ -602,68 +558,68 @@ rnp_armored_guess_type_by_readahead(pgp_source_t *src) return guessed; } -pgp_armored_msg_t -rnp_armored_get_type(pgp_source_t *src) +static std::string +peek_armor_header(pgp_source_t &src, size_t *skip, bool check) { - pgp_armored_msg_t guessed = rnp_armored_guess_type_by_readahead(src); - if (guessed != PGP_ARMORED_UNKNOWN) { - return guessed; + std::string hdr(ARMORED_PEEK_BUF_SIZE, '\0'); + size_t read = 0; + if (skip) { + *skip = 0; + } + if (!src.peek(&hdr.front(), hdr.size(), &read) || (read < 20)) { + return ""; } + hdr.resize(read); - char hdr[ARMORED_PEEK_BUF_SIZE]; - const char *armhdr; - size_t armhdrlen; - size_t read; + std::string type; + size_t armhdrlen = 0; + size_t pos = find_armor_header(hdr, armhdrlen, type); + if (pos == std::string::npos) { + return ""; + } - if (!src->peek(hdr, sizeof(hdr), &read) || (read < 20)) { - return PGP_ARMORED_UNKNOWN; + /* if there are non-whitespaces before the armor header then issue warning */ + for (size_t idx = 0; check && (idx < pos); idx++) { + if (B64DEC[(uint8_t) hdr[idx]] != 0xfd) { + RNP_LOG("extra data before the header line"); + break; + } } - if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { - return PGP_ARMORED_UNKNOWN; + + if (skip) { + *skip = armhdrlen; } + return type; +} - return armor_str_to_data_type(armhdr + 5, armhdrlen - 10); +pgp_armored_msg_t +rnp_armored_get_type(pgp_source_t *src) +{ + auto guessed = rnp_armored_guess_type_by_readahead(src); + if (guessed != PGP_ARMORED_UNKNOWN) { + return guessed; + } + return armor_str_to_data_type(peek_armor_header(*src, nullptr, false)); } static bool armor_parse_header(pgp_source_armored_param_t *param) { - char hdr[ARMORED_PEEK_BUF_SIZE]; - const char *armhdr; - size_t armhdrlen; - size_t read; - - if (!param->readsrc->peek(hdr, sizeof(hdr), &read) || (read < 20)) { - return false; - } - - if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { + size_t skip = 0; + std::string hdr = peek_armor_header(*param->readsrc, &skip, true); + if (hdr.empty()) { 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); + param->type = armor_str_to_data_type(hdr); 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'; - param->readsrc->skip(armhdr - hdr + armhdrlen); + param->armorhdr = hdr; + param->readsrc->skip(skip); param->readsrc->skip_chars("\t "); return true; } @@ -697,26 +653,25 @@ is_base64_line(const char *line, size_t len) static bool armor_parse_headers(pgp_source_armored_param_t *param) { - char header[ARMORED_PEEK_BUF_SIZE] = {0}; + std::vector header(ARMORED_PEEK_BUF_SIZE, '\0'); do { size_t hdrlen = 0; - if (!param->readsrc->peek_line(header, sizeof(header), &hdrlen)) { + if (!param->readsrc->peek_line(header.data(), header.size(), &hdrlen)) { /* if line is too long let's cut it to the reasonable size */ param->readsrc->skip(hdrlen); - if ((hdrlen != sizeof(header) - 1) || !armor_skip_line(*param->readsrc)) { + if ((hdrlen != header.size() - 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)) { + if (is_base64_line(header.data(), hdrlen)) { RNP_LOG("Warning: no empty line after the base64 headers"); return true; } param->readsrc->skip(hdrlen); - if (rnp::is_blank_line(header, hdrlen)) { + if (rnp::is_blank_line(header.data(), hdrlen)) { return param->readsrc->skip_eol(); } } else { @@ -724,31 +679,12 @@ armor_parse_headers(pgp_source_armored_param_t *param) return param->readsrc->skip_eol(); } - 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; + std::string hdrst(header.begin(), header.begin() + hdrlen); + if (!hdrst.find(ST_HEADER_VERSION) || !hdrst.find(ST_HEADER_COMMENT) || + !hdrst.find(ST_HEADER_CHARSET) || !hdrst.find(ST_HEADER_HASH)) { + // do nothing at the moment, just check whether header is known } else { - RNP_LOG("unknown header '%s'", header); - free(hdrval); + RNP_LOG("unknown header '%s'", hdrst.c_str()); } if (!param->readsrc->skip_eol()) { diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 2946afac51..63fecf0a0c 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -4329,6 +4329,12 @@ def test_armor_with_spaces_import(self): clear_workfiles() shutil.rmtree(RNP2, ignore_errors=True) + def test_gpg_armored(self): + ret, out, err = run_proc(RNP, ['--homedir', RNP, '--dearmor', data_path('test_messages/message.txt.gpg-armored')]) + self.assertEqual(ret, 0) + self.assertRegex(out, r'(?s)^.*This is test message to be signed.*') + self.assertFalse(err) + class Encryption(unittest.TestCase): ''' Things to try later: diff --git a/src/tests/data/test_messages/message.txt.gpg-armored b/src/tests/data/test_messages/message.txt.gpg-armored new file mode 100644 index 0000000000..5f21069b95 --- /dev/null +++ b/src/tests/data/test_messages/message.txt.gpg-armored @@ -0,0 +1,8 @@ +-----BEGIN PGP ARMORED FILE----- +Comment: Use "gpg --dearmor" for unpacking + +VGhpcyBpcyB0ZXN0IG1lc3NhZ2UgdG8gYmUgc2lnbmVkLCBhbmQvb3IgZW5jcnlw +dGVkLCBjbGVhcnRleHQgc2lnbmVkIGFuZCBkZXRhY2hlZCBzaWduZWQuCkl0IHdp +bGwgdXNlIGtleXMgZnJvbSBrZXlyaW5ncy8xLgpFbmQgb2YgbWVzc2FnZS4= +=N8nn +-----END PGP ARMORED FILE-----