diff --git a/common/ksba-io-support.c b/common/ksba-io-support.c index a279b67ad..352485ffa 100644 --- a/common/ksba-io-support.c +++ b/common/ksba-io-support.c @@ -1,867 +1,953 @@ /* kska-io-support.c - Supporting functions for ksba reader and writer * Copyright (C) 2001-2005, 2007, 2010-2011, 2017 Werner Koch - * Copyright (C) 2006 g10 Code GmbH + * Copyright (C) 2006, 2023 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - 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. * * or both in parallel, as here. * * This file 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, see . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #include #include #include #include #include #include #include #include #include #include "util.h" #include "i18n.h" #include "tlv.h" #include "ksba-io-support.h" #ifdef HAVE_DOSISH_SYSTEM #define LF "\r\n" #else #define LF "\n" #endif /* Data used by the reader callbacks. */ struct reader_cb_parm_s { estream_t fp; unsigned char line[1024]; int linelen; int readpos; int have_lf; unsigned long line_counter; int allow_multi_pem; /* Allow processing of multiple PEM objects. */ int autodetect; /* Try to detect the input encoding. */ int assume_pem; /* Assume input encoding is PEM. */ int assume_base64; /* Assume input is base64 encoded. */ int strip_zeroes; /* Expect a SEQUENCE followed by zero padding. */ /* 1 = check state; 2 = reading; 3 = checking */ /* for zeroes. */ int use_maxread; /* If true read not more than MAXREAD. */ unsigned int maxread; /* # of bytes left to read. */ off_t nzeroes; /* Number of padding zeroes red. */ int identified; int is_pem; int is_base64; int stop_seen; int might_be_smime; int eof_seen; struct { int idx; unsigned char val; int stop_seen; } base64; }; /* Data used by the writer callbacks. */ struct writer_cb_parm_s { estream_t stream; /* Output stream. */ char *pem_name; /* Malloced. */ + struct { + gnupg_ksba_progress_cb_t cb; + ctrl_t ctrl; + u32 last_time; /* last time reported */ + uint64_t last; /* last amount reported */ + uint64_t current; /* current amount */ + uint64_t total; /* total amount */ + } progress; + int wrote_begin; int did_finish; struct { int idx; int quad_count; unsigned char radbuf[4]; } base64; }; /* Context for this module's functions. */ struct gnupg_ksba_io_s { + int is_writer; /* True if this context refers a writer object. */ union { struct reader_cb_parm_s rparm; struct writer_cb_parm_s wparm; } u; union { ksba_reader_t reader; ksba_writer_t writer; } u2; }; /* The base-64 character list */ static char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; /* The reverse base-64 list */ static unsigned char asctobin[256] = { 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, 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 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 int has_only_base64 (const unsigned char *line, int linelen) { if (linelen < 20) return 0; for (; linelen; line++, linelen--) { if (*line == '\n' || (linelen > 1 && *line == '\r' && line[1] == '\n')) break; if ( !strchr (bintoasc, *line) ) return 0; } return 1; /* yes */ } static int is_empty_line (const unsigned char *line, int linelen) { if (linelen >= 2 && *line == '\r' && line[1] == '\n') return 1; if (linelen >= 1 && *line == '\n') return 1; return 0; } static int base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { struct reader_cb_parm_s *parm = cb_value; size_t n; int c, c2; *nread = 0; if (!buffer) return -1; /* not supported */ next: if (!parm->linelen) { /* read an entire line or up to the size of the buffer */ parm->line_counter++; parm->have_lf = 0; for (n=0; n < DIM(parm->line);) { c = es_getc (parm->fp); if (c == EOF) { parm->eof_seen = 1; if (es_ferror (parm->fp)) return -1; break; } parm->line[n++] = c; if (c == '\n') { parm->have_lf = 1; /* Fixme: we need to skip overlong lines while detecting the dashed lines */ break; } } parm->linelen = n; if (!n) return -1; /* eof */ parm->readpos = 0; } if (!parm->identified) { if (!parm->autodetect) { if (parm->assume_pem) { /* wait for the header line */ parm->linelen = parm->readpos = 0; if (!parm->have_lf || strncmp ((char*)parm->line, "-----BEGIN ", 11) || !strncmp ((char*)parm->line+11, "PGP ", 4)) goto next; parm->is_pem = 1; } else if (parm->assume_base64) parm->is_base64 = 1; } else if (parm->line_counter == 1 && !parm->have_lf) { /* first line too long - assume DER encoding */ parm->is_pem = 0; } else if (parm->line_counter == 1 && parm->linelen && *parm->line == 0x30) { /* the very first byte does pretty much look like a SEQUENCE tag*/ parm->is_pem = 0; } else if ( parm->have_lf && !strncmp ((char*)parm->line, "-----BEGIN ", 11) && strncmp ((char *)parm->line+11, "PGP ", 4) ) { /* Fixme: we must only compare if the line really starts at the beginning */ parm->is_pem = 1; parm->linelen = parm->readpos = 0; } else if ( parm->have_lf && parm->line_counter == 1 && parm->linelen >= 13 && !ascii_memcasecmp (parm->line, "Content-Type:", 13)) { /* might be a S/MIME body */ parm->might_be_smime = 1; parm->linelen = parm->readpos = 0; goto next; } else if (parm->might_be_smime == 1 && is_empty_line (parm->line, parm->linelen)) { parm->might_be_smime = 2; parm->linelen = parm->readpos = 0; goto next; } else if (parm->might_be_smime == 2) { parm->might_be_smime = 0; if ( !has_only_base64 (parm->line, parm->linelen)) { parm->linelen = parm->readpos = 0; goto next; } parm->is_pem = 1; } else { parm->linelen = parm->readpos = 0; goto next; } parm->identified = 1; parm->base64.stop_seen = 0; parm->base64.idx = 0; } n = 0; if (parm->is_pem || parm->is_base64) { if (parm->is_pem && parm->have_lf && !strncmp ((char*)parm->line, "-----END ", 9)) { parm->identified = 0; parm->linelen = parm->readpos = 0; /* If the caller want to read multiple PEM objects from one file, we have to reset our internal state and return a EOF immediately. The caller is the expected to use ksba_reader_clear to clear the EOF condition and continue to read. If we don't want to do that we just return 0 bytes which will force the ksba_reader to skip until EOF. */ if (parm->allow_multi_pem) { parm->identified = 0; parm->autodetect = 0; parm->assume_pem = 1; parm->stop_seen = 0; return -1; /* Send EOF now. */ } } else if (parm->stop_seen) { /* skip the rest of the line */ parm->linelen = parm->readpos = 0; } else { int idx = parm->base64.idx; unsigned char val = parm->base64.val; while (n < count && parm->readpos < parm->linelen ) { c = parm->line[parm->readpos++]; if (c == '\n' || c == ' ' || c == '\r' || c == '\t') continue; if ((c = asctobin[(c2=c)]) == 255) { if (c2 == '=') { /* pad character: stop */ if (idx == 1) buffer[n++] = val; parm->stop_seen = 1; break; } else if (c2 == '-' && parm->readpos == 1 && parm->readpos-1+9 < parm->linelen && !strncmp ((char*)parm->line + parm->readpos-1, "-----END ", 9)) { /* END line seen (padding was not needed). */ parm->stop_seen = 1; break; } log_error (_("invalid radix64 character %02x skipped\n"), c2); continue; } switch (idx) { case 0: val = c << 2; break; case 1: val |= (c>>4)&3; buffer[n++] = val; val = (c<<4)&0xf0; break; case 2: val |= (c>>2)&15; buffer[n++] = val; val = (c<<6)&0xc0; break; case 3: val |= c&0x3f; buffer[n++] = val; break; } idx = (idx+1) % 4; } if (parm->readpos == parm->linelen) parm->linelen = parm->readpos = 0; parm->base64.idx = idx; parm->base64.val = val; } } else { /* DER encoded */ while (n < count && parm->readpos < parm->linelen) buffer[n++] = parm->line[parm->readpos++]; if (parm->readpos == parm->linelen) parm->linelen = parm->readpos = 0; } *nread = n; return 0; } /* Read up to 10 bytes to test whether the data consist of a sequence; * if that is true, set the limited flag and record the length of the * entire sequence in PARM. Unget everything then. Return true if we * have a sequence with a fixed length. */ static int starts_with_sequence (struct reader_cb_parm_s *parm) { gpg_error_t err; unsigned char peekbuf[10]; int npeeked, c; int found = 0; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; for (npeeked=0; npeeked < sizeof peekbuf; npeeked++) { c = es_getc (parm->fp); if (c == EOF) goto leave; peekbuf[npeeked] = c; } /* Enough to check for a sequence. */ p = peekbuf; n = npeeked; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) { log_debug ("%s: error parsing data: %s\n", __func__, gpg_strerror (err)); goto leave; } if (class == CLASS_UNIVERSAL && constructed && tag == TAG_SEQUENCE && !ndef) { /* We need to add 1 due to the way we implement the limit. */ parm->maxread = objlen + hdrlen + 1; if (!(parm->maxread < objlen + hdrlen) && parm->maxread) parm->use_maxread = 1; found = 1; } leave: while (npeeked) es_ungetc (peekbuf[--npeeked], parm->fp); return found; } static int simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { struct reader_cb_parm_s *parm = cb_value; size_t n; int c = 0; *nread = 0; if (!buffer) return -1; /* not supported */ restart: if (parm->strip_zeroes) { if (parm->strip_zeroes == 1) { if (starts_with_sequence (parm)) parm->strip_zeroes = 2; /* Found fixed length sequence. */ else parm->strip_zeroes = 0; /* Disable zero padding check. */ } else if (parm->strip_zeroes == 3) { /* Limit reached - check that only zeroes follow. */ while (!(c = es_getc (parm->fp))) parm->nzeroes++; if (c == EOF) { /* only zeroes found. Reset zero padding engine and * return EOF. */ parm->strip_zeroes = 0; parm->eof_seen = 1; return -1; } /* Not only zeroes. Reset engine and continue. */ parm->strip_zeroes = 0; } } for (n=0; n < count; n++) { if (parm->use_maxread && !--parm->maxread) { parm->use_maxread = 0; if (parm->strip_zeroes) { parm->strip_zeroes = 3; parm->nzeroes = 0; if (n) goto leave; /* Return what we already got. */ goto restart; /* Immediately check for trailing zeroes. */ } } if (parm->nzeroes) { parm->nzeroes--; c = 0; } else c = es_getc (parm->fp); if (c == EOF) { parm->eof_seen = 1; if (es_ferror (parm->fp)) return -1; if (n) break; /* Return what we have before an EOF. */ return -1; } *(byte *)buffer++ = c; } leave: *nread = n; return 0; } +/* Call the progress callback if its time. We do this very 2 seconds + * or if FORCE is set. However, we also require that at least 64KiB + * have been written to avoid unnecessary progress lines for small + * files. */ +static gpg_error_t +update_write_progress (struct writer_cb_parm_s *parm, size_t count, int force) +{ + gpg_error_t err = 0; + u32 timestamp; + + parm->progress.current += count; + if (parm->progress.current >= (64*1024)) + { + timestamp = make_timestamp (); + if (force || (timestamp - parm->progress.last_time > 1)) + { + parm->progress.last = parm->progress.current; + parm->progress.last_time = timestamp; + err = parm->progress.cb (parm->progress.ctrl, + parm->progress.current, + parm->progress.total); + } + } + return err; +} + + static int base64_writer_cb (void *cb_value, const void *buffer, size_t count) { struct writer_cb_parm_s *parm = cb_value; unsigned char radbuf[4]; int i, c, idx, quad_count; const unsigned char *p; estream_t stream = parm->stream; + int rc; + size_t nleft; if (!count) return 0; if (!parm->wrote_begin) { if (parm->pem_name) { es_fputs ("-----BEGIN ", stream); es_fputs (parm->pem_name, stream); es_fputs ("-----\n", stream); } parm->wrote_begin = 1; parm->base64.idx = 0; parm->base64.quad_count = 0; } idx = parm->base64.idx; quad_count = parm->base64.quad_count; for (i=0; i < idx; i++) radbuf[i] = parm->base64.radbuf[i]; - for (p=buffer; count; p++, count--) + for (p=buffer, nleft = count; nleft; p++, nleft--) { radbuf[idx++] = *p; if (idx > 2) { idx = 0; c = bintoasc[(*radbuf >> 2) & 077]; es_putc (c, stream); c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; es_putc (c, stream); c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; es_putc (c, stream); c = bintoasc[radbuf[2]&077]; es_putc (c, stream); if (++quad_count >= (64/4)) { es_fputs (LF, stream); quad_count = 0; } } } for (i=0; i < idx; i++) parm->base64.radbuf[i] = radbuf[i]; parm->base64.idx = idx; parm->base64.quad_count = quad_count; - return es_ferror (stream)? gpg_error_from_syserror () : 0; + rc = es_ferror (stream)? gpg_error_from_syserror () : 0; + /* Note that we use the unencoded count for the progress. */ + if (!rc && parm->progress.cb) + rc = update_write_progress (parm, count, 0); + return rc; } /* This callback is only used in stream mode. However, we don't restrict it to this. */ static int plain_writer_cb (void *cb_value, const void *buffer, size_t count) { struct writer_cb_parm_s *parm = cb_value; estream_t stream = parm->stream; + int rc; if (!count) return 0; es_write (stream, buffer, count, NULL); - - return es_ferror (stream)? gpg_error_from_syserror () : 0; + rc = es_ferror (stream)? gpg_error_from_syserror () : 0; + if (!rc && parm->progress.cb) + rc = update_write_progress (parm, count, 0); + return rc; } static int base64_finish_write (struct writer_cb_parm_s *parm) { unsigned char *radbuf; int c, idx, quad_count; estream_t stream = parm->stream; + int rc; if (!parm->wrote_begin) return 0; /* Nothing written or we are not called in base-64 mode. */ /* flush the base64 encoding */ idx = parm->base64.idx; quad_count = parm->base64.quad_count; if (idx) { radbuf = parm->base64.radbuf; c = bintoasc[(*radbuf>>2)&077]; es_putc (c, stream); if (idx == 1) { c = bintoasc[((*radbuf << 4) & 060) & 077]; es_putc (c, stream); es_putc ('=', stream); es_putc ('=', stream); } else { c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; es_putc (c, stream); c = bintoasc[((radbuf[1] << 2) & 074) & 077]; es_putc (c, stream); es_putc ('=', stream); } if (++quad_count >= (64/4)) { es_fputs (LF, stream); quad_count = 0; } } if (quad_count) es_fputs (LF, stream); if (parm->pem_name) { es_fputs ("-----END ", stream); es_fputs (parm->pem_name, stream); es_fputs ("-----\n", stream); } - return es_ferror (stream)? gpg_error_from_syserror () : 0; + rc = es_ferror (stream)? gpg_error_from_syserror () : 0; + if (!rc && parm->progress.cb) + rc = update_write_progress (parm, 0, 1); + return rc; } /* Create a reader for the stream FP. FLAGS can be used to specify * the expected input encoding. * * The function returns a gnupg_ksba_io_t object which must be passed to * the gpgme_destroy_reader function. The created ksba_reader_t * object is stored at R_READER - the caller must not call the * ksba_reader_release function on. * * The supported flags are: * * GNUPG_KSBA_IO_PEM - Assume the input is PEM encoded * GNUPG_KSBA_IO_BASE64 - Assume the input is Base64 encoded. * GNUPG_KSBA_IO_AUTODETECT - The reader tries to detect the encoding. * GNUPG_KSBA_IO_MULTIPEM - The reader expects that the caller uses * ksba_reader_clear after EOF until no more * objects were found. * GNUPG_KSBA_IO_STRIP - Strip zero padding from some CMS objects. * * Note that the PEM flag has a higher priority than the BASE64 flag * which in turn has a gight priority than the AUTODETECT flag. */ gpg_error_t gnupg_ksba_create_reader (gnupg_ksba_io_t *ctx, unsigned int flags, estream_t fp, ksba_reader_t *r_reader) { int rc; ksba_reader_t r; *r_reader = NULL; *ctx = xtrycalloc (1, sizeof **ctx); if (!*ctx) return out_of_core (); (*ctx)->u.rparm.allow_multi_pem = !!(flags & GNUPG_KSBA_IO_MULTIPEM); (*ctx)->u.rparm.strip_zeroes = !!(flags & GNUPG_KSBA_IO_STRIP); rc = ksba_reader_new (&r); if (rc) { xfree (*ctx); *ctx = NULL; return rc; } (*ctx)->u.rparm.fp = fp; if ((flags & GNUPG_KSBA_IO_PEM)) { (*ctx)->u.rparm.assume_pem = 1; (*ctx)->u.rparm.assume_base64 = 1; rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); } else if ((flags & GNUPG_KSBA_IO_BASE64)) { (*ctx)->u.rparm.assume_base64 = 1; rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); } else if ((flags & GNUPG_KSBA_IO_AUTODETECT)) { (*ctx)->u.rparm.autodetect = 1; rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); } else rc = ksba_reader_set_cb (r, simple_reader_cb, &(*ctx)->u.rparm); if (rc) { ksba_reader_release (r); xfree (*ctx); *ctx = NULL; return rc; } (*ctx)->u2.reader = r; *r_reader = r; return 0; } /* Return True if an EOF as been seen. */ int gnupg_ksba_reader_eof_seen (gnupg_ksba_io_t ctx) { return ctx && ctx->u.rparm.eof_seen; } /* Destroy a reader object. */ void gnupg_ksba_destroy_reader (gnupg_ksba_io_t ctx) { if (!ctx) return; ksba_reader_release (ctx->u2.reader); xfree (ctx); } /* Create a writer for the given STREAM. Depending on FLAGS an output * encoding is chosen. In PEM mode PEM_NAME is used for the header * and footer lines; if PEM_NAME is NULL the string "CMS OBJECT" is * used. * * The function returns a gnupg_ksba_io_t object which must be passed to * the gpgme_destroy_writer function. The created ksba_writer_t * object is stored at R_WRITER - the caller must not call the * ksba_reader_release function on it. * * The supported flags are: * * GNUPG_KSBA_IO_PEM - Write output as PEM * GNUPG_KSBA_IO_BASE64 - Write output as plain Base64; note that the PEM * flag overrides this flag. * */ gpg_error_t gnupg_ksba_create_writer (gnupg_ksba_io_t *ctx, unsigned int flags, const char *pem_name, estream_t stream, ksba_writer_t *r_writer) { int rc; ksba_writer_t w; *r_writer = NULL; *ctx = xtrycalloc (1, sizeof **ctx); if (!*ctx) return gpg_error_from_syserror (); + (*ctx)->is_writer = 1; rc = ksba_writer_new (&w); if (rc) { xfree (*ctx); *ctx = NULL; return rc; } if ((flags & GNUPG_KSBA_IO_PEM) || (flags & GNUPG_KSBA_IO_BASE64)) { (*ctx)->u.wparm.stream = stream; if ((flags & GNUPG_KSBA_IO_PEM)) { (*ctx)->u.wparm.pem_name = xtrystrdup (pem_name ? pem_name : "CMS OBJECT"); if (!(*ctx)->u.wparm.pem_name) { rc = gpg_error_from_syserror (); ksba_writer_release (w); xfree (*ctx); *ctx = NULL; return rc; } } rc = ksba_writer_set_cb (w, base64_writer_cb, &(*ctx)->u.wparm); } else if (stream) { (*ctx)->u.wparm.stream = stream; rc = ksba_writer_set_cb (w, plain_writer_cb, &(*ctx)->u.wparm); } else rc = gpg_error (GPG_ERR_INV_ARG); if (rc) { ksba_writer_release (w); xfree (*ctx); *ctx = NULL; return rc; } (*ctx)->u2.writer = w; *r_writer = w; return 0; } /* Flush a writer. This is for example required to write the padding * or the PEM footer. */ gpg_error_t gnupg_ksba_finish_writer (gnupg_ksba_io_t ctx) { struct writer_cb_parm_s *parm; if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); parm = &ctx->u.wparm; if (parm->did_finish) return 0; /* Already done. */ parm->did_finish = 1; if (!parm->stream) return 0; /* Callback was not used. */ return base64_finish_write (parm); } /* Destroy a writer object. */ void gnupg_ksba_destroy_writer (gnupg_ksba_io_t ctx) { if (!ctx) return; ksba_writer_release (ctx->u2.writer); xfree (ctx->u.wparm.pem_name); xfree (ctx); } + + +/* Set a callback to the writer object. CTRL will be bassed to the + * callback. */ +void +gnupg_ksba_set_progress_cb (gnupg_ksba_io_t ctx, + gnupg_ksba_progress_cb_t cb, ctrl_t ctrl) +{ + struct writer_cb_parm_s *parm; + + if (!ctx || !ctx->is_writer) + return; /* Currently only supported for writer objects. */ + parm = &ctx->u.wparm; + + parm->progress.cb = cb; + parm->progress.ctrl = ctrl; + parm->progress.last_time = 0; + parm->progress.last = 0; + parm->progress.current = 0; + parm->progress.total = 0; +} + + +/* Update the total count for the progress thingy. */ +void +gnupg_ksba_set_total (gnupg_ksba_io_t ctx, uint64_t total) +{ + struct writer_cb_parm_s *parm; + + if (!ctx || !ctx->is_writer) + return; /* Currently only supported for writer objects. */ + parm = &ctx->u.wparm; + parm->progress.total = total; +} diff --git a/common/ksba-io-support.h b/common/ksba-io-support.h index 02e541b16..1dbc303c8 100644 --- a/common/ksba-io-support.h +++ b/common/ksba-io-support.h @@ -1,67 +1,75 @@ /* ksba-io-support.h - Supporting functions for ksba reader and writer * Copyright (C) 2017 Werner Koch * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - 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. * * or both in parallel, as here. * * This file 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, see . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #ifndef GNUPG_KSBA_IO_SUPPORT_H #define GNUPG_KSBA_IO_SUPPORT_H /* Flags used with gnupg_ksba_create_reader and * gnupg_ksba_create_writer. */ #define GNUPG_KSBA_IO_PEM 1 /* X.509 PEM format. */ #define GNUPG_KSBA_IO_BASE64 2 /* Plain Base64 format. */ #define GNUPG_KSBA_IO_AUTODETECT 4 /* Try to autodetect the format. */ #define GNUPG_KSBA_IO_MULTIPEM 8 /* Allow more than one PEM chunk. */ #define GNUPG_KSBA_IO_STRIP 16 /* Strip off zero padding. */ /* Context object. */ typedef struct gnupg_ksba_io_s *gnupg_ksba_io_t; +/* Progress callback type. */ +typedef gpg_error_t (*gnupg_ksba_progress_cb_t)(ctrl_t ctrl, + uint64_t current, + uint64_t total); gpg_error_t gnupg_ksba_create_reader (gnupg_ksba_io_t *ctx, unsigned int flags, estream_t fp, ksba_reader_t *r_reader); int gnupg_ksba_reader_eof_seen (gnupg_ksba_io_t ctx); void gnupg_ksba_destroy_reader (gnupg_ksba_io_t ctx); gpg_error_t gnupg_ksba_create_writer (gnupg_ksba_io_t *ctx, unsigned int flags, const char *pem_name, estream_t stream, ksba_writer_t *r_writer); - gpg_error_t gnupg_ksba_finish_writer (gnupg_ksba_io_t ctx); void gnupg_ksba_destroy_writer (gnupg_ksba_io_t ctx); +void gnupg_ksba_set_progress_cb (gnupg_ksba_io_t ctx, + gnupg_ksba_progress_cb_t cb, ctrl_t ctrl); +void gnupg_ksba_set_total (gnupg_ksba_io_t ctx, uint64_t total); + #endif /*GNUPG_KSBA_IO_SUPPORT_H*/ diff --git a/sm/decrypt.c b/sm/decrypt.c index 68b362b45..abc1f2602 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -1,1519 +1,1521 @@ /* decrypt.c - Decrypt a message * Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc. * Copyright (C) 2001-2019 Werner Koch * Copyright (C) 2015-2021 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG 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 3 of the License, or * (at your option) any later version. * * GnuPG 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, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../common/i18n.h" #include "../common/tlv.h" #include "../common/compliance.h" struct decrypt_filter_parm_s { int algo; int mode; int blklen; gcry_cipher_hd_t hd; char iv[16]; size_t ivlen; int any_data; /* did we push anything through the filter at all? */ unsigned char lastblock[16]; /* to strip the padding we have to keep this one */ char helpblock[16]; /* needed because there is no block buffering in libgcrypt (yet) */ int helpblocklen; int is_de_vs; /* Helper to track CO_DE_VS state. */ }; /* Return the hash algorithm's algo id from its name given in the * non-null termnated string in (buffer,buflen). Returns 0 on failure * or if the algo is not known. */ static char * string_from_gcry_buffer (gcry_buffer_t *buffer) { char *string; string = xtrymalloc (buffer->len + 1); if (!string) return NULL; memcpy (string, buffer->data, buffer->len); string[buffer->len] = 0; return string; } /* Helper to construct and hash the * ECC-CMS-SharedInfo ::= SEQUENCE { * keyInfo AlgorithmIdentifier, * entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, * suppPubInfo [2] EXPLICIT OCTET STRING } * as described in RFC-5753, 7.2. */ static gpg_error_t hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd, const char *wrap_algo_str, unsigned int keylen, const void *ukm, unsigned int ukmlen) { gpg_error_t err; void *p; unsigned char *oid; size_t n, oidlen, toidlen, tkeyinfo, tukmlen, tsupppubinfo; unsigned char keylenbuf[6]; membuf_t mb = MEMBUF_ZERO; err = ksba_oid_from_str (wrap_algo_str, &oid, &oidlen); if (err) return err; toidlen = get_tlv_length (CLASS_UNIVERSAL, TAG_OBJECT_ID, 0, oidlen); tkeyinfo = get_tlv_length (CLASS_UNIVERSAL, TAG_SEQUENCE, 1, toidlen); tukmlen = ukm? get_tlv_length (CLASS_CONTEXT, 0, 1, ukmlen) : 0; keylen *= 8; keylenbuf[0] = TAG_OCTET_STRING; keylenbuf[1] = 4; keylenbuf[2] = (keylen >> 24); keylenbuf[3] = (keylen >> 16); keylenbuf[4] = (keylen >> 8); keylenbuf[5] = keylen; tsupppubinfo = get_tlv_length (CLASS_CONTEXT, 2, 1, sizeof keylenbuf); put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_SEQUENCE, 1, tkeyinfo + tukmlen + tsupppubinfo); put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_SEQUENCE, 1, toidlen); put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_OBJECT_ID, 0, oidlen); put_membuf (&mb, oid, oidlen); ksba_free (oid); if (ukm) { put_tlv_to_membuf (&mb, CLASS_CONTEXT, 0, 1, ukmlen); put_membuf (&mb, ukm, ukmlen); } put_tlv_to_membuf (&mb, CLASS_CONTEXT, 2, 1, sizeof keylenbuf); put_membuf (&mb, keylenbuf, sizeof keylenbuf); p = get_membuf (&mb, &n); if (!p) return gpg_error_from_syserror (); gcry_md_write (hash_hd, p, n); xfree (p); return 0; } /* Derive a KEK (key wrapping key) using (SECRET,SECRETLEN), an * optional (UKM,ULMLEN), the wrap algorithm WRAP_ALGO_STR in decimal * dotted form, and the hash algorithm HASH_ALGO. On success a key of * length KEYLEN is stored at KEY. */ gpg_error_t ecdh_derive_kek (unsigned char *key, unsigned int keylen, int hash_algo, const char *wrap_algo_str, const void *secret, unsigned int secretlen, const void *ukm, unsigned int ukmlen) { gpg_error_t err = 0; unsigned int hashlen; gcry_md_hd_t hash_hd; unsigned char counter; unsigned int n, ncopy; hashlen = gcry_md_get_algo_dlen (hash_algo); if (!hashlen) return gpg_error (GPG_ERR_INV_ARG); err = gcry_md_open (&hash_hd, hash_algo, 0); if (err) return err; /* According to SEC1 3.6.1 we should check that * SECRETLEN + UKMLEN + 4 < maxhashlen * However, we have no practical limit on the hash length and thus * there is no point in checking this. The second check that * KEYLEN < hashlen*(2^32-1) * is obviously also not needed. */ for (n=0, counter=1; n < keylen; counter++) { if (counter > 1) gcry_md_reset (hash_hd); gcry_md_write (hash_hd, secret, secretlen); gcry_md_write (hash_hd, "\x00\x00\x00", 3); /* MSBs of counter */ gcry_md_write (hash_hd, &counter, 1); err = hash_ecc_cms_shared_info (hash_hd, wrap_algo_str, keylen, ukm, ukmlen); if (err) break; gcry_md_final (hash_hd); if (n + hashlen > keylen) ncopy = keylen - n; else ncopy = hashlen; memcpy (key+n, gcry_md_read (hash_hd, 0), ncopy); n += ncopy; } gcry_md_close (hash_hd); return err; } /* This function will modify SECRET. NBITS is the size of the curve * which which we took from the certificate. */ static gpg_error_t ecdh_decrypt (unsigned char *secret, size_t secretlen, unsigned int nbits, gcry_sexp_t enc_val, unsigned char **r_result, unsigned int *r_resultlen) { gpg_error_t err; gcry_buffer_t ioarray[4] = { {0}, {0}, {0}, {0} }; char *encr_algo_str = NULL; char *wrap_algo_str = NULL; int hash_algo, cipher_algo; const unsigned char *ukm; /* Alias for ioarray[2]. */ unsigned int ukmlen; const unsigned char *data; /* Alias for ioarray[3]. */ unsigned int datalen; unsigned int keylen; unsigned char key[32]; gcry_cipher_hd_t cipher_hd = NULL; unsigned char *result = NULL; unsigned int resultlen; *r_resultlen = 0; *r_result = NULL; /* Extract X from SECRET; this is the actual secret. Unless a * smartcard diretcly returns X, it must be in the format of: * * 04 || X || Y * 40 || X * 41 || X */ if (secretlen < 2) return gpg_error (GPG_ERR_BAD_DATA); if (secretlen == (nbits+7)/8) ; /* Matches curve length - this is already the X coordinate. */ else if (*secret == 0x04) { secretlen--; memmove (secret, secret+1, secretlen); if ((secretlen & 1)) return gpg_error (GPG_ERR_BAD_DATA); secretlen /= 2; } else if (*secret == 0x40 || *secret == 0x41) { secretlen--; memmove (secret, secret+1, secretlen); } else return gpg_error (GPG_ERR_BAD_DATA); if (!secretlen) return gpg_error (GPG_ERR_BAD_DATA); if (DBG_CRYPTO) log_printhex (secret, secretlen, "ECDH X ..:"); /* We have now the shared secret bytes in (SECRET,SECRETLEN). Now * we will compute the KEK using a value dervied from the secret * bytes. */ err = gcry_sexp_extract_param (enc_val, "enc-val", "&'encr-algo''wrap-algo''ukm'?s", ioarray+0, ioarray+1, ioarray+2, ioarray+3, NULL); if (err) { log_error ("extracting ECDH parameter failed: %s\n", gpg_strerror (err)); goto leave; } encr_algo_str = string_from_gcry_buffer (ioarray); if (!encr_algo_str) { err = gpg_error_from_syserror (); goto leave; } wrap_algo_str = string_from_gcry_buffer (ioarray+1); if (!wrap_algo_str) { err = gpg_error_from_syserror (); goto leave; } ukm = ioarray[2].data; ukmlen = ioarray[2].len; data = ioarray[3].data; datalen = ioarray[3].len; /* Check parameters. */ if (DBG_CRYPTO) { log_debug ("encr_algo: %s\n", encr_algo_str); log_debug ("wrap_algo: %s\n", wrap_algo_str); log_printhex (ukm, ukmlen, "ukm .....:"); log_printhex (data, datalen, "data ....:"); } if (!strcmp (encr_algo_str, "1.3.132.1.11.1")) { /* dhSinglePass-stdDH-sha256kdf-scheme */ hash_algo = GCRY_MD_SHA256; } else if (!strcmp (encr_algo_str, "1.3.132.1.11.2")) { /* dhSinglePass-stdDH-sha384kdf-scheme */ hash_algo = GCRY_MD_SHA384; } else if (!strcmp (encr_algo_str, "1.3.132.1.11.3")) { /* dhSinglePass-stdDH-sha512kdf-scheme */ hash_algo = GCRY_MD_SHA512; } else if (!strcmp (encr_algo_str, "1.3.133.16.840.63.0.2")) { /* dhSinglePass-stdDH-sha1kdf-scheme */ hash_algo = GCRY_MD_SHA1; } else { err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; } if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.5")) { cipher_algo = GCRY_CIPHER_AES128; keylen = 16; } else if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.25")) { cipher_algo = GCRY_CIPHER_AES192; keylen = 24; } else if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.45")) { cipher_algo = GCRY_CIPHER_AES256; keylen = 32; } else { err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; } err = ecdh_derive_kek (key, keylen, hash_algo, wrap_algo_str, secret, secretlen, ukm, ukmlen); if (err) goto leave; if (DBG_CRYPTO) log_printhex (key, keylen, "KEK .....:"); /* Unwrap the key. */ if ((datalen % 8) || datalen < 16) { log_error ("can't use a shared secret of %u bytes for ecdh\n", datalen); err = gpg_error (GPG_ERR_BAD_DATA); goto leave; } resultlen = datalen - 8; result = xtrymalloc_secure (resultlen); if (!result) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_open (&cipher_hd, cipher_algo, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) { log_error ("ecdh failed to initialize AESWRAP: %s\n", gpg_strerror (err)); goto leave; } err = gcry_cipher_setkey (cipher_hd, key, keylen); wipememory (key, sizeof key); if (err) { log_error ("ecdh failed in gcry_cipher_setkey: %s\n", gpg_strerror (err)); goto leave; } err = gcry_cipher_decrypt (cipher_hd, result, resultlen, data, datalen); if (err) { log_error ("ecdh failed in gcry_cipher_decrypt: %s\n",gpg_strerror (err)); goto leave; } *r_resultlen = resultlen; *r_result = result; result = NULL; leave: if (result) { wipememory (result, resultlen); xfree (result); } gcry_cipher_close (cipher_hd); xfree (encr_algo_str); xfree (wrap_algo_str); xfree (ioarray[0].data); xfree (ioarray[1].data); xfree (ioarray[2].data); xfree (ioarray[3].data); return err; } /* Helper for pwri_decrypt to parse the derive info. * Example data for (DER,DERLEN): * SEQUENCE { * OCTET STRING * 60 76 4B E9 5E DF 3C F8 B2 F9 B6 C2 7D 5A FB 90 * 23 B6 47 DF * INTEGER 10000 * SEQUENCE { * OBJECT IDENTIFIER * hmacWithSHA512 (1 2 840 113549 2 11) * NULL * } * } */ static gpg_error_t pwri_parse_pbkdf2 (const unsigned char *der, size_t derlen, unsigned char const **r_salt, unsigned int *r_saltlen, unsigned long *r_iterations, int *r_digest) { gpg_error_t err; size_t objlen, hdrlen; int class, tag, constructed, ndef; char *oidstr; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_SEQUENCE || !constructed || ndef)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) return err; derlen = objlen; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_OCTET_STRING || constructed || ndef)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) return err; *r_salt = der; *r_saltlen = objlen; der += objlen; derlen -= objlen; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_INTEGER || constructed || ndef)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) return err; *r_iterations = 0; for (; objlen; objlen--) { *r_iterations <<= 8; *r_iterations |= (*der++) & 0xff; derlen--; } err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_SEQUENCE || !constructed || ndef)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) return err; derlen = objlen; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_OBJECT_ID || constructed || ndef)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) return err; oidstr = ksba_oid_to_str (der, objlen); if (!oidstr) return gpg_error_from_syserror (); *r_digest = gcry_md_map_name (oidstr); if (*r_digest) ; else if (!strcmp (oidstr, "1.2.840.113549.2.7")) *r_digest = GCRY_MD_SHA1; else if (!strcmp (oidstr, "1.2.840.113549.2.8")) *r_digest = GCRY_MD_SHA224; else if (!strcmp (oidstr, "1.2.840.113549.2.9")) *r_digest = GCRY_MD_SHA256; else if (!strcmp (oidstr, "1.2.840.113549.2.10")) *r_digest = GCRY_MD_SHA384; else if (!strcmp (oidstr, "1.2.840.113549.2.11")) *r_digest = GCRY_MD_SHA512; else err = gpg_error (GPG_ERR_DIGEST_ALGO); ksba_free (oidstr); return err; } /* Password based decryption. * ENC_VAL has the form: * (enc-val * (pwri * (derive-algo ) --| both are optional * (derive-parm ) --| * (encr-algo ) * (encr-parm ) * (encr-key ))) -- this is the encrypted session key * */ static gpg_error_t pwri_decrypt (ctrl_t ctrl, gcry_sexp_t enc_val, unsigned char **r_result, unsigned int *r_resultlen, struct decrypt_filter_parm_s *parm) { gpg_error_t err; gcry_buffer_t ioarray[5] = { {0} }; char *derive_algo_str = NULL; char *encr_algo_str = NULL; const unsigned char *dparm; /* Alias for ioarray[1]. */ unsigned int dparmlen; const unsigned char *eparm; /* Alias for ioarray[3]. */ unsigned int eparmlen; const unsigned char *ekey; /* Alias for ioarray[4]. */ unsigned int ekeylen; unsigned char kek[32]; unsigned int keklen; int encr_algo; enum gcry_cipher_modes encr_mode; gcry_cipher_hd_t encr_hd = NULL; unsigned char *result = NULL; unsigned int resultlen; unsigned int blklen; const unsigned char *salt; /* Points int dparm. */ unsigned int saltlen; unsigned long iterations; int digest_algo; char *passphrase = NULL; *r_resultlen = 0; *r_result = NULL; err = gcry_sexp_extract_param (enc_val, "enc-val!pwri", "&'derive-algo'?'derive-parm'?" "'encr-algo''encr-parm''encr-key'", ioarray+0, ioarray+1, ioarray+2, ioarray+3, ioarray+4, NULL); if (err) { /* If this is not pwri element, it is likly a kekri element * which we do not yet support. Change the error back to the * original as returned by ksba_cms_get_issuer. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = gpg_error (GPG_ERR_UNSUPPORTED_CMS_OBJ); else log_error ("extracting PWRI parameter failed: %s\n", gpg_strerror (err)); goto leave; } if (ioarray[0].data) { derive_algo_str = string_from_gcry_buffer (ioarray+0); if (!derive_algo_str) { err = gpg_error_from_syserror (); goto leave; } } dparm = ioarray[1].data; dparmlen = ioarray[1].len; encr_algo_str = string_from_gcry_buffer (ioarray+2); if (!encr_algo_str) { err = gpg_error_from_syserror (); goto leave; } eparm = ioarray[3].data; eparmlen = ioarray[3].len; ekey = ioarray[4].data; ekeylen = ioarray[4].len; /* Check parameters. */ if (DBG_CRYPTO) { if (derive_algo_str) { log_debug ("derive algo: %s\n", derive_algo_str); log_printhex (dparm, dparmlen, "derive parm:"); } log_debug ("encr algo .: %s\n", encr_algo_str); log_printhex (eparm, eparmlen, "encr parm .:"); log_printhex (ekey, ekeylen, "encr key .:"); } if (!derive_algo_str) { err = gpg_error (GPG_ERR_NOT_SUPPORTED); log_info ("PWRI with no key derivation detected\n"); goto leave; } if (strcmp (derive_algo_str, "1.2.840.113549.1.5.12")) { err = gpg_error (GPG_ERR_NOT_SUPPORTED); log_info ("PWRI does not use PBKDF2 (but %s)\n", derive_algo_str); goto leave; } digest_algo = 0; /*(silence cc warning)*/ err = pwri_parse_pbkdf2 (dparm, dparmlen, &salt, &saltlen, &iterations, &digest_algo); if (err) { log_error ("parsing PWRI parameter failed: %s\n", gpg_strerror (err)); goto leave; } parm->is_de_vs = (parm->is_de_vs && gnupg_digest_is_compliant (CO_DE_VS, digest_algo)); encr_algo = gcry_cipher_map_name (encr_algo_str); encr_mode = gcry_cipher_mode_from_oid (encr_algo_str); if (!encr_algo || !encr_mode) { log_error ("PWRI uses unknown algorithm %s\n", encr_algo_str); err = gpg_error (GPG_ERR_CIPHER_ALGO); goto leave; } parm->is_de_vs = (parm->is_de_vs && gnupg_cipher_is_compliant (CO_DE_VS, encr_algo, encr_mode)); keklen = gcry_cipher_get_algo_keylen (encr_algo); blklen = gcry_cipher_get_algo_blklen (encr_algo); if (!keklen || keklen > sizeof kek || blklen != 16 ) { log_error ("PWRI algorithm %s cannot be used\n", encr_algo_str); err = gpg_error (GPG_ERR_INV_KEYLEN); goto leave; } if ((ekeylen % blklen) || (ekeylen / blklen < 2)) { /* Note that we need at least two full blocks. */ log_error ("PWRI uses a wrong length of encrypted key\n"); err = gpg_error (GPG_ERR_INV_KEYLEN); goto leave; } err = gpgsm_agent_ask_passphrase (ctrl, i18n_utf8 (N_("Please enter the passphrase for decryption.")), 0, &passphrase); if (err) goto leave; err = gcry_kdf_derive (passphrase, strlen (passphrase), GCRY_KDF_PBKDF2, digest_algo, salt, saltlen, iterations, keklen, kek); if (passphrase) { wipememory (passphrase, strlen (passphrase)); xfree (passphrase); passphrase = NULL; } if (err) { log_error ("deriving key from passphrase failed: %s\n", gpg_strerror (err)); goto leave; } if (DBG_CRYPTO) log_printhex (kek, keklen, "KEK .......:"); /* Unwrap the key. */ resultlen = ekeylen; result = xtrymalloc_secure (resultlen); if (!result) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_open (&encr_hd, encr_algo, encr_mode, 0); if (err) { log_error ("PWRI failed to open cipher: %s\n", gpg_strerror (err)); goto leave; } err = gcry_cipher_setkey (encr_hd, kek, keklen); wipememory (kek, sizeof kek); if (!err) err = gcry_cipher_setiv (encr_hd, ekey + ekeylen - 2 * blklen, blklen); if (!err) err = gcry_cipher_decrypt (encr_hd, result + ekeylen - blklen, blklen, ekey + ekeylen - blklen, blklen); if (!err) err = gcry_cipher_setiv (encr_hd, result + ekeylen - blklen, blklen); if (!err) err = gcry_cipher_decrypt (encr_hd, result, ekeylen - blklen, ekey, ekeylen - blklen); /* (We assume that that eparm is the octet string with the IV) */ if (!err) err = gcry_cipher_setiv (encr_hd, eparm, eparmlen); if (!err) err = gcry_cipher_decrypt (encr_hd, result, resultlen, NULL, 0); if (err) { log_error ("KEK decryption failed for PWRI: %s\n", gpg_strerror (err)); goto leave; } if (DBG_CRYPTO) log_printhex (result, resultlen, "Frame .....:"); if (result[0] < 8 /* At least 64 bits */ || (result[0] % 8) /* Multiple of 64 bits */ || result[0] > resultlen - 4 /* Not more than the size of the input */ || ( (result[1] ^ result[4]) /* Matching check bytes. */ & (result[2] ^ result[5]) & (result[3] ^ result[6]) ) != 0xff) { err = gpg_error (GPG_ERR_BAD_PASSPHRASE); goto leave; } *r_resultlen = result[0]; *r_result = memmove (result, result + 4, result[0]); result = NULL; leave: if (result) { wipememory (result, resultlen); xfree (result); } if (passphrase) { wipememory (passphrase, strlen (passphrase)); xfree (passphrase); } gcry_cipher_close (encr_hd); xfree (derive_algo_str); xfree (encr_algo_str); xfree (ioarray[0].data); xfree (ioarray[1].data); xfree (ioarray[2].data); xfree (ioarray[3].data); xfree (ioarray[4].data); return err; } /* Decrypt the session key and fill in the parm structure. The algo and the IV is expected to be already in PARM. */ static int prepare_decryption (ctrl_t ctrl, const char *hexkeygrip, int pk_algo, unsigned int nbits, const char *desc, ksba_const_sexp_t enc_val, struct decrypt_filter_parm_s *parm) { char *seskey = NULL; size_t n, seskeylen; int pwri = !hexkeygrip && !pk_algo; int rc; if (DBG_CRYPTO) log_printcanon ("decrypting:", enc_val, 0); if (!pwri) { rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, enc_val, &seskey, &seskeylen); if (rc) { log_error ("error decrypting session key: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_CRYPTO) log_printhex (seskey, seskeylen, "DEK frame:"); } n=0; if (pwri) /* Password based encryption. */ { gcry_sexp_t s_enc_val; unsigned char *decrypted; unsigned int decryptedlen; rc = gcry_sexp_sscan (&s_enc_val, NULL, enc_val, gcry_sexp_canon_len (enc_val, 0, NULL, NULL)); if (rc) goto leave; rc = pwri_decrypt (ctrl, s_enc_val, &decrypted, &decryptedlen, parm); gcry_sexp_release (s_enc_val); if (rc) goto leave; xfree (seskey); seskey = decrypted; seskeylen = decryptedlen; } else if (pk_algo == GCRY_PK_ECC) { gcry_sexp_t s_enc_val; unsigned char *decrypted; unsigned int decryptedlen; rc = gcry_sexp_sscan (&s_enc_val, NULL, enc_val, gcry_sexp_canon_len (enc_val, 0, NULL, NULL)); if (rc) goto leave; rc = ecdh_decrypt (seskey, seskeylen, nbits, s_enc_val, &decrypted, &decryptedlen); gcry_sexp_release (s_enc_val); if (rc) goto leave; xfree (seskey); seskey = decrypted; seskeylen = decryptedlen; } else if (seskeylen == 32 || seskeylen == 24 || seskeylen == 16) { /* Smells like an AES-128, 3-DES, or AES-256 key. This might * happen because a SC has already done the unpacking. A better * solution would be to test for this only after we triggered * the GPG_ERR_INV_SESSION_KEY. */ } else { if (n + 7 > seskeylen ) { rc = gpg_error (GPG_ERR_INV_SESSION_KEY); goto leave; } /* FIXME: Actually the leading zero is required but due to the way we encode the output in libgcrypt as an MPI we are not able to encode that leading zero. However, when using a Smartcard we are doing it the right way and therefore we have to skip the zero. This should be fixed in gpg-agent of course. */ if (!seskey[n]) n++; if (seskey[n] != 2 ) /* Wrong block type version. */ { rc = gpg_error (GPG_ERR_INV_SESSION_KEY); goto leave; } for (n++; n < seskeylen && seskey[n]; n++) /* Skip the random bytes. */ ; n++; /* and the zero byte */ if (n >= seskeylen ) { rc = gpg_error (GPG_ERR_INV_SESSION_KEY); goto leave; } } if (DBG_CRYPTO) { log_printhex (seskey+n, seskeylen-n, "CEK .......:"); log_printhex (parm->iv, parm->ivlen, "IV ........:"); } if (opt.verbose) log_info (_("%s.%s encrypted data\n"), gcry_cipher_algo_name (parm->algo), cipher_mode_to_string (parm->mode)); rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0); if (rc) { log_error ("error creating decryptor: %s\n", gpg_strerror (rc)); goto leave; } rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n); if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) { log_info (_("WARNING: message was encrypted with " "a weak key in the symmetric cipher.\n")); rc = 0; } if (rc) { log_error("key setup failed: %s\n", gpg_strerror(rc) ); goto leave; } rc = gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen); if (rc) { log_error("IV setup failed: %s\n", gpg_strerror(rc) ); goto leave; } if (parm->mode == GCRY_CIPHER_MODE_GCM) { /* GCM mode really sucks in CMS. We need to know the AAD before * we start decrypting but CMS puts the AAD after the content. * Thus temporary files are required. Let's hope that no real * messages with actual AAD are ever used. OCB Rules! */ } leave: xfree (seskey); return rc; } /* This function is called by the KSBA writer just before the actual write is done. The function must take INLEN bytes from INBUF, decrypt it and store it inoutbuf which has a maximum size of maxoutlen. The valid bytes in outbuf should be return in outlen. Due to different buffer sizes or different length of input and output, it may happen that fewer bytes are processed or fewer bytes are written. */ static gpg_error_t decrypt_filter (void *arg, const void *inbuf, size_t inlen, size_t *inused, void *outbuf, size_t maxoutlen, size_t *outlen) { struct decrypt_filter_parm_s *parm = arg; int blklen = parm->blklen; size_t orig_inlen = inlen; /* fixme: Should we issue an error when we have not seen one full block? */ if (!inlen) return gpg_error (GPG_ERR_BUG); if (maxoutlen < 2*parm->blklen) return gpg_error (GPG_ERR_BUG); /* Make some space because we will later need an extra block at the end. */ maxoutlen -= blklen; if (parm->helpblocklen) { int i, j; for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++) parm->helpblock[i] = ((const char*)inbuf)[j]; inlen -= j; if (blklen > maxoutlen) return gpg_error (GPG_ERR_BUG); if (i < blklen) { parm->helpblocklen = i; *outlen = 0; } else { parm->helpblocklen = 0; if (parm->any_data) { memcpy (outbuf, parm->lastblock, blklen); *outlen =blklen; } else *outlen = 0; gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen, parm->helpblock, blklen); parm->any_data = 1; } *inused = orig_inlen - inlen; return 0; } if (inlen > maxoutlen) inlen = maxoutlen; if (inlen % blklen) { /* store the remainder away */ parm->helpblocklen = inlen%blklen; inlen = inlen/blklen*blklen; memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen); } *inused = inlen + parm->helpblocklen; if (inlen) { log_assert (inlen >= blklen); if (parm->any_data) { gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen, inbuf, inlen); memcpy (outbuf, parm->lastblock, blklen); memcpy (parm->lastblock,(char*)outbuf+inlen, blklen); *outlen = inlen; } else { gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen); memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen); *outlen = inlen - blklen; parm->any_data = 1; } } else *outlen = 0; return 0; } /* This is the GCM version of decrypt_filter. */ static gpg_error_t decrypt_gcm_filter (void *arg, const void *inbuf, size_t inlen, size_t *inused, void *outbuf, size_t maxoutlen, size_t *outlen) { struct decrypt_filter_parm_s *parm = arg; if (!inlen) return gpg_error (GPG_ERR_BUG); if (maxoutlen < parm->blklen) return gpg_error (GPG_ERR_BUG); if (inlen > maxoutlen) inlen = maxoutlen; *inused = inlen; if (inlen) { gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen); *outlen = inlen; parm->any_data = 1; } else *outlen = 0; return 0; } /* Perform a decrypt operation. */ int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp) { int rc; gnupg_ksba_io_t b64reader = NULL; gnupg_ksba_io_t b64writer = NULL; ksba_reader_t reader; ksba_writer_t writer; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh; int recp; estream_t in_fp = NULL; struct decrypt_filter_parm_s dfparm; memset (&dfparm, 0, sizeof dfparm); audit_set_type (ctrl->audit, AUDIT_TYPE_DECRYPT); kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } in_fp = es_fdopen_nc (in_fd, "rb"); if (!in_fp) { rc = gpg_error_from_syserror (); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } rc = gnupg_ksba_create_reader (&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0) | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)), in_fp, &reader); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); goto leave; } rc = gnupg_ksba_create_writer (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), ctrl->pem_name, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + rc = ksba_cms_new (&cms); if (rc) goto leave; rc = ksba_cms_set_reader_writer (cms, reader, writer); if (rc) { log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (rc)); goto leave; } audit_log (ctrl->audit, AUDIT_SETUP_READY); /* Parser loop. */ do { rc = ksba_cms_parse (cms, &stopreason); if (rc) { log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA || stopreason == KSBA_SR_DETACHED_DATA) { int algo, mode; const char *algoid; int any_key = 0; audit_log (ctrl->audit, AUDIT_GOT_DATA); algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/); algo = gcry_cipher_map_name (algoid); mode = gcry_cipher_mode_from_oid (algoid); if (!algo || !mode) { rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); log_error ("unsupported algorithm '%s'\n", algoid? algoid:"?"); if (algoid && !strcmp (algoid, "1.2.840.113549.3.2")) log_info (_("(this is the RC2 algorithm)\n")); else if (!algoid) log_info (_("(this does not seem to be an encrypted" " message)\n")); { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm", numbuf, algoid?algoid:"?", NULL); audit_log_s (ctrl->audit, AUDIT_BAD_DATA_CIPHER_ALGO, algoid); } /* If it seems that this is not an encrypted message we return a more sensible error code. */ if (!algoid) rc = gpg_error (GPG_ERR_NO_DATA); goto leave; } /* Check compliance. */ if (! gnupg_cipher_is_allowed (opt.compliance, 0, algo, mode)) { log_error (_("cipher algorithm '%s'" " may not be used in %s mode\n"), gcry_cipher_algo_name (algo), gnupg_compliance_option_string (opt.compliance)); rc = gpg_error (GPG_ERR_CIPHER_ALGO); goto leave; } /* For CMS, CO_DE_VS demands CBC mode. */ dfparm.is_de_vs = gnupg_cipher_is_compliant (CO_DE_VS, algo, mode); audit_log_i (ctrl->audit, AUDIT_DATA_CIPHER_ALGO, algo); dfparm.algo = algo; dfparm.mode = mode; dfparm.blklen = gcry_cipher_get_algo_blklen (algo); if (dfparm.blklen > sizeof (dfparm.helpblock)) { rc = gpg_error (GPG_ERR_BUG); goto leave; } rc = ksba_cms_get_content_enc_iv (cms, dfparm.iv, sizeof (dfparm.iv), &dfparm.ivlen); if (rc) { log_error ("error getting IV: %s\n", gpg_strerror (rc)); goto leave; } for (recp=0; !any_key; recp++) { char *issuer; ksba_sexp_t serial; ksba_sexp_t enc_val; char *hexkeygrip = NULL; char *pkalgostr = NULL; char *pkfpr = NULL; char *desc = NULL; char kidbuf[16+1]; int tmp_rc; ksba_cert_t cert = NULL; unsigned int nbits; int pk_algo = 0; int maybe_pwri = 0; *kidbuf = 0; tmp_rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial); if (tmp_rc == -1 && recp) break; /* no more recipients */ audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp); if (gpg_err_code (tmp_rc) == GPG_ERR_UNSUPPORTED_CMS_OBJ) { maybe_pwri = 1; } else if (tmp_rc) { log_error ("recp %d - error getting info: %s\n", recp, gpg_strerror (tmp_rc)); } else { if (opt.verbose) { log_info ("recp %d - issuer: '%s'\n", recp, issuer? issuer:"[NONE]"); log_info ("recp %d - serial: ", recp); gpgsm_dump_serial (serial); log_printf ("\n"); } if (ctrl->audit) { char *tmpstr = gpgsm_format_sn_issuer (serial, issuer); audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr); xfree (tmpstr); } keydb_search_reset (kh); rc = keydb_search_issuer_sn (ctrl, kh, issuer, serial); if (rc) { log_error ("failed to find the certificate: %s\n", gpg_strerror(rc)); goto oops; } rc = keydb_get_cert (kh, &cert); if (rc) { log_error ("failed to get cert: %s\n", gpg_strerror (rc)); goto oops; } /* Print the ENC_TO status line. Note that we can do so only if we have the certificate. This is in contrast to gpg where the keyID is commonly included in the encrypted messages. It is too cumbersome to retrieve the used algorithm, thus we don't print it for now. We also record the keyid for later use. */ { unsigned long kid[2]; kid[0] = gpgsm_get_short_fingerprint (cert, kid+1); snprintf (kidbuf, sizeof kidbuf, "%08lX%08lX", kid[1], kid[0]); gpgsm_status2 (ctrl, STATUS_ENC_TO, kidbuf, "0", "0", NULL); } /* Put the certificate into the audit log. */ audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert, 0); /* Just in case there is a problem with the own certificate we print this message - should never happen of course */ rc = gpgsm_cert_use_decrypt_p (cert); if (rc) { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage", numbuf, NULL); rc = 0; } hexkeygrip = gpgsm_get_keygrip_hexstring (cert); desc = gpgsm_format_keydesc (cert); pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); pkalgostr = gpgsm_pubkey_algo_string (cert, NULL); pk_algo = gpgsm_get_key_algo_info (cert, &nbits); if (!opt.quiet) log_info (_("encrypted to %s key %s\n"), pkalgostr, pkfpr); /* Check compliance. */ if (!gnupg_pk_is_allowed (opt.compliance, PK_USE_DECRYPTION, pk_algo, 0, NULL, nbits, NULL)) { char kidstr[10+1]; snprintf (kidstr, sizeof kidstr, "0x%08lX", gpgsm_get_short_fingerprint (cert, NULL)); log_info (_("key %s is not suitable for decryption" " in %s mode\n"), kidstr, gnupg_compliance_option_string(opt.compliance)); rc = gpg_error (GPG_ERR_PUBKEY_ALGO); goto oops; } /* Check that all certs are compliant with CO_DE_VS. */ dfparm.is_de_vs = (dfparm.is_de_vs && gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL)); oops: if (rc) { /* We cannot check compliance of certs that we * don't have. */ dfparm.is_de_vs = 0; } xfree (issuer); xfree (serial); ksba_cert_release (cert); } if ((!hexkeygrip || !pk_algo) && !maybe_pwri) ; else if (!(enc_val = ksba_cms_get_enc_val (cms, recp))) { log_error ("recp %d - error getting encrypted session key\n", recp); if (maybe_pwri) log_info ("(possibly unsupported KEK info)\n"); } else { if (maybe_pwri && opt.verbose) log_info ("recp %d - KEKRI or PWRI\n", recp); rc = prepare_decryption (ctrl, hexkeygrip, pk_algo, nbits, desc, enc_val, &dfparm); xfree (enc_val); if (rc) { log_info ("decrypting session key failed: %s\n", gpg_strerror (rc)); if (gpg_err_code (rc) == GPG_ERR_NO_SECKEY && *kidbuf) gpgsm_status2 (ctrl, STATUS_NO_SECKEY, kidbuf, NULL); } else { /* setup the bulk decrypter */ any_key = 1; ksba_writer_set_filter (writer, dfparm.mode == GCRY_CIPHER_MODE_GCM? decrypt_gcm_filter : decrypt_filter, &dfparm); if (dfparm.is_de_vs && gnupg_gcrypt_is_compliant (CO_DE_VS)) gpgsm_status (ctrl, STATUS_DECRYPTION_COMPLIANCE_MODE, gnupg_status_compliance_flag (CO_DE_VS)); else if (opt.require_compliance && opt.compliance == CO_DE_VS) { log_error (_("operation forced to fail due to" " unfulfilled compliance rules\n")); gpgsm_errors_seen = 1; } } audit_log_ok (ctrl->audit, AUDIT_RECP_RESULT, rc); } xfree (pkalgostr); xfree (pkfpr); xfree (hexkeygrip); xfree (desc); } /* If we write an audit log add the unused recipients to the log as well. */ if (ctrl->audit && any_key) { for (;; recp++) { char *issuer; ksba_sexp_t serial; int tmp_rc; tmp_rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial); if (tmp_rc == -1) break; /* no more recipients */ audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp); if (tmp_rc) log_error ("recp %d - error getting info: %s\n", recp, gpg_strerror (tmp_rc)); else { char *tmpstr = gpgsm_format_sn_issuer (serial, issuer); audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr); xfree (tmpstr); xfree (issuer); xfree (serial); } } } if (!any_key) { if (!rc) rc = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } } else if (stopreason == KSBA_SR_END_DATA) { ksba_writer_set_filter (writer, NULL, NULL); if (dfparm.mode == GCRY_CIPHER_MODE_GCM) { /* Nothing yet to do. We wait for the ready event. */ } else if (dfparm.any_data ) { /* write the last block with padding removed */ int i, npadding = dfparm.lastblock[dfparm.blklen-1]; if (!npadding || npadding > dfparm.blklen) { log_error ("invalid padding with value %d\n", npadding); rc = gpg_error (GPG_ERR_INV_DATA); goto leave; } rc = ksba_writer_write (writer, dfparm.lastblock, dfparm.blklen - npadding); if (rc) goto leave; for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++) { if (dfparm.lastblock[i] != npadding) { log_error ("inconsistent padding\n"); rc = gpg_error (GPG_ERR_INV_DATA); goto leave; } } } } else if (stopreason == KSBA_SR_READY) { if (dfparm.mode == GCRY_CIPHER_MODE_GCM) { char *authtag; size_t authtaglen; rc = ksba_cms_get_message_digest (cms, 0, &authtag, &authtaglen); if (rc) { log_error ("error getting authtag: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_CRYPTO) log_printhex (authtag, authtaglen, "Authtag ...:"); rc = gcry_cipher_checktag (dfparm.hd, authtag, authtaglen); xfree (authtag); if (rc) log_error ("data is not authentic: %s\n", gpg_strerror (rc)); goto leave; } } } while (stopreason != KSBA_SR_READY); rc = gnupg_ksba_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL); leave: audit_log_ok (ctrl->audit, AUDIT_DECRYPTION_RESULT, rc); if (rc) { gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL); log_error ("message decryption failed: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); } ksba_cms_release (cms); gnupg_ksba_destroy_reader (b64reader); gnupg_ksba_destroy_writer (b64writer); keydb_release (kh); es_fclose (in_fp); if (dfparm.hd) gcry_cipher_close (dfparm.hd); return rc; } diff --git a/sm/encrypt.c b/sm/encrypt.c index 4fd4f93b9..b0e59f73e 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -1,865 +1,867 @@ /* encrypt.c - Encrypt a message * Copyright (C) 2001, 2003, 2004, 2007, 2008, * 2010 Free Software Foundation, Inc. * Copyright (C) 2001-2019 Werner Koch * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG 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 3 of the License, or * (at your option) any later version. * * GnuPG 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, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../common/i18n.h" #include "../common/compliance.h" struct dek_s { const char *algoid; int algo; gcry_cipher_hd_t chd; char key[32]; int keylen; char iv[32]; int ivlen; }; typedef struct dek_s *DEK; /* Callback parameters for the encryption. */ struct encrypt_cb_parm_s { estream_t fp; DEK dek; int eof_seen; int ready; int readerror; int bufsize; unsigned char *buffer; int buflen; }; /* Initialize the data encryption key (session key). */ static int init_dek (DEK dek) { int rc=0, mode, i; dek->algo = gcry_cipher_map_name (dek->algoid); mode = gcry_cipher_mode_from_oid (dek->algoid); if (!dek->algo || !mode) { log_error ("unsupported algorithm '%s'\n", dek->algoid); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); } /* Extra check for algorithms we consider to be too weak for encryption, although we support them for decryption. Note that there is another check below discriminating on the key length. */ switch (dek->algo) { case GCRY_CIPHER_DES: case GCRY_CIPHER_RFC2268_40: log_error ("cipher algorithm '%s' not allowed: too weak\n", gnupg_cipher_algo_name (dek->algo)); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); default: break; } dek->keylen = gcry_cipher_get_algo_keylen (dek->algo); if (!dek->keylen || dek->keylen > sizeof (dek->key)) return gpg_error (GPG_ERR_BUG); dek->ivlen = gcry_cipher_get_algo_blklen (dek->algo); if (!dek->ivlen || dek->ivlen > sizeof (dek->iv)) return gpg_error (GPG_ERR_BUG); /* Make sure we don't use weak keys. */ if (dek->keylen < 100/8) { log_error ("key length of '%s' too small\n", dek->algoid); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); } rc = gcry_cipher_open (&dek->chd, dek->algo, mode, GCRY_CIPHER_SECURE); if (rc) { log_error ("failed to create cipher context: %s\n", gpg_strerror (rc)); return rc; } for (i=0; i < 8; i++) { gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); rc = gcry_cipher_setkey (dek->chd, dek->key, dek->keylen); if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) break; log_info(_("weak key created - retrying\n") ); } if (rc) { log_error ("failed to set the key: %s\n", gpg_strerror (rc)); gcry_cipher_close (dek->chd); dek->chd = NULL; return rc; } gcry_create_nonce (dek->iv, dek->ivlen); rc = gcry_cipher_setiv (dek->chd, dek->iv, dek->ivlen); if (rc) { log_error ("failed to set the IV: %s\n", gpg_strerror (rc)); gcry_cipher_close (dek->chd); dek->chd = NULL; return rc; } return 0; } /* Encode an RSA session key. */ static int encode_session_key (DEK dek, gcry_sexp_t * r_data) { gcry_sexp_t data; char *p; int rc; p = xtrymalloc (64 + 2 * dek->keylen); if (!p) return gpg_error_from_syserror (); strcpy (p, "(data\n (flags pkcs1)\n (value #"); bin2hex (dek->key, dek->keylen, p + strlen (p)); strcat (p, "#))\n"); rc = gcry_sexp_sscan (&data, NULL, p, strlen (p)); xfree (p); *r_data = data; return rc; } /* Encrypt DEK using ECDH. S_PKEY is the public key. On success the * result is stored at R_ENCVAL. Example of a public key: * * (public-key (ecc (curve "1.3.132.0.34") (q #04B0[...]B8#))) * */ static gpg_error_t ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval) { gpg_error_t err; gcry_sexp_t l1; char *curvebuf = NULL; const char *curve; unsigned int curvebits; const char *encr_algo_str; const char *wrap_algo_str; int hash_algo, cipher_algo; unsigned int keylen; unsigned char key[32]; gcry_sexp_t s_data = NULL; gcry_sexp_t s_encr = NULL; gcry_buffer_t ioarray[2] = { {0}, {0} }; unsigned char *secret; /* Alias for ioarray[0]. */ unsigned int secretlen; unsigned char *pubkey; /* Alias for ioarray[1]. */ unsigned int pubkeylen; gcry_cipher_hd_t cipher_hd = NULL; unsigned char *result = NULL; unsigned int resultlen; *r_encval = NULL; /* Figure out the encryption and wrap algo OIDs. */ /* Get the curve name if any, */ l1 = gcry_sexp_find_token (s_pkey, "curve", 0); if (l1) { curvebuf = gcry_sexp_nth_string (l1, 1); gcry_sexp_release (l1); } if (!curvebuf) { err = gpg_error (GPG_ERR_INV_CURVE); log_error ("%s: invalid public key: no curve\n", __func__); goto leave; } /* We need to use our OpenPGP mapping to turn a curve name into its * canonical numerical OID. We also use this to get the size of the * curve which we need to figure out a suitable hash algo. We * should have a Libgcrypt function to do this; see bug report #4926. */ curve = openpgp_curve_to_oid (curvebuf, &curvebits, NULL); if (!curve) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); log_error ("%s: invalid public key: %s\n", __func__, gpg_strerror (err)); goto leave; } xfree (curvebuf); curvebuf = NULL; /* Our mapping matches the recommended algorithms from RFC-5753 but * not supporting the short curves which would require 3DES. */ if (curvebits < 255) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); log_error ("%s: curve '%s' is not supported\n", __func__, curve); goto leave; } else if (opt.force_ecdh_sha1kdf) { /* dhSinglePass-stdDH-sha1kdf-scheme */ encr_algo_str = "1.3.133.16.840.63.0.2"; wrap_algo_str = "2.16.840.1.101.3.4.1.45"; hash_algo = GCRY_MD_SHA1; cipher_algo = GCRY_CIPHER_AES256; keylen = 32; } else if (curvebits <= 256) { /* dhSinglePass-stdDH-sha256kdf-scheme */ encr_algo_str = "1.3.132.1.11.1"; wrap_algo_str = "2.16.840.1.101.3.4.1.5"; hash_algo = GCRY_MD_SHA256; cipher_algo = GCRY_CIPHER_AES128; keylen = 16; } else if (curvebits <= 384) { /* dhSinglePass-stdDH-sha384kdf-scheme */ encr_algo_str = "1.3.132.1.11.2"; wrap_algo_str = "2.16.840.1.101.3.4.1.25"; hash_algo = GCRY_MD_SHA384; cipher_algo = GCRY_CIPHER_AES256; keylen = 24; } else { /* dhSinglePass-stdDH-sha512kdf-scheme*/ encr_algo_str = "1.3.132.1.11.3"; wrap_algo_str = "2.16.840.1.101.3.4.1.45"; hash_algo = GCRY_MD_SHA512; cipher_algo = GCRY_CIPHER_AES256; keylen = 32; } /* Create a secret and an ephemeral key. */ { char *k; k = gcry_random_bytes_secure ((curvebits+7)/8, GCRY_STRONG_RANDOM); if (DBG_CRYPTO) log_printhex (k, (curvebits+7)/8, "ephm. k .:"); err = gcry_sexp_build (&s_data, NULL, "%b", (int)(curvebits+7)/8, k); xfree (k); } if (err) { log_error ("%s: error building ephemeral secret: %s\n", __func__, gpg_strerror (err)); goto leave; } err = gcry_pk_encrypt (&s_encr, s_data, s_pkey); if (err) { log_error ("%s: error encrypting ephemeral secret: %s\n", __func__, gpg_strerror (err)); goto leave; } err = gcry_sexp_extract_param (s_encr, NULL, "&se", &ioarray+0, ioarray+1, NULL); if (err) { log_error ("%s: error extracting ephemeral key and secret: %s\n", __func__, gpg_strerror (err)); goto leave; } secret = ioarray[0].data; secretlen = ioarray[0].len; pubkey = ioarray[1].data; pubkeylen = ioarray[1].len; if (DBG_CRYPTO) { log_printhex (pubkey, pubkeylen, "pubkey ..:"); log_printhex (secret, secretlen, "secret ..:"); } /* Extract X coordinate from SECRET. */ if (secretlen < 5) /* 5 because N could be reduced to (n-1)/2. */ err = gpg_error (GPG_ERR_BAD_DATA); else if (*secret == 0x04) { secretlen--; memmove (secret, secret+1, secretlen); if ((secretlen & 1)) { err = gpg_error (GPG_ERR_BAD_DATA); goto leave; } secretlen /= 2; } else if (*secret == 0x40 || *secret == 0x41) { secretlen--; memmove (secret, secret+1, secretlen); } else err = gpg_error (GPG_ERR_BAD_DATA); if (err) goto leave; if (DBG_CRYPTO) log_printhex (secret, secretlen, "ECDH X ..:"); err = ecdh_derive_kek (key, keylen, hash_algo, wrap_algo_str, secret, secretlen, NULL, 0); if (err) goto leave; if (DBG_CRYPTO) log_printhex (key, keylen, "KEK .....:"); /* Wrap the key. */ if ((dek->keylen % 8) || dek->keylen < 16) { log_error ("%s: can't use a session key of %u bytes\n", __func__, dek->keylen); err = gpg_error (GPG_ERR_BAD_DATA); goto leave; } resultlen = dek->keylen + 8; result = xtrymalloc_secure (resultlen); if (!result) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_open (&cipher_hd, cipher_algo, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) { log_error ("%s: failed to initialize AESWRAP: %s\n", __func__, gpg_strerror (err)); goto leave; } err = gcry_cipher_setkey (cipher_hd, key, keylen); wipememory (key, sizeof key); if (err) { log_error ("%s: failed in gcry_cipher_setkey: %s\n", __func__, gpg_strerror (err)); goto leave; } err = gcry_cipher_encrypt (cipher_hd, result, resultlen, dek->key, dek->keylen); if (err) { log_error ("%s: failed in gcry_cipher_encrypt: %s\n", __func__, gpg_strerror (err)); goto leave; } if (DBG_CRYPTO) log_printhex (result, resultlen, "w(CEK) ..:"); err = gcry_sexp_build (r_encval, NULL, "(enc-val(ecdh(e%b)(s%b)(encr-algo%s)(wrap-algo%s)))", (int)pubkeylen, pubkey, (int)resultlen, result, encr_algo_str, wrap_algo_str, NULL); if (err) log_error ("%s: failed building final S-exp: %s\n", __func__, gpg_strerror (err)); leave: gcry_cipher_close (cipher_hd); wipememory (key, sizeof key); xfree (result); xfree (ioarray[0].data); xfree (ioarray[1].data); gcry_sexp_release (s_data); gcry_sexp_release (s_encr); xfree (curvebuf); return err; } /* Encrypt the DEK under the key contained in CERT and return it as a * canonical S-expressions at ENCVAL. PK_ALGO is the public key * algorithm which the caller has already retrieved from CERT. */ static int encrypt_dek (const DEK dek, ksba_cert_t cert, int pk_algo, unsigned char **encval) { gcry_sexp_t s_ciph, s_data, s_pkey; int rc; ksba_sexp_t buf; size_t len; *encval = NULL; /* get the key from the cert */ buf = ksba_cert_get_public_key (cert); if (!buf) { log_error ("no public key for recipient\n"); return gpg_error (GPG_ERR_NO_PUBKEY); } len = gcry_sexp_canon_len (buf, 0, NULL, NULL); if (!len) { log_error ("libksba did not return a proper S-Exp\n"); return gpg_error (GPG_ERR_BUG); } rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)buf, len); xfree (buf); buf = NULL; if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); return rc; } if (DBG_CRYPTO) { log_printsexp (" pubkey:", s_pkey); log_printhex (dek->key, dek->keylen, "CEK .....:"); } /* Put the encoded cleartext into a simple list. */ s_data = NULL; /* (avoid compiler warning) */ if (pk_algo == GCRY_PK_ECC) { rc = ecdh_encrypt (dek, s_pkey, &s_ciph); } else { rc = encode_session_key (dek, &s_data); if (rc) { gcry_sexp_release (s_pkey); log_error ("encode_session_key failed: %s\n", gpg_strerror (rc)); return rc; } if (DBG_CRYPTO) log_printsexp (" data:", s_data); /* pass it to libgcrypt */ rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); } gcry_sexp_release (s_data); gcry_sexp_release (s_pkey); if (DBG_CRYPTO) log_printsexp ("enc-val:", s_ciph); /* Reformat it. */ if (!rc) { rc = make_canon_sexp (s_ciph, encval, NULL); gcry_sexp_release (s_ciph); } return rc; } /* do the actual encryption */ static int encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { struct encrypt_cb_parm_s *parm = cb_value; int blklen = parm->dek->ivlen; unsigned char *p; size_t n; *nread = 0; if (!buffer) return -1; /* not supported */ if (parm->ready) return -1; if (count < blklen) BUG (); if (!parm->eof_seen) { /* fillup the buffer */ p = parm->buffer; for (n=parm->buflen; n < parm->bufsize; n++) { int c = es_getc (parm->fp); if (c == EOF) { if (es_ferror (parm->fp)) { parm->readerror = errno; return -1; } parm->eof_seen = 1; break; } p[n] = c; } parm->buflen = n; } n = parm->buflen < count? parm->buflen : count; n = n/blklen * blklen; if (n) { /* encrypt the stuff */ gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); *nread = n; /* Who cares about cycles, take the easy way and shift the buffer */ parm->buflen -= n; memmove (parm->buffer, parm->buffer+n, parm->buflen); } else if (parm->eof_seen) { /* no complete block but eof: add padding */ /* fixme: we should try to do this also in the above code path */ int i, npad = blklen - (parm->buflen % blklen); p = parm->buffer; for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++) p[n] = npad; gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); *nread = n; parm->ready = 1; } return 0; } /* Perform an encrypt operation. Encrypt the data received on DATA-FD and write it to OUT_FP. The recipients are take from the certificate given in recplist; if this is NULL it will be encrypted for a default recipient */ int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) { int rc = 0; gnupg_ksba_io_t b64writer = NULL; gpg_error_t err; ksba_writer_t writer; ksba_reader_t reader = NULL; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; struct encrypt_cb_parm_s encparm; DEK dek = NULL; int recpno; estream_t data_fp = NULL; certlist_t cl; int count; int compliant; memset (&encparm, 0, sizeof encparm); audit_set_type (ctrl->audit, AUDIT_TYPE_ENCRYPT); /* Check that the certificate list is not empty and that at least one certificate is not flagged as encrypt_to; i.e. is a real recipient. */ for (cl = recplist; cl; cl = cl->next) if (!cl->is_encrypt_to) break; if (!cl) { log_error(_("no valid recipients given\n")); gpgsm_status (ctrl, STATUS_NO_RECP, "0"); audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, 0); rc = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } for (count = 0, cl = recplist; cl; cl = cl->next) count++; audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, count); kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } /* Fixme: We should use the unlocked version of the es functions. */ data_fp = es_fdopen_nc (data_fd, "rb"); if (!data_fp) { rc = gpg_error_from_syserror (); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } err = ksba_reader_new (&reader); if (err) rc = err; if (!rc) rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm); if (rc) goto leave; encparm.fp = data_fp; ctrl->pem_name = "ENCRYPTED MESSAGE"; rc = gnupg_ksba_create_writer (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), ctrl->pem_name, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + err = ksba_cms_new (&cms); if (err) { rc = err; goto leave; } err = ksba_cms_set_reader_writer (cms, reader, writer); if (err) { log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } audit_log (ctrl->audit, AUDIT_GOT_DATA); /* We are going to create enveloped data with uninterpreted data as inner content */ err = ksba_cms_set_content_type (cms, 0, KSBA_CT_ENVELOPED_DATA); if (!err) err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); if (err) { log_error ("ksba_cms_set_content_type failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* Check compliance. */ if (!gnupg_cipher_is_allowed (opt.compliance, 1, gcry_cipher_map_name (opt.def_cipher_algoid), gcry_cipher_mode_from_oid (opt.def_cipher_algoid))) { log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), opt.def_cipher_algoid, gnupg_compliance_option_string (opt.compliance)); rc = gpg_error (GPG_ERR_CIPHER_ALGO); goto leave; } if (!gnupg_rng_is_compliant (opt.compliance)) { rc = gpg_error (GPG_ERR_FORBIDDEN); log_error (_("%s is not compliant with %s mode\n"), "RNG", gnupg_compliance_option_string (opt.compliance)); gpgsm_status_with_error (ctrl, STATUS_ERROR, "random-compliance", rc); goto leave; } /* Create a session key */ dek = xtrycalloc_secure (1, sizeof *dek); if (!dek) rc = out_of_core (); else { dek->algoid = opt.def_cipher_algoid; rc = init_dek (dek); } if (rc) { log_error ("failed to create the session key: %s\n", gpg_strerror (rc)); goto leave; } err = ksba_cms_set_content_enc_algo (cms, dek->algoid, dek->iv, dek->ivlen); if (err) { log_error ("ksba_cms_set_content_enc_algo failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } encparm.dek = dek; /* Use a ~8k (AES) or ~4k (3DES) buffer */ encparm.bufsize = 500 * dek->ivlen; encparm.buffer = xtrymalloc (encparm.bufsize); if (!encparm.buffer) { rc = out_of_core (); goto leave; } audit_log_s (ctrl->audit, AUDIT_SESSION_KEY, dek->algoid); compliant = gnupg_cipher_is_compliant (CO_DE_VS, dek->algo, GCRY_CIPHER_MODE_CBC); /* Gather certificates of recipients, encrypt the session key for each and store them in the CMS object */ for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next) { unsigned char *encval; unsigned int nbits; int pk_algo; /* Check compliance. */ pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits); if (!gnupg_pk_is_compliant (opt.compliance, pk_algo, 0, NULL, nbits, NULL)) { char kidstr[10+1]; snprintf (kidstr, sizeof kidstr, "0x%08lX", gpgsm_get_short_fingerprint (cl->cert, NULL)); log_info (_("WARNING: key %s is not suitable for encryption" " in %s mode\n"), kidstr, gnupg_compliance_option_string (opt.compliance)); } /* Fixme: When adding ECC we need to provide the curvename and * the key to gnupg_pk_is_compliant. */ if (compliant && !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL)) compliant = 0; rc = encrypt_dek (dek, cl->cert, pk_algo, &encval); if (rc) { audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, rc); log_error ("encryption failed for recipient no. %d: %s\n", recpno, gpg_strerror (rc)); goto leave; } err = ksba_cms_add_recipient (cms, cl->cert); if (err) { audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err); log_error ("ksba_cms_add_recipient failed: %s\n", gpg_strerror (err)); rc = err; xfree (encval); goto leave; } err = ksba_cms_set_enc_val (cms, recpno, encval); xfree (encval); audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err); if (err) { log_error ("ksba_cms_set_enc_val failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } if (compliant && gnupg_gcrypt_is_compliant (CO_DE_VS)) gpgsm_status (ctrl, STATUS_ENCRYPTION_COMPLIANCE_MODE, gnupg_status_compliance_flag (CO_DE_VS)); else if (opt.require_compliance && opt.compliance == CO_DE_VS) { log_error (_("operation forced to fail due to" " unfulfilled compliance rules\n")); gpgsm_errors_seen = 1; rc = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } /* Main control loop for encryption. */ recpno = 0; do { err = ksba_cms_build (cms, &stopreason); if (err) { - log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); + log_error ("creating CMS object failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } while (stopreason != KSBA_SR_READY); if (encparm.readerror) { log_error ("error reading input: %s\n", strerror (encparm.readerror)); rc = gpg_error (gpg_err_code_from_errno (encparm.readerror)); goto leave; } rc = gnupg_ksba_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } audit_log (ctrl->audit, AUDIT_ENCRYPTION_DONE); if (!opt.quiet) log_info ("encrypted data created\n"); leave: ksba_cms_release (cms); gnupg_ksba_destroy_writer (b64writer); ksba_reader_release (reader); keydb_release (kh); xfree (dek); es_fclose (data_fp); xfree (encparm.buffer); return rc; } diff --git a/sm/gpgsm.h b/sm/gpgsm.h index cef39ff2a..46c77803d 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -1,532 +1,533 @@ /* gpgsm.h - Global definitions for GpgSM * Copyright (C) 2001, 2003, 2004, 2007, 2009, * 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG 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 3 of the License, or * (at your option) any later version. * * GnuPG 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, see . */ #ifndef GPGSM_H #define GPGSM_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGSM #include #include #include "../common/util.h" #include "../common/status.h" #include "../common/audit.h" #include "../common/session-env.h" #include "../common/ksba-io-support.h" #include "../common/compliance.h" /* The maximum length of a binary fingerprints. This is used to * provide a static buffer and will be increased if we need to support * longer fingerprints. */ #define MAX_FINGERPRINT_LEN 32 /* The maximum length of a binary digest. */ #define MAX_DIGEST_LEN 64 /* Fits for SHA-512 */ /* A large struct named "opt" to keep global flags. */ EXTERN_UNLESS_MAIN_MODULE struct { unsigned int debug; /* debug flags (DBG_foo_VALUE) */ int verbose; /* verbosity level */ int quiet; /* be as quiet as possible */ int batch; /* run in batch mode, i.e w/o any user interaction */ int answer_yes; /* assume yes on most questions */ int answer_no; /* assume no on most questions */ int dry_run; /* don't change any persistent data */ int no_homedir_creation; int use_keyboxd; /* Use the external keyboxd as storage backend. */ const char *config_filename; /* Name of the used config file. */ const char *agent_program; const char *keyboxd_program; session_env_t session_env; char *lc_ctype; char *lc_messages; int autostart; const char *dirmngr_program; int disable_dirmngr; /* Do not do any dirmngr calls. */ const char *protect_tool_program; char *outfile; /* name of output file */ int with_key_data;/* include raw key in the column delimited output */ int fingerprint; /* list fingerprints in all key listings */ int with_md5_fingerprint; /* Also print an MD5 fingerprint for standard key listings. */ int with_keygrip; /* Option --with-keygrip active. */ int with_key_screening; /* Option --with-key-screening active. */ int no_pretty_dn; /* Option --no-pretty-dn */ int pinentry_mode; int request_origin; int armor; /* force base64 armoring (see also ctrl.with_base64) */ int no_armor; /* don't try to figure out whether data is base64 armored*/ const char *p12_charset; /* Use this charset for encoding the pkcs#12 passphrase. */ const char *def_cipher_algoid; /* cipher algorithm to use if nothing else is specified */ int def_compress_algo; /* Ditto for compress algorithm */ int forced_digest_algo; /* User forced hash algorithm. */ int force_ecdh_sha1kdf; /* Only for debugging and testing. */ char *def_recipient; /* userID of the default recipient */ int def_recipient_self; /* The default recipient is the default key */ int no_encrypt_to; /* Ignore all as encrypt to marked recipients. */ char *local_user; /* NULL or argument to -u */ int extra_digest_algo; /* A digest algorithm also used for verification of signatures. */ int always_trust; /* Trust the given keys even if there is no valid certification chain */ int skip_verify; /* do not check signatures on data */ int lock_once; /* Keep lock once they are set */ int ignore_time_conflict; /* Ignore certain time conflicts */ int no_crl_check; /* Don't do a CRL check */ int no_trusted_cert_crl_check; /* Don't run a CRL check for trusted certs. */ int force_crl_refresh; /* Force refreshing the CRL. */ int enable_issuer_based_crl_check; /* Backward compatibility hack. */ int enable_ocsp; /* Default to use OCSP checks. */ char *policy_file; /* full pathname of policy file */ int no_policy_check; /* ignore certificate policies */ int no_chain_validation; /* Bypass all cert chain validity tests */ int ignore_expiration; /* Ignore the notAfter validity checks. */ int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */ int qualsig_approval; /* Set to true if this software has officially been approved to create an verify qualified signatures. This is a runtime option in case we want to check the integrity of the software at runtime. */ unsigned int min_rsa_length; /* Used for compliance checks. */ strlist_t keyserver; /* A list of certificate extension OIDs which are ignored so that one can claim that a critical extension has been handled. One OID per string. */ strlist_t ignored_cert_extensions; /* A list of OIDs which will be used to ignore certificates with * sunch an OID during --learn-card. */ strlist_t ignore_cert_with_oid; /* The current compliance mode. */ enum gnupg_compliance_mode compliance; /* Fail if an operation can't be done in the requested compliance * mode. */ int require_compliance; /* Enable creation of authenticode signatures. */ int authenticode; /* A list of extra attributes put into a signed data object. For a * signed each attribute each string has the format: * :s: * and for an unsigned attribute * :u: * The OID is in the usual dotted decimal for. The HEX_OR_FILENAME * is either a list of hex digits or a filename with the DER encoded * value. A filename is detected by the presence of a slash in the * HEX_OR_FILENAME. The actual value needs to be encoded as a SET OF * attribute values. */ strlist_t attributes; /* Compatibility flags (COMPAT_FLAG_xxxx). */ unsigned int compat_flags; } opt; /* Debug values and macros. */ #define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */ #define DBG_MPI_VALUE 2 /* debug mpi details */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_IPC_VALUE 1024 /* debug assuan communication */ #define DBG_CLOCK_VALUE 4096 #define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */ #define DBG_X509 (opt.debug & DBG_X509_VALUE) #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE) #define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) /* Compatibility flags */ /* Telesec RSA cards produced for NRW in 2022 came with only the * keyAgreement bit set. This flag allows there use for encryption * anyway. Example cert: * Issuer: /CN=DOI CA 10a/OU=DOI/O=PKI-1-Verwaltung/C=DE * key usage: digitalSignature nonRepudiation keyAgreement * policies: 1.3.6.1.4.1.7924.1.1:N: */ #define COMPAT_ALLOW_KA_TO_ENCR 1 /* Forward declaration for an object defined in server.c */ struct server_local_s; /* Object used to keep state locally in keydb.c */ struct keydb_local_s; typedef struct keydb_local_s *keydb_local_t; /* Session control object. This object is passed down to most functions. Note that the default values for it are set by gpgsm_init_default_ctrl(). */ struct server_control_s { int no_server; /* We are not running under server control */ int status_fd; /* Only for non-server mode */ struct server_local_s *server_local; keydb_local_t keydb_local; /* Local data for call-keyboxd.c */ audit_ctx_t audit; /* NULL or a context for the audit subsystem. */ int agent_seen; /* Flag indicating that the gpg-agent has been accessed. */ int with_colons; /* Use column delimited output format */ int with_secret; /* Mark secret keys in a public key listing. */ int with_chain; /* Include the certifying certs in a listing */ int with_validation;/* Validate each key while listing. */ int with_ephemeral_keys; /* Include ephemeral flagged keys in the keylisting. */ int autodetect_encoding; /* Try to detect the input encoding */ int is_pem; /* Is in PEM format */ int is_base64; /* is in plain base-64 format */ int create_base64; /* Create base64 encoded output */ int create_pem; /* create PEM output */ const char *pem_name; /* PEM name to use */ int include_certs; /* -1 to send all certificates in the chain along with a signature or the number of certificates up the chain (0 = none, 1 = only signer) */ int use_ocsp; /* Set to true if OCSP should be used. */ int validation_model; /* 0 := standard model (shell), 1 := chain model, 2 := STEED model. */ int offline; /* If true gpgsm won't do any network access. */ /* The current time. Used as a helper in certchain.c. */ ksba_isotime_t current_time; /* The revocation info. Used as a helper inc ertchain.c */ gnupg_isotime_t revoked_at; char *revocation_reason; }; /* An object to keep a list of certificates. */ struct certlist_s { struct certlist_s *next; ksba_cert_t cert; int is_encrypt_to; /* True if the certificate has been set through the --encrypto-to option. */ int pk_algo; /* The PK_ALGO from CERT or 0 if not yet known. */ int hash_algo; /* Used to track the hash algorithm to use. */ const char *hash_algo_oid; /* And the corresponding OID. */ }; typedef struct certlist_s *certlist_t; /* A structure carrying information about trusted root certificates. */ struct rootca_flags_s { unsigned int valid:1; /* The rest of the structure has valid information. */ unsigned int relax:1; /* Relax checking of root certificates. */ unsigned int chain_model:1; /* Root requires the use of the chain model. */ unsigned int qualified:1; /* Root CA used for qualfied signatures. */ unsigned int de_vs:1; /* Root CA is de-vs compliant. */ }; /*-- gpgsm.c --*/ extern int gpgsm_errors_seen; void gpgsm_exit (int rc); void gpgsm_init_default_ctrl (struct server_control_s *ctrl); void gpgsm_deinit_default_ctrl (ctrl_t ctrl); int gpgsm_parse_validation_model (const char *model); /*-- server.c --*/ void gpgsm_server (certlist_t default_recplist); gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text); gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) GPGRT_ATTR_SENTINEL(0); gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec); gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, gpg_error_t err); +gpg_error_t gpgsm_progress_cb (ctrl_t ctrl, uint64_t current, uint64_t total); gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line); /*-- fingerprint --*/ unsigned char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo, unsigned char *array, int *r_len); char *gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo); char *gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo); unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert, unsigned long *r_high); unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array); char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert); int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits); int gpgsm_get_key_algo_info2 (ksba_cert_t cert, unsigned int *nbits, char **r_curve); int gpgsm_is_ecc_key (ksba_cert_t cert); char *gpgsm_pubkey_algo_string (ksba_cert_t cert, int *r_algoid); gcry_mpi_t gpgsm_get_rsa_modulus (ksba_cert_t cert); char *gpgsm_get_certid (ksba_cert_t cert); /*-- certdump.c --*/ const void *gpgsm_get_serial (ksba_const_sexp_t sn, size_t *r_length); void gpgsm_print_serial (estream_t fp, ksba_const_sexp_t p); void gpgsm_print_serial_decimal (estream_t fp, ksba_const_sexp_t sn); void gpgsm_print_time (estream_t fp, ksba_isotime_t t); void gpgsm_print_name2 (FILE *fp, const char *string, int translate); void gpgsm_print_name (FILE *fp, const char *string); void gpgsm_es_print_name (estream_t fp, const char *string); void gpgsm_es_print_name2 (estream_t fp, const char *string, int translate); void gpgsm_cert_log_name (const char *text, ksba_cert_t cert); void gpgsm_dump_cert (const char *text, ksba_cert_t cert); void gpgsm_dump_serial (ksba_const_sexp_t p); void gpgsm_dump_time (ksba_isotime_t t); void gpgsm_dump_string (const char *string); char *gpgsm_format_serial (ksba_const_sexp_t p); char *gpgsm_format_name2 (const char *name, int translate); char *gpgsm_format_name (const char *name); char *gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer); char *gpgsm_fpr_and_name_for_status (ksba_cert_t cert); char *gpgsm_format_keydesc (ksba_cert_t cert); /*-- certcheck.c --*/ int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert); int gpgsm_check_cms_signature (ksba_cert_t cert, gcry_sexp_t sigval, gcry_md_hd_t md, int hash_algo, unsigned int pkalgoflags, int *r_pkalgo); /* fixme: move create functions to another file */ int gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, gcry_md_hd_t md, int mdalgo, unsigned char **r_sigval); /*-- certchain.c --*/ /* Flags used with gpgsm_validate_chain. */ #define VALIDATE_FLAG_NO_DIRMNGR 1 #define VALIDATE_FLAG_CHAIN_MODEL 2 #define VALIDATE_FLAG_STEED 4 gpg_error_t gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next); int gpgsm_is_root_cert (ksba_cert_t cert); int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime, ksba_isotime_t r_exptime, int listmode, estream_t listfp, unsigned int flags, unsigned int *retflags); int gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert); /*-- certlist.c --*/ int gpgsm_cert_use_sign_p (ksba_cert_t cert, int silent); int gpgsm_cert_use_encrypt_p (ksba_cert_t cert); int gpgsm_cert_use_verify_p (ksba_cert_t cert); int gpgsm_cert_use_decrypt_p (ksba_cert_t cert); int gpgsm_cert_use_cert_p (ksba_cert_t cert); int gpgsm_cert_use_ocsp_p (ksba_cert_t cert); int gpgsm_cert_has_well_known_private_key (ksba_cert_t cert); int gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b); int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, certlist_t *listaddr, int is_encrypt_to); int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, certlist_t *listaddr, int is_encrypt_to); void gpgsm_release_certlist (certlist_t list); #define FIND_CERT_ALLOW_AMBIG 1 #define FIND_CERT_WITH_EPHEM 2 int gpgsm_find_cert (ctrl_t ctrl, const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert, unsigned int flags); /*-- keylist.c --*/ gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp, unsigned int mode); gpg_error_t gpgsm_show_certs (ctrl_t ctrl, int nfiles, char **files, estream_t fp); /*-- import.c --*/ int gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode); int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, int (*of)(const char *fname)); /*-- export.c --*/ void gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream); void gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode); /*-- delete.c --*/ int gpgsm_delete (ctrl_t ctrl, strlist_t names); /*-- verify.c --*/ int gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp); /*-- sign.c --*/ int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert); int gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, int data_fd, int detached, estream_t out_fp); /*-- encrypt.c --*/ int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int in_fd, estream_t out_fp); /*-- decrypt.c --*/ gpg_error_t ecdh_derive_kek (unsigned char *key, unsigned int keylen, int hash_algo, const char *wrap_algo_str, const void *secret, unsigned int secretlen, const void *ukm, unsigned int ukmlen); int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp); /*-- certreqgen.c --*/ int gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream); /*-- certreqgen-ui.c --*/ void gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t out_stream); /*-- qualified.c --*/ gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country); gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert); gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert); /*-- call-agent.c --*/ int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen); int gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen); int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, ksba_const_sexp_t ciphertext, char **r_buf, size_t *r_buflen); int gpgsm_agent_genkey (ctrl_t ctrl, ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey); int gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, ksba_sexp_t *r_pubkey); int gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno); int gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list); int gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr, struct rootca_flags_s *rootca_flags); int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip); int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert); int gpgsm_agent_learn (ctrl_t ctrl); int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc); gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc); gpg_error_t gpgsm_agent_send_nop (ctrl_t ctrl); gpg_error_t gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno); gpg_error_t gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, int repeat, char **r_passphrase); gpg_error_t gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen); gpg_error_t gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen); gpg_error_t gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char **r_result, size_t *r_resultlen); /*-- call-dirmngr.c --*/ gpg_error_t gpgsm_dirmngr_isvalid (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp, gnupg_isotime_t r_revoked_at, char **r_reason); int gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, const char *uri, int cache_only, void (*cb)(void*, ksba_cert_t), void *cb_value); int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command, int argc, char **argv); /*-- misc.c --*/ void gpgsm_print_further_info (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); void setup_pinentry_env (void); gpg_error_t transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, unsigned char **r_newsigval, size_t *r_newsigvallen); gcry_sexp_t gpgsm_ksba_cms_get_sig_val (ksba_cms_t cms, int idx); int gpgsm_get_hash_algo_from_sigval (gcry_sexp_t sigval, unsigned int *r_pkalgo_flags); #endif /*GPGSM_H*/ diff --git a/sm/server.c b/sm/server.c index 3ec1c0c4b..455f5707a 100644 --- a/sm/server.c +++ b/sm/server.c @@ -1,1564 +1,1618 @@ /* server.c - Server mode and main entry point * Copyright (C) 2001-2010 Free Software Foundation, Inc. * Copyright (C) 2001-2011, 2013-2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG 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 3 of the License, or * (at your option) any later version. * * GnuPG 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, see . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include "../common/sysutils.h" #include "../common/server-help.h" #include "../common/asshelp.h" #include "../common/shareddefs.h" #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* The filepointer for status message used in non-server mode */ static FILE *statusfp; /* Data used to assuciate an Assuan context with local server data */ struct server_local_s { assuan_context_t assuan_ctx; int message_fd; int list_internal; int list_external; int list_to_output; /* Write keylistings to the output fd. */ int enable_audit_log; /* Use an audit log. */ certlist_t recplist; certlist_t signerlist; certlist_t default_recplist; /* As set by main() - don't release. */ int allow_pinentry_notify; /* Set if pinentry notifications should be passed back to the client. */ int no_encrypt_to; /* Local version of option. */ }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; static int command_has_option (const char *cmd, const char *cmdopt); /* Note that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped_plus (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 (s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, buffer, size)) { gpg_err_set_errno (EIO); return -1; } return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } static void close_message_fd (ctrl_t ctrl) { if (ctrl->server_local->message_fd != -1) { close (ctrl->server_local->message_fd); ctrl->server_local->message_fd = -1; } } /* Start a new audit session if this has been enabled. */ static gpg_error_t start_audit_session (ctrl_t ctrl) { audit_release (ctrl->audit); ctrl->audit = NULL; if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) ) return gpg_error_from_syserror (); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (opt.session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (opt.session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { err = session_env_setenv (opt.session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { err = session_env_setenv (opt.session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { xfree (opt.lc_ctype); opt.lc_ctype = xtrystrdup (value); if (!opt.lc_ctype) err = gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { xfree (opt.lc_messages); opt.lc_messages = xtrystrdup (value); if (!opt.lc_messages) err = gpg_error_from_syserror (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (opt.session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "include-certs")) { int i = *value? atoi (value) : -1; if (ctrl->include_certs < -2) err = gpg_error (GPG_ERR_ASS_PARAMETER); else ctrl->include_certs = i; } else if (!strcmp (key, "list-mode")) { int i = *value? atoi (value) : 0; if (!i || i == 1) /* default and mode 1 */ { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 0; } else if (i == 2) { ctrl->server_local->list_internal = 0; ctrl->server_local->list_external = 1; } else if (i == 3) { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 1; } else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "list-to-output")) { int i = *value? atoi (value) : 0; ctrl->server_local->list_to_output = i; } else if (!strcmp (key, "with-validation")) { int i = *value? atoi (value) : 0; ctrl->with_validation = i; } else if (!strcmp (key, "with-secret")) { int i = *value? atoi (value) : 0; ctrl->with_secret = i; } else if (!strcmp (key, "validation-model")) { int i = gpgsm_parse_validation_model (value); if ( i >= 0 && i <= 2 ) ctrl->validation_model = i; else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "with-key-data")) { opt.with_key_data = 1; } else if (!strcmp (key, "enable-audit-log")) { int i = *value? atoi (value) : 0; ctrl->server_local->enable_audit_log = i; } else if (!strcmp (key, "allow-pinentry-notify")) { ctrl->server_local->allow_pinentry_notify = 1; } else if (!strcmp (key, "with-ephemeral-keys")) { int i = *value? atoi (value) : 0; ctrl->with_ephemeral_keys = i; } else if (!strcmp (key, "no-encrypt-to")) { ctrl->server_local->no_encrypt_to = 1; } else if (!strcmp (key, "offline")) { /* We ignore this option if gpgsm has been started with --disable-dirmngr (which also sets offline). */ if (!opt.disable_dirmngr) { int i = *value? !!atoi (value) : 1; ctrl->offline = i; } } else if (!strcmp (key, "request-origin")) { if (!opt.request_origin) { int i = parse_request_origin (value); if (i == -1) err = gpg_error (GPG_ERR_INV_VALUE); else opt.request_origin = i; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; gpgsm_release_certlist (ctrl->server_local->recplist); gpgsm_release_certlist (ctrl->server_local->signerlist); ctrl->server_local->recplist = NULL; ctrl->server_local->signerlist = NULL; close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static gpg_error_t input_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->autodetect_encoding = 0; ctrl->is_pem = 0; ctrl->is_base64 = 0; if (strstr (line, "--armor")) ctrl->is_pem = 1; else if (strstr (line, "--base64")) ctrl->is_base64 = 1; else if (strstr (line, "--binary")) ; else ctrl->autodetect_encoding = 1; return 0; } static gpg_error_t output_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->create_pem = 0; ctrl->create_base64 = 0; if (strstr (line, "--armor")) ctrl->create_pem = 1; else if (strstr (line, "--base64")) ctrl->create_base64 = 1; /* just the raw output */ return 0; } static const char hlp_recipient[] = "RECIPIENT \n" "\n" "Set the recipient for the encryption. USERID shall be the\n" "internal representation of the key; the server may accept any other\n" "way of specification [we will support this]. If this is a valid and\n" "trusted recipient the server does respond with OK, otherwise the\n" "return is an ERR with the reason why the recipient can't be used,\n" "the encryption will then not be done for this recipient. If the\n" "policy is not to encrypt at all if not all recipients are valid, the\n" "client has to take care of this. All RECIPIENT commands are\n" "cumulative until a RESET or an successful ENCRYPT command."; static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; if (!ctrl->audit) rc = start_audit_session (ctrl); else rc = 0; if (!rc) rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_signer[] = "SIGNER \n" "\n" "Set the signer's keys for the signature creation. USERID should\n" "be the internal representation of the key; the server may accept any\n" "other way of specification [we will support this]. If this is a\n" "valid and usable signing key the server does respond with OK,\n" "otherwise it returns an ERR with the reason why the key can't be\n" "used, the signing will then not be done for this key. If the policy\n" "is not to sign at all if not all signer keys are valid, the client\n" "has to take care of this. All SIGNER commands are cumulative until\n" "a RESET but they are *not* reset by an SIGN command because it can\n" "be expected that set of signers are used for more than one sign\n" "operation."; static gpg_error_t cmd_signer (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), line, NULL); /* For compatibility reasons we also issue the old code after the new one. */ gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_encrypt[] = "ENCRYPT \n" "\n" "Do the actual encryption process. Takes the plaintext from the INPUT\n" "command, writes to the ciphertext to the file descriptor set with\n" "the OUTPUT command, take the recipients form all the recipients set\n" "so far. If this command fails the clients should try to delete all\n" "output currently done or otherwise mark it as invalid. GPGSM does\n" "ensure that there won't be any security problem with leftover data\n" "on the output in this case.\n" "\n" "This command should in general not fail, as all necessary checks\n" "have been done while setting the recipients. The input and output\n" "pipes are closed."; static gpg_error_t cmd_encrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); certlist_t cl; int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); /* Now add all encrypt-to marked recipients from the default list. */ rc = 0; if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to) { for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next) if (cl->is_encrypt_to) rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert, &ctrl->server_local->recplist, 1); } if (!rc) rc = ctrl->audit? 0 : start_audit_session (ctrl); if (!rc) rc = gpgsm_encrypt (assuan_get_pointer (ctx), ctrl->server_local->recplist, inp_fd, out_fp); es_fclose (out_fp); gpgsm_release_certlist (ctrl->server_local->recplist); ctrl->server_local->recplist = NULL; /* Close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_decrypt[] = "DECRYPT\n" "\n" "This performs the decrypt operation after doing some check on the\n" "internal state. (e.g. that only needed data has been set). Because\n" "it utilizes the GPG-Agent for the session key decryption, there is\n" "no need to ask the client for a protecting passphrase - GPG-Agent\n" "does take care of this by requesting this from the user."; static gpg_error_t cmd_decrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); es_fclose (out_fp); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_verify[] = "VERIFY\n" "\n" "This does a verify operation on the message send to the input FD.\n" "The result is written out using status lines. If an output FD was\n" "given, the signed text will be written to that.\n" "\n" "If the signature is a detached one, the server will inquire about\n" "the signed material and the client must provide it."; static gpg_error_t cmd_verify (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp = NULL; (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); if (out_fd != -1) { out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_verify (assuan_get_pointer (ctx), fd, ctrl->server_local->message_fd, out_fp); es_fclose (out_fp); /* Close and reset the fd. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_sign[] = "SIGN [--detached]\n" "\n" "Sign the data set with the INPUT command and write it to the sink\n" "set by OUTPUT. With \"--detached\", a detached signature is\n" "created (surprise)."; static gpg_error_t cmd_sign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int detached; int rc; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); detached = has_option (line, "--detached"); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, inp_fd, detached, out_fp); es_fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_import[] = "IMPORT [--re-import]\n" "\n" "Import the certificates read form the input-fd, return status\n" "message for each imported one. The import checks the validity of\n" "the certificate but not of the entire chain. It is possible to\n" "import expired certificates.\n" "\n" "With the option --re-import the input data is expected to a be a LF\n" "separated list of fingerprints. The command will re-import these\n" "certificates, meaning that they are made permanent by removing\n" "their ephemeral flag."; static gpg_error_t cmd_import (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int reimport = has_option (line, "--re-import"); (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_export[] = "EXPORT [--data [--armor|--base64]] [--secret [--(raw|pkcs12)] [--] \n" "\n" "Export the certificates selected by PATTERN. With --data the output\n" "is returned using Assuan D lines; the default is to use the sink given\n" "by the last \"OUTPUT\" command. The options --armor or --base64 encode \n" "the output using the PEM respective a plain base-64 format; the default\n" "is a binary format which is only suitable for a single certificate.\n" "With --secret the secret key is exported using the PKCS#8 format,\n" "with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container."; static gpg_error_t cmd_export (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int use_data; int opt_secret; int opt_raw = 0; int opt_pkcs12 = 0; use_data = has_option (line, "--data"); if (use_data) { /* We need to override any possible setting done by an OUTPUT command. */ ctrl->create_pem = has_option (line, "--armor"); ctrl->create_base64 = has_option (line, "--base64"); } opt_secret = has_option (line, "--secret"); if (opt_secret) { opt_raw = has_option (line, "--raw"); opt_pkcs12 = has_option (line, "--pkcs12"); } line = skip_options (line); /* Break the line down into an strlist_t. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (opt_secret) { if (!list) return set_error (GPG_ERR_NO_DATA, "No key given"); if (!*list->d) { free_strlist (list); return set_error (GPG_ERR_NO_DATA, "No key given"); } if (list->next) return set_error (GPG_ERR_TOO_MANY, "Only one key allowed"); } if (use_data) { estream_t stream; stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!stream) { free_strlist (list); return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, stream, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, stream); es_fclose (stream); } else { int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp; if (fd == -1) { free_strlist (list); return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); } out_fp = es_fdopen_nc (fd, "w"); if (!out_fp) { free_strlist (list); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, out_fp, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, out_fp); es_fclose (out_fp); } free_strlist (list); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static const char hlp_delkeys[] = "DELKEYS \n" "\n" "Delete the certificates specified by PATTERNS. Each pattern shall be\n" "a percent-plus escaped certificate specification. Usually a\n" "fingerprint will be used for this."; static gpg_error_t cmd_delkeys (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int rc; /* break the line down into an strlist_t */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } rc = gpgsm_delete (ctrl, list); free_strlist (list); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_output[] = "OUTPUT FD[=]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"INPUT\" and \"MESSAGE\" commands."; static const char hlp_input[] = "INPUT FD[=]\n" "\n" "Set the file descriptor to read the input data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"MESSAGE\" and \"OUTPUT\" commands."; static const char hlp_message[] = "MESSAGE FD[=]\n" "\n" "Set the file descriptor to read the message for a detached\n" "signatures to N. If N is not given and the operating system\n" "supports file descriptor passing, the file descriptor currently in\n" "flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands."; static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { int rc; gnupg_fd_t sysfd; int fd; ctrl_t ctrl = assuan_get_pointer (ctx); rc = assuan_command_parse_fd (ctx, line, &sysfd); if (rc) return rc; fd = translate_sys2libc_fd (sysfd, 0); if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); ctrl->server_local->message_fd = fd; return 0; } static const char hlp_listkeys[] = "LISTKEYS [] []\n" "LISTSECRETKEYS [] []\n" "DUMPKEYS [] []\n" "DUMPSECRETKEYS [] []\n" "\n" "List all certificates or only those specified by PATTERNS. Each\n" "pattern shall be a percent-plus escaped certificate specification.\n" "The \"SECRET\" versions of the command filter the output to include\n" "only certificates where the secret key is available or a corresponding\n" "smartcard has been registered. The \"DUMP\" versions of the command\n" "are only useful for debugging. The output format is a percent escaped\n" "colon delimited listing as described in the manual.\n" "Supported values for OPTIONS are:\n" " -- Stop option processing\n" " --issuer-der PATTERN is a DER of the serialnumber as hexstring;\n" " the issuer is then inquired with \"ISSUER_DER\".\n" "\n" "These Assuan \"OPTION\" command keys effect the output::\n" "\n" " \"list-mode\" set to 0: List only local certificates (default).\n" " 1: Ditto.\n" " 2: List only external certificates.\n" " 3: List local and external certificates.\n" "\n" " \"with-validation\" set to true: Validate each certificate.\n" "\n" " \"with-ephemeral-key\" set to true: Always include ephemeral\n" " certificates.\n" "\n" " \"list-to-output\" set to true: Write output to the file descriptor\n" " given by the last \"OUTPUT\" command."; static int do_listkeys (assuan_context_t ctx, char *line, int mode) { ctrl_t ctrl = assuan_get_pointer (ctx); estream_t fp; char *p; size_t n; strlist_t list, sl; unsigned int listmode; gpg_error_t err; int opt_issuer_der; opt_issuer_der = has_option (line, "--issuer-der"); line = skip_options (line); /* Break the line down into an strlist. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (opt_issuer_der && (!list || list->next)) { free_strlist (list); return set_error (GPG_ERR_INV_ARG, "only one arg for --issuer-der please"); } if (opt_issuer_der) { unsigned char *value = NULL; size_t valuelen; char *issuer; err = assuan_inquire (ctx, "ISSUER_DER", &value, &valuelen, 0); if (err) { free_strlist (list); return err; } if (!valuelen) { xfree (value); free_strlist (list); return gpg_error (GPG_ERR_MISSING_VALUE); } err = ksba_dn_der2str (value, valuelen, &issuer); xfree (value); if (err) { free_strlist (list); return err; } /* ksba_dn_der2str seems to always append "\\0A". Trim that. */ n = strlen (issuer); if (n > 3 && !strcmp (issuer + n - 3, "\\0A")) issuer[n-3] = 0; p = strconcat ("#", list->d, "/", issuer, NULL); if (!p) { err = gpg_error_from_syserror (); ksba_free (issuer); free_strlist (list); return err; } ksba_free (issuer); free_strlist (list); list = NULL; if (!add_to_strlist_try (&list, p)) { err = gpg_error_from_syserror (); xfree (p); return err; } xfree (p); } if (ctrl->server_local->list_to_output) { int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if ( outfd == -1 ) { free_strlist (list); return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); } fp = es_fdopen_nc (outfd, "w"); if (!fp) { free_strlist (list); return set_error (gpg_err_code_from_syserror (), "es_fdopen() failed"); } } else { fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) { free_strlist (list); return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } } ctrl->with_colons = 1; listmode = mode; if (ctrl->server_local->list_internal) listmode |= (1<<6); if (ctrl->server_local->list_external) listmode |= (1<<7); err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); free_strlist (list); es_fclose (fp); if (ctrl->server_local->list_to_output) assuan_close_output_fd (ctx); return err; } static gpg_error_t cmd_listkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 3); } static gpg_error_t cmd_dumpkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 259); } static gpg_error_t cmd_listsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 2); } static gpg_error_t cmd_dumpsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 258); } static const char hlp_genkey[] = "GENKEY\n" "\n" "Read the parameters in native format from the input fd and write a\n" "certificate request to the output."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t in_stream, out_stream; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); in_stream = es_fdopen_nc (inp_fd, "r"); if (!in_stream) return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed"); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { es_fclose (in_stream); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = gpgsm_genkey (ctrl, in_stream, out_stream); es_fclose (out_stream); es_fclose (in_stream); /* close and reset the fds */ assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_getauditlog[] = "GETAUDITLOG [--data] [--html]\n" "\n" "If --data is used, the output is send using D-lines and not to the\n" "file descriptor given by an OUTPUT command.\n" "\n" "If --html is used the output is formatted as an XHTML block. This is\n" "designed to be incorporated into a HTML document."; static gpg_error_t cmd_getauditlog (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int out_fd; estream_t out_stream; int opt_data, opt_html; int rc; opt_data = has_option (line, "--data"); opt_html = has_option (line, "--html"); /* Not needed: line = skip_options (line); */ if (!ctrl->audit) return gpg_error (GPG_ERR_NO_DATA); if (opt_data) { out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!out_stream) return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } else { out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed"); } } audit_print_result (ctrl->audit, out_stream, opt_html); rc = 0; es_fclose (out_stream); /* Close and reset the fd. */ if (!opt_data) assuan_close_output_fd (ctx); return rc; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " agent-check - Return success if the agent is running.\n" " cmd_has_option CMD OPT\n" " - Returns OK if the command CMD implements the option OPT.\n" " offline - Returns OK if the connection is in offline mode."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "agent-check")) { rc = gpgsm_agent_send_nop (ctrl); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_FALSE); } } } } else if (!strcmp (line, "offline")) { rc = ctrl->offline? 0 : gpg_error (GPG_ERR_FALSE); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_passwd[] = "PASSWD \n" "\n" "Change the passphrase of the secret key for USERID."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; char *grip = NULL; line = skip_options (line); err = gpgsm_find_cert (ctrl, line, NULL, &cert, 0); if (err) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) err = gpg_error (GPG_ERR_INTERNAL); else { char *desc = gpgsm_format_keydesc (cert); err = gpgsm_agent_passwd (ctrl, grip, desc); xfree (desc); } xfree (grip); ksba_cert_release (cert); return err; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "IMPORT")) { if (!strcmp (cmdopt, "re-import")) return 1; } return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "RECIPIENT", cmd_recipient, hlp_recipient }, { "SIGNER", cmd_signer, hlp_signer }, { "ENCRYPT", cmd_encrypt, hlp_encrypt }, { "DECRYPT", cmd_decrypt, hlp_decrypt }, { "VERIFY", cmd_verify, hlp_verify }, { "SIGN", cmd_sign, hlp_sign }, { "IMPORT", cmd_import, hlp_import }, { "EXPORT", cmd_export, hlp_export }, { "INPUT", NULL, hlp_input }, { "OUTPUT", NULL, hlp_output }, { "MESSAGE", cmd_message, hlp_message }, { "LISTKEYS", cmd_listkeys, hlp_listkeys }, { "DUMPKEYS", cmd_dumpkeys, hlp_listkeys }, { "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys }, { "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys }, { "GENKEY", cmd_genkey, hlp_genkey }, { "DELKEYS", cmd_delkeys, hlp_delkeys }, { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "PASSWD", cmd_passwd, hlp_passwd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Startup the server. DEFAULT_RECPLIST is the list of recipients as set from the command line or config file. We only require those marked as encrypt-to. */ void gpgsm_server (certlist_t default_recplist) { int rc; assuan_fd_t filedes[2]; assuan_context_t ctx; struct server_control_s ctrl; static const char hello[] = ("GNU Privacy Guard's S/M server " VERSION " ready"); memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); /* We use a pipe based server so that we can work from scripts. assuan_init_pipe_server will automagically detect when we are called with a socketpair and ignore FILEDES in this case. */ #define SERVER_STDIN 0 #define SERVER_STDOUT 1 filedes[0] = assuan_fdopen (SERVER_STDIN); filedes[1] = assuan_fdopen (SERVER_STDOUT); rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = assuan_init_pipe_server (ctx, filedes); if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to the register commands with Assuan: %s\n", gpg_strerror(rc)); gpgsm_exit (2); } if (opt.verbose || opt.debug) { char *tmp; /* Fixme: Use the really used socket name. */ if (asprintf (&tmp, "Home: %s\n" "Config: %s\n" "DirmngrInfo: %s\n" "%s", gnupg_homedir (), opt.config_filename, dirmngr_socket_name (), hello) > 0) { assuan_set_hello_line (ctx, tmp); free (tmp); } } else assuan_set_hello_line (ctx, hello); assuan_register_reset_notify (ctx, reset_notify); assuan_register_input_notify (ctx, input_notify); assuan_register_output_notify (ctx, output_notify); assuan_register_option_handler (ctx, option_handler); assuan_set_pointer (ctx, &ctrl); ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); ctrl.server_local->assuan_ctx = ctx; ctrl.server_local->message_fd = -1; ctrl.server_local->list_internal = 1; ctrl.server_local->list_external = 0; ctrl.server_local->default_recplist = default_recplist; for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } gpgsm_release_certlist (ctrl.server_local->recplist); ctrl.server_local->recplist = NULL; gpgsm_release_certlist (ctrl.server_local->signerlist); ctrl.server_local->signerlist = NULL; xfree (ctrl.server_local); audit_release (ctrl.audit); ctrl.audit = NULL; gpgsm_deinit_default_ctrl (&ctrl); assuan_release (ctx); } gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, no); if (ctrl->no_server && ctrl->status_fd == -1) ; /* No status wanted. */ else if (ctrl->no_server) { if (!statusfp) { if (ctrl->status_fd == 1) statusfp = stdout; else if (ctrl->status_fd == 2) statusfp = stderr; else statusfp = fdopen (ctrl->status_fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", ctrl->status_fd, strerror(errno)); } } fputs ("[GNUPG:] ", statusfp); fputs (get_status_string (no), statusfp); while ( (text = va_arg (arg_ptr, const char*) )) { putc ( ' ', statusfp ); for (; *text; text++) { if (*text == '\n') fputs ( "\\n", statusfp ); else if (*text == '\r') fputs ( "\\r", statusfp ); else putc ( *(const byte *)text, statusfp ); } } putc ('\n', statusfp); - fflush (statusfp); + if (ferror (statusfp)) + err = gpg_error_from_syserror (); + else + { + fflush (statusfp); + if (ferror (statusfp)) + err = gpg_error_from_syserror (); + } } else { err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx, get_status_string (no), arg_ptr); } va_end (arg_ptr); return err; } gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text) { return gpgsm_status2 (ctrl, no, text, NULL); } gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec) { char buf[30]; snprintf (buf, sizeof buf, "%u", (unsigned int)ec); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, gpg_error_t err) { char buf[30]; snprintf (buf, sizeof buf, "%u", err); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } +/* This callback is used to emit progress status lines. */ +gpg_error_t +gpgsm_progress_cb (ctrl_t ctrl, uint64_t current, uint64_t total) +{ + char buffer[60]; + char units[] = "BKMGTPEZY?"; + int unitidx = 0; + + if (current > 1024*1024*20) + { + static int closed; + if (closed) + close (5); + closed = 1; + } + + if (total) + { + if (total > current) + current = total; + + while (total > 1024*1024) + { + total /= 1024; + current /= 1024; + unitidx++; + } + } + else + { + while (current > 1024*1024) + { + current /= 1024; + unitidx++; + } + } + + if (unitidx > 9) + unitidx = 9; + + snprintf (buffer, sizeof buffer, "? %lu %lu %c%s", + (unsigned long)current, (unsigned long)total, + units[unitidx], unitidx? "iB" : ""); + return gpgsm_status2 (ctrl, STATUS_PROGRESS, "?", buffer, NULL); +} + + /* Helper to notify the client about Pinentry events. Because that might disturb some older clients, this is only done when enabled via an option. Returns an gpg error code. */ gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } diff --git a/sm/sign.c b/sm/sign.c index b3b7e1883..4f5e8e3a0 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -1,1210 +1,1212 @@ /* sign.c - Sign a message * Copyright (C) 2001, 2002, 2003, 2008, * 2010 Free Software Foundation, Inc. * Copyright (C) 2003-2012, 2016-2017, 2019, * 2020, 2022-2023 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG 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 3 of the License, or * (at your option) any later version. * * GnuPG 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, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../common/i18n.h" #include "../common/tlv.h" /* Hash the data and return if something was hashed. Return -1 on error. */ static int hash_data (int fd, gcry_md_hd_t md) { estream_t fp; char buffer[4096]; int nread; int rc = 0; fp = es_fdopen_nc (fd, "rb"); if (!fp) { log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return -1; } do { nread = es_fread (buffer, 1, DIM(buffer), fp); gcry_md_write (md, buffer, nread); } while (nread); if (es_ferror (fp)) { log_error ("read error on fd %d: %s\n", fd, strerror (errno)); rc = -1; } es_fclose (fp); return rc; } static int hash_and_copy_data (int fd, gcry_md_hd_t md, ksba_writer_t writer) { gpg_error_t err; estream_t fp; char buffer[4096]; int nread; int rc = 0; int any = 0; fp = es_fdopen_nc (fd, "rb"); if (!fp) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return tmperr; } do { nread = es_fread (buffer, 1, DIM(buffer), fp); if (nread) { any = 1; gcry_md_write (md, buffer, nread); err = ksba_writer_write_octet_string (writer, buffer, nread, 0); if (err) { log_error ("write failed: %s\n", gpg_strerror (err)); rc = err; } } } while (nread && !rc); if (es_ferror (fp)) { rc = gpg_error_from_syserror (); log_error ("read error on fd %d: %s\n", fd, strerror (errno)); } es_fclose (fp); if (!any) { /* We can't allow signing an empty message because it does not make much sense and more seriously, ksba_cms_build has already written the tag for data and now expects an octet string and an octet string of size 0 is illegal. */ log_error ("cannot sign an empty message\n"); rc = gpg_error (GPG_ERR_NO_DATA); } if (!rc) { err = ksba_writer_write_octet_string (writer, NULL, 0, 1); if (err) { log_error ("write failed: %s\n", gpg_strerror (err)); rc = err; } } return rc; } /* Get the default certificate which is defined as the first certificate capable of signing returned by the keyDB and has a secret key available. */ int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert) { KEYDB_HANDLE hd; ksba_cert_t cert = NULL; int rc; char *p; hd = keydb_new (ctrl); if (!hd) return gpg_error (GPG_ERR_GENERAL); rc = keydb_search_first (ctrl, hd); if (rc) { keydb_release (hd); return rc; } do { rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); keydb_release (hd); return rc; } if (!gpgsm_cert_use_sign_p (cert, 1)) { p = gpgsm_get_keygrip_hexstring (cert); if (p) { if (!gpgsm_agent_havekey (ctrl, p)) { xfree (p); keydb_release (hd); *r_cert = cert; return 0; /* got it */ } xfree (p); } } ksba_cert_release (cert); cert = NULL; } while (!(rc = keydb_search_next (ctrl, hd))); if (rc && rc != -1) log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); ksba_cert_release (cert); keydb_release (hd); return rc; } static ksba_cert_t get_default_signer (ctrl_t ctrl) { KEYDB_SEARCH_DESC desc; ksba_cert_t cert = NULL; KEYDB_HANDLE kh = NULL; int rc; if (!opt.local_user) { rc = gpgsm_get_default_cert (ctrl, &cert); if (rc) { if (rc != -1) log_debug ("failed to find default certificate: %s\n", gpg_strerror (rc)); return NULL; } return cert; } rc = classify_user_id (opt.local_user, &desc, 0); if (rc) { log_error ("failed to find default signer: %s\n", gpg_strerror (rc)); return NULL; } kh = keydb_new (ctrl); if (!kh) return NULL; rc = keydb_search (ctrl, kh, &desc, 1); if (rc) { log_debug ("failed to find default certificate: rc=%d\n", rc); } else { rc = keydb_get_cert (kh, &cert); if (rc) { log_debug ("failed to get cert: rc=%d\n", rc); } } keydb_release (kh); return cert; } /* Depending on the options in CTRL add the certificate CERT as well as other certificate up in the chain to the Root-CA to the CMS object. */ static int add_certificate_list (ctrl_t ctrl, ksba_cms_t cms, ksba_cert_t cert) { gpg_error_t err; int rc = 0; ksba_cert_t next = NULL; int n; int not_root = 0; ksba_cert_ref (cert); n = ctrl->include_certs; if (n == -2) { not_root = 1; n = -1; } if (n < 0 || n > 50) n = 50; /* We better apply an upper bound */ /* First add my own certificate unless we don't want any certificate included at all. */ if (n) { if (not_root && gpgsm_is_root_cert (cert)) err = 0; else err = ksba_cms_add_cert (cms, cert); if (err) goto ksba_failure; if (n>0) n--; } /* Walk the chain to include all other certificates. Note that a -1 used for N makes sure that there is no limit and all certs get included. */ while ( n-- && !(rc = gpgsm_walk_cert_chain (ctrl, cert, &next)) ) { if (not_root && gpgsm_is_root_cert (next)) err = 0; else err = ksba_cms_add_cert (cms, next); ksba_cert_release (cert); cert = next; next = NULL; if (err) goto ksba_failure; } ksba_cert_release (cert); return gpg_err_code (rc) == GPG_ERR_NOT_FOUND? 0 : rc; ksba_failure: ksba_cert_release (cert); log_error ("ksba_cms_add_cert failed: %s\n", gpg_strerror (err)); return err; } static gpg_error_t add_signed_attribute (ksba_cms_t cms, const char *attrstr) { gpg_error_t err; char **fields = NULL; const char *s; int i; unsigned char *der = NULL; size_t derlen; fields = strtokenize (attrstr, ":"); if (!fields) { err = gpg_error_from_syserror (); log_error ("strtokenize failed: %s\n", gpg_strerror (err)); goto leave; } for (i=0; fields[i]; i++) ; if (i != 3) { err = gpg_error (GPG_ERR_SYNTAX); log_error ("invalid attribute specification '%s': %s\n", attrstr, i < 3 ? "not enough fields":"too many fields"); goto leave; } if (!ascii_strcasecmp (fields[1], "u")) { err = 0; goto leave; /* Skip unsigned attributes. */ } if (ascii_strcasecmp (fields[1], "s")) { err = gpg_error (GPG_ERR_SYNTAX); log_error ("invalid attribute specification '%s': %s\n", attrstr, "type is not 's' or 'u'"); goto leave; } /* Check that the OID is valid. */ err = ksba_oid_from_str (fields[0], &der, &derlen); if (err) { log_error ("invalid attribute specification '%s': %s\n", attrstr, gpg_strerror (err)); goto leave; } xfree (der); der = NULL; if (strchr (fields[2], '/')) { /* FIXME: read from file. */ } else /* Directly given in hex. */ { for (i=0, s = fields[2]; hexdigitp (s); s++, i++) ; if (*s || !i || (i&1)) { log_error ("invalid attribute specification '%s': %s\n", attrstr, "invalid hex encoding of the data"); err = gpg_error (GPG_ERR_SYNTAX); goto leave; } der = xtrystrdup (fields[2]); if (!der) { err = gpg_error_from_syserror (); log_error ("malloc failed: %s\n", gpg_strerror (err)); goto leave; } for (s=fields[2], derlen=0; s[0] && s[1]; s += 2) der[derlen++] = xtoi_2 (s); } /* Store the data in the CMS object for all signers. */ #if 0 err = ksba_cms_add_attribute (cms, -1, fields[0], 0, der, derlen); #else (void)cms; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif if (err) { log_error ("invalid attribute specification '%s': %s\n", attrstr, gpg_strerror (err)); goto leave; } leave: xfree (der); xfree (fields); return err; } /* This function takes a binary detached signature in (BLOB,BLOBLEN) * and writes it to OUT_FP. The core of the function is to replace * NDEF length sequences in the input to those with fixed inputs. * This helps certain other implementations to properly verify * detached signature. Moreover, it allows our own trailing zero * stripping code - which we need for PDF signatures - to work * correctly. * * Example start of a detached signature as created by us: * 0 NDEF: SEQUENCE { -- 1st sequence * 2 9: OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2) * 13 NDEF: [0] { -- 2nd sequence * 15 NDEF: SEQUENCE { -- 3rd sequence * 17 1: INTEGER 1 -- version * 20 15: SET { -- set of algorithms * 22 13: SEQUENCE { * 24 9: OBJECT IDENTIFIER sha-256 (2 16 840 1 101 3 4 2 1) * 35 0: NULL * : } * : } * 37 NDEF: SEQUENCE { -- 4th pretty short sequence * 39 9: OBJECT IDENTIFIER data (1 2 840 113549 1 7 1) * : } * 52 869: [0] { * Our goal is to replace the NDEF by fixed length tags. */ static gpg_error_t write_detached_signature (ctrl_t ctrl, const void *blob, size_t bloblen, estream_t out_fp) { gpg_error_t err; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, cons, ndef; const unsigned char *p_ctoid, *p_version, *p_algoset, *p_dataoid; size_t n_ctoid, n_version, n_algoset, n_dataoid; const unsigned char *p_certset, *p_signerinfos; size_t n_certset, n_signerinfos; int i; ksba_der_t dbld; unsigned char *finalder = NULL; size_t finalderlen; (void)ctrl; p = blob; n = bloblen; if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 1st sequence. */ if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No signedData OID. */ if (objlen > n) return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ p_ctoid = p; n_ctoid = objlen; p += objlen; n -= objlen; if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_CONTEXT && tag == 0 && cons)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 2nd sequence. */ if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 3rd sequence. */ if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No version. */ if (objlen > n) return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ p_version = p; n_version = objlen; p += objlen; n -= objlen; p_algoset = p; if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_SET && cons && !ndef)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No set of algorithms. */ if (objlen > n) return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ n_algoset = hdrlen + objlen; p += objlen; n -= objlen; if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 4th sequence. */ if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No data OID. */ if (objlen > n) return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ p_dataoid = p; n_dataoid = objlen; p += objlen; n -= objlen; if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_NONE && !cons && !objlen)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No End tag. */ /* certificates [0] IMPLICIT CertificateSet OPTIONAL, * Note: We ignore the following * crls [1] IMPLICIT CertificateRevocationLists OPTIONAL * because gpgsm does not create them. */ if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) return err; if (class == CLASS_CONTEXT && tag == 0 && cons) { if (objlen > n) return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ p_certset = p; n_certset = objlen; p += objlen; n -= objlen; if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef, &objlen,&hdrlen))) return err; } else { p_certset = NULL; n_certset = 0; } /* SignerInfos ::= SET OF SignerInfo */ if (!(class == CLASS_UNIVERSAL && tag == TAG_SET && cons && !ndef)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No set of signerInfos. */ if (objlen > n) return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ p_signerinfos = p; n_signerinfos = objlen; p += objlen; n -= objlen; /* For the fun of it check the 3 end tags. */ for (i=0; i < 3; i++) { if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef, &objlen,&hdrlen))) return err; if (!(class == CLASS_UNIVERSAL && tag == TAG_NONE && !cons && !objlen)) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No End tag. */ } if (n) return gpg_error (GPG_ERR_INV_CMS_OBJ); /* Garbage */ /*---- From here on we jump to leave on error. ----*/ /* Now create a new object from the collected data. */ dbld = ksba_der_builder_new (16); /* (pre-allocate 16 items) */ if (!dbld) { err = gpg_error_from_syserror (); goto leave; } ksba_der_add_tag (dbld, 0, KSBA_TYPE_SEQUENCE); ksba_der_add_val ( dbld, 0, KSBA_TYPE_OBJECT_ID, p_ctoid, n_ctoid); ksba_der_add_tag ( dbld, KSBA_CLASS_CONTEXT, 0); ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SEQUENCE); ksba_der_add_val ( dbld, 0, KSBA_TYPE_INTEGER, p_version, n_version); ksba_der_add_der ( dbld, p_algoset, n_algoset); ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SEQUENCE); ksba_der_add_val ( dbld, 0, KSBA_TYPE_OBJECT_ID, p_dataoid, n_dataoid); ksba_der_add_end ( dbld); if (p_certset) { ksba_der_add_tag ( dbld, KSBA_CLASS_CONTEXT, 0); ksba_der_add_der ( dbld, p_certset, n_certset); ksba_der_add_end ( dbld); } ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SET); ksba_der_add_der ( dbld, p_signerinfos, n_signerinfos); ksba_der_add_end ( dbld); ksba_der_add_end ( dbld); ksba_der_add_end ( dbld); ksba_der_add_end (dbld); err = ksba_der_builder_get (dbld, &finalder, &finalderlen); if (err) goto leave; if (es_fwrite (finalder, finalderlen, 1, out_fp) != 1) { err = gpg_error_from_syserror (); goto leave; } leave: ksba_der_release (dbld); ksba_free (finalder); return err; } /* Perform a sign operation. Sign the data received on DATA-FD in embedded mode or in detached mode when DETACHED is true. Write the signature to OUT_FP. The keys used to sign are taken from SIGNERLIST or the default one will be used if the value of this argument is NULL. */ int gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, int data_fd, int detached, estream_t out_fp) { int i, rc; gpg_error_t err; gnupg_ksba_io_t b64writer = NULL; ksba_writer_t writer; estream_t sig_fp = NULL; /* Used for detached signatures. */ ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; gcry_md_hd_t data_md = NULL; int signer; const char *algoid; int algo; ksba_isotime_t signed_at; certlist_t cl; int release_signerlist = 0; int binary_detached = detached && !ctrl->create_pem && !ctrl->create_base64; audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN); kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } if (!gnupg_rng_is_compliant (opt.compliance)) { rc = gpg_error (GPG_ERR_FORBIDDEN); log_error (_("%s is not compliant with %s mode\n"), "RNG", gnupg_compliance_option_string (opt.compliance)); gpgsm_status_with_error (ctrl, STATUS_ERROR, "random-compliance", rc); goto leave; } /* Note that in detached mode the b64 write is actually a binary * writer because we need to fixup the created signature later. * Note that we do this only for binary output because we have no * PEM writer interface outside of the ksba create writer code. */ ctrl->pem_name = "SIGNED MESSAGE"; if (binary_detached) { sig_fp = es_fopenmem (0, "w+"); rc = sig_fp? 0 : gpg_error_from_syserror (); if (!rc) rc = gnupg_ksba_create_writer (&b64writer, 0, NULL, sig_fp, &writer); } else { rc = gnupg_ksba_create_writer (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), ctrl->pem_name, out_fp, &writer); } if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + err = ksba_cms_new (&cms); if (err) { rc = err; goto leave; } err = ksba_cms_set_reader_writer (cms, NULL, writer); if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* We are going to create signed data with data as encap. content. * In authenticode mode we use spcIndirectDataContext instead. */ err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA); if (!err) err = ksba_cms_set_content_type (cms, 1, opt.authenticode? KSBA_CT_SPC_IND_DATA_CTX : KSBA_CT_DATA ); if (err) { log_debug ("ksba_cms_set_content_type failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* If no list of signers is given, use the default certificate. */ if (!signerlist) { ksba_cert_t cert = get_default_signer (ctrl); if (!cert) { log_error ("no default signer found\n"); gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (GPG_ERR_NO_SECKEY), NULL); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } /* Although we don't check for ambiguous specification we will check that the signer's certificate is usable and valid. */ rc = gpgsm_cert_use_sign_p (cert, 0); if (!rc) rc = gpgsm_validate_chain (ctrl, cert, GNUPG_ISOTIME_NONE, NULL, 0, NULL, 0, NULL); if (rc) { char *tmpfpr; tmpfpr = gpgsm_get_fingerprint_hexstring (cert, 0); gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), tmpfpr, NULL); xfree (tmpfpr); goto leave; } /* That one is fine - create signerlist. */ signerlist = xtrycalloc (1, sizeof *signerlist); if (!signerlist) { rc = out_of_core (); ksba_cert_release (cert); goto leave; } signerlist->cert = cert; release_signerlist = 1; } /* Figure out the hash algorithm to use. We do not want to use the one for the certificate but if possible an OID for the plain algorithm. */ if (opt.forced_digest_algo && opt.verbose) log_info ("user requested hash algorithm %d\n", opt.forced_digest_algo); for (i=0, cl=signerlist; cl; cl = cl->next, i++) { const char *oid; unsigned int nbits; int pk_algo; pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits); cl->pk_algo = pk_algo; if (opt.forced_digest_algo) { oid = NULL; cl->hash_algo = opt.forced_digest_algo; } else { if (pk_algo == GCRY_PK_ECC) { /* Map the Curve to a corresponding hash algo. */ if (nbits <= 256) oid = "2.16.840.1.101.3.4.2.1"; /* sha256 */ else if (nbits <= 384) oid = "2.16.840.1.101.3.4.2.2"; /* sha384 */ else oid = "2.16.840.1.101.3.4.2.3"; /* sha512 */ } else { /* For RSA we reuse the hash algo used by the certificate. */ oid = ksba_cert_get_digest_algo (cl->cert); } cl->hash_algo = oid ? gcry_md_map_name (oid) : 0; } switch (cl->hash_algo) { case GCRY_MD_SHA1: oid = "1.3.14.3.2.26"; break; case GCRY_MD_RMD160: oid = "1.3.36.3.2.1"; break; case GCRY_MD_SHA224: oid = "2.16.840.1.101.3.4.2.4"; break; case GCRY_MD_SHA256: oid = "2.16.840.1.101.3.4.2.1"; break; case GCRY_MD_SHA384: oid = "2.16.840.1.101.3.4.2.2"; break; case GCRY_MD_SHA512: oid = "2.16.840.1.101.3.4.2.3"; break; /* case GCRY_MD_WHIRLPOOL: oid = "No OID yet"; break; */ case GCRY_MD_MD5: /* We don't want to use MD5. */ case 0: /* No algorithm found in cert. */ default: /* Other algorithms. */ log_info (_("hash algorithm %d (%s) for signer %d not supported;" " using %s\n"), cl->hash_algo, oid? oid: "?", i, gcry_md_algo_name (GCRY_MD_SHA1)); cl->hash_algo = GCRY_MD_SHA1; oid = "1.3.14.3.2.26"; break; } cl->hash_algo_oid = oid; /* Check compliance. */ if (! gnupg_digest_is_allowed (opt.compliance, 1, cl->hash_algo)) { log_error (_("digest algorithm '%s' may not be used in %s mode\n"), gcry_md_algo_name (cl->hash_algo), gnupg_compliance_option_string (opt.compliance)); err = gpg_error (GPG_ERR_DIGEST_ALGO); goto leave; } if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, pk_algo, 0, NULL, nbits, NULL)) { char kidstr[10+1]; snprintf (kidstr, sizeof kidstr, "0x%08lX", gpgsm_get_short_fingerprint (cl->cert, NULL)); log_error (_("key %s may not be used for signing in %s mode\n"), kidstr, gnupg_compliance_option_string (opt.compliance)); err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; } } if (opt.verbose > 1 || opt.debug) { for (i=0, cl=signerlist; cl; cl = cl->next, i++) log_info (_("hash algorithm used for signer %d: %s (%s)\n"), i, gcry_md_algo_name (cl->hash_algo), cl->hash_algo_oid); } /* Gather certificates of signers and store them in the CMS object. */ for (cl=signerlist; cl; cl = cl->next) { rc = gpgsm_cert_use_sign_p (cl->cert, 0); if (rc) goto leave; err = ksba_cms_add_signer (cms, cl->cert); if (err) { log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } rc = add_certificate_list (ctrl, cms, cl->cert); if (rc) { log_error ("failed to store list of certificates: %s\n", gpg_strerror(rc)); goto leave; } /* Set the hash algorithm we are going to use */ err = ksba_cms_add_digest_algo (cms, cl->hash_algo_oid); if (err) { log_debug ("ksba_cms_add_digest_algo failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } /* Check whether one of the certificates is qualified. Note that we already validated the certificate and thus the user data stored flag must be available. */ if (!opt.no_chain_validation) { for (cl=signerlist; cl; cl = cl->next) { size_t buflen; char buffer[1]; err = ksba_cert_get_user_data (cl->cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (err || !buflen) { log_error (_("checking for qualified certificate failed: %s\n"), gpg_strerror (err)); rc = err; goto leave; } if (*buffer) err = gpgsm_qualified_consent (ctrl, cl->cert); else err = gpgsm_not_qualified_warning (ctrl, cl->cert); if (err) { rc = err; goto leave; } } } /* Prepare hashing (actually we are figuring out what we have set above). */ rc = gcry_md_open (&data_md, 0, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_debug (data_md, "sign.data"); for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) { algo = gcry_md_map_name (algoid); if (!algo) { log_error ("unknown hash algorithm '%s'\n", algoid? algoid:"?"); rc = gpg_error (GPG_ERR_BUG); goto leave; } gcry_md_enable (data_md, algo); audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); } audit_log (ctrl->audit, AUDIT_SETUP_READY); if (detached) { /* We hash the data right now so that we can store the message digest. ksba_cms_build() takes this as an flag that detached data is expected. */ unsigned char *digest; size_t digest_len; if (!hash_data (data_fd, data_md)) audit_log (ctrl->audit, AUDIT_GOT_DATA); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { digest = gcry_md_read (data_md, cl->hash_algo); digest_len = gcry_md_get_algo_dlen (cl->hash_algo); if ( !digest || !digest_len ) { log_error ("problem getting the hash of the data\n"); rc = gpg_error (GPG_ERR_BUG); goto leave; } err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } } gnupg_get_isotime (signed_at); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { err = ksba_cms_set_signing_time (cms, signer, signed_at); if (err) { log_error ("ksba_cms_set_signing_time failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } { strlist_t sl; for (sl = opt.attributes; sl; sl = sl->next) if ((err = add_signed_attribute (cms, sl->d))) goto leave; } /* We need to write at least a minimal list of our capabilities to * try to convince some MUAs to use 3DES and not the crippled * RC2. Our list is: * * aes256-CBC * aes128-CBC * des-EDE3-CBC */ err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.42", NULL,0); if (!err) err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.2", NULL,0); if (!err) err = ksba_cms_add_smime_capability (cms, "1.2.840.113549.3.7", NULL, 0); if (err) { log_error ("ksba_cms_add_smime_capability failed: %s\n", gpg_strerror (err)); goto leave; } /* Main building loop. */ do { err = ksba_cms_build (cms, &stopreason); if (err) { - log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); + log_error ("creating CMS object failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA) { /* Hash the data and store the message digest. */ unsigned char *digest; size_t digest_len; log_assert (!detached); rc = hash_and_copy_data (data_fd, data_md, writer); if (rc) goto leave; audit_log (ctrl->audit, AUDIT_GOT_DATA); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { digest = gcry_md_read (data_md, cl->hash_algo); digest_len = gcry_md_get_algo_dlen (cl->hash_algo); if ( !digest || !digest_len ) { log_error ("problem getting the hash of the data\n"); rc = gpg_error (GPG_ERR_BUG); goto leave; } err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } } else if (stopreason == KSBA_SR_NEED_SIG) { /* Compute the signature for all signers. */ gcry_md_hd_t md; rc = gcry_md_open (&md, 0, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_debug (md, "sign.attr"); ksba_cms_set_hash_function (cms, HASH_FNC, md); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { unsigned char *sigval = NULL; char *buf, *fpr; audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer); if (signer) gcry_md_reset (md); { certlist_t cl_tmp; for (cl_tmp=signerlist; cl_tmp; cl_tmp = cl_tmp->next) { gcry_md_enable (md, cl_tmp->hash_algo); audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO, cl_tmp->hash_algo); } } rc = ksba_cms_hash_signed_attrs (cms, signer); if (rc) { log_debug ("hashing signed attrs failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); goto leave; } rc = gpgsm_create_cms_signature (ctrl, cl->cert, md, cl->hash_algo, &sigval); if (rc) { audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, rc); gcry_md_close (md); goto leave; } err = ksba_cms_set_sig_val (cms, signer, sigval); xfree (sigval); if (err) { audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, err); log_error ("failed to store the signature: %s\n", gpg_strerror (err)); rc = err; gcry_md_close (md); goto leave; } /* write a status message */ fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1); if (!fpr) { rc = gpg_error (GPG_ERR_ENOMEM); gcry_md_close (md); goto leave; } rc = 0; if (opt.verbose) { char *pkalgostr = gpgsm_pubkey_algo_string (cl->cert, NULL); log_info (_("%s/%s signature using %s key %s\n"), pubkey_algo_to_string (cl->pk_algo), gcry_md_algo_name (cl->hash_algo), pkalgostr, fpr); xfree (pkalgostr); } buf = xtryasprintf ("%c %d %d 00 %s %s", detached? 'D':'S', cl->pk_algo, cl->hash_algo, signed_at, fpr); if (!buf) rc = gpg_error_from_syserror (); xfree (fpr); if (rc) { gcry_md_close (md); goto leave; } gpgsm_status (ctrl, STATUS_SIG_CREATED, buf); xfree (buf); audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, 0); } gcry_md_close (md); } } while (stopreason != KSBA_SR_READY); rc = gnupg_ksba_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } if (binary_detached) { void *blob = NULL; size_t bloblen; rc = es_fclose_snatch (sig_fp, &blob, &bloblen); sig_fp = NULL; if (rc) goto leave; rc = write_detached_signature (ctrl, blob, bloblen, out_fp); xfree (blob); if (rc) goto leave; } audit_log (ctrl->audit, AUDIT_SIGNING_DONE); log_info ("signature created\n"); leave: if (rc) log_error ("error creating signature: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc) ); if (release_signerlist) gpgsm_release_certlist (signerlist); ksba_cms_release (cms); gnupg_ksba_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); es_fclose (sig_fp); return rc; } diff --git a/sm/verify.c b/sm/verify.c index a07d1c9c7..9125b2b06 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -1,758 +1,760 @@ /* verify.c - Verify a messages signature * Copyright (C) 2001, 2002, 2003, 2007, * 2010 Free Software Foundation, Inc. * Copyright (C) 2001-2019 Werner Koch * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG 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 3 of the License, or * (at your option) any later version. * * GnuPG 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, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../common/i18n.h" #include "../common/compliance.h" static char * strtimestamp_r (ksba_isotime_t atime) { char *buffer = xmalloc (15); if (!atime || !*atime) strcpy (buffer, "none"); else sprintf (buffer, "%.4s-%.2s-%.2s", atime, atime+4, atime+6); return buffer; } /* Hash the data for a detached signature. Returns 0 on success. */ static gpg_error_t hash_data (int fd, gcry_md_hd_t md) { gpg_error_t err = 0; estream_t fp; char buffer[4096]; int nread; fp = es_fdopen_nc (fd, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error ("fdopen(%d) failed: %s\n", fd, gpg_strerror (err)); return err; } do { nread = es_fread (buffer, 1, DIM(buffer), fp); gcry_md_write (md, buffer, nread); } while (nread); if (es_ferror (fp)) { err = gpg_error_from_syserror (); log_error ("read error on fd %d: %s\n", fd, gpg_strerror (err)); } es_fclose (fp); return err; } /* Perform a verify operation. To verify detached signatures, DATA_FD must be different than -1. With OUT_FP given and a non-detached signature, the signed material is written to that stream. */ int gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp) { int i, rc; gnupg_ksba_io_t b64reader = NULL; gnupg_ksba_io_t b64writer = NULL; ksba_reader_t reader; ksba_writer_t writer = NULL; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; ksba_cert_t cert; KEYDB_HANDLE kh; gcry_md_hd_t data_md = NULL; int signer; const char *algoid; int algo; int is_detached, maybe_detached; estream_t in_fp = NULL; char *p; audit_set_type (ctrl->audit, AUDIT_TYPE_VERIFY); /* Although we detect detached signatures during the parsing phase, * we need to know it earlier and thus accept the caller idea of * what to verify. */ maybe_detached = (data_fd != -1); kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } in_fp = es_fdopen_nc (in_fd, "rb"); if (!in_fp) { rc = gpg_error_from_syserror (); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } rc = gnupg_ksba_create_reader (&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0) | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0) | (maybe_detached? GNUPG_KSBA_IO_STRIP : 0)), in_fp, &reader); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); goto leave; } if (out_fp) { rc = gnupg_ksba_create_writer (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), ctrl->pem_name, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + rc = ksba_cms_new (&cms); if (rc) goto leave; rc = ksba_cms_set_reader_writer (cms, reader, writer); if (rc) { log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (rc)); goto leave; } rc = gcry_md_open (&data_md, 0, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_debug (data_md, "vrfy.data"); audit_log (ctrl->audit, AUDIT_SETUP_READY); is_detached = 0; do { rc = ksba_cms_parse (cms, &stopreason); if (rc) { log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); goto leave; } if (stopreason == KSBA_SR_NEED_HASH) { is_detached = 1; audit_log (ctrl->audit, AUDIT_DETACHED_SIGNATURE); if (opt.verbose) log_info ("detached signature\n"); } if (stopreason == KSBA_SR_NEED_HASH || stopreason == KSBA_SR_BEGIN_DATA) { audit_log (ctrl->audit, AUDIT_GOT_DATA); /* We are now able to enable the hash algorithms */ for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) { algo = gcry_md_map_name (algoid); if (!algo) { log_error ("unknown hash algorithm '%s'\n", algoid? algoid:"?"); if (algoid && ( !strcmp (algoid, "1.2.840.113549.1.1.2") ||!strcmp (algoid, "1.2.840.113549.2.2"))) log_info (_("(this is the MD2 algorithm)\n")); audit_log_s (ctrl->audit, AUDIT_BAD_DATA_HASH_ALGO, algoid); } else { if (DBG_X509) log_debug ("enabling hash algorithm %d (%s)\n", algo, algoid? algoid:""); gcry_md_enable (data_md, algo); audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); } } if (opt.extra_digest_algo) { if (DBG_X509) log_debug ("enabling extra hash algorithm %d\n", opt.extra_digest_algo); gcry_md_enable (data_md, opt.extra_digest_algo); audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, opt.extra_digest_algo); } if (is_detached) { if (data_fd == -1) { log_info ("detached signature w/o data " "- assuming certs-only\n"); audit_log (ctrl->audit, AUDIT_CERT_ONLY_SIG); } else audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING, hash_data (data_fd, data_md)); } else { ksba_cms_set_hash_function (cms, HASH_FNC, data_md); } } else if (stopreason == KSBA_SR_END_DATA) { /* The data bas been hashed */ audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING, 0); } } while (stopreason != KSBA_SR_READY); if (b64writer) { rc = gnupg_ksba_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); audit_log_ok (ctrl->audit, AUDIT_WRITE_ERROR, rc); goto leave; } } if (data_fd != -1 && !is_detached) { log_error ("data given for a non-detached signature\n"); rc = gpg_error (GPG_ERR_CONFLICT); audit_log (ctrl->audit, AUDIT_USAGE_ERROR); goto leave; } for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) { /* Fixme: it might be better to check the validity of the certificate first before entering it into the DB. This way we would avoid cluttering the DB with invalid certificates. */ audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert, keydb_store_cert (ctrl, cert, 0, NULL)); ksba_cert_release (cert); } cert = NULL; for (signer=0; ; signer++) { char *issuer = NULL; gcry_sexp_t sigval = NULL; ksba_isotime_t sigtime, keyexptime; ksba_sexp_t serial; char *msgdigest = NULL; size_t msgdigestlen; char *ctattr; int sigval_hash_algo; int info_pkalgo; unsigned int nbits; int pkalgo; char *pkalgostr = NULL; char *pkcurve = NULL; char *pkfpr = NULL; unsigned int pkalgoflags, verifyflags; rc = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial); if (!signer && gpg_err_code (rc) == GPG_ERR_NO_DATA && data_fd == -1 && is_detached) { log_info ("certs-only message accepted\n"); rc = 0; break; } if (rc) { if (signer && rc == -1) rc = 0; break; } gpgsm_status (ctrl, STATUS_NEWSIG, NULL); audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer); if (DBG_X509) { log_debug ("signer %d - issuer: '%s'\n", signer, issuer? issuer:"[NONE]"); log_debug ("signer %d - serial: ", signer); gpgsm_dump_serial (serial); log_printf ("\n"); } if (ctrl->audit) { char *tmpstr = gpgsm_format_sn_issuer (serial, issuer); audit_log_s (ctrl->audit, AUDIT_SIG_NAME, tmpstr); xfree (tmpstr); } rc = ksba_cms_get_signing_time (cms, signer, sigtime); if (gpg_err_code (rc) == GPG_ERR_NO_DATA) *sigtime = 0; else if (rc) { log_error ("error getting signing time: %s\n", gpg_strerror (rc)); *sigtime = 0; /* (we can't encode an error in the time string.) */ } rc = ksba_cms_get_message_digest (cms, signer, &msgdigest, &msgdigestlen); if (!rc) { algoid = ksba_cms_get_digest_algo (cms, signer); algo = gcry_md_map_name (algoid); if (DBG_X509) log_debug ("signer %d - digest algo: %d\n", signer, algo); if (! gcry_md_is_enabled (data_md, algo)) { log_error ("digest algo %d (%s) has not been enabled\n", algo, algoid?algoid:""); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "unsupported"); goto next_signer; } } else if (gpg_err_code (rc) == GPG_ERR_NO_DATA) { log_assert (!msgdigest); rc = 0; algoid = NULL; algo = 0; } else /* real error */ { audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); break; } rc = ksba_cms_get_sigattr_oids (cms, signer, "1.2.840.113549.1.9.3", &ctattr); if (!rc) { const char *s; if (DBG_X509) log_debug ("signer %d - content-type attribute: %s", signer, ctattr); s = ksba_cms_get_content_oid (cms, 1); if (!s || strcmp (ctattr, s)) { log_error ("content-type attribute does not match " "actual content-type\n"); ksba_free (ctattr); ctattr = NULL; audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); goto next_signer; } ksba_free (ctattr); ctattr = NULL; } else if (rc != -1) { log_error ("error getting content-type attribute: %s\n", gpg_strerror (rc)); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); goto next_signer; } rc = 0; sigval = gpgsm_ksba_cms_get_sig_val (cms, signer); if (!sigval) { log_error ("no signature value available\n"); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); goto next_signer; } sigval_hash_algo = gpgsm_get_hash_algo_from_sigval (sigval, &pkalgoflags); if (DBG_X509) { log_debug ("signer %d - signature available (sigval hash=%d pkaf=%u)", signer, sigval_hash_algo, pkalgoflags); } if (!sigval_hash_algo) sigval_hash_algo = algo; /* Fallback used e.g. with old libksba. */ /* Find the certificate of the signer */ keydb_search_reset (kh); rc = keydb_search_issuer_sn (ctrl, kh, issuer, serial); if (rc) { if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { log_error ("certificate not found\n"); rc = gpg_error (GPG_ERR_NO_PUBKEY); } else log_error ("failed to find the certificate: %s\n", gpg_strerror(rc)); { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey", numbuf, NULL); } audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "no-cert"); goto next_signer; } rc = keydb_get_cert (kh, &cert); if (rc) { log_error ("failed to get cert: %s\n", gpg_strerror (rc)); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); goto next_signer; } pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); pkalgostr = gpgsm_pubkey_algo_string (cert, NULL); pkalgo = gpgsm_get_key_algo_info2 (cert, &nbits, &pkcurve); /* Remap the ECC algo to the algo we use. Note that EdDSA has * already been mapped. */ if (pkalgo == GCRY_PK_ECC) pkalgo = GCRY_PK_ECDSA; /* Print infos about the signature. */ log_info (_("Signature made ")); if (*sigtime) { /* We take the freedom as noted in RFC3339 to use a space * instead of the "T" delimiter between date and time. We * also append a separate UTC instead of a "Z" or "+00:00" * suffix because that makes it clear to everyone what kind * of time this is. */ dump_isotime (sigtime); log_printf (" UTC"); } else log_printf (_("[date not given]")); log_info (_(" using %s key %s\n"), pkalgostr, pkfpr); if (opt.verbose) { log_info (_("algorithm:")); log_printf (" %s + %s", pubkey_algo_to_string (pkalgo), gcry_md_algo_name (sigval_hash_algo)); if (algo != sigval_hash_algo) log_printf (" (%s)", gcry_md_algo_name (algo)); log_printf ("\n"); } audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); /* Check compliance. */ if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION, pkalgo, pkalgoflags, NULL, nbits, NULL)) { char kidstr[10+1]; snprintf (kidstr, sizeof kidstr, "0x%08lX", gpgsm_get_short_fingerprint (cert, NULL)); log_error (_("key %s may not be used for signing in %s mode\n"), kidstr, gnupg_compliance_option_string (opt.compliance)); goto next_signer; } if (! gnupg_digest_is_allowed (opt.compliance, 0, sigval_hash_algo)) { log_error (_("digest algorithm '%s' may not be used in %s mode\n"), gcry_md_algo_name (sigval_hash_algo), gnupg_compliance_option_string (opt.compliance)); goto next_signer; } /* Print compliance warning for the key. */ if (!opt.quiet && !gnupg_pk_is_compliant (opt.compliance, pkalgo, pkalgoflags, NULL, nbits, pkcurve)) { log_info (_("WARNING: This key is not suitable for signing" " in %s mode\n"), gnupg_compliance_option_string (opt.compliance)); } /* Check compliance with CO_DE_VS. */ if (gnupg_pk_is_compliant (CO_DE_VS, pkalgo, pkalgoflags, NULL, nbits, pkcurve) && gnupg_gcrypt_is_compliant (CO_DE_VS) && gnupg_digest_is_compliant (CO_DE_VS, sigval_hash_algo)) gpgsm_status (ctrl, STATUS_VERIFICATION_COMPLIANCE_MODE, gnupg_status_compliance_flag (CO_DE_VS)); else if (opt.require_compliance && opt.compliance == CO_DE_VS) { log_error (_("operation forced to fail due to" " unfulfilled compliance rules\n")); gpgsm_errors_seen = 1; } /* Now we can check the signature. */ if (msgdigest) { /* Signed attributes are available. */ gcry_md_hd_t md; unsigned char *s; /* Check that the message digest in the signed attributes matches the one we calculated on the data. */ s = gcry_md_read (data_md, algo); if ( !s || !msgdigestlen || gcry_md_get_algo_dlen (algo) != msgdigestlen || memcmp (s, msgdigest, msgdigestlen) ) { char *fpr; log_error (_("invalid signature: message digest attribute " "does not match computed one\n")); if (DBG_X509) { if (msgdigest) log_printhex (msgdigest, msgdigestlen, "message: "); if (s) log_printhex (s, gcry_md_get_algo_dlen (algo), "computed: "); } fpr = gpgsm_fpr_and_name_for_status (cert); gpgsm_status (ctrl, STATUS_BADSIG, fpr); xfree (fpr); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); goto next_signer; } audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO, sigval_hash_algo); rc = gcry_md_open (&md, sigval_hash_algo, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); goto next_signer; } if (DBG_HASHING) gcry_md_debug (md, "vrfy.attr"); ksba_cms_set_hash_function (cms, HASH_FNC, md); rc = ksba_cms_hash_signed_attrs (cms, signer); if (rc) { log_error ("hashing signed attrs failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); goto next_signer; } rc = gpgsm_check_cms_signature (cert, sigval, md, sigval_hash_algo, pkalgoflags, &info_pkalgo); gcry_md_close (md); } else { rc = gpgsm_check_cms_signature (cert, sigval, data_md, algo, pkalgoflags, &info_pkalgo); } if (rc) { char *fpr; log_error ("invalid signature: %s\n", gpg_strerror (rc)); fpr = gpgsm_fpr_and_name_for_status (cert); gpgsm_status (ctrl, STATUS_BADSIG, fpr); xfree (fpr); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); goto next_signer; } rc = gpgsm_cert_use_verify_p (cert); /*(this displays an info message)*/ if (rc) { gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "verify.keyusage", gpg_err_code (rc)); rc = 0; } if (DBG_X509) log_debug ("signature okay - checking certs\n"); audit_log (ctrl->audit, AUDIT_VALIDATE_CHAIN); rc = gpgsm_validate_chain (ctrl, cert, *sigtime? sigtime : "19700101T000000", keyexptime, 0, NULL, 0, &verifyflags); { char *fpr, *buf, *tstr; fpr = gpgsm_fpr_and_name_for_status (cert); if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) { gpgsm_status (ctrl, STATUS_EXPKEYSIG, fpr); rc = 0; } else gpgsm_status (ctrl, STATUS_GOODSIG, fpr); xfree (fpr); /* FIXME: INFO_PKALGO correctly shows ECDSA but PKALGO is then * ECC. We should use the ECDSA here and need to find a way to * figure this out without using the bogus assumption in * gpgsm_check_cms_signature that ECC is always ECDSA. */ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); tstr = strtimestamp_r (sigtime); buf = xasprintf ("%s %s %s %s 0 0 %d %d 00", fpr, tstr, *sigtime? sigtime : "0", *keyexptime? keyexptime : "0", info_pkalgo, algo); xfree (tstr); xfree (fpr); gpgsm_status (ctrl, STATUS_VALIDSIG, buf); xfree (buf); } audit_log_ok (ctrl->audit, AUDIT_CHAIN_STATUS, rc); if (rc) /* of validate_chain */ { log_error ("invalid certification chain: %s\n", gpg_strerror (rc)); if (gpg_err_code (rc) == GPG_ERR_BAD_CERT_CHAIN || gpg_err_code (rc) == GPG_ERR_BAD_CERT || gpg_err_code (rc) == GPG_ERR_BAD_CA_CERT || gpg_err_code (rc) == GPG_ERR_CERT_REVOKED) gpgsm_status_with_err_code (ctrl, STATUS_TRUST_NEVER, NULL, gpg_err_code (rc)); else gpgsm_status_with_err_code (ctrl, STATUS_TRUST_UNDEFINED, NULL, gpg_err_code (rc)); audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); goto next_signer; } audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "good"); for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) { log_info (!i? _("Good signature from") : _(" aka")); log_printf (" \""); gpgsm_es_print_name (log_get_stream (), p); log_printf ("\"\n"); ksba_free (p); } /* Print a note if this is a qualified signature. */ { size_t qualbuflen; char qualbuffer[1]; rc = ksba_cert_get_user_data (cert, "is_qualified", &qualbuffer, sizeof (qualbuffer), &qualbuflen); if (!rc && qualbuflen) { if (*qualbuffer) { log_info (_("This is a qualified signature\n")); if (!opt.qualsig_approval) log_info (_("Note, that this software is not officially approved " "to create or verify such signatures.\n")); } } else if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("get_user_data(is_qualified) failed: %s\n", gpg_strerror (rc)); } gpgsm_status (ctrl, STATUS_TRUST_FULLY, (verifyflags & VALIDATE_FLAG_STEED)? "0 steed": (verifyflags & VALIDATE_FLAG_CHAIN_MODEL)? "0 chain": "0 shell"); next_signer: rc = 0; xfree (issuer); xfree (serial); gcry_sexp_release (sigval); xfree (msgdigest); xfree (pkalgostr); xfree (pkcurve); xfree (pkfpr); ksba_cert_release (cert); cert = NULL; } rc = 0; leave: ksba_cms_release (cms); gnupg_ksba_destroy_reader (b64reader); gnupg_ksba_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); es_fclose (in_fp); if (rc) { char numbuf[50]; sprintf (numbuf, "%d", rc ); gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave", numbuf, NULL); } return rc; }