Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 86 additions & 150 deletions src/librepgp/stream-armor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
#else
#include "uniwin.h"
#endif
#include <string.h>
#include <string>
#include <algorithm>
#include <cassert>
#include "stream-def.h"
#include "stream-armor.h"
#include "stream-packet.h"
Expand All @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -697,58 +653,38 @@ 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<char> 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 {
/* empty line - end of the headers */
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()) {
Expand Down
6 changes: 6 additions & 0 deletions src/tests/cli_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions src/tests/data/test_messages/message.txt.gpg-armored
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN PGP ARMORED FILE-----
Comment: Use "gpg --dearmor" for unpacking

VGhpcyBpcyB0ZXN0IG1lc3NhZ2UgdG8gYmUgc2lnbmVkLCBhbmQvb3IgZW5jcnlw
dGVkLCBjbGVhcnRleHQgc2lnbmVkIGFuZCBkZXRhY2hlZCBzaWduZWQuCkl0IHdp
bGwgdXNlIGtleXMgZnJvbSBrZXlyaW5ncy8xLgpFbmQgb2YgbWVzc2FnZS4=
=N8nn
-----END PGP ARMORED FILE-----
Loading