/*- * Copyright (c) 2009 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Alistair Crooks (agc@NetBSD.org) * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) * All rights reserved. * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted * their moral rights under the UK Copyright Design and Patents Act 1988 to * be recorded as the authors of this copyright work. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. * * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #ifdef HAVE_SYS_CDEFS_H #include #endif #if defined(__NetBSD__) __COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved."); __RCSID("$NetBSD: reader.c,v 1.52 2017/03/27 20:50:19 khorben Exp $"); #endif #include #include #ifdef HAVE_SYS_MMAN_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_DIRECT_H #include #endif #ifdef HAVE_INTTYPES_H #include #endif #ifdef HAVE_OPENSSL_IDEA_H #include #endif #ifdef HAVE_OPENSSL_IDEA_H #include #endif #ifdef HAVE_OPENSSL_AES_H #include #endif #ifdef HAVE_OPENSSL_DES_H #include #endif #include #include #include #ifdef HAVE_TERMIOS_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #include "errors.h" #include "crypto.h" #include "create.h" #include "signature.h" #include "packet.h" #include "packet-parse.h" #include "packet-show.h" #include "packet.h" #include "keyring.h" #include "readerwriter.h" #include "netpgpsdk.h" #include "netpgpdefs.h" #include "netpgpdigest.h" /* data from partial blocks is queued up in virtual block in stream */ static int read_partial_data(pgp_stream_t *stream, void *dest, size_t length) { unsigned n; if (pgp_get_debug_level(__FILE__)) { (void) fprintf(stderr, "fd_reader: coalesced data, off %d\n", stream->virtualoff); } n = MIN(stream->virtualc - stream->virtualoff, (unsigned)length); (void) memcpy(dest, &stream->virtualpkt[stream->virtualoff], n); stream->virtualoff += n; if (stream->virtualoff == stream->virtualc) { free(stream->virtualpkt); stream->virtualpkt = NULL; stream->virtualc = stream->virtualoff = 0; } return (int)n; } /* get a pass phrase from the user */ int pgp_getpassphrase(void *in, char *phrase, size_t size) { char *p; size_t len; if (in == NULL) { while ((p = getpass("netpgp passphrase: ")) == NULL) { } (void) snprintf(phrase, size, "%s", p); } else { if (fgets(phrase, (int)size, in) == NULL) { return 0; } len = strlen(phrase); if (len >= 1 && phrase[len - 1] == '\n') { phrase[len - 1] = '\0'; } } return 1; } /** * \ingroup Internal_Readers_Generic * \brief Starts reader stack * \param stream Parse settings * \param reader Reader to use * \param destroyer Destroyer to use * \param vp Reader-specific arg */ void pgp_reader_set(pgp_stream_t *stream, pgp_reader_func_t *reader, pgp_reader_destroyer_t *destroyer, void *vp) { stream->readinfo.reader = reader; stream->readinfo.destroyer = destroyer; stream->readinfo.arg = vp; } /** * \ingroup Internal_Readers_Generic * \brief Adds to reader stack * \param stream Parse settings * \param reader Reader to use * \param destroyer Reader's destroyer * \param vp Reader-specific arg */ void pgp_reader_push(pgp_stream_t *stream, pgp_reader_func_t *reader, pgp_reader_destroyer_t *destroyer, void *vp) { pgp_reader_t *readinfo; if ((readinfo = calloc(1, sizeof(*readinfo))) == NULL) { (void) fprintf(stderr, "pgp_reader_push: bad alloc\n"); } else { *readinfo = stream->readinfo; (void) memset(&stream->readinfo, 0x0, sizeof(stream->readinfo)); stream->readinfo.next = readinfo; stream->readinfo.parent = stream; /* should copy accumulate flags from other reader? RW */ stream->readinfo.accumulate = readinfo->accumulate; pgp_reader_set(stream, reader, destroyer, vp); } } /** * \ingroup Internal_Readers_Generic * \brief Removes from reader stack * \param stream Parse settings */ void pgp_reader_pop(pgp_stream_t *stream) { pgp_reader_t *next = stream->readinfo.next; stream->readinfo = *next; free(next); } /** * \ingroup Internal_Readers_Generic * \brief Gets arg from reader * \param readinfo Reader info * \return Pointer to reader info's arg */ void * pgp_reader_get_arg(pgp_reader_t *readinfo) { return readinfo->arg; } /**************************************************************************/ #define CRC24_POLY 0x1864cfbL enum { NONE = 0, BEGIN_PGP_MESSAGE, BEGIN_PGP_PUBLIC_KEY_BLOCK, BEGIN_PGP_PRIVATE_KEY_BLOCK, BEGIN_PGP_MULTI, BEGIN_PGP_SIGNATURE, END_PGP_MESSAGE, END_PGP_PUBLIC_KEY_BLOCK, END_PGP_PRIVATE_KEY_BLOCK, END_PGP_MULTI, END_PGP_SIGNATURE, BEGIN_PGP_SIGNED_MESSAGE }; /** * \struct dearmour_t */ typedef struct { enum { OUTSIDE_BLOCK = 0, BASE64, AT_TRAILER_NAME } state; int lastseen; pgp_stream_t *parse_info; unsigned seen_nl:1; unsigned prev_nl:1; unsigned allow_headers_without_gap:1; /* !< allow headers in armoured data that are * not separated from the data by a blank line * */ unsigned allow_no_gap:1; /* !< allow no blank line at the start of * armoured data */ unsigned allow_trailing_whitespace:1; /* !< allow armoured stuff to have trailing * whitespace where we wouldn't strictly expect * it */ /* it is an error to get a cleartext message without a sig */ unsigned expect_sig:1; unsigned got_sig:1; /* base64 stuff */ unsigned buffered; uint8_t buffer[3]; unsigned eof64; uint32_t checksum; uint32_t read_checksum; /* unarmoured text blocks */ uint8_t unarmoured[NETPGP_BUFSIZ]; size_t unarmoredc; /* pushed back data (stored backwards) */ uint8_t *pushback; unsigned pushbackc; /* armoured block headers */ pgp_headers_t headers; } dearmour_t; static void push_back(dearmour_t *dearmour, const uint8_t *buf, unsigned length) { unsigned n; if (dearmour->pushback) { (void) fprintf(stderr, "push_back: already pushed back\n"); } else if ((dearmour->pushback = calloc(1, length)) == NULL) { (void) fprintf(stderr, "push_back: bad alloc\n"); } else { for (n = 0; n < length; ++n) { dearmour->pushback[n] = buf[(length - n) - 1]; } dearmour->pushbackc = length; } } /* this struct holds a textual header line */ typedef struct headerline_t { const char *s; /* the header line */ size_t len; /* its length */ int type; /* the defined type */ } headerline_t; static headerline_t headerlines[] = { { "BEGIN PGP MESSAGE", 17, BEGIN_PGP_MESSAGE }, { "BEGIN PGP PUBLIC KEY BLOCK", 26, BEGIN_PGP_PUBLIC_KEY_BLOCK }, { "BEGIN PGP PRIVATE KEY BLOCK",27, BEGIN_PGP_PRIVATE_KEY_BLOCK }, { "BEGIN PGP MESSAGE, PART ", 25, BEGIN_PGP_MULTI }, { "BEGIN PGP SIGNATURE", 19, BEGIN_PGP_SIGNATURE }, { "END PGP MESSAGE", 15, END_PGP_MESSAGE }, { "END PGP PUBLIC KEY BLOCK", 24, END_PGP_PUBLIC_KEY_BLOCK }, { "END PGP PRIVATE KEY BLOCK", 25, END_PGP_PRIVATE_KEY_BLOCK }, { "END PGP MESSAGE, PART ", 22, END_PGP_MULTI }, { "END PGP SIGNATURE", 17, END_PGP_SIGNATURE }, { "BEGIN PGP SIGNED MESSAGE", 24, BEGIN_PGP_SIGNED_MESSAGE }, { NULL, 0, -1 } }; /* search through the table of header lines */ static int findheaderline(char *headerline) { headerline_t *hp; for (hp = headerlines ; hp->s ; hp++) { if (strncmp(headerline, hp->s, hp->len) == 0) { break; } } return hp->type; } static int set_lastseen_headerline(dearmour_t *dearmour, char *hdr, pgp_error_t **errors) { int lastseen; int prev; prev = dearmour->lastseen; if ((lastseen = findheaderline(hdr)) == -1) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "Unrecognised Header Line %s", hdr); return 0; } dearmour->lastseen = lastseen; if (pgp_get_debug_level(__FILE__)) { printf("set header: hdr=%s, dearmour->lastseen=%d, prev=%d\n", hdr, dearmour->lastseen, prev); } switch (dearmour->lastseen) { case NONE: PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "Unrecognised last seen Header Line %s", hdr); break; case END_PGP_MESSAGE: if (prev != BEGIN_PGP_MESSAGE) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Got END PGP MESSAGE, but not after BEGIN"); } break; case END_PGP_PUBLIC_KEY_BLOCK: if (prev != BEGIN_PGP_PUBLIC_KEY_BLOCK) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Got END PGP PUBLIC KEY BLOCK, but not after BEGIN"); } break; case END_PGP_PRIVATE_KEY_BLOCK: if (prev != BEGIN_PGP_PRIVATE_KEY_BLOCK) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Got END PGP PRIVATE KEY BLOCK, but not after BEGIN"); } break; case BEGIN_PGP_MULTI: case END_PGP_MULTI: PGP_ERROR_1(errors, PGP_E_R_UNSUPPORTED, "%s", "Multi-part messages are not yet supported"); break; case END_PGP_SIGNATURE: if (prev != BEGIN_PGP_SIGNATURE) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Got END PGP SIGNATURE, but not after BEGIN"); } break; case BEGIN_PGP_MESSAGE: case BEGIN_PGP_PUBLIC_KEY_BLOCK: case BEGIN_PGP_PRIVATE_KEY_BLOCK: case BEGIN_PGP_SIGNATURE: case BEGIN_PGP_SIGNED_MESSAGE: break; } return 1; } static int read_char(pgp_stream_t *stream, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo, unsigned skip) { uint8_t c; do { if (dearmour->pushbackc) { c = dearmour->pushback[--dearmour->pushbackc]; if (dearmour->pushbackc == 0) { free(dearmour->pushback); dearmour->pushback = NULL; } } else if (pgp_stacked_read(stream, &c, 1, errors, readinfo, cbinfo) != 1) { return -1; } } while (skip && c == '\r'); dearmour->prev_nl = dearmour->seen_nl; dearmour->seen_nl = c == '\n'; return c; } static int eat_whitespace(pgp_stream_t *stream, int first, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo, unsigned skip) { int c = first; while (c == ' ' || c == '\t') { c = read_char(stream, dearmour, errors, readinfo, cbinfo, skip); } return c; } static int read_and_eat_whitespace(pgp_stream_t *stream, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo, unsigned skip) { int c; do { c = read_char(stream, dearmour, errors, readinfo, cbinfo, skip); } while (c == ' ' || c == '\t'); return c; } static void flush(dearmour_t *dearmour, pgp_cbdata_t *cbinfo) { pgp_packet_t content; if (dearmour->unarmoredc > 0) { content.u.unarmoured_text.data = dearmour->unarmoured; content.u.unarmoured_text.length = (unsigned)dearmour->unarmoredc; CALLBACK(PGP_PTAG_CT_UNARMOURED_TEXT, cbinfo, &content); dearmour->unarmoredc = 0; } } static int unarmoured_read_char(pgp_stream_t *stream, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo, unsigned skip) { int c; do { c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0); if (c < 0) { return c; } dearmour->unarmoured[dearmour->unarmoredc++] = c; if (dearmour->unarmoredc == sizeof(dearmour->unarmoured)) { flush(dearmour, cbinfo); } } while (skip && c == '\r'); return c; } /** * \param headers * \param key * * \return header value if found, otherwise NULL */ static const char * find_header(pgp_headers_t *headers, const char *key) { unsigned n; for (n = 0; n < headers->headerc; ++n) { if (strcmp(headers->headers[n].key, key) == 0) { return headers->headers[n].value; } } return NULL; } /** * \param dest * \param src */ static void dup_headers(pgp_headers_t *dest, const pgp_headers_t *src) { unsigned n; if ((dest->headers = calloc(src->headerc, sizeof(*dest->headers))) == NULL) { (void) fprintf(stderr, "dup_headers: bad alloc\n"); } else { dest->headerc = src->headerc; for (n = 0; n < src->headerc; ++n) { dest->headers[n].key = netpgp_strdup(src->headers[n].key); dest->headers[n].value = netpgp_strdup(src->headers[n].value); } } } /* * Note that this skips CRs so implementations always see just straight LFs * as line terminators */ static int process_dash_escaped(pgp_stream_t *stream, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { pgp_fixed_body_t *body; pgp_packet_t content2; pgp_packet_t content; const char *hashstr; pgp_hash_t *hash; int total; body = &content.u.cleartext_body; if ((hash = calloc(1, sizeof(*hash))) == NULL) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "process_dash_escaped: bad alloc"); return -1; } hashstr = find_header(&dearmour->headers, "Hash"); if (hashstr) { pgp_hash_alg_t alg; alg = pgp_str_to_hash_alg(hashstr); if (!pgp_is_hash_alg_supported(&alg)) { free(hash); PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "Unsupported hash algorithm '%s'", hashstr); return -1; } if (alg == PGP_HASH_UNKNOWN) { free(hash); PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "Unknown hash algorithm '%s'", hashstr); return -1; } pgp_hash_any(hash, alg); } else { pgp_hash_md5(hash); } if (!hash->init(hash)) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "can't initialise hash"); return -1; } body->length = 0; total = 0; for (;;) { int c; unsigned count; c = read_char(stream, dearmour, errors, readinfo, cbinfo, 1); if (c < 0) { return -1; } if (dearmour->prev_nl && c == '-') { if ((c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return -1; } if (c != ' ') { /* then this had better be a trailer! */ if (c != '-') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Bad dash-escaping"); } for (count = 2; count < 5; ++count) { if ((c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return -1; } if (c != '-') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Bad dash-escaping (2)"); } } dearmour->state = AT_TRAILER_NAME; break; } /* otherwise we read the next character */ if ((c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return -1; } } if (c == '\n' && body->length) { if (memchr(body->data + 1, '\n', body->length - 1) != NULL) { (void) fprintf(stderr, "process_dash_escaped: newline found\n"); return -1; } if (body->data[0] == '\n') { hash->add(hash, (const uint8_t *)"\r", 1); } hash->add(hash, body->data, body->length); if (pgp_get_debug_level(__FILE__)) { fprintf(stderr, "Got body:\n%s\n", body->data); } CALLBACK(PGP_PTAG_CT_SIGNED_CLEARTEXT_BODY, cbinfo, &content); body->length = 0; } body->data[body->length++] = c; total += 1; if (body->length == sizeof(body->data)) { if (pgp_get_debug_level(__FILE__)) { (void) fprintf(stderr, "Got body (2):\n%s\n", body->data); } CALLBACK(PGP_PTAG_CT_SIGNED_CLEARTEXT_BODY, cbinfo, &content); body->length = 0; } } if (body->data[0] != '\n') { (void) fprintf(stderr, "process_dash_escaped: no newline in body data\n"); return -1; } if (body->length != 1) { (void) fprintf(stderr, "process_dash_escaped: bad body length\n"); return -1; } /* don't send that one character, because it's part of the trailer */ (void) memset(&content2, 0x0, sizeof(content2)); CALLBACK(PGP_PTAG_CT_SIGNED_CLEARTEXT_TRAILER, cbinfo, &content2); return total; } static int add_header(dearmour_t *dearmour, const char *key, const char *value) { int n; /* * Check that the header is valid */ if (strcmp(key, "Version") == 0 || strcmp(key, "Comment") == 0 || strcmp(key, "MessageID") == 0 || strcmp(key, "Hash") == 0 || strcmp(key, "Charset") == 0) { n = dearmour->headers.headerc; dearmour->headers.headers = realloc(dearmour->headers.headers, (n + 1) * sizeof(*dearmour->headers.headers)); if (dearmour->headers.headers == NULL) { (void) fprintf(stderr, "add_header: bad alloc\n"); return 0; } dearmour->headers.headers[n].key = netpgp_strdup(key); dearmour->headers.headers[n].value = netpgp_strdup(value); dearmour->headers.headerc = n + 1; return 1; } return 0; } /* \todo what does a return value of 0 indicate? 1 is good, -1 is bad */ static int parse_headers(pgp_stream_t *stream, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t * readinfo, pgp_cbdata_t * cbinfo) { unsigned nbuf; unsigned size; unsigned first = 1; char *buf; int ret = 1; nbuf = 0; size = 80; if ((buf = calloc(1, size)) == NULL) { (void) fprintf(stderr, "parse_headers: bad calloc\n"); return -1; } for (;;) { int c; if ((c = read_char(stream, dearmour, errors, readinfo, cbinfo, 1)) < 0) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Unexpected EOF"); ret = -1; break; } if (c == '\n') { char *s; if (nbuf == 0) { break; } if (nbuf >= size) { (void) fprintf(stderr, "parse_headers: bad size\n"); return -1; } buf[nbuf] = '\0'; if ((s = strchr(buf, ':')) == NULL) { if (!first && !dearmour->allow_headers_without_gap) { /* * then we have seriously malformed * armour */ PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "No colon in armour header"); ret = -1; break; } else { if (first && !(dearmour->allow_headers_without_gap || dearmour->allow_no_gap)) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "No colon in" " armour header (2)"); /* * then we have a nasty * armoured block with no * headers, not even a blank * line. */ buf[nbuf] = '\n'; push_back(dearmour, (uint8_t *) buf, nbuf + 1); ret = -1; break; } } } else { *s = '\0'; if (s[1] != ' ') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "No space in armour header"); ret = -1; goto end; } if (!add_header(dearmour, buf, s + 2)) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "Invalid header %s", buf); ret = -1; goto end; } nbuf = 0; } first = 0; } else { if (size <= nbuf + 1) { size += size + 80; buf = realloc(buf, size); if (buf == NULL) { (void) fprintf(stderr, "bad alloc\n"); ret = -1; goto end; } } buf[nbuf++] = c; } } end: free(buf); return ret; } static int read4(pgp_stream_t *stream, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo, int *pc, unsigned *pn, uint32_t *pl) { int n, c; uint32_t l = 0; for (n = 0; n < 4; ++n) { c = read_char(stream, dearmour, errors, readinfo, cbinfo, 1); if (c < 0) { dearmour->eof64 = 1; return -1; } if (c == '-' || c == '=') { break; } l <<= 6; if (c >= 'A' && c <= 'Z') { l += (uint32_t)(c - 'A'); } else if (c >= 'a' && c <= 'z') { l += (uint32_t)(c - 'a') + 26; } else if (c >= '0' && c <= '9') { l += (uint32_t)(c - '0') + 52; } else if (c == '+') { l += 62; } else if (c == '/') { l += 63; } else { --n; l >>= 6; } } *pc = c; *pn = n; *pl = l; return 4; } unsigned pgp_crc24(unsigned checksum, uint8_t c) { unsigned i; checksum ^= c << 16; for (i = 0; i < 8; i++) { checksum <<= 1; if (checksum & 0x1000000) checksum ^= CRC24_POLY; } return (unsigned)(checksum & 0xffffffL); } static int decode64(pgp_stream_t *stream, dearmour_t *dearmour, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { unsigned n; int n2; uint32_t l; int c; int ret; if (dearmour->buffered) { (void) fprintf(stderr, "decode64: bad dearmour->buffered\n"); return 0; } ret = read4(stream, dearmour, errors, readinfo, cbinfo, &c, &n, &l); if (ret < 0) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Badly formed base64"); return 0; } if (n == 3) { if (c != '=') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Badly terminated base64 (2)"); return 0; } dearmour->buffered = 2; dearmour->eof64 = 1; l >>= 2; } else if (n == 2) { if (c != '=') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Badly terminated base64 (3)"); return 0; } dearmour->buffered = 1; dearmour->eof64 = 1; l >>= 4; c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0); if (c != '=') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Badly terminated base64"); return 0; } } else if (n == 0) { if (!dearmour->prev_nl || c != '=') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Badly terminated base64 (4)"); return 0; } dearmour->buffered = 0; } else { if (n != 4) { (void) fprintf(stderr, "decode64: bad n (!= 4)\n"); return 0; } dearmour->buffered = 3; if (c == '-' || c == '=') { (void) fprintf(stderr, "decode64: bad c\n"); return 0; } } if (dearmour->buffered < 3 && dearmour->buffered > 0) { /* then we saw padding */ if (c != '=') { (void) fprintf(stderr, "decode64: bad c (=)\n"); return 0; } c = read_and_eat_whitespace(stream, dearmour, errors, readinfo, cbinfo, 1); if (c != '\n') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "No newline at base64 end"); return 0; } c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0); if (c != '=') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "No checksum at base64 end"); return 0; } } if (c == '=') { /* now we are at the checksum */ ret = read4(stream, dearmour, errors, readinfo, cbinfo, &c, &n, &dearmour->read_checksum); if (ret < 0 || n != 4) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Error in checksum"); return 0; } c = read_char(stream, dearmour, errors, readinfo, cbinfo, 1); if (dearmour->allow_trailing_whitespace) c = eat_whitespace(stream, c, dearmour, errors, readinfo, cbinfo, 1); if (c != '\n') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Badly terminated checksum"); return 0; } c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0); if (c != '-') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Bad base64 trailer (2)"); return 0; } } if (c == '-') { for (n = 0; n < 4; ++n) if (read_char(stream, dearmour, errors, readinfo, cbinfo, 0) != '-') { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Bad base64 trailer"); return 0; } dearmour->eof64 = 1; } else { if (!dearmour->buffered) { (void) fprintf(stderr, "decode64: not buffered\n"); return 0; } } for (n = 0; n < dearmour->buffered; ++n) { dearmour->buffer[n] = (uint8_t)l; l >>= 8; } for (n2 = dearmour->buffered - 1; n2 >= 0; --n2) dearmour->checksum = pgp_crc24((unsigned)dearmour->checksum, dearmour->buffer[n2]); if (dearmour->eof64 && dearmour->read_checksum != dearmour->checksum) { PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Checksum mismatch"); return 0; } return 1; } static void base64(dearmour_t *dearmour) { dearmour->state = BASE64; dearmour->checksum = CRC24_INIT; dearmour->eof64 = 0; dearmour->buffered = 0; } /* This reader is rather strange in that it can generate callbacks for */ /* content - this is because plaintext is not encapsulated in PGP */ /* packets... it also calls back for the text between the blocks. */ static int armoured_data_reader(pgp_stream_t *stream, void *dest_, size_t length, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { pgp_packet_t content; dearmour_t *dearmour; unsigned first; uint8_t *dest = dest_; char buf[1024]; int saved; int ret; dearmour = pgp_reader_get_arg(readinfo); saved = (int)length; if (dearmour->eof64 && !dearmour->buffered) { if (dearmour->state != OUTSIDE_BLOCK && dearmour->state != AT_TRAILER_NAME) { (void) fprintf(stderr, "armoured_data_reader: bad dearmour state\n"); return 0; } } while (length > 0) { unsigned count; unsigned n; int c; flush(dearmour, cbinfo); switch (dearmour->state) { case OUTSIDE_BLOCK: /* * This code returns EOF rather than EARLY_EOF * because if we don't see a header line at all, then * it is just an EOF (and not a BLOCK_END) */ while (!dearmour->seen_nl) { if ((c = unarmoured_read_char(stream, dearmour, errors, readinfo, cbinfo, 1)) < 0) { return 0; } } /* * flush at this point so we definitely have room for * the header, and so we can easily erase it from the * buffer */ flush(dearmour, cbinfo); /* Find and consume the 5 leading '-' */ for (count = 0; count < 5; ++count) { if ((c = unarmoured_read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return 0; } if (c != '-') { goto reloop; } } /* Now find the block type */ for (n = 0; n < sizeof(buf) - 1;) { if ((c = unarmoured_read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return 0; } if (c == '-') { goto got_minus; } buf[n++] = c; } /* then I guess this wasn't a proper header */ break; got_minus: buf[n] = '\0'; /* Consume trailing '-' */ for (count = 1; count < 5; ++count) { if ((c = unarmoured_read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return 0; } if (c != '-') { /* wasn't a header after all */ goto reloop; } } /* Consume final NL */ if ((c = unarmoured_read_char(stream, dearmour, errors, readinfo, cbinfo, 1)) < 0) { return 0; } if (dearmour->allow_trailing_whitespace) { if ((c = eat_whitespace(stream, c, dearmour, errors, readinfo, cbinfo, 1)) < 0) { return 0; } } if (c != '\n') { /* wasn't a header line after all */ break; } /* * Now we've seen the header, scrub it from the * buffer */ dearmour->unarmoredc = 0; /* * But now we've seen a header line, then errors are * EARLY_EOF */ if ((ret = parse_headers(stream, dearmour, errors, readinfo, cbinfo)) <= 0) { return -1; } if (!set_lastseen_headerline(dearmour, buf, errors)) { return -1; } if (strcmp(buf, "BEGIN PGP SIGNED MESSAGE") == 0) { dup_headers(&content.u.cleartext_head, &dearmour->headers); CALLBACK(PGP_PTAG_CT_SIGNED_CLEARTEXT_HEADER, cbinfo, &content); ret = process_dash_escaped(stream, dearmour, errors, readinfo, cbinfo); if (ret <= 0) { return ret; } } else { content.u.armour_header.type = buf; content.u.armour_header.headers = dearmour->headers; (void) memset(&dearmour->headers, 0x0, sizeof(dearmour->headers)); CALLBACK(PGP_PTAG_CT_ARMOUR_HEADER, cbinfo, &content); base64(dearmour); } break; case BASE64: first = 1; while (length > 0) { if (!dearmour->buffered) { if (!dearmour->eof64) { ret = decode64(stream, dearmour, errors, readinfo, cbinfo); if (ret <= 0) { return ret; } } if (!dearmour->buffered) { if (!dearmour->eof64) { (void) fprintf(stderr, "armoured_data_reader: bad dearmour eof64\n"); return 0; } if (first) { dearmour->state = AT_TRAILER_NAME; goto reloop; } return -1; } } if (!dearmour->buffered) { (void) fprintf(stderr, "armoured_data_reader: bad dearmour buffered\n"); return 0; } *dest = dearmour->buffer[--dearmour->buffered]; ++dest; --length; first = 0; } if (dearmour->eof64 && !dearmour->buffered) { dearmour->state = AT_TRAILER_NAME; } break; case AT_TRAILER_NAME: for (n = 0; n < sizeof(buf) - 1;) { if ((c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return -1; } if (c == '-') { goto got_minus2; } buf[n++] = c; } /* then I guess this wasn't a proper trailer */ PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Bad ASCII armour trailer"); break; got_minus2: buf[n] = '\0'; if (!set_lastseen_headerline(dearmour, buf, errors)) { return -1; } /* Consume trailing '-' */ for (count = 1; count < 5; ++count) { if ((c = read_char(stream, dearmour, errors, readinfo, cbinfo, 0)) < 0) { return -1; } if (c != '-') { /* wasn't a trailer after all */ PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Bad ASCII armour trailer (2)"); } } /* Consume final NL */ if ((c = read_char(stream, dearmour, errors, readinfo, cbinfo, 1)) < 0) { return -1; } if (dearmour->allow_trailing_whitespace) { if ((c = eat_whitespace(stream, c, dearmour, errors, readinfo, cbinfo, 1)) < 0) { return 0; } } if (c != '\n') { /* wasn't a trailer line after all */ PGP_ERROR_1(errors, PGP_E_R_BAD_FORMAT, "%s", "Bad ASCII armour trailer (3)"); } if (strncmp(buf, "BEGIN ", 6) == 0) { if (!set_lastseen_headerline(dearmour, buf, errors)) { return -1; } if ((ret = parse_headers(stream, dearmour, errors, readinfo, cbinfo)) <= 0) { return ret; } content.u.armour_header.type = buf; content.u.armour_header.headers = dearmour->headers; (void) memset(&dearmour->headers, 0x0, sizeof(dearmour->headers)); CALLBACK(PGP_PTAG_CT_ARMOUR_HEADER, cbinfo, &content); base64(dearmour); } else { content.u.armour_trailer = buf; CALLBACK(PGP_PTAG_CT_ARMOUR_TRAILER, cbinfo, &content); dearmour->state = OUTSIDE_BLOCK; } break; } reloop: continue; } return saved; } static void armoured_data_destroyer(pgp_reader_t *readinfo) { free(pgp_reader_get_arg(readinfo)); } /** * \ingroup Core_Readers_Armour * \brief Pushes dearmouring reader onto stack * \param parse_info Usual structure containing information about to how to do the parse * \sa pgp_reader_pop_dearmour() */ void pgp_reader_push_dearmour(pgp_stream_t *parse_info) /* * This function originally had these params to cater for packets which * didn't strictly match the RFC. The initial 0.5 release is only going to * support strict checking. If it becomes desirable to support loose checking * of armoured packets and these params are reinstated, parse_headers() must * be fixed so that these flags work correctly. * * // Allow headers in armoured data that are not separated from the data by a * blank line unsigned without_gap, * * // Allow no blank line at the start of armoured data unsigned no_gap, * * //Allow armoured data to have trailing whitespace where we strictly would not * expect it unsigned trailing_whitespace */ { dearmour_t *dearmour; if ((dearmour = calloc(1, sizeof(*dearmour))) == NULL) { (void) fprintf(stderr, "pgp_reader_push_dearmour: bad alloc\n"); } else { dearmour->seen_nl = 1; /* dearmour->allow_headers_without_gap=without_gap; dearmour->allow_no_gap=no_gap; dearmour->allow_trailing_whitespace=trailing_whitespace; */ dearmour->expect_sig = 0; dearmour->got_sig = 0; pgp_reader_push(parse_info, armoured_data_reader, armoured_data_destroyer, dearmour); } } /** * \ingroup Core_Readers_Armour * \brief Pops dearmour reader from stock * \param stream * \sa pgp_reader_push_dearmour() */ void pgp_reader_pop_dearmour(pgp_stream_t *stream) { dearmour_t *dearmour; dearmour = pgp_reader_get_arg(pgp_readinfo(stream)); free(dearmour); pgp_reader_pop(stream); } /**************************************************************************/ /* this is actually used for *decrypting* */ typedef struct { uint8_t decrypted[1024 * 15]; size_t c; size_t off; pgp_crypt_t *decrypt; pgp_region_t *region; unsigned prevplain:1; } encrypted_t; static int encrypted_data_reader(pgp_stream_t *stream, void *dest, size_t length, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { encrypted_t *encrypted; char *cdest; int saved; encrypted = pgp_reader_get_arg(readinfo); saved = (int)length; /* * V3 MPIs have the count plain and the cipher is reset after each * count */ if (encrypted->prevplain && !readinfo->parent->reading_mpi_len) { if (!readinfo->parent->reading_v3_secret) { (void) fprintf(stderr, "encrypted_data_reader: bad v3 secret\n"); return -1; } encrypted->decrypt->decrypt_resync(encrypted->decrypt); encrypted->prevplain = 0; } else if (readinfo->parent->reading_v3_secret && readinfo->parent->reading_mpi_len) { encrypted->prevplain = 1; } while (length > 0) { if (encrypted->c) { unsigned n; /* * if we are reading v3 we should never read * more than we're asked for */ if (length < encrypted->c && (readinfo->parent->reading_v3_secret || readinfo->parent->exact_read)) { (void) fprintf(stderr, "encrypted_data_reader: bad v3 read\n"); return 0; } n = (int)MIN(length, encrypted->c); (void) memcpy(dest, encrypted->decrypted + encrypted->off, n); encrypted->c -= n; encrypted->off += n; length -= n; cdest = dest; cdest += n; dest = cdest; } else { unsigned n = encrypted->region->length; uint8_t buffer[1024]; if (!n) { return -1; } if (!encrypted->region->indeterminate) { n -= encrypted->region->readc; if (n == 0) { return (int)(saved - length); } if (n > sizeof(buffer)) { n = sizeof(buffer); } } else { n = sizeof(buffer); } /* * we can only read as much as we're asked for * in v3 keys because they're partially * unencrypted! */ if ((readinfo->parent->reading_v3_secret || readinfo->parent->exact_read) && n > length) { n = (unsigned)length; } if (!pgp_stacked_limited_read(stream, buffer, n, encrypted->region, errors, readinfo, cbinfo)) { return -1; } if (!readinfo->parent->reading_v3_secret || !readinfo->parent->reading_mpi_len) { encrypted->c = pgp_decrypt_se_ip(encrypted->decrypt, encrypted->decrypted, buffer, n); if (pgp_get_debug_level(__FILE__)) { hexdump(stderr, "encrypted", buffer, 16); hexdump(stderr, "decrypted", encrypted->decrypted, 16); } } else { (void) memcpy( &encrypted->decrypted[encrypted->off], buffer, n); encrypted->c = n; } if (encrypted->c == 0) { (void) fprintf(stderr, "encrypted_data_reader: 0 decrypted count\n"); return 0; } encrypted->off = 0; } } return saved; } static void encrypted_data_destroyer(pgp_reader_t *readinfo) { free(pgp_reader_get_arg(readinfo)); } /** * \ingroup Core_Readers_SE * \brief Pushes decryption reader onto stack * \sa pgp_reader_pop_decrypt() */ void pgp_reader_push_decrypt(pgp_stream_t *stream, pgp_crypt_t *decrypt, pgp_region_t *region) { encrypted_t *encrypted; if ((encrypted = calloc(1, sizeof(*encrypted))) == NULL) { (void) fprintf(stderr, "pgp_reader_push_decrypted: bad alloc\n"); } else { encrypted->decrypt = decrypt; encrypted->region = region; pgp_decrypt_init(encrypted->decrypt); pgp_reader_push(stream, encrypted_data_reader, encrypted_data_destroyer, encrypted); } } /** * \ingroup Core_Readers_Encrypted * \brief Pops decryption reader from stack * \sa pgp_reader_push_decrypt() */ void pgp_reader_pop_decrypt(pgp_stream_t *stream) { encrypted_t *encrypted; encrypted = pgp_reader_get_arg(pgp_readinfo(stream)); encrypted->decrypt->decrypt_finish(encrypted->decrypt); free(encrypted); pgp_reader_pop(stream); } /**************************************************************************/ typedef struct { /* boolean: 0 once we've done the preamble/MDC checks */ /* and are reading from the plaintext */ int passed_checks; uint8_t *plaintext; size_t plaintext_available; size_t plaintext_offset; pgp_region_t *region; pgp_crypt_t *decrypt; } decrypt_se_ip_t; /* Gets entire SE_IP data packet. Verifies leading preamble Verifies trailing MDC packet Then passes up plaintext as requested */ static int se_ip_data_reader(pgp_stream_t *stream, void *dest_, size_t len, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { decrypt_se_ip_t *se_ip; pgp_region_t decrypted_region; unsigned n = 0; se_ip = pgp_reader_get_arg(readinfo); if (!se_ip->passed_checks) { uint8_t *buf = NULL; uint8_t hashed[PGP_SHA1_HASH_SIZE]; uint8_t *preamble; uint8_t *plaintext; uint8_t *mdc; uint8_t *mdc_hash; pgp_hash_t hash; size_t b; size_t sz_preamble; size_t sz_mdc_hash; size_t sz_mdc; size_t sz_plaintext; pgp_hash_any(&hash, PGP_HASH_SHA1); if (!hash.init(&hash)) { (void) fprintf(stderr, "se_ip_data_reader: can't init hash\n"); return -1; } pgp_init_subregion(&decrypted_region, NULL); decrypted_region.length = se_ip->region->length - se_ip->region->readc; if ((buf = calloc(1, decrypted_region.length)) == NULL) { (void) fprintf(stderr, "se_ip_data_reader: bad alloc\n"); return -1; } /* read entire SE IP packet */ if (!pgp_stacked_limited_read(stream, buf, decrypted_region.length, &decrypted_region, errors, readinfo, cbinfo)) { free(buf); return -1; } if (pgp_get_debug_level(__FILE__)) { hexdump(stderr, "SE IP packet", buf, decrypted_region.length); } /* verify leading preamble */ if (pgp_get_debug_level(__FILE__)) { hexdump(stderr, "preamble", buf, se_ip->decrypt->blocksize); } b = se_ip->decrypt->blocksize; if (buf[b - 2] != buf[b] || buf[b - 1] != buf[b + 1]) { fprintf(stderr, "Bad symmetric decrypt (%02x%02x vs %02x%02x)\n", buf[b - 2], buf[b - 1], buf[b], buf[b + 1]); PGP_ERROR_1(errors, PGP_E_PROTO_BAD_SYMMETRIC_DECRYPT, "%s", "Bad symmetric decrypt when parsing SE IP" " packet"); free(buf); return -1; } /* Verify trailing MDC hash */ sz_preamble = se_ip->decrypt->blocksize + 2; sz_mdc_hash = PGP_SHA1_HASH_SIZE; sz_mdc = 1 + 1 + sz_mdc_hash; sz_plaintext = (decrypted_region.length - sz_preamble) - sz_mdc; preamble = buf; plaintext = buf + sz_preamble; mdc = plaintext + sz_plaintext; mdc_hash = mdc + 2; if (pgp_get_debug_level(__FILE__)) { hexdump(stderr, "plaintext", plaintext, sz_plaintext); hexdump(stderr, "mdc", mdc, sz_mdc); } pgp_calc_mdc_hash(preamble, sz_preamble, plaintext, (unsigned)sz_plaintext, hashed); if (memcmp(mdc_hash, hashed, PGP_SHA1_HASH_SIZE) != 0) { PGP_ERROR_1(errors, PGP_E_V_BAD_HASH, "%s", "Bad hash in MDC packet"); free(buf); return 0; } /* all done with the checks */ /* now can start reading from the plaintext */ if (se_ip->plaintext) { (void) fprintf(stderr, "se_ip_data_reader: bad plaintext\n"); return 0; } if ((se_ip->plaintext = calloc(1, sz_plaintext)) == NULL) { (void) fprintf(stderr, "se_ip_data_reader: bad alloc\n"); return 0; } memcpy(se_ip->plaintext, plaintext, sz_plaintext); se_ip->plaintext_available = sz_plaintext; se_ip->passed_checks = 1; free(buf); } n = (unsigned)len; if (n > se_ip->plaintext_available) { n = (unsigned)se_ip->plaintext_available; } memcpy(dest_, se_ip->plaintext + se_ip->plaintext_offset, n); se_ip->plaintext_available -= n; se_ip->plaintext_offset += n; /* len -= n; - not used at all, for info only */ return n; } static void se_ip_data_destroyer(pgp_reader_t *readinfo) { decrypt_se_ip_t *se_ip; se_ip = pgp_reader_get_arg(readinfo); free(se_ip->plaintext); free(se_ip); } /** \ingroup Internal_Readers_SEIP */ void pgp_reader_push_se_ip_data(pgp_stream_t *stream, pgp_crypt_t *decrypt, pgp_region_t * region) { decrypt_se_ip_t *se_ip; if ((se_ip = calloc(1, sizeof(*se_ip))) == NULL) { (void) fprintf(stderr, "pgp_reader_push_se_ip_data: bad alloc\n"); } else { se_ip->region = region; se_ip->decrypt = decrypt; pgp_reader_push(stream, se_ip_data_reader, se_ip_data_destroyer, se_ip); } } /** \ingroup Internal_Readers_SEIP */ void pgp_reader_pop_se_ip_data(pgp_stream_t *stream) { /* * decrypt_se_ip_t * *se_ip=pgp_reader_get_arg(pgp_readinfo(stream)); */ /* free(se_ip); */ pgp_reader_pop(stream); } /**************************************************************************/ /** Arguments for reader_fd */ typedef struct mmap_reader_t { void *mem; /* memory mapped file */ uint64_t size; /* size of file */ uint64_t offset; /* current offset in file */ int fd; /* file descriptor */ } mmap_reader_t; /** * \ingroup Core_Readers * * pgp_reader_fd() attempts to read up to "plength" bytes from the file * descriptor in "parse_info" into the buffer starting at "dest" using the * rules contained in "flags" * * \param dest Pointer to previously allocated buffer * \param plength Number of bytes to try to read * \param flags Rules about reading to use * \param readinfo Reader info * \param cbinfo Callback info * * \return n Number of bytes read * * PGP_R_EARLY_EOF and PGP_R_ERROR push errors on the stack */ static int fd_reader(pgp_stream_t *stream, void *dest, size_t length, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { mmap_reader_t *reader; int n; __PGP_USED(cbinfo); reader = pgp_reader_get_arg(readinfo); if (!stream->coalescing && stream->virtualc && stream->virtualoff < stream->virtualc) { n = read_partial_data(stream, dest, length); } else { n = (int)read(reader->fd, dest, length); } if (n == 0) { return 0; } if (n < 0) { PGP_SYSTEM_ERROR_1(errors, PGP_E_R_READ_FAILED, "read", "file descriptor %d", reader->fd); return -1; } return n; } static void reader_fd_destroyer(pgp_reader_t *readinfo) { free(pgp_reader_get_arg(readinfo)); } /** \ingroup Core_Readers_First \brief Starts stack with file reader */ void pgp_reader_set_fd(pgp_stream_t *stream, int fd) { mmap_reader_t *reader; if ((reader = calloc(1, sizeof(*reader))) == NULL) { (void) fprintf(stderr, "pgp_reader_set_fd: bad alloc\n"); } else { reader->fd = fd; pgp_reader_set(stream, fd_reader, reader_fd_destroyer, reader); } } /**************************************************************************/ typedef struct { const uint8_t *buffer; size_t length; size_t offset; } reader_mem_t; static int mem_reader(pgp_stream_t *stream, void *dest, size_t length, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { reader_mem_t *reader = pgp_reader_get_arg(readinfo); unsigned n; __PGP_USED(cbinfo); __PGP_USED(errors); if (!stream->coalescing && stream->virtualc && stream->virtualoff < stream->virtualc) { n = read_partial_data(stream, dest, length); } else { if (reader->offset + length > reader->length) { n = (unsigned)(reader->length - reader->offset); } else { n = (unsigned)length; } if (n == (unsigned)0) { return 0; } memcpy(dest, reader->buffer + reader->offset, n); reader->offset += n; } return n; } static void mem_destroyer(pgp_reader_t *readinfo) { free(pgp_reader_get_arg(readinfo)); } /** \ingroup Core_Readers_First \brief Starts stack with memory reader */ void pgp_reader_set_memory(pgp_stream_t *stream, const void *buffer, size_t length) { reader_mem_t *mem; if ((mem = calloc(1, sizeof(*mem))) == NULL) { (void) fprintf(stderr, "pgp_reader_set_memory: bad alloc\n"); } else { mem->buffer = buffer; mem->length = length; mem->offset = 0; pgp_reader_set(stream, mem_reader, mem_destroyer, mem); } } /**************************************************************************/ /** \ingroup Core_Writers \brief Create and initialise output and mem; Set for writing to mem \param output Address where new output pointer will be set \param mem Address when new mem pointer will be set \param bufsz Initial buffer size (will automatically be increased when necessary) \note It is the caller's responsiblity to free output and mem. \sa pgp_teardown_memory_write() */ void pgp_setup_memory_write(pgp_output_t **output, pgp_memory_t **mem, size_t bufsz) { /* * initialise needed structures for writing to memory */ *output = pgp_output_new(); *mem = pgp_memory_new(); pgp_memory_init(*mem, bufsz); pgp_writer_set_memory(*output, *mem); } /** \ingroup Core_Writers \brief Closes writer and frees output and mem \param output \param mem \sa pgp_setup_memory_write() */ void pgp_teardown_memory_write(pgp_output_t *output, pgp_memory_t *mem) { pgp_writer_close(output);/* new */ pgp_output_delete(output); pgp_memory_free(mem); } /** \ingroup Core_Readers \brief Create parse_info and sets to read from memory \param stream Address where new parse_info will be set \param mem Memory to read from \param arg Reader-specific arg \param callback Callback to use with reader \param accumulate Set if we need to accumulate as we read. (Usually 0 unless doing signature verification) \note It is the caller's responsiblity to free parse_info \sa pgp_teardown_memory_read() */ void pgp_setup_memory_read(pgp_io_t *io, pgp_stream_t **stream, pgp_memory_t *mem, void *vp, pgp_cb_ret_t callback(const pgp_packet_t *, pgp_cbdata_t *), unsigned accumulate) { *stream = pgp_new(sizeof(**stream)); (*stream)->io = (*stream)->cbinfo.io = io; pgp_set_callback(*stream, callback, vp); pgp_reader_set_memory(*stream, pgp_mem_data(mem), pgp_mem_len(mem)); if (accumulate) { (*stream)->readinfo.accumulate = 1; } } /** \ingroup Core_Readers \brief Frees stream and mem \param stream \param mem \sa pgp_setup_memory_read() */ void pgp_teardown_memory_read(pgp_stream_t *stream, pgp_memory_t *mem) { pgp_stream_delete(stream); pgp_memory_free(mem); } /** \ingroup Core_Writers \brief Create and initialise output and mem; Set for writing to file \param output Address where new output pointer will be set \param filename File to write to \param allow_overwrite Allows file to be overwritten, if set. \return Newly-opened file descriptor \note It is the caller's responsiblity to free output and to close fd. \sa pgp_teardown_file_write() */ int pgp_setup_file_write(pgp_output_t **output, const char *filename, unsigned allow_overwrite) { int fd = 0; int flags = 0; /* * initialise needed structures for writing to file */ if (filename == NULL) { /* write to stdout */ fd = STDOUT_FILENO; } else { flags = O_WRONLY | O_CREAT; if (allow_overwrite) flags |= O_TRUNC; else flags |= O_EXCL; #ifdef O_BINARY flags |= O_BINARY; #endif fd = open(filename, flags, 0600); if (fd < 0) { perror(filename); return fd; } } *output = pgp_output_new(); pgp_writer_set_fd(*output, fd); return fd; } /** \ingroup Core_Writers \brief Closes writer, frees info, closes fd \param output \param fd */ void pgp_teardown_file_write(pgp_output_t *output, int fd) { pgp_writer_close(output); close(fd); pgp_output_delete(output); } /** \ingroup Core_Writers \brief As pgp_setup_file_write, but appends to file */ int pgp_setup_file_append(pgp_output_t **output, const char *filename) { int fd; /* * initialise needed structures for writing to file */ #ifdef O_BINARY fd = open(filename, O_WRONLY | O_APPEND | O_BINARY, 0600); #else fd = open(filename, O_WRONLY | O_APPEND, 0600); #endif if (fd >= 0) { *output = pgp_output_new(); pgp_writer_set_fd(*output, fd); } return fd; } /** \ingroup Core_Writers \brief As pgp_teardown_file_write() */ void pgp_teardown_file_append(pgp_output_t *output, int fd) { pgp_teardown_file_write(output, fd); } /** \ingroup Core_Readers \brief Creates parse_info, opens file, and sets to read from file \param stream Address where new parse_info will be set \param filename Name of file to read \param vp Reader-specific arg \param callback Callback to use when reading \param accumulate Set if we need to accumulate as we read. (Usually 0 unless doing signature verification) \note It is the caller's responsiblity to free parse_info and to close fd \sa pgp_teardown_file_read() */ int pgp_setup_file_read(pgp_io_t *io, pgp_stream_t **stream, const char *filename, void *vp, pgp_cb_ret_t callback(const pgp_packet_t *, pgp_cbdata_t *), unsigned accumulate) { int fd; #ifdef O_BINARY fd = open(filename, O_RDONLY | O_BINARY); #else fd = open(filename, O_RDONLY); #endif if (fd < 0) { (void) fprintf(io->errs, "can't open \"%s\"\n", filename); return fd; } *stream = pgp_new(sizeof(**stream)); (*stream)->io = (*stream)->cbinfo.io = io; pgp_set_callback(*stream, callback, vp); #ifdef USE_MMAP_FOR_FILES pgp_reader_set_mmap(*stream, fd); #else pgp_reader_set_fd(*stream, fd); #endif if (accumulate) { (*stream)->readinfo.accumulate = 1; } return fd; } /** \ingroup Core_Readers \brief Frees stream and closes fd \param stream \param fd \sa pgp_setup_file_read() */ void pgp_teardown_file_read(pgp_stream_t *stream, int fd) { close(fd); pgp_stream_delete(stream); } pgp_cb_ret_t pgp_litdata_cb(const pgp_packet_t *pkt, pgp_cbdata_t *cbinfo) { const pgp_contents_t *content = &pkt->u; if (pgp_get_debug_level(__FILE__)) { printf("pgp_litdata_cb: "); pgp_print_packet(&cbinfo->printstate, pkt); } /* Read data from packet into static buffer */ switch (pkt->tag) { case PGP_PTAG_CT_LITDATA_BODY: /* if writer enabled, use it */ if (cbinfo->output) { if (pgp_get_debug_level(__FILE__)) { printf("pgp_litdata_cb: length is %u\n", content->litdata_body.length); } pgp_write(cbinfo->output, content->litdata_body.data, content->litdata_body.length); } break; case PGP_PTAG_CT_LITDATA_HEADER: /* ignore */ break; default: break; } return PGP_RELEASE_MEMORY; } pgp_cb_ret_t pgp_pk_sesskey_cb(const pgp_packet_t *pkt, pgp_cbdata_t *cbinfo) { const pgp_contents_t *content = &pkt->u; unsigned from; pgp_io_t *io; io = cbinfo->io; if (pgp_get_debug_level(__FILE__)) { pgp_print_packet(&cbinfo->printstate, pkt); } /* Read data from packet into static buffer */ switch (pkt->tag) { case PGP_PTAG_CT_PK_SESSION_KEY: if (pgp_get_debug_level(__FILE__)) { printf("PGP_PTAG_CT_PK_SESSION_KEY\n"); } if (!cbinfo->cryptinfo.secring) { (void) fprintf(io->errs, "pgp_pk_sesskey_cb: bad keyring\n"); return (pgp_cb_ret_t)0; } from = 0; cbinfo->cryptinfo.keydata = pgp_getkeybyid(io, cbinfo->cryptinfo.secring, content->pk_sesskey.key_id, &from, NULL); if (!cbinfo->cryptinfo.keydata) { break; } break; default: break; } return PGP_RELEASE_MEMORY; } /** \ingroup Core_Callbacks \brief Callback to get secret key, decrypting if necessary. @verbatim This callback does the following: * finds the session key in the keyring * gets a passphrase if required * decrypts the secret key, if necessary * sets the seckey in the content struct @endverbatim */ pgp_cb_ret_t pgp_get_seckey_cb(const pgp_packet_t *pkt, pgp_cbdata_t *cbinfo) { const pgp_contents_t *content = &pkt->u; const pgp_seckey_t *secret; const pgp_key_t *pubkey; const pgp_key_t *keypair; unsigned from; pgp_io_t *io; int i; io = cbinfo->io; if (pgp_get_debug_level(__FILE__)) { pgp_print_packet(&cbinfo->printstate, pkt); } switch (pkt->tag) { case PGP_GET_SECKEY: /* print key from pubring */ from = 0; pubkey = pgp_getkeybyid(io, cbinfo->cryptinfo.pubring, content->get_seckey.pk_sesskey->key_id, &from, NULL); /* validate key from secring */ from = 0; cbinfo->cryptinfo.keydata = pgp_getkeybyid(io, cbinfo->cryptinfo.secring, content->get_seckey.pk_sesskey->key_id, &from, NULL); if (!cbinfo->cryptinfo.keydata || !pgp_is_key_secret(cbinfo->cryptinfo.keydata)) { return (pgp_cb_ret_t)0; } keypair = cbinfo->cryptinfo.keydata; if (pubkey == NULL) { pubkey = keypair; } secret = NULL; cbinfo->gotpass = 0; for (i = 0 ; cbinfo->numtries == -1 || i < cbinfo->numtries ; i++) { /* print out the user id */ pgp_print_keydata(io, cbinfo->cryptinfo.pubring, pubkey, "signature ", &pubkey->key.pubkey, 0); /* now decrypt key */ secret = pgp_decrypt_seckey(keypair, cbinfo->passfp); if (secret != NULL) { break; } (void) fprintf(io->errs, "Bad passphrase\n"); } if (secret == NULL) { (void) fprintf(io->errs, "Exhausted passphrase attempts\n"); return (pgp_cb_ret_t)PGP_RELEASE_MEMORY; } cbinfo->gotpass = 1; *content->get_seckey.seckey = secret; break; default: break; } return PGP_RELEASE_MEMORY; } /** \ingroup HighLevel_Callbacks \brief Callback to use when you need to prompt user for passphrase \param contents \param cbinfo */ pgp_cb_ret_t get_passphrase_cb(const pgp_packet_t *pkt, pgp_cbdata_t *cbinfo) { const pgp_contents_t *content = &pkt->u; pgp_io_t *io; io = cbinfo->io; if (pgp_get_debug_level(__FILE__)) { pgp_print_packet(&cbinfo->printstate, pkt); } if (cbinfo->cryptinfo.keydata == NULL) { (void) fprintf(io->errs, "get_passphrase_cb: NULL keydata\n"); } else { pgp_print_keydata(io, cbinfo->cryptinfo.pubring, cbinfo->cryptinfo.keydata, "signature ", &cbinfo->cryptinfo.keydata->key.pubkey, 0); } switch (pkt->tag) { case PGP_GET_PASSPHRASE: *(content->skey_passphrase.passphrase) = netpgp_strdup(getpass("netpgp passphrase: ")); return PGP_KEEP_MEMORY; default: break; } return PGP_RELEASE_MEMORY; } unsigned pgp_reader_set_accumulate(pgp_stream_t *stream, unsigned state) { return stream->readinfo.accumulate = state; } /**************************************************************************/ static int hash_reader(pgp_stream_t *stream, void *dest, size_t length, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { pgp_hash_t *hash = pgp_reader_get_arg(readinfo); int r; r = pgp_stacked_read(stream, dest, length, errors, readinfo, cbinfo); if (r <= 0) { return r; } hash->add(hash, dest, (unsigned)r); return r; } /** \ingroup Internal_Readers_Hash \brief Push hashed data reader on stack */ void pgp_reader_push_hash(pgp_stream_t *stream, pgp_hash_t *hash) { if (!hash->init(hash)) { (void) fprintf(stderr, "pgp_reader_push_hash: can't init hash\n"); /* just continue and die */ /* XXX - agc - no way to return failure */ } pgp_reader_push(stream, hash_reader, NULL, hash); } /** \ingroup Internal_Readers_Hash \brief Pop hashed data reader from stack */ void pgp_reader_pop_hash(pgp_stream_t *stream) { pgp_reader_pop(stream); } /* read memory from the previously mmap-ed file */ static int mmap_reader(pgp_stream_t *stream, void *dest, size_t length, pgp_error_t **errors, pgp_reader_t *readinfo, pgp_cbdata_t *cbinfo) { mmap_reader_t *mem = pgp_reader_get_arg(readinfo); unsigned n; char *cmem = mem->mem; __PGP_USED(errors); __PGP_USED(cbinfo); if (!stream->coalescing && stream->virtualc && stream->virtualoff < stream->virtualc) { n = read_partial_data(stream, dest, length); } else { n = (unsigned)MIN(length, (unsigned)(mem->size - mem->offset)); if (n > 0) { (void) memcpy(dest, &cmem[(int)mem->offset], (unsigned)n); mem->offset += n; } } return (int)n; } /* tear down the mmap, close the fd */ static void mmap_destroyer(pgp_reader_t *readinfo) { mmap_reader_t *mem = pgp_reader_get_arg(readinfo); (void) munmap(mem->mem, (unsigned)mem->size); (void) close(mem->fd); free(pgp_reader_get_arg(readinfo)); } /* set up the file to use mmap-ed memory if available, file IO otherwise */ void pgp_reader_set_mmap(pgp_stream_t *stream, int fd) { mmap_reader_t *mem; struct stat st; if (fstat(fd, &st) != 0) { (void) fprintf(stderr, "pgp_reader_set_mmap: can't fstat\n"); } else if ((mem = calloc(1, sizeof(*mem))) == NULL) { (void) fprintf(stderr, "pgp_reader_set_mmap: bad alloc\n"); } else { mem->size = (uint64_t)st.st_size; mem->offset = 0; mem->fd = fd; mem->mem = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_PRIVATE | MAP_FILE, fd, 0); if (mem->mem == MAP_FAILED) { pgp_reader_set(stream, fd_reader, reader_fd_destroyer, mem); } else { pgp_reader_set(stream, mmap_reader, mmap_destroyer, mem); } } }