Modules/src/m_paper_key/parse.cpp

397 lines
11 KiB
C++

/*
* Copyright (C) 2007, 2008, 2012, 2017 David Shaw <dshaw@jabberwocky.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "parse.h"
#include <qcryptographichash.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "GFModuleCommonUtils.hpp"
#include "GFSDKBasic.h"
#include "output.h"
#include "packets.h"
extern int verbose;
extern int ignore_crc_error;
struct packet *parse(FILE *input, unsigned char want, unsigned char stop) {
int byte;
struct packet *packet = NULL;
while ((byte = fgetc(input)) != EOF) {
unsigned char type;
unsigned int length;
if (byte & 0x80) {
int tmp;
type = byte & 0x3F;
/* Old-style packet type */
if (!(byte & 0x40)) type >>= 2;
if (type == stop) {
ungetc(byte, input);
break;
}
if (byte & 0x40) {
/* New-style packets */
byte = fgetc(input);
if (byte == EOF) goto fail;
if (byte == 255) {
/* 4-byte length */
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = tmp << 24;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 16;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 8;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp;
} else if (byte >= 224) {
/* Partial body length, so fail (keys can't use
partial body) */
LOG_ERROR("Invalid partial packet encoding");
goto fail;
} else if (byte >= 192) {
/* 2-byte length */
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = ((byte - 192) << 8) + tmp + 192;
} else
length = byte;
} else {
/* Old-style packets */
switch (byte & 0x03) {
case 0:
/* 1-byte length */
byte = fgetc(input);
if (byte == EOF) goto fail;
length = byte;
break;
case 1:
/* 2-byte length */
byte = fgetc(input);
if (byte == EOF) goto fail;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = byte << 8;
length |= tmp;
break;
case 2:
/* 4-byte length */
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length = tmp << 24;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 16;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp << 8;
tmp = fgetc(input);
if (tmp == EOF) goto fail;
length |= tmp;
break;
default:
LOG_ERROR("unable to parse old-style length");
goto fail;
}
}
if (verbose > 1) {
FLOG_DEBUG("Found packet of type %d, length %d", type, length);
}
} else {
LOG_ERROR("unable to parse OpenPGP packets (is this armored data?)");
goto fail;
}
if (want == 0 || type == want) {
packet =
static_cast<struct packet *>(GFAllocateMemory(sizeof(struct packet)));
packet->type = type;
packet->buf = static_cast<unsigned char *>(GFAllocateMemory(length));
packet->len = length;
packet->size = length;
if (fread(packet->buf, 1, packet->len, input) < packet->len) {
FLOG_ERROR("Short read on packet type %d", type);
goto fail;
}
break;
} else {
/* We don't want it, so skip the packet. We don't use fseek
here since the input might be on stdin and that isn't
seekable. */
size_t i;
for (i = 0; i < length; i++) fgetc(input);
}
}
return packet;
fail:
return NULL;
}
int calculate_fingerprint(struct packet *packet, size_t public_len,
unsigned char fingerprint[20]) {
if (packet->buf[0] == 3) {
return -1;
} else if (packet->buf[0] == 4) {
QCryptographicHash sha(QCryptographicHash::Sha1);
QByteArray head;
head.append(static_cast<char>(0x99));
head.append(static_cast<char>(public_len >> 8));
head.append(static_cast<char>(public_len & 0xFF));
sha.addData(head);
sha.addData(reinterpret_cast<const char *>(packet->buf), public_len);
QByteArray result = sha.result();
if (result.size() != 20) {
return -1;
}
std::memcpy(fingerprint, result.constData(), 20);
}
return 0;
}
#define MPI_LENGTH(_start) (((((_start)[0] << 8 | (_start)[1]) + 7) / 8) + 2)
ssize_t extract_secrets(struct packet *packet) {
size_t offset;
if (packet->len == 0) return -1;
/* Secret keys consist of a public key with some secret material
stuck on the end. To get to the secrets, we have to skip the
public stuff. */
if (packet->buf[0] == 3) {
LOG_ERROR("Version 3 (PGP 2.x style) keys are not supported.");
return -1;
} else if (packet->buf[0] == 4) {
/* Jump 5 bytes in. That gets us past 1 byte of version, and 4
bytes of timestamp. */
offset = 5;
} else
return -1;
if (packet->len <= offset) return -1;
switch (packet->buf[offset++]) {
case 1: /* RSA */
/* Skip 2 MPIs */
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
case 16: /* Elgamal */
/* Skip 3 MPIs */
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
case 17: /* DSA */
/* Skip 4 MPIs */
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
case 18: /* ECDH */
/* Skip the curve ID and its length byte, plus an MPI, plus the
KDF parameters and their length byte */
offset += packet->buf[offset] + 1;
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
offset += packet->buf[offset] + 1;
if (packet->len <= offset) return -1;
break;
case 19: /* ECDSA */
case 22: /* EdDSA - note that this is from an expired draft
https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04,
but GnuPG is using algorithm 22 for it. */
/* Skip the curve ID and its length byte, plus an MPI */
offset += packet->buf[offset] + 1;
if (packet->len <= offset) return -1;
offset += MPI_LENGTH(&packet->buf[offset]);
if (packet->len <= offset) return -1;
break;
default:
/* What algorithm? */
FLOG_ERROR("Unable to parse algorithm %u", packet->buf[offset - 1]);
return -1;
}
return offset;
}
struct packet *read_secrets_file(FILE *secrets, enum data_type input_type) {
struct packet *packet = NULL;
int final_crc = 0;
unsigned long my_crc = 0;
if (input_type == RAW) {
unsigned char buffer[1024];
size_t got;
while ((got = fread(buffer, 1, 1024, secrets)))
packet = append_packet(packet, buffer, got);
if (got == 0 && !feof(secrets)) {
LOG_ERROR("unable to read secrets file");
free_packet(packet);
return NULL;
}
if (packet->len >= 3) {
/* Grab the last 3 bytes to be the CRC24 */
my_crc = packet->buf[packet->len - 3] << 16;
my_crc |= packet->buf[packet->len - 2] << 8;
my_crc |= packet->buf[packet->len - 1];
final_crc = 1;
packet->len -= 3;
}
} else {
char line[1024];
unsigned int next_linenum = 1;
while (fgets(line, 1024, secrets)) {
unsigned int linenum, did_digit = 0;
unsigned long line_crc = CRC24_INIT;
char *tok;
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;
linenum = atoi(line);
if (linenum != next_linenum) {
FLOG_ERROR("missing line number %u (saw %u)", next_linenum, linenum);
free_packet(packet);
return NULL;
} else
next_linenum = linenum + 1;
tok = strchr(line, ':');
if (tok) {
tok = strchr(tok, ' ');
while (tok) {
char *next;
while (*tok == ' ') tok++;
next = strchr(tok, ' ');
if (next == NULL) {
/* End of line, so check the CRC. */
unsigned long new_crc;
if (sscanf(tok, "%06lX", &new_crc)) {
if (did_digit) {
if ((new_crc & 0xFFFFFFL) != (line_crc & 0xFFFFFFL)) {
FLOG_ERROR("CRC on line %d does not match (%06lX!=%06lX)",
linenum, new_crc & 0xFFFFFFL,
line_crc & 0xFFFFFFL);
if (!ignore_crc_error) {
free_packet(packet);
return NULL;
}
}
} else {
final_crc = 1;
my_crc = new_crc;
}
}
} else {
unsigned int digit;
if (sscanf(tok, "%02X", &digit)) {
unsigned char d = digit;
packet = append_packet(packet, &d, 1);
do_crc24(&line_crc, &d, 1);
did_digit = 1;
}
}
tok = next;
}
} else {
FLOG_ERROR("No colon ':' found in line %u", linenum);
free_packet(packet);
return NULL;
}
}
}
if (final_crc) {
unsigned long all_crc = CRC24_INIT;
do_crc24(&all_crc, packet->buf, packet->len);
if ((my_crc & 0xFFFFFFL) != (all_crc & 0xFFFFFFL)) {
FLOG_ERROR("CRC of secret does not match (%06lX!=%06lX)",
my_crc & 0xFFFFFFL, all_crc & 0xFFFFFFL);
if (!ignore_crc_error) {
free_packet(packet);
return NULL;
}
}
} else {
LOG_ERROR("CRC of secret is missing");
if (!ignore_crc_error) {
free_packet(packet);
return NULL;
}
}
return packet;
}