diff options
author | Werner Koch <[email protected]> | 2023-07-04 15:37:54 +0000 |
---|---|---|
committer | Werner Koch <[email protected]> | 2023-07-04 15:37:54 +0000 |
commit | 334f5d95c825f5c8a0785f6ab7cb6f7e94269a81 (patch) | |
tree | ef15051d5b299885684c77f876ddbd374b8e5cde /sm | |
parent | agent: Fix formatting thread ID of nPth. (diff) | |
parent | Post release updates (diff) | |
download | gnupg-334f5d95c825f5c8a0785f6ab7cb6f7e94269a81.tar.gz gnupg-334f5d95c825f5c8a0785f6ab7cb6f7e94269a81.zip |
Merge branch 'STABLE-BRANCH-2-4' into master
Diffstat (limited to 'sm')
-rw-r--r-- | sm/Makefile.am | 4 | ||||
-rw-r--r-- | sm/call-dirmngr.c | 45 | ||||
-rw-r--r-- | sm/decrypt.c | 4 | ||||
-rw-r--r-- | sm/encrypt.c | 6 | ||||
-rw-r--r-- | sm/gpgsm.c | 8 | ||||
-rw-r--r-- | sm/gpgsm.h | 6 | ||||
-rw-r--r-- | sm/minip12.c | 2136 | ||||
-rw-r--r-- | sm/minip12.h | 2 | ||||
-rw-r--r-- | sm/server.c | 52 | ||||
-rw-r--r-- | sm/sign.c | 6 | ||||
-rw-r--r-- | sm/t-minip12.c | 714 | ||||
-rw-r--r-- | sm/verify.c | 4 |
12 files changed, 2094 insertions, 893 deletions
diff --git a/sm/Makefile.am b/sm/Makefile.am index 03de7026a..ee728e851 100644 --- a/sm/Makefile.am +++ b/sm/Makefile.am @@ -77,8 +77,8 @@ gpgsm_LDFLAGS = gpgsm_DEPENDENCIES = $(resource_objs) -module_tests = -module_maint_tests = t-minip12 +module_tests = t-minip12 +module_maint_tests = t_common_src = t_common_ldadd = $(libcommon) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) \ diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index 86beeedc1..7fe7a68f5 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -1001,16 +1001,17 @@ static gpg_error_t run_command_inq_cb (void *opaque, const char *line) { struct run_command_parm_s *parm = opaque; + gpg_error_t err; const char *s; int rc = 0; + ksba_cert_t cert = NULL; + ksba_sexp_t ski = NULL; + const unsigned char *der; + size_t derlen, n; if ((s = has_leading_keyword (line, "SENDCERT"))) - { /* send the given certificate */ - int err; - ksba_cert_t cert; - const unsigned char *der; - size_t derlen; - + { + /* Send the given certificate. */ line = s; if (!*line) return gpg_error (GPG_ERR_ASS_PARAMETER); @@ -1029,11 +1030,36 @@ run_command_inq_cb (void *opaque, const char *line) rc = gpg_error (GPG_ERR_INV_CERT_OBJ); else rc = assuan_send_data (parm->ctx, der, derlen); - ksba_cert_release (cert); + } + } + else if ((s = has_leading_keyword (line, "SENDCERT_SKI"))) + { + /* Send a certificate where a sourceKeyIdentifier is included. */ + line = s; + ski = make_simple_sexp_from_hexstr (line, &n); + line += n; + while (*line == ' ') + line++; + + err = gpgsm_find_cert (parm->ctrl, line, ski, &cert, + FIND_CERT_ALLOW_AMBIG|FIND_CERT_WITH_EPHEM); + if (err) + { + log_error ("certificate not found: %s\n", gpg_strerror (err)); + rc = gpg_error (GPG_ERR_NOT_FOUND); + } + else + { + der = ksba_cert_get_image (cert, &derlen); + if (!der) + rc = gpg_error (GPG_ERR_INV_CERT_OBJ); + else + rc = assuan_send_data (parm->ctx, der, derlen); } } else if ((s = has_leading_keyword (line, "PRINTINFO"))) - { /* Simply show the message given in the argument. */ + { + /* Simply show the message given in the argument. */ line = s; log_info ("dirmngr: %s\n", line); } @@ -1043,7 +1069,6 @@ run_command_inq_cb (void *opaque, const char *line) root certificate. */ char fpr[41]; struct rootca_flags_s rootca_flags; - int n; line = s; @@ -1067,6 +1092,8 @@ run_command_inq_cb (void *opaque, const char *line) rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } + ksba_cert_release (cert); + xfree (ski); return rc; } diff --git a/sm/decrypt.c b/sm/decrypt.c index 68b362b45..62983fe9c 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -1107,6 +1107,10 @@ gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp) goto leave; } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + if (ctrl->input_size_hint) + gnupg_ksba_set_total (b64writer, ctrl->input_size_hint); + rc = ksba_cms_new (&cms); if (rc) goto leave; diff --git a/sm/encrypt.c b/sm/encrypt.c index 4fd4f93b9..6e78a0620 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -653,6 +653,10 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) goto leave; } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + if (ctrl->input_size_hint) + gnupg_ksba_set_total (b64writer, ctrl->input_size_hint); + err = ksba_cms_new (&cms); if (err) { @@ -828,7 +832,7 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) 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; } diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 07c3ff480..ce977413d 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -139,6 +139,7 @@ enum cmd_and_opt_values { oAssumeArmor, oAssumeBase64, oAssumeBinary, + oInputSizeHint, oBase64, oNoArmor, @@ -326,6 +327,7 @@ static gpgrt_opt_t opts[] = { N_("assume input is in base-64 format")), ARGPARSE_s_n (oAssumeBinary, "assume-binary", N_("assume input is in binary format")), + ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"), ARGPARSE_header ("Output", N_("Options controlling the output")), @@ -802,7 +804,7 @@ set_debug (void) /* minip12.c may be used outside of GnuPG, thus we don't have the * opt structure over there. */ - p12_set_verbosity (opt.verbose); + p12_set_verbosity (opt.verbose, opt.debug); } @@ -1188,6 +1190,10 @@ main ( int argc, char **argv) ctrl.is_base64 = 0; break; + case oInputSizeHint: + ctrl.input_size_hint = string_to_u64 (pargs.r.ret_str); + break; + case oDisableCRLChecks: opt.no_crl_check = 1; break; diff --git a/sm/gpgsm.h b/sm/gpgsm.h index cef39ff2a..e1aca8bb7 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -250,6 +250,11 @@ struct server_control_s int is_pem; /* Is in PEM format */ int is_base64; /* is in plain base-64 format */ + /* If > 0 a hint with the expected number of input data bytes. This + * is not necessary an exact number but intended to be used for + * progress info and to decide on how to allocate buffers. */ + uint64_t input_size_hint; + int create_base64; /* Create base64 encoded output */ int create_pem; /* create PEM output */ const char *pem_name; /* PEM name to use */ @@ -316,6 +321,7 @@ 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); diff --git a/sm/minip12.c b/sm/minip12.c index 29b48984e..265243f3e 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -1,7 +1,7 @@ /* minip12.c - A minimal pkcs-12 implementation. * Copyright (C) 2002, 2003, 2004, 2006, 2011 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch - * Copyright (C) 2022 g10 Code GmbH + * Copyright (C) 2022, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -78,6 +78,8 @@ static unsigned char const oid_pkcs5PBES2[9] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D }; static unsigned char const oid_aes128_CBC[9] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02 }; +static unsigned char const oid_aes256_CBC[9] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2A }; static unsigned char const oid_rsaEncryption[9] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; @@ -138,11 +140,50 @@ struct tag_info int class; int is_constructed; unsigned long tag; - unsigned long length; /* length part of the TLV */ - int nhdr; + size_t length; /* length part of the TLV */ + size_t nhdr; int ndef; /* It is an indefinite length */ }; + +#define TLV_MAX_DEPTH 20 + + +struct bufferlist_s +{ + struct bufferlist_s *next; + char *buffer; +}; + + +/* An object to control the ASN.1 parsing. */ +struct tlv_ctx_s +{ + /* The current buffer we are working on and its length. */ + const unsigned char *buffer; + size_t bufsize; + + size_t offset; /* The current offset into this buffer. */ + int in_ndef; /* Flag indicating that we are in a NDEF. */ + int pending; /* The last tlv_next has not yet been processed. */ + + struct tag_info ti; /* The current tag. */ + gpg_error_t lasterr; /* Last error from tlv function. */ + const char *lastfunc;/* Name of last called function. */ + + struct bufferlist_s *bufferlist; /* To keep track of amlloced buffers. */ + + unsigned int pop_count;/* Number of pops by tlv_next. */ + unsigned int stacklen; /* Used size of the stack. */ + struct { + const unsigned char *buffer; /* Saved value of BUFFER. */ + size_t bufsize; /* Saved value of BUFSIZE. */ + size_t offset; /* Saved value of OFFSET. */ + int in_ndef; /* Saved IN_NDEF flag. */ + } stack[TLV_MAX_DEPTH]; +}; + + /* Parser communication object. */ struct p12_parse_ctx_s { @@ -167,21 +208,31 @@ struct p12_parse_ctx_s static int opt_verbose; +static unsigned char *cram_octet_string (const unsigned char *input, + size_t length, size_t *r_newlength); + + + + void -p12_set_verbosity (int verbose) +p12_set_verbosity (int verbose, int debug) { - opt_verbose = verbose; + opt_verbose = !!verbose; + if (debug) + opt_verbose = 2; } -/* static void */ -/* dump_tag_info (struct tag_info *ti) */ -/* { */ -/* log_debug ("p12_parse: ti.class=%d tag=%lu len=%lu nhdr=%d %s%s\n", */ -/* ti->class, ti->tag, ti->length, ti->nhdr, */ -/* ti->is_constructed?" cons":"", */ -/* ti->ndef?" ndef":""); */ -/* } */ +static void +dump_tag_info (const char *text, struct tag_info *ti) +{ + if (opt_verbose > 1) + log_debug ("p12_parse(%s): ti.class=%d tag=%lu len=%zu nhdr=%zu %s%s\n", + text, + ti->class, ti->tag, ti->length, ti->nhdr, + ti->is_constructed?" cons":"", + ti->ndef?" ndef":""); +} /* Wrapper around tlv_builder_add_ptr to add an OID. When we @@ -276,121 +327,507 @@ builder_add_mpi (tlv_builder_t tb, int class, int tag, gcry_mpi_t mpi, } + /* Parse the buffer at the address BUFFER which is of SIZE and return - the tag and the length part from the TLV triplet. Update BUFFER - and SIZE on success. Checks that the encoded length does not - exhaust the length of the provided buffer. */ + * the tag and the length part from the TLV triplet. Update BUFFER + * and SIZE on success. Checks that the encoded length does not + * exhaust the length of the provided buffer. */ static int parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) { - int c; - unsigned long tag; - const unsigned char *buf = *buffer; - size_t length = *size; + gpg_error_t err; + int tag; + + err = parse_ber_header (buffer, size, + &ti->class, &tag, + &ti->is_constructed, &ti->ndef, + &ti->length, &ti->nhdr); + if (err) + return err; + if (tag < 0) + return gpg_error (GPG_ERR_EOVERFLOW); + ti->tag = tag; + + if (ti->length > *size) + return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); /* data larger than buffer. */ + + return 0; +} + + +/* Create a new TLV object. */ +static struct tlv_ctx_s * +tlv_new (const unsigned char *buffer, size_t bufsize) +{ + struct tlv_ctx_s *tlv; + tlv = xtrycalloc (1, sizeof *tlv); + if (tlv) + { + tlv->buffer = buffer; + tlv->bufsize = bufsize; + } + return tlv; +} - ti->length = 0; - ti->ndef = 0; - ti->nhdr = 0; - /* Get the tag */ - if (!length) - return -1; /* premature eof */ - c = *buf++; length--; - ti->nhdr++; +/* This function can be used to store a malloced buffer into the TLV + * object. Ownership of BUFFER is thus transferred to TLV. This + * buffer will then only be released by tlv_release. */ +static gpg_error_t +tlv_register_buffer (struct tlv_ctx_s *tlv, char *buffer) +{ + struct bufferlist_s *item; + + item = xtrycalloc (1, sizeof *item); + if (!item) + return gpg_error_from_syserror (); + item->buffer = buffer; + item->next = tlv->bufferlist; + tlv->bufferlist = item; + return 0; +} - ti->class = (c & 0xc0) >> 6; - ti->is_constructed = !!(c & 0x20); - tag = c & 0x1f; - if (tag == 0x1f) +static void +tlv_release (struct tlv_ctx_s *tlv) +{ + if (!tlv) + return; + while (tlv->bufferlist) { - tag = 0; + struct bufferlist_s *save = tlv->bufferlist->next; + xfree (tlv->bufferlist->buffer); + xfree (tlv->bufferlist); + tlv->bufferlist = save; + } + xfree (tlv); +} + + +/* Helper for tlv_next and tlv_peek. */ +static gpg_error_t +_tlv_peek (struct tlv_ctx_s *tlv, size_t *r_n) +{ + const unsigned char *p; + + if (tlv->offset > tlv->bufsize) + return gpg_error (GPG_ERR_BUG); + p = tlv->buffer + tlv->offset; + *r_n = tlv->bufsize - tlv->offset; + return parse_tag (&p, r_n, &tlv->ti); +} + + +/* Helper for tlv_expect_sequence and tlv_expect_context_tag. */ +static gpg_error_t +_tlv_push (struct tlv_ctx_s *tlv) +{ + if (tlv->stacklen >= TLV_MAX_DEPTH) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_MANY)); + tlv->stack[tlv->stacklen].buffer = tlv->buffer; + tlv->stack[tlv->stacklen].bufsize = tlv->bufsize; + tlv->stack[tlv->stacklen].offset = tlv->offset; + tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef; + tlv->stacklen++; + tlv->buffer += tlv->offset; + tlv->bufsize = tlv->ti.length; + tlv->offset = 0; + tlv->in_ndef = tlv->ti.ndef; + return 0; +} + + +/* Helper for tlv_next. */ +static gpg_error_t +_tlv_pop (struct tlv_ctx_s *tlv) +{ + size_t saveoff; + + if (!tlv->stacklen) + return gpg_error (GPG_ERR_EOF); + + saveoff = tlv->offset; + + tlv->stacklen--; + tlv->buffer = tlv->stack[tlv->stacklen].buffer; + tlv->bufsize = tlv->stack[tlv->stacklen].bufsize; + tlv->offset = tlv->stack[tlv->stacklen].offset; + tlv->in_ndef = tlv->stack[tlv->stacklen].in_ndef; + + /* Move offset of the container to the end of the container. */ + tlv->offset += saveoff; + if (tlv->offset > tlv->bufsize) + return gpg_error (GPG_ERR_INV_BER); + + tlv->pop_count++; + return 0; +} + + +/* Parse the next tag and value. Also detect the end of a container; + * tlv_popped() can be used to detect this. */ +static gpg_error_t +tlv_next (struct tlv_ctx_s *tlv) +{ + gpg_error_t err; + size_t n; + + tlv->pop_count = 0; + tlv->lasterr = 0; + tlv->lastfunc = __func__; + if (tlv->pending) + { + tlv->pending = 0; + return 0; + } + + if (!tlv->in_ndef && tlv->offset == tlv->bufsize) + { + /* We are at the end of a container. Pop the stack. */ do + err = _tlv_pop (tlv); + while (!err && !tlv->in_ndef && tlv->offset == tlv->bufsize); + if (err) + return (tlv->lasterr = err); + } + + err = _tlv_peek (tlv, &n); + if (err) + return err; + if (tlv->in_ndef && (tlv->ti.class == CLASS_UNIVERSAL + && !tlv->ti.tag && !tlv->ti.is_constructed)) + { + /* End tag while in ndef container. Skip the tag, and pop. */ + tlv->offset += n - (tlv->bufsize - tlv->offset); + err = _tlv_pop (tlv); + /* FIXME: We need to peek whether there is another end tag and + * pop again. We can't modify the TLV object, though. */ + if (err) + return (tlv->lasterr = err); + } + + /* Set offset to the value of the TLV. */ + tlv->offset += tlv->bufsize - tlv->offset - n; + dump_tag_info ("tlv_next", &tlv->ti); + return 0; +} + + +/* Return the current neting level of the TLV object. */ +static unsigned int +tlv_level (struct tlv_ctx_s *tlv) +{ + return tlv->stacklen; +} + + +/* If called right after tlv_next the number of container levels + * popped are returned. */ +static unsigned int +tlv_popped (struct tlv_ctx_s *tlv) +{ + return tlv->pop_count; +} + + +/* Set a flag to indicate that the last tlv_next has not yet been + * consumed. */ +static void +tlv_set_pending (struct tlv_ctx_s *tlv) +{ + tlv->pending = 1; +} + + +/* Skip over the value of the current tag. */ +static void +tlv_skip (struct tlv_ctx_s *tlv) +{ + tlv->lastfunc = __func__; + tlv->offset += tlv->ti.length; +} + + +/* Expect that the current tag is a sequence and setup the context for + * processing. */ +static gpg_error_t +tlv_expect_sequence (struct tlv_ctx_s *tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE + && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + return _tlv_push (tlv); +} + +/* Variant of tlv_expect_sequence to be used for the ouyter sequence + * of an object which might have padding after the ASN.1 data. */ +static gpg_error_t +tlv_expect_top_sequence (struct tlv_ctx_s *tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE + && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + tlv->bufsize = tlv->ti.nhdr + tlv->ti.length; + return _tlv_push (tlv); +} + + +/* Expect that the current tag is a context tag and setup the context + * for processing. The tag of the context is returned at R_TAG. */ +static gpg_error_t +tlv_expect_context_tag (struct tlv_ctx_s *tlv, int *r_tag) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_CONTEXT && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + *r_tag = tlv->ti.tag; + return _tlv_push (tlv); +} + + +/* Expect that the current tag is a SET and setup the context for + * processing. */ +static gpg_error_t +tlv_expect_set (struct tlv_ctx_s *tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SET + && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + return _tlv_push (tlv); +} + + +/* Expect an object of CLASS with TAG and store its value at + * (R_DATA,R_DATALEN). Then skip over its value to the next tag. + * Note that the stored value is not allocated but points into + * TLV. */ +static gpg_error_t +tlv_expect_object (struct tlv_ctx_s *tlv, int class, int tag, + unsigned char const **r_data, size_t *r_datalen) +{ + gpg_error_t err; + const unsigned char *p; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == class && tlv->ti.tag == tag)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer + tlv->offset; + if (!tlv->ti.length) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (class == CLASS_CONTEXT && tag == 0 && tlv->ti.is_constructed) + { + char *newbuffer; + + newbuffer = cram_octet_string (p, tlv->ti.length, r_datalen); + if (!newbuffer) + return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); + err = tlv_register_buffer (tlv, newbuffer); + if (err) { - tag <<= 7; - if (!length) - return -1; /* premature eof */ - c = *buf++; length--; - ti->nhdr++; - tag |= c & 0x7f; + xfree (newbuffer); + return (tlv->lasterr = err); } - while (c & 0x80); + *r_data = newbuffer; } - ti->tag = tag; - - /* Get the length */ - if (!length) - return -1; /* prematureeof */ - c = *buf++; length--; - ti->nhdr++; - - if ( !(c & 0x80) ) - ti->length = c; - else if (c == 0x80) - ti->ndef = 1; - else if (c == 0xff) - return -1; /* forbidden length value */ else { - unsigned long len = 0; - int count = c & 0x7f; + *r_data = p; + *r_datalen = tlv->ti.length; + } - for (; count; count--) + tlv->offset += tlv->ti.length; + return 0; +} + + +/* Expect that the current tag is an object string and store its value + * at (R_DATA,R_DATALEN). Then skip over its value to the next tag. + * Note that the stored value are not allocated but point into TLV. + * If ENCAPSULATES is set the octet string is used as a new + * container. R_DATA and R_DATALEN are optional. */ +static gpg_error_t +tlv_expect_octet_string (struct tlv_ctx_s *tlv, int encapsulates, + unsigned char const **r_data, size_t *r_datalen) +{ + gpg_error_t err; + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING + && (!tlv->ti.is_constructed || encapsulates))) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer + tlv->offset; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (encapsulates && tlv->ti.is_constructed) + { + char *newbuffer; + + newbuffer = cram_octet_string (p, n, r_datalen); + if (!newbuffer) + return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); + err = tlv_register_buffer (tlv, newbuffer); + if (err) { - len <<= 8; - if (!length) - return -1; /* premature_eof */ - c = *buf++; length--; - ti->nhdr++; - len |= c & 0xff; + xfree (newbuffer); + return (tlv->lasterr = err); } - ti->length = len; + *r_data = newbuffer; } + else + { + if (r_data) + *r_data = p; + if (r_datalen) + *r_datalen = tlv->ti.length; + } + if (encapsulates) + return _tlv_push (tlv); - if (ti->class == CLASS_UNIVERSAL && !ti->tag) - ti->length = 0; + tlv->offset += tlv->ti.length; + return 0; +} - if (ti->length > length) - return -1; /* data larger than buffer. */ - *buffer = buf; - *size = length; +/* Expect a NULL tag. */ +static gpg_error_t +tlv_expect_null (struct tlv_ctx_s *tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_NULL + && !tlv->ti.is_constructed && !tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); return 0; } -/* Given an ASN.1 chunk of a structure like: +/* Expect that the current tag is an integer and return its value at + * R_VALUE. Then skip over its value to the next tag. */ +static gpg_error_t +tlv_expect_integer (struct tlv_ctx_s *tlv, int *r_value) +{ + const unsigned char *p; + size_t n; + int value; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer + tlv->offset; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + /* We currently support only positive values. */ + if ((*p & 0x80)) + return (tlv->lasterr = gpg_error (GPG_ERR_ERANGE)); + + for (value = 0; n; n--) + { + value <<= 8; + value |= (*p++) & 0xff; + if (value < 0) + return (tlv->lasterr = gpg_error (GPG_ERR_EOVERFLOW)); + } + *r_value = value; + tlv->offset += tlv->ti.length; + return 0; +} + + +/* Variant of tlv_expect_integer which returns an MPI. If IGNORE_ZERO + * is set a value of 0 is ignored and R_VALUE not changed and the + * function returns GPG_ERR_FALSE. No check for negative encoded + * integers is doe because the old code here worked the same and we + * can't foreclose invalid encoded PKCS#12 stuff - after all it is + * PKCS#12 see https://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html */ +static gpg_error_t +tlv_expect_mpinteger (struct tlv_ctx_s *tlv, int ignore_zero, + gcry_mpi_t *r_value) +{ + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer + tlv->offset; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + tlv->offset += tlv->ti.length; + if (ignore_zero && n == 1 && !*p) + return gpg_error (GPG_ERR_FALSE); + + return gcry_mpi_scan (r_value, GCRYMPI_FMT_USG, p, n, NULL); +} + - 24 NDEF: OCTET STRING -- This is not passed to us - 04 1: OCTET STRING -- INPUT point s to here - : 30 - 04 1: OCTET STRING - : 80 - [...] - 04 2: OCTET STRING - : 00 00 - : } -- This denotes a Null tag and are the last - -- two bytes in INPUT. - - Create a new buffer with the content of that octet string. INPUT - is the original buffer with a length as stored at LENGTH. Returns - NULL on error or a new malloced buffer with the length of this new - buffer stored at LENGTH and the number of bytes parsed from input - are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is - allowed to be passed as NULL if the caller is not interested in - this value. */ +/* Expect that the current tag is an object id and store its value at + * (R_OID,R_OIDLEN). Then skip over its value to the next tag. Note + * that the stored value is not allocated but points into TLV. */ +static gpg_error_t +tlv_expect_object_id (struct tlv_ctx_s *tlv, + unsigned char const **r_oid, size_t *r_oidlen) +{ + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OBJECT_ID + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer + tlv->offset; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + *r_oid = p; + *r_oidlen = tlv->ti.length; + tlv->offset += tlv->ti.length; + return 0; +} + + + +/* Given an ASN.1 chunk of a structure like: + * + * 24 NDEF: OCTET STRING -- This is not passed to us + * 04 1: OCTET STRING -- INPUT point s to here + * : 30 + * 04 1: OCTET STRING + * : 80 + * [...] + * 04 2: OCTET STRING + * : 00 00 + * : } -- This denotes a Null tag and are the last + * -- two bytes in INPUT. + * + * The example is from Mozilla Firefox 1.0.4 which actually exports + * certs as single byte chunks of octet strings. + * + * Create a new buffer with the content of that octet string. INPUT + * is the original buffer with a LENGTH. Returns + * NULL on error or a new malloced buffer with its actual used length + * stored at R_NEWLENGTH. */ static unsigned char * -cram_octet_string (const unsigned char *input, size_t *length, - size_t *input_consumed) +cram_octet_string (const unsigned char *input, size_t length, + size_t *r_newlength) { const unsigned char *s = input; - size_t n = *length; + size_t n = length; unsigned char *output, *d; struct tag_info ti; /* Allocate output buf. We know that it won't be longer than the input buffer. */ - d = output = gcry_malloc (n); + d = output = gcry_malloc (length); if (!output) goto bailout; @@ -413,20 +850,15 @@ cram_octet_string (const unsigned char *input, size_t *length, } - *length = d - output; - if (input_consumed) - *input_consumed += s - input; + *r_newlength = d - output; return output; bailout: - if (input_consumed) - *input_consumed += s - input; gcry_free (output); return NULL; } - static int string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, int req_keylen, unsigned char *keybuf) @@ -614,7 +1046,7 @@ crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, return; } - if (cipher_algo == GCRY_CIPHER_AES128 + if ((cipher_algo == GCRY_CIPHER_AES128 || cipher_algo == GCRY_CIPHER_AES256) ? set_key_iv_pbes2 (chd, salt, saltlen, iter, iv, ivlen, pw, cipher_algo) : set_key_iv (chd, salt, saltlen, iter, pw, cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24)) @@ -761,482 +1193,477 @@ bag_decrypted_data_p (const void *plaintext, size_t length) static int -parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, - const unsigned char *buffer, size_t length, - int startoffset, size_t *r_consumed) +parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) { - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; + gpg_error_t err = 0; const char *where; + const unsigned char *oid; + size_t oidlen; + const unsigned char *data; + size_t datalen; + int intval; char salt[20]; size_t saltlen; char iv[16]; unsigned int iter; unsigned char *plain = NULL; - unsigned char *cram_buffer = NULL; - size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ int is_3des = 0; int is_pbes2 = 0; + int is_aes256 = 0; int keyelem_count; + int renewed_tlv = 0; + int loopcount; + unsigned int startlevel; - where = "start"; - if (parse_tag (&p, &n, &ti)) + where = "bag.encryptedData"; + if (opt_verbose) + log_info ("processing %s\n", where); + + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval) || intval != 0 ) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; where = "bag.encryptedData.version"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 0) + if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - p++; n--; - if (parse_tag (&p, &n, &ti)) + if (intval) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; + } + + if (tlv_next (tlv)) goto bailout; - if (ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; where = "bag.encryptedData.data"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) - || memcmp (p, oid_data, DIM(oid_data))) + if (oidlen != DIM(oid_data) || memcmp (oid, oid_data, DIM(oid_data))) goto bailout; - p += DIM(oid_data); - n -= DIM(oid_data); where = "bag.encryptedData.keyinfo"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if (!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pbeWithSHAAnd40BitRC2_CBC) - && !memcmp (p, oid_pbeWithSHAAnd40BitRC2_CBC, + if (oidlen == DIM(oid_pbeWithSHAAnd40BitRC2_CBC) + && !memcmp (oid, oid_pbeWithSHAAnd40BitRC2_CBC, DIM(oid_pbeWithSHAAnd40BitRC2_CBC))) + ; + else if (oidlen == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) + && !memcmp (oid, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, + DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) + is_3des = 1; + else if (oidlen == DIM(oid_pkcs5PBES2) + && !memcmp (oid, oid_pkcs5PBES2, oidlen)) + is_pbes2 = 1; + else { - p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC); - n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC); - } - else if (!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) - && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, - DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) - { - p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - is_3des = 1; - } - else if (!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pkcs5PBES2) - && !memcmp (p, oid_pkcs5PBES2, ti.length)) - { - p += ti.length; - n -= ti.length; - is_pbes2 = 1; + err = gpg_error (GPG_ERR_UNKNOWN_ALGORITHM); + goto bailout; } - else - goto bailout; + /*FIXME: This code is duplicated in parse_shrouded_key_bag. */ if (is_pbes2) { where = "pkcs5PBES2-params"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pkcs5PBKDF2) - && !memcmp (p, oid_pkcs5PBKDF2, ti.length))) - goto bailout; /* Not PBKDF2. */ - p += ti.length; - n -= ti.length; - if (parse_tag (&p, &n, &ti)) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (oidlen != DIM(oid_pkcs5PBKDF2) + || memcmp (oid, oid_pkcs5PBKDF2, oidlen)) + { + err = gpg_error (GPG_ERR_INV_BER); /* Not PBKDF2. */ + goto bailout; + } + + if (tlv_next (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + if (tlv_expect_sequence (tlv)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OCTET_STRING - && ti.length >= 8 && ti.length < sizeof salt)) - goto bailout; /* No salt or unsupported length. */ - saltlen = ti.length; - memcpy (salt, p, saltlen); - p += saltlen; - n -= saltlen; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) + goto bailout; + if (datalen < 8 || datalen > sizeof salt) + { + log_info ("bad length of salt (%zu)\n", datalen); + err = gpg_error (GPG_ERR_INV_LENGTH); + goto bailout; + } + saltlen = datalen; + memcpy (salt, data, saltlen); + + if (tlv_next (tlv)) + goto bailout; + if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - if (!(!ti.class && ti.tag == TAG_INTEGER && ti.length)) - goto bailout; /* No valid iteration count. */ - for (iter=0; ti.length; ti.length--) + if (!intval) /* Not a valid iteration count. */ { - iter <<= 8; - iter |= (*p++) & 0xff; - n--; + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; } + iter = intval; + /* Note: We don't support the optional parameters but assume that the algorithmIdentifier follows. */ - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_aes128_CBC) - && !memcmp (p, oid_aes128_CBC, ti.length))) - goto bailout; /* Not AES-128. */ - p += ti.length; - n -= ti.length; - if (parse_tag (&p, &n, &ti)) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OCTET_STRING && ti.length == sizeof iv)) - goto bailout; /* Bad IV. */ - memcpy (iv, p, sizeof iv); - p += sizeof iv; - n -= sizeof iv; + + if (oidlen == DIM(oid_aes128_CBC) + && !memcmp (oid, oid_aes128_CBC, oidlen)) + ; + else if (oidlen == DIM(oid_aes256_CBC) + && !memcmp (oid, oid_aes256_CBC, oidlen)) + is_aes256 = 1; + else + { + gpgrt_log_printhex (oid, oidlen, "cipher algo:"); + err = gpg_error (GPG_ERR_CIPHER_ALGO); + goto bailout; + } + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) + goto bailout; + if (datalen != sizeof iv) + { + err = gpg_error (GPG_ERR_INV_LENGTH); + goto bailout; /* Bad IV. */ + } + memcpy (iv, data, datalen); } else { where = "rc2or3des-params"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING - || ti.length < 8 || ti.length > 20 ) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - saltlen = ti.length; - memcpy (salt, p, saltlen); - p += saltlen; - n -= saltlen; - if (parse_tag (&p, &n, &ti)) + if (datalen < 8 || datalen > 20) + { + log_info ("bad length of salt (%zu) for 3DES\n", datalen); + err = gpg_error (GPG_ERR_INV_LENGTH); + goto bailout; + } + saltlen = datalen; + memcpy (salt, data, saltlen); + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) + if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - for (iter=0; ti.length; ti.length--) + if (!intval) { - iter <<= 8; - iter |= (*p++) & 0xff; - n--; + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; } + iter = intval; } where = "rc2or3desoraes-ciphertext"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - consumed = p - p_start; - if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) - { - /* Mozilla exported certs now come with single byte chunks of - octet strings. (Mozilla Firefox 1.0.4). Arghh. */ - where = "cram-rc2or3des-ciphertext"; - cram_buffer = cram_octet_string ( p, &n, &consumed); - if (!cram_buffer) - goto bailout; - p = p_start = cram_buffer; - if (r_consumed) - *r_consumed = consumed; - r_consumed = NULL; /* Donot update that value on return. */ - ti.length = n; - } - else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed) - { - where = "octets-rc2or3des-ciphertext"; - n = ti.length; - cram_buffer = cram_octet_string ( p, &n, &consumed); - if (!cram_buffer) - goto bailout; - p = p_start = cram_buffer; - if (r_consumed) - *r_consumed = consumed; - r_consumed = NULL; /* Do not update that value on return. */ - ti.length = n; - } - else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.length ) - ; - else + if (tlv_expect_object (tlv, CLASS_CONTEXT, 0, &data, &datalen)) goto bailout; if (opt_verbose) - log_info ("%lu bytes of %s encrypted text\n",ti.length, - is_pbes2?"AES128":is_3des?"3DES":"RC2"); + log_info ("%zu bytes of %s encrypted text\n", datalen, + is_pbes2?(is_aes256?"AES256":"AES128"):is_3des?"3DES":"RC2"); - plain = gcry_malloc_secure (ti.length); + plain = gcry_malloc_secure (datalen); if (!plain) { + err = gpg_error_from_syserror (); log_error ("error allocating decryption buffer\n"); goto bailout; } - decrypt_block (p, plain, ti.length, salt, saltlen, iter, + decrypt_block (data, plain, datalen, salt, saltlen, iter, iv, is_pbes2?16:0, ctx->password, - is_pbes2 ? GCRY_CIPHER_AES128 : + is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128) : is_3des ? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, bag_decrypted_data_p); - n = ti.length; - startoffset = 0; - p_start = p = plain; - where = "outer.outer.seq"; - if (parse_tag (&p, &n, &ti)) + /* We do not need the TLV anymore and allocated a new one. */ + where = "bag.encryptedData.decrypted-text"; + tlv = tlv_new (plain, datalen); + if (!tlv) { - ctx->badpass = 1; + err = gpg_error_from_syserror (); goto bailout; } - if (ti.class || ti.tag != TAG_SEQUENCE) + renewed_tlv = 1; + + if (tlv_next (tlv)) { ctx->badpass = 1; goto bailout; } - - if (parse_tag (&p, &n, &ti)) + if (tlv_expect_top_sequence (tlv)) { ctx->badpass = 1; goto bailout; } /* Loop over all certificates inside the bag. */ - while (n) + loopcount = 0; + startlevel = tlv_level (tlv); + while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) { int iscrlbag = 0; int iskeybag = 0; + loopcount++; where = "certbag.nextcert"; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - where = "certbag.objectidentifier"; - if (parse_tag (&p, &n, &ti)) + where = "certbag.oid"; + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if ( ti.length == DIM(oid_pkcs_12_CertBag) - && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) - { - p += DIM(oid_pkcs_12_CertBag); - n -= DIM(oid_pkcs_12_CertBag); - } - else if ( ti.length == DIM(oid_pkcs_12_CrlBag) - && !memcmp (p, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag))) - { - p += DIM(oid_pkcs_12_CrlBag); - n -= DIM(oid_pkcs_12_CrlBag); - iscrlbag = 1; - } - else if ( ti.length == DIM(oid_pkcs_12_keyBag) - && !memcmp (p, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag))) + if (oidlen == DIM(oid_pkcs_12_CertBag) + && !memcmp (oid, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) + ; + else if (oidlen == DIM(oid_pkcs_12_CrlBag) + && !memcmp (oid, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag))) + iscrlbag = 1; + else if (oidlen == DIM(oid_pkcs_12_keyBag) + && !memcmp (oid, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag))) { /* The TrustedMIME plugin for MS Outlook started to create files with just one outer 3DES encrypted container and inside the certificates as well as the key. */ - p += DIM(oid_pkcs_12_keyBag); - n -= DIM(oid_pkcs_12_keyBag); iskeybag = 1; } else - goto bailout; + { + gpgrt_log_printhex (oid, oidlen, "cert bag type OID:"); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto bailout; + } where = "certbag.before.certheader"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval) || intval != 0 ) goto bailout; + if (iscrlbag) { log_info ("skipping unsupported crlBag\n"); - p += ti.length; - n -= ti.length; } else if (iskeybag && ctx->privatekey) { log_info ("one keyBag already processed; skipping this one\n"); - p += ti.length; - n -= ti.length; } else if (iskeybag) { - int len; - if (opt_verbose) log_info ("processing simple keyBag\n"); - /* Fixme: This code is duplicated from parse_bag_data. */ - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_next (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER - || ti.length != 1 || *p) + if (tlv_expect_sequence (tlv)) goto bailout; - p++; n--; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + + if (tlv_next (tlv)) goto bailout; - len = ti.length; - if (parse_tag (&p, &n, &ti)) + if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - if (len < ti.nhdr) + if (intval) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; + } + + if (tlv_next (tlv)) goto bailout; - len -= ti.nhdr; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_rsaEncryption) - || memcmp (p, oid_rsaEncryption, - DIM(oid_rsaEncryption))) + if (tlv_expect_sequence (tlv)) goto bailout; - p += DIM (oid_rsaEncryption); - n -= DIM (oid_rsaEncryption); - if (len < ti.length) + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) + goto bailout; + if (oidlen != DIM(oid_rsaEncryption) + || memcmp (oid, oid_rsaEncryption, oidlen)) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto bailout; + } + + /* We ignore the next octet string. */ + if (tlv_next (tlv)) goto bailout; - len -= ti.length; - if (n < len) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - p += len; - n -= len; - if ( parse_tag (&p, &n, &ti) - || ti.class || ti.tag != TAG_OCTET_STRING) + + if (tlv_next (tlv)) goto bailout; - if ( parse_tag (&p, &n, &ti) - || ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - len = ti.length; - log_assert (!ctx->privatekey); + if (ctx->privatekey) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + log_error ("a private key has already been received\n"); + goto bailout; + } ctx->privatekey = gcry_calloc (10, sizeof *ctx->privatekey); if (!ctx->privatekey) { + err = gpg_error_from_syserror (); log_error ("error allocating private key element array\n"); goto bailout; } - keyelem_count = 0; where = "reading.keybag.key-parameters"; - for (keyelem_count = 0; len && keyelem_count < 9;) + keyelem_count = 0; + while (!(err = tlv_next (tlv)) && !tlv_popped (tlv)) { - if ( parse_tag (&p, &n, &ti) - || ti.class || ti.tag != TAG_INTEGER) - goto bailout; - if (len < ti.nhdr) - goto bailout; - len -= ti.nhdr; - if (len < ti.length) - goto bailout; - len -= ti.length; - if (!keyelem_count && ti.length == 1 && !*p) - ; /* ignore the very first one if it is a 0 */ - else + if (keyelem_count >= 9) { - int rc; - - rc = gcry_mpi_scan (ctx->privatekey+keyelem_count, - GCRYMPI_FMT_USG, p, - ti.length, NULL); - if (rc) - { - log_error ("error parsing key parameter: %s\n", - gpg_strerror (rc)); - goto bailout; - } - keyelem_count++; + err = gpg_error (GPG_ERR_TOO_MANY); + goto bailout; } - p += ti.length; - n -= ti.length; + + err = tlv_expect_mpinteger (tlv, !keyelem_count, + ctx->privatekey+keyelem_count); + if (!keyelem_count && gpg_err_code (err) == GPG_ERR_FALSE) + ; /* Ignore the first value iff it is zero. */ + else if (err) + { + log_error ("error parsing RSA key parameter %d: %s\n", + keyelem_count, gpg_strerror (err)); + goto bailout; + } + if (opt_verbose > 1) + log_debug ("RSA key parameter %d found\n", keyelem_count); + keyelem_count++; } - if (len) + if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; + err = 0; } else { if (opt_verbose) log_info ("processing certBag\n"); - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_x509Certificate_for_pkcs_12) - || memcmp (p, oid_x509Certificate_for_pkcs_12, - DIM(oid_x509Certificate_for_pkcs_12))) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - p += DIM(oid_x509Certificate_for_pkcs_12); - n -= DIM(oid_x509Certificate_for_pkcs_12); + if (oidlen != DIM(oid_x509Certificate_for_pkcs_12) + || memcmp (oid, oid_x509Certificate_for_pkcs_12, + DIM(oid_x509Certificate_for_pkcs_12))) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_CERT); + goto bailout; + } where = "certbag.before.octetstring"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval)) goto bailout; - if (parse_tag (&p, &n, &ti)) + if (intval) + { + err = gpg_error (GPG_ERR_BAD_BER); + goto bailout; + } + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; /* Return the certificate. */ if (ctx->certcb) - ctx->certcb (ctx->certcbarg, p, ti.length); - - p += ti.length; - n -= ti.length; + ctx->certcb (ctx->certcbarg, data, datalen); } - /* Ugly hack to cope with the padding: Forget about the rest if - that is less or equal to the cipher's block length. We can - reasonable assume that all valid data will be longer than - just one block. */ - if (n <= (is_pbes2? 16:8)) - n = 0; - /* Skip the optional SET with the pkcs12 cert attributes. */ - if (n) - { - where = "bag.attributes"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (!ti.class && ti.tag == TAG_SEQUENCE) - ; /* No attributes. */ - else if (!ti.class && ti.tag == TAG_SET && !ti.ndef) - { /* The optional SET. */ - p += ti.length; - n -= ti.length; - if (n <= (is_pbes2?16:8)) - n = 0; - if (n && parse_tag (&p, &n, &ti)) - goto bailout; - } - else - goto bailout; + where = "bag.attribute_set"; + err = tlv_next (tlv); + if (gpg_err_code (err) == GPG_ERR_EOF) + break; + if (err) + goto bailout; + err = tlv_expect_set (tlv); + if (!err) + { /* This is the optional set of attributes. Skip it. */ + tlv_skip (tlv); + if (opt_verbose) + log_info ("skipping bag.attribute_set\n"); } + else if (gpg_err_code (err) == GPG_ERR_INV_OBJ) + tlv_set_pending (tlv); /* The next tlv_next will be skipped. */ + else + goto bailout; } + if (err && gpg_err_code (err) != GPG_ERR_EOF) + { + if (!loopcount) /* The first while(tlv_next) failed. */ + ctx->badpass = 1; + goto bailout; + } + err = 0; - if (r_consumed) - *r_consumed = consumed; - gcry_free (plain); - gcry_free (cram_buffer); - return 0; - - bailout: - if (r_consumed) - *r_consumed = consumed; + leave: + if (renewed_tlv) + tlv_release (tlv); gcry_free (plain); - gcry_free (cram_buffer); - log_error ("encryptedData error at \"%s\", offset %u\n", - where, (unsigned int)((p - p_start)+startoffset)); if (ctx->badpass) { /* Note, that the following string might be used by other programs @@ -1244,7 +1671,19 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, translated or changed. */ log_error ("possibly bad passphrase given\n"); } - return -1; + return err; + + bailout: + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + __func__, where, + tlv? tlv->stacklen : 0, + tlv? tlv->offset : 0, + tlv? tlv->lastfunc : "", + tlv ? gpg_strerror (tlv->lasterr) : "init failed", + gpg_strerror (err)); + goto leave; } @@ -1276,363 +1715,437 @@ bag_data_p (const void *plaintext, size_t length) static gpg_error_t -parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, - const unsigned char *buffer, size_t length, - int startoffset, - size_t *r_consumed) +parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) { gpg_error_t err = 0; - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; const char *where; + const unsigned char *oid; + size_t oidlen; + const unsigned char *data; + size_t datalen; + int intval; char salt[20]; size_t saltlen; char iv[16]; unsigned int iter; - int len; + struct tlv_ctx_s *saved_tlv = NULL; + int renewed_tlv = 0; /* True if the TLV must be released. */ unsigned char *plain = NULL; - unsigned char *cram_buffer = NULL; - size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ int is_pbes2 = 0; - int keyelem_count = 0; + int is_aes256 = 0; where = "shrouded_key_bag"; - if (parse_tag (&p, &n, &ti)) + if (opt_verbose) + log_info ("processing %s\n", where); + + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval) || intval != 0 ) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + where = "shrouded_key_bag.cipherinfo"; + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class == 0 && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) - && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, + if (tlv_expect_object_id (tlv, &oid, &oidlen)) + goto bailout; + + if (oidlen == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) + && !memcmp (oid, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) + ; /* Standard cipher. */ + else if (oidlen == DIM(oid_pkcs5PBES2) + && !memcmp (oid, oid_pkcs5PBES2, DIM(oid_pkcs5PBES2))) + is_pbes2 = 1; + else { - p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - } - else if (ti.class == 0 && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pkcs5PBES2) - && !memcmp (p, oid_pkcs5PBES2, DIM(oid_pkcs5PBES2))) - { - p += DIM(oid_pkcs5PBES2); - n -= DIM(oid_pkcs5PBES2); - is_pbes2 = 1; + err = gpg_error (GPG_ERR_UNKNOWN_ALGORITHM); + goto bailout; } - else - goto bailout; if (is_pbes2) { where = "shrouded_key_bag.pkcs5PBES2-params"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pkcs5PBKDF2) - && !memcmp (p, oid_pkcs5PBKDF2, ti.length))) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) + goto bailout; + if (!(oidlen == DIM(oid_pkcs5PBKDF2) + && !memcmp (oid, oid_pkcs5PBKDF2, oidlen))) goto bailout; /* Not PBKDF2. */ - p += ti.length; - n -= ti.length; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OCTET_STRING - && ti.length >= 8 && ti.length < sizeof salt)) - goto bailout; /* No salt or unsupported length. */ - saltlen = ti.length; - memcpy (salt, p, saltlen); - p += saltlen; - n -= saltlen; + if (datalen < 8 || datalen > sizeof salt) + { + log_info ("bad length of salt (%zu) for AES\n", datalen); + err = gpg_error (GPG_ERR_INV_LENGTH); + goto bailout; + } + saltlen = datalen; + memcpy (salt, data, saltlen); - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (!(!ti.class && ti.tag == TAG_INTEGER && ti.length)) - goto bailout; /* No valid iteration count. */ - for (iter=0; ti.length; ti.length--) + if ((err = tlv_expect_integer (tlv, &intval))) + goto bailout; + if (!intval) /* Not a valid iteration count. */ { - iter <<= 8; - iter |= (*p++) & 0xff; - n--; + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; } + iter = intval; + /* Note: We don't support the optional parameters but assume that the algorithmIdentifier follows. */ - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) + goto bailout; + + if (tlv_next (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) + goto bailout; + if (oidlen == DIM(oid_aes128_CBC) + && !memcmp (oid, oid_aes128_CBC, oidlen)) + ; + else if (oidlen == DIM(oid_aes256_CBC) + && !memcmp (oid, oid_aes256_CBC, oidlen)) + is_aes256 = 1; + else + { + gpgrt_log_printhex (oid, oidlen, "cipher is:"); + err = gpg_error (GPG_ERR_CIPHER_ALGO); + goto bailout; + } + + if (tlv_next (tlv)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_aes128_CBC) - && !memcmp (p, oid_aes128_CBC, ti.length))) - goto bailout; /* Not AES-128. */ - p += ti.length; - n -= ti.length; - if (parse_tag (&p, &n, &ti)) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - if (!(!ti.class && ti.tag == TAG_OCTET_STRING && ti.length == sizeof iv)) + if (datalen != sizeof iv) goto bailout; /* Bad IV. */ - memcpy (iv, p, sizeof iv); - p += sizeof iv; - n -= sizeof iv; + memcpy (iv, data, datalen); } else { where = "shrouded_key_bag.3des-params"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING - || ti.length < 8 || ti.length > 20) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - saltlen = ti.length; - memcpy (salt, p, saltlen); - p += saltlen; - n -= saltlen; - if (parse_tag (&p, &n, &ti)) + if (datalen < 8 || datalen > 20) + { + log_info ("bad length of salt (%zu) for 3DES\n", datalen); + err = gpg_error (GPG_ERR_INV_LENGTH); + goto bailout; + } + saltlen = datalen; + memcpy (salt, data, saltlen); + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) + if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - for (iter=0; ti.length; ti.length--) + if (!intval) { - iter <<= 8; - iter |= (*p++) & 0xff; - n--; + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; } + iter = intval; } where = "shrouded_key_bag.3desoraes-ciphertext"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length ) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; if (opt_verbose) - log_info ("%lu bytes of %s encrypted text\n", - ti.length, is_pbes2? "AES128":"3DES"); + log_info ("%zu bytes of %s encrypted text\n", + datalen, is_pbes2? (is_aes256?"AES256":"AES128"):"3DES"); + + plain = gcry_malloc_secure (datalen); - plain = gcry_malloc_secure (ti.length); if (!plain) { + err = gpg_error_from_syserror (); log_error ("error allocating decryption buffer\n"); goto bailout; } - consumed += p - p_start + ti.length; - decrypt_block (p, plain, ti.length, salt, saltlen, iter, + decrypt_block (data, plain, datalen, salt, saltlen, iter, iv, is_pbes2? 16:0, ctx->password, - is_pbes2? GCRY_CIPHER_AES128 : GCRY_CIPHER_3DES, + is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128) + : GCRY_CIPHER_3DES, bag_data_p); - n = ti.length; - startoffset = 0; - p_start = p = plain; + + /* We do not need the TLV anymore and allocated a new one. */ where = "shrouded_key_bag.decrypted-text"; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER - || ti.length != 1 || *p) - goto bailout; - p++; n--; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + saved_tlv = tlv; + tlv = tlv_new (plain, datalen); + if (!tlv) + { + err = gpg_error_from_syserror (); + goto bailout; + } + renewed_tlv = 1; + if (opt_verbose > 1) + log_debug ("new parser context\n"); + + if (tlv_next (tlv)) + { + ctx->badpass = 1; + goto bailout; + } + if (tlv_expect_top_sequence (tlv)) + { + ctx->badpass = 1; + goto bailout; + } + + if (tlv_next (tlv)) + { + ctx->badpass = 1; + goto bailout; + } + if ((err = tlv_expect_integer (tlv, &intval))) + { + ctx->badpass = 1; + goto bailout; + } + if (intval) + { + ctx->badpass = 1; + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; + } + + if (tlv_next (tlv)) goto bailout; - len = ti.length; - if (parse_tag (&p, &n, &ti)) + if (tlv_expect_sequence (tlv)) goto bailout; - if (len < ti.nhdr) + + if (tlv_next (tlv)) goto bailout; - len -= ti.nhdr; - if (ti.class || ti.tag != TAG_OBJECT_ID) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - /* gpgrt_log_printhex (p, ti.length, "OID:"); */ - if (ti.length == DIM(oid_rsaEncryption) - && !memcmp (p, oid_rsaEncryption, DIM(oid_rsaEncryption))) + if (oidlen == DIM(oid_rsaEncryption) + && !memcmp (oid, oid_rsaEncryption, oidlen)) { - p += DIM (oid_rsaEncryption); - n -= DIM (oid_rsaEncryption); + if (opt_verbose > 1) + log_debug ("RSA parameters\n"); + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_null (tlv)) + tlv_set_pending (tlv); /* NULL tag missing - ignore this. */ } - else if (ti.length == DIM(oid_pcPublicKey) - && !memcmp (p, oid_pcPublicKey, DIM(oid_pcPublicKey))) + else if (oidlen == DIM(oid_pcPublicKey) + && !memcmp (oid, oid_pcPublicKey, oidlen)) { /* See RFC-5915 for the format. */ - p += DIM (oid_pcPublicKey); - n -= DIM (oid_pcPublicKey); - if (len < ti.length) - goto bailout; - len -= ti.length; - if (n < len) - goto bailout; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - /* gpgrt_log_debug ("ti=%d/%lu len=%lu\n",ti.class,ti.tag,ti.length); */ - if (len < ti.nhdr) - goto bailout; - len -= ti.nhdr; - if (ti.class || ti.tag != TAG_OBJECT_ID) + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; ksba_free (ctx->curve); - ctx->curve = ksba_oid_to_str (p, ti.length); + ctx->curve = ksba_oid_to_str (oid, oidlen); if (!ctx->curve) - goto bailout; - /* log_debug ("OID of curve is: %s\n", curve); */ - p += ti.length; - n -= ti.length; + { + err = gpg_error (GPG_ERR_INV_OID_STRING); + goto bailout; + } + if (opt_verbose > 1) + log_debug ("OID of curve is: %s\n", ctx->curve); } - else - goto bailout; - if (len < ti.length) + else /* Unknown key format */ + { + gpgrt_log_printhex (oid, oidlen, "key format OID:"); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto bailout; + } + + /* An octet string to encapsulate the key elements. */ + if (tlv_next (tlv)) goto bailout; - len -= ti.length; - if (n < len) + if (tlv_expect_octet_string (tlv, 1, &data, &datalen)) goto bailout; - p += len; - n -= len; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) + + if (tlv_next (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - len = ti.length; if (ctx->privatekey) { - log_error ("a key has already been received\n"); + err = gpg_error (GPG_ERR_DUP_VALUE); + log_error ("a private key has already been received\n"); goto bailout; } ctx->privatekey = gcry_calloc (10, sizeof *ctx->privatekey); if (!ctx->privatekey) { - + err = gpg_error_from_syserror (); log_error ("error allocating privatekey element array\n"); goto bailout; } - keyelem_count = 0; where = "shrouded_key_bag.reading.key-parameters"; if (ctx->curve) /* ECC case. */ { - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) + if (tlv_next (tlv)) goto bailout; - if (len < ti.nhdr) + if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - len -= ti.nhdr; - if (len < ti.length) - goto bailout; - len -= ti.length; - if (ti.length != 1 && *p != 1) + if (intval != 1) { + err = gpg_error (GPG_ERR_INV_VALUE); log_error ("error parsing private ecPublicKey parameter: %s\n", "bad version"); goto bailout; } - p += ti.length; - n -= ti.length; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) - goto bailout; - if (len < ti.nhdr) + + if (tlv_next (tlv)) goto bailout; - len -= ti.nhdr; - if (len < ti.length) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - len -= ti.length; - /* log_printhex (p, ti.length, "ecc q="); */ + if (opt_verbose > 1) + log_printhex (data, datalen, "ecc q="); err = gcry_mpi_scan (ctx->privatekey, GCRYMPI_FMT_USG, - p, ti.length, NULL); + data, datalen, NULL); if (err) { log_error ("error parsing key parameter: %s\n", gpg_strerror (err)); goto bailout; } - p += ti.length; - n -= ti.length; - - len = 0; /* Skip the rest. */ } else /* RSA case */ { - for (keyelem_count=0; len && keyelem_count < 9;) + int keyelem_count = 0; + int firstparam = 1; + + while (!(err = tlv_next (tlv)) && !tlv_popped (tlv)) { - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) - goto bailout; - if (len < ti.nhdr) - goto bailout; - len -= ti.nhdr; - if (len < ti.length) - goto bailout; - len -= ti.length; - if (!keyelem_count && ti.length == 1 && !*p) - ; /* ignore the very first one if it is a 0 */ + if (keyelem_count >= 9) + { + err = gpg_error (GPG_ERR_TOO_MANY); + goto bailout; + } + + err = tlv_expect_mpinteger (tlv, firstparam, + ctx->privatekey+keyelem_count); + if (firstparam && gpg_err_code (err) == GPG_ERR_FALSE) + ; /* Ignore the first value iff it is zero. */ + else if (err) + { + log_error ("error parsing RSA key parameter %d: %s\n", + keyelem_count, gpg_strerror (err)); + goto bailout; + } else { - err = gcry_mpi_scan (ctx->privatekey+keyelem_count, - GCRYMPI_FMT_USG, p, ti.length, NULL); - if (err) - { - log_error ("error parsing key parameter: %s\n", - gpg_strerror (err)); - goto bailout; - } + if (opt_verbose > 1) + log_debug ("RSA key parameter %d found\n", keyelem_count); keyelem_count++; } - p += ti.length; - n -= ti.length; + firstparam = 0; } + if (err && gpg_err_code (err) != GPG_ERR_EOF) + goto bailout; + err = 0; } - if (len) - goto bailout; - goto leave; + if (opt_verbose > 1) + log_debug ("restoring parser context\n"); + tlv_release (tlv); + renewed_tlv = 0; + tlv = saved_tlv; - bailout: - gcry_free (plain); - log_error ("data error at \"%s\", offset %zu\n", - where, (size_t)((p - p_start) + startoffset)); + where = "shrouded_key_bag.attribute_set"; + err = tlv_next (tlv); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto leave; + if (err) + goto bailout; + err = tlv_expect_set (tlv); if (!err) - err = gpg_error (GPG_ERR_GENERAL); + { /* This is the optional set of attributes. Skip it. */ + tlv_skip (tlv); + if (opt_verbose) + log_info ("skipping %s\n", where); + } + else if (gpg_err_code (err) == GPG_ERR_INV_OBJ) + tlv_set_pending (tlv); /* The next tlv_next will be skipped. */ + else /* Other error. */ + goto bailout; + leave: - gcry_free (cram_buffer); - if (r_consumed) - *r_consumed = consumed; + gcry_free (plain); + if (renewed_tlv) + { + tlv_release (tlv); + if (opt_verbose > 1) + log_debug ("parser context released\n"); + } return err; + + bailout: + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + __func__, where, + tlv? tlv->stacklen : 0, + tlv? tlv->offset : 0, + tlv? tlv->lastfunc : "", + tlv ? gpg_strerror (tlv->lasterr) : "init failed", + gpg_strerror (err)); + goto leave; } static gpg_error_t -parse_cert_bag (struct p12_parse_ctx_s *ctx, - const unsigned char *buffer, size_t length, - int startoffset, - size_t *r_consumed) +parse_cert_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) { gpg_error_t err = 0; - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; const char *where; - size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ + int intval; + const unsigned char *oid; + size_t oidlen; + const unsigned char *data; + size_t datalen; if (opt_verbose) log_info ("processing certBag\n"); @@ -1643,181 +2156,182 @@ parse_cert_bag (struct p12_parse_ctx_s *ctx, * OBJECT IDENTIFIER pkcs-12-certBag */ where = "certbag.before.certheader"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval)) goto bailout; - if (parse_tag (&p, &n, &ti)) + if (intval) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto bailout; + } + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_x509Certificate_for_pkcs_12) - || memcmp (p, oid_x509Certificate_for_pkcs_12, - DIM(oid_x509Certificate_for_pkcs_12))) + if (oidlen != DIM(oid_x509Certificate_for_pkcs_12) + || memcmp (oid, oid_x509Certificate_for_pkcs_12, oidlen)) goto bailout; - p += DIM(oid_x509Certificate_for_pkcs_12); - n -= DIM(oid_x509Certificate_for_pkcs_12); + /* Expect: * [0] * OCTET STRING encapsulates -- the certificates */ where = "certbag.before.octetstring"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval) || intval != 0 ) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef) + if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; /* Return the certificate from the octet string. */ if (ctx->certcb) - ctx->certcb (ctx->certcbarg, p, ti.length); - - p += ti.length; - n -= ti.length; + ctx->certcb (ctx->certcbarg, data, datalen); - if (!n) - goto leave; /* ready. */ - - /* Expect: + /* Expect optional: * SET * SEQUENCE -- we actually ignore this. */ where = "certbag.attribute_set"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (!ti.class && ti.tag == TAG_SET && !ti.ndef) - { /* Comsume the optional SET. */ - p += ti.length; - n -= ti.length; - if (parse_tag (&p, &n, &ti)) - goto bailout; + err = tlv_expect_set (tlv); + if (!err) + { /* This is the optional set of attributes. Skip it. */ + tlv_skip (tlv); + if (opt_verbose) + log_info ("skipping certbag.attribute_set\n"); } - - goto leave; - - bailout: - log_error ( "data error at \"%s\", offset %u\n", - where, (unsigned int)((p - p_start) + startoffset)); - err = gpg_error (GPG_ERR_GENERAL); + else if (gpg_err_code (err) == GPG_ERR_INV_OBJ) + tlv_set_pending (tlv); /* The next tlv_next will be skipped. */ + else + goto bailout; leave: - if (r_consumed) - *r_consumed = consumed; return err; + + bailout: + log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + __func__, where, + tlv? tlv->stacklen : 0, + tlv? tlv->offset : 0, + tlv? tlv->lastfunc : "", + tlv ? gpg_strerror (tlv->lasterr) : "init failed", + gpg_strerror (err)); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + goto leave; } static gpg_error_t -parse_bag_data (struct p12_parse_ctx_s *ctx, - const unsigned char *buffer, size_t length, int startoffset, - size_t *r_consumed) +parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) { gpg_error_t err = 0; - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; const char *where; - unsigned char *cram_buffer = NULL; - size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ + int intval; + const unsigned char *oid; + size_t oidlen; + unsigned int startlevel; + + if (opt_verbose) + log_info ("processing bag data\n"); /* Expect: * [0] * OCTET STRING, encapsulates */ where = "data"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval) || intval != 0 ) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING) + if (tlv_expect_octet_string (tlv, 1, NULL, NULL)) goto bailout; - consumed = p - p_start; - if (ti.is_constructed && ti.ndef) - { - /* Mozilla exported certs now come with single byte chunks of - octet strings. (Mozilla Firefox 1.0.4). Arghh. */ - where = "data.cram_os"; - cram_buffer = cram_octet_string ( p, &n, &consumed); - if (!cram_buffer) - goto bailout; - p = p_start = cram_buffer; - if (r_consumed) - *r_consumed = consumed; - r_consumed = NULL; /* Ugly hack to not update that value on return. */ - } - /* Expect: * SEQUENCE - * SEQUENCE - */ - where = "data.2seqs"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - - /* Expect: - * OBJECT IDENTIFIER */ - where = "data.oid"; - if (parse_tag (&p, &n, &ti)) + where = "data.outerseqs"; + if (tlv_next (tlv)) goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID) + if (tlv_expect_sequence (tlv)) goto bailout; - /* Now divert to the actual parser. */ - if (ti.length == DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) - && !memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, - DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) + startlevel = tlv_level (tlv); + while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) { - p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); - n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); - - if (parse_shrouded_key_bag (ctx, p, n, - startoffset + (p - p_start), r_consumed)) + /* Expect: + * SEQUENCE + */ + where = "data.innerseqs"; + if (tlv_expect_sequence (tlv)) goto bailout; - } - else if ( ti.length == DIM(oid_pkcs_12_CertBag) - && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) - { - p += DIM(oid_pkcs_12_CertBag); - n -= DIM(oid_pkcs_12_CertBag); - if (parse_cert_bag (ctx, p, n, - startoffset + (p - p_start), r_consumed)) + /* Expect: + * OBJECT IDENTIFIER + */ + where = "data.oid"; + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; + + /* Divert to the actual parser. */ + if (oidlen == DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) + && !memcmp (oid, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) + { + if ((err = parse_shrouded_key_bag (ctx, tlv))) + goto bailout; + } + else if (oidlen == DIM(oid_pkcs_12_CertBag) + && !memcmp (oid, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) + { + if ((err = parse_cert_bag (ctx, tlv))) + goto bailout; + } + else + { + tlv_skip (tlv); + log_info ("unknown inner data type - skipped\n"); + } } - else + if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; - - goto leave; - - bailout: - log_error ( "data error at \"%s\", offset %u\n", - where, (unsigned int)((p - p_start) + startoffset)); - err = gpg_error (GPG_ERR_GENERAL); + err = 0; + if (tlv_popped (tlv)) + tlv_set_pending (tlv); leave: - gcry_free (cram_buffer); - if (r_consumed) /* Store the number of consumed bytes unless already done. */ - *r_consumed = consumed; return err; + + bailout: + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + __func__, where, + tlv? tlv->stacklen : 0, + tlv? tlv->offset : 0, + tlv? tlv->lastfunc : "", + tlv ? gpg_strerror (tlv->lasterr) : "init failed", + gpg_strerror (err)); + goto leave; } @@ -1825,7 +2339,7 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, secret key parameters. This is a very limited implementation in that it is only able to look for 3DES encoded encryptedData and tries to extract the first private key object it finds. In case of - an error NULL is returned. CERTCB and CERRTCBARG are used to pass + an error NULL is returned. CERTCB and CERTCBARG are used to pass X.509 certificates back to the caller. If R_CURVE is not NULL and an ECC key was found the OID of the curve is stored there. */ gcry_mpi_t * @@ -1833,16 +2347,14 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), void *certcbarg, int *r_badpass, char **r_curve) { - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; - const char *where; - int bagseqlength, len; - int bagseqndef, lenndef; - unsigned char *cram_buffer = NULL; - size_t consumed; + gpg_error_t err; + const char *where = ""; + struct tlv_ctx_s *tlv; struct p12_parse_ctx_s ctx = { NULL }; + const unsigned char *oid; + size_t oidlen; + int intval; + unsigned int startlevel; *r_badpass = 0; @@ -1850,146 +2362,100 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, ctx.certcbarg = certcbarg; ctx.password = pw; + tlv = tlv_new (buffer, length); + if (!tlv) + { + err = gpg_error_from_syserror (); + goto bailout; + } where = "pfx"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; where = "pfxVersion"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3) + if (tlv_expect_integer (tlv, &intval) || intval != 3) goto bailout; - p++; n--; where = "authSave"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) - || memcmp (p, oid_data, DIM(oid_data))) + if (oidlen != DIM(oid_data) || memcmp (oid, oid_data, DIM(oid_data))) goto bailout; - p += DIM(oid_data); - n -= DIM(oid_data); - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_CONTEXT || ti.tag) + if (tlv_expect_context_tag (tlv, &intval) || intval != 0 ) goto bailout; - if (parse_tag (&p, &n, &ti)) + + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_UNIVERSAL || ti.tag != TAG_OCTET_STRING) + if (tlv_expect_octet_string (tlv, 1, NULL, NULL)) goto bailout; - if (ti.is_constructed && ti.ndef) - { - /* Mozilla exported certs now come with single byte chunks of - octet strings. (Mozilla Firefox 1.0.4). Arghh. */ - where = "cram-bags"; - cram_buffer = cram_octet_string ( p, &n, NULL); - if (!cram_buffer) - goto bailout; - p = p_start = cram_buffer; - } where = "bags"; - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) goto bailout; - if (ti.class != CLASS_UNIVERSAL || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - bagseqndef = ti.ndef; - bagseqlength = ti.length; - while (bagseqlength || bagseqndef) + + startlevel = tlv_level (tlv); + while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) { - /* log_debug ("p12_parse: at offset %ld\n", (p - p_start)); */ where = "bag-sequence"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (bagseqndef && ti.class == CLASS_UNIVERSAL - && !ti.tag && !ti.is_constructed) - break; /* Ready */ - if (ti.class != CLASS_UNIVERSAL || ti.tag != TAG_SEQUENCE) + if (tlv_expect_sequence (tlv)) goto bailout; - if (!bagseqndef) - { - if (bagseqlength < ti.nhdr) - goto bailout; - bagseqlength -= ti.nhdr; - if (bagseqlength < ti.length) - goto bailout; - bagseqlength -= ti.length; - } - lenndef = ti.ndef; - len = ti.length; - - if (parse_tag (&p, &n, &ti)) + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) goto bailout; - if (lenndef) - len = ti.nhdr; - else - len -= ti.nhdr; - if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) - && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) + if (oidlen == DIM(oid_encryptedData) + && !memcmp (oid, oid_encryptedData, DIM(oid_encryptedData))) { - - p += DIM(oid_encryptedData); - n -= DIM(oid_encryptedData); - if (!lenndef) - len -= DIM(oid_encryptedData); where = "bag.encryptedData"; - consumed = 0; - if (parse_bag_encrypted_data (&ctx, p, n, (p - p_start), &consumed)) - { - *r_badpass = ctx.badpass; - goto bailout; - } - if (lenndef) - len += consumed; + if ((err=parse_bag_encrypted_data (&ctx, tlv))) + goto bailout; } - else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) - && !memcmp (p, oid_data, DIM(oid_data))) + else if (oidlen == DIM(oid_data) + && !memcmp (oid, oid_data, DIM(oid_data))) { - p += DIM(oid_data); - n -= DIM(oid_data); - if (!lenndef) - len -= DIM(oid_data); - where = "bag.data"; - consumed = 0; - if (parse_bag_data (&ctx, p, n, (p - p_start), &consumed)) + if ((err=parse_bag_data (&ctx, tlv))) goto bailout; - if (lenndef) - len += consumed; } - else + else if (oidlen == DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) + && !memcmp (oid, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) { - log_info ("unknown outer bag type - skipped\n"); - p += ti.length; - n -= ti.length; + where = "bag.shroudedkeybag"; + if ((err = parse_shrouded_key_bag (&ctx, tlv))) + goto bailout; } - - if (len < 0 || len > n) - goto bailout; - p += len; - n -= len; - if (lenndef) + else { - /* Need to skip the Null Tag. */ - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (!(ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed)) - goto bailout; + tlv_skip (tlv); + log_info ("unknown outer bag type - skipped\n"); } } + if (err && gpg_err_code (err) != GPG_ERR_EOF) + goto bailout; + err = 0; - gcry_free (cram_buffer); + tlv_release (tlv); if (r_curve) *r_curve = ctx.curve; else @@ -1998,8 +2464,14 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, return ctx.privatekey; bailout: - log_error ("error at \"%s\", offset %u\n", - where, (unsigned int)(p - p_start)); + *r_badpass = ctx.badpass; + log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + __func__, where, + tlv? tlv->stacklen : 0, + tlv? tlv->offset : 0, + tlv? tlv->lastfunc : "", + tlv ? gpg_strerror (tlv->lasterr) : "init failed", + gpg_strerror (err)); if (ctx.privatekey) { int i; @@ -2009,7 +2481,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, gcry_free (ctx.privatekey); ctx.privatekey = NULL; } - gcry_free (cram_buffer); + tlv_release (tlv); gcry_free (ctx.curve); if (r_curve) *r_curve = NULL; diff --git a/sm/minip12.h b/sm/minip12.h index 84c5f5f79..654cab0e6 100644 --- a/sm/minip12.h +++ b/sm/minip12.h @@ -23,7 +23,7 @@ #include <gcrypt.h> -void p12_set_verbosity (int verbose); +void p12_set_verbosity (int verbose, int debug); gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length, const char *pw, diff --git a/sm/server.c b/sm/server.c index 3ec1c0c4b..b545c1bfb 100644 --- a/sm/server.c +++ b/sm/server.c @@ -298,6 +298,10 @@ option_handler (assuan_context_t ctx, const char *key, const char *value) opt.request_origin = i; } } + else if (!strcmp (key, "input-size-hint")) + { + ctrl->input_size_hint = string_to_u64 (value); + } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); @@ -1506,7 +1510,14 @@ gpgsm_status2 (ctrl_t ctrl, int no, ...) } } 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 { @@ -1551,6 +1562,45 @@ gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, } +/* 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 (total) + { + if (current > total) + 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. */ @@ -687,6 +687,10 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, goto leave; } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + if (ctrl->input_size_hint) + gnupg_ksba_set_total (b64writer, ctrl->input_size_hint); + err = ksba_cms_new (&cms); if (err) { @@ -1027,7 +1031,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, 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; } diff --git a/sm/t-minip12.c b/sm/t-minip12.c index 97bbcb9dc..de6b7e5cc 100644 --- a/sm/t-minip12.c +++ b/sm/t-minip12.c @@ -1,5 +1,5 @@ /* t-minip12.c - Test driver for minip12.c - * Copyright (C) 2020 g10 Code GmbH + * Copyright (C) 2020, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -15,6 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later */ #include <config.h> @@ -22,6 +23,8 @@ #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> +#include <stdarg.h> +#include <ctype.h> #include "../common/util.h" #include "minip12.h" @@ -31,7 +34,336 @@ static int verbose; static int debug; +static int any_error; +static void die (const char *format, ...) GPGRT_ATTR_NR_PRINTF(1,2); +static void err (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); +static void inf (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); +/* static void dbg (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); */ +static void printresult (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); +static char *my_xstrconcat (const char *s1, ...) GPGRT_ATTR_SENTINEL(0); + +#define xstrconcat my_xstrconcat +#define trim_spaces(a) my_trim_spaces ((a)) +#define my_isascii(c) (!((c) & 0x80)) + + + + + +/* Print diagnostic message and exit with failure. */ +static void +die (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + if (!*format || format[strlen(format)-1] != '\n') + putc ('\n', stderr); + + exit (1); +} + + +/* Print diagnostic message. */ +static void +err (const char *format, ...) +{ + va_list arg_ptr; + + any_error = 1; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + if (!*format || format[strlen(format)-1] != '\n') + putc ('\n', stderr); +} + + +/* Print an info message. */ +static void +inf (const char *format, ...) +{ + va_list arg_ptr; + + if (verbose) + { + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + if (!*format || format[strlen(format)-1] != '\n') + putc ('\n', stderr); + } +} + + +/* Print a debug message. */ +/* static void */ +/* dbg (const char *format, ...) */ +/* { */ +/* va_list arg_ptr; */ + +/* if (debug) */ +/* { */ +/* fprintf (stderr, "%s: DBG: ", PGM); */ + +/* va_start (arg_ptr, format); */ +/* vfprintf (stderr, format, arg_ptr); */ +/* va_end (arg_ptr); */ +/* if (!*format || format[strlen(format)-1] != '\n') */ +/* putc ('\n', stderr); */ +/* } */ +/* } */ + + +/* Print a result line to stdout. */ +static void +printresult (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); +#ifdef HAVE_FLOCKFILE + flockfile (stdout); +#endif + va_start (arg_ptr, format); + vfprintf (stdout, format, arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stdout); + va_end (arg_ptr); + fflush (stdout); +#ifdef HAVE_FLOCKFILE + funlockfile (stdout); +#endif +} + + +/* Helper for xstrconcat and strconcat. */ +static char * +do_strconcat (int xmode, const char *s1, va_list arg_ptr) +{ + const char *argv[48]; + size_t argc; + size_t needed; + char *buffer, *p; + + argc = 0; + argv[argc++] = s1; + needed = strlen (s1); + while (((argv[argc] = va_arg (arg_ptr, const char *)))) + { + needed += strlen (argv[argc]); + if (argc >= DIM (argv)-1) + die ("too may args for strconcat\n"); + argc++; + } + needed++; + buffer = xmode? xmalloc (needed) : malloc (needed); + for (p = buffer, argc=0; argv[argc]; argc++) + p = stpcpy (p, argv[argc]); + + return buffer; +} + + +/* Concatenate the string S1 with all the following strings up to a + NULL. Returns a malloced buffer with the new string or dies on error. */ +static char * +my_xstrconcat (const char *s1, ...) +{ + va_list arg_ptr; + char *result; + + if (!s1) + result = xstrdup (""); + else + { + va_start (arg_ptr, s1); + result = do_strconcat (1, s1, arg_ptr); + va_end (arg_ptr); + } + return result; +} + + +static char * +my_trim_spaces (char *str ) +{ + char *string, *p, *mark; + + string = str; + for (p=string; *p && isspace (*(unsigned char *)p) ; p++) + ; + for (mark=NULL; (*string = *p); string++, p++ ) + if (isspace (*(unsigned char *)p)) + { + if (!mark) + mark = string; + } + else + mark = NULL; + if (mark) + *mark = '\0'; + + return str ; +} + + +/* Prepend FNAME with the srcdir environment variable's value and + * return an allocated filename. */ +static char * +prepend_srcdir (const char *fname) +{ + static const char *srcdir; + + if (!srcdir && !(srcdir = getenv ("srcdir"))) + return xstrdup (fname); + else + return xstrconcat (srcdir, "/", fname, NULL); +} + + +/* (BUFFER,BUFLEN) and return a malloced hexstring. */ +static char * +hash_buffer (const void *buffer, size_t buflen) +{ + unsigned char hash[20]; + char *result; + int i; + + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, buffer, buflen); + result = xmalloc (41); + for (i=0; i < 20; i++) + snprintf (result + 2*i, 3, "%02x", hash[i]); + return result; +} + + +/* Read next line but skip over empty and comment lines. Caller must + xfree the result. */ +static char * +read_textline (FILE *fp, int *lineno) +{ + char line[4096]; + char *p; + + do + { + if (!fgets (line, sizeof line, fp)) + { + if (feof (fp)) + return NULL; + die ("error reading input line: %s\n", strerror (errno)); + } + ++*lineno; + p = strchr (line, '\n'); + if (!p) + die ("input line %d not terminated or too long\n", *lineno); + *p = 0; + for (p--;p > line && my_isascii (*p) && isspace (*p); p--) + *p = 0; + } + while (!*line || *line == '#'); + return xstrdup (line); +} + + +/* Copy the data after the tag to BUFFER. BUFFER will be allocated as + needed. */ +static void +copy_data (char **buffer, const char *line, int lineno) +{ + const char *s; + + xfree (*buffer); + *buffer = NULL; + + s = strchr (line, ':'); + if (!s) + { + err ("syntax error at input line %d", lineno); + return; + } + for (s++; my_isascii (*s) && isspace (*s); s++) + ; + *buffer = xstrdup (s); +} + + +static void +hexdowncase (char *string) +{ + char *p; + + if (string) + for (p=string; *p; p++) + if (my_isascii (*p)) + *p = tolower (*p); +} + + +/* Return the value of the variable VARNAME from ~/.gnupg-autogen.rc + * or NULL if it does not exists or is empty. */ +static char * +value_from_gnupg_autogen_rc (const char *varname) +{ + const char *home; + char *fname; + FILE *fp; + char *line = NULL; + char *p; + int lineno = 0; + + if (!(home = getenv ("HOME"))) + home = ""; + fname = xstrconcat (home, "/.gnupg-autogen.rc", NULL); + fp = fopen (fname, "r"); + if (!fp) + goto leave; + + while ((line = read_textline (fp, &lineno))) + { + p = strchr (line, '='); + if (p) + { + *p++ = 0; + trim_spaces (line); + if (!strcmp (line, varname)) + { + trim_spaces (p); + if (*p) + { + memmove (line, p, strlen (p)+1); + if (*line == '~' && line[1] == '/') + { + p = xstrconcat (home, line+1, NULL); + xfree (line); + line = p; + } + break; /* found. */ + } + } + } + xfree (line); + } + + leave: + if (fp) + fclose (fp); + xfree (fname); + return line; +} static void @@ -45,13 +377,92 @@ cert_cb (void *opaque, const unsigned char *cert, size_t certlen) } +/* Parse one PKCS#12 file. Returns zero on success. */ +static int +one_file (const char *name, const char *pass) +{ + FILE *fp; + struct stat st; + unsigned char *buf; + size_t buflen; + gcry_mpi_t *result; + int badpass; + char *curve = NULL; + + fp = fopen (name, "rb"); + if (!fp) + { + fprintf (stderr, PGM": can't open '%s': %s\n", name, strerror (errno)); + return 1; + } + + if (fstat (fileno(fp), &st)) + { + fprintf (stderr, PGM": can't stat '%s': %s\n", name, strerror (errno)); + return 1; + } -int -main (int argc, char **argv) + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + fprintf (stderr, "error reading '%s': %s\n", name, strerror (errno)); + return 1; + } + fclose (fp); + + result = p12_parse (buf, buflen, pass, cert_cb, NULL, &badpass, &curve); + if (result) + { + int i, rc; + unsigned char *tmpbuf; + + if (curve) + log_info ("curve: %s\n", curve); + for (i=0; result[i]; i++) + { + rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, NULL, result[i]); + if (rc) + log_error ("%d: [error printing number: %s]\n", + i, gpg_strerror (rc)); + else + { + log_info ("%d: %s\n", i, tmpbuf); + gcry_free (tmpbuf); + } + } + } + if (badpass) + log_error ("Bad password given?\n"); + + xfree (buf); + return 0; +} + + +static void +cert_collect_cb (void *opaque, const unsigned char *cert, size_t certlen) +{ + char **certstr = opaque; + char *hash, *save; + + hash = hash_buffer (cert, certlen); + if (*certstr) + { + save = *certstr; + *certstr = xstrconcat (save, ",", hash, NULL); + xfree (save); + xfree (hash); + } + else + *certstr = hash; +} + + +static int +run_one_test (const char *name, const char *desc, const char *pass, + const char *certexpected, const char *keyexpected) { - int last_argc = -1; - char const *name = NULL; - char const *pass = NULL; FILE *fp; struct stat st; unsigned char *buf; @@ -59,6 +470,225 @@ main (int argc, char **argv) gcry_mpi_t *result; int badpass; char *curve = NULL; + char *resulthash = NULL; + char *p; + char *certstr = NULL; + int ret; + + inf ("testing '%s' (%s)", name , desc? desc:""); + fp = fopen (name, "rb"); + if (!fp) + { + err ("can't open '%s': %s\n", name, strerror (errno)); + printresult ("FAIL: %s - test file not found\n", name); + return 1; + } + + if (fstat (fileno (fp), &st)) + { + err ("can't stat '%s': %s\n", name, strerror (errno)); + printresult ("FAIL: %s - error stating test file\n", name); + fclose (fp); + return 1; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + err ("error reading '%s': %s\n", name, strerror (errno)); + printresult ("FAIL: %s - error reading test file\n", name); + fclose (fp); + xfree (buf); + return 1; + } + fclose (fp); + + result = p12_parse (buf, buflen, pass? pass:"", cert_collect_cb, &certstr, + &badpass, &curve); + if (result) + { + int i, rc; + char *tmpstring; + unsigned char *tmpbuf; + char numbuf[20]; + + if (curve) + { + if (verbose > 1) + inf ("curve: %s\n", curve); + tmpstring = xstrconcat ("curve:", curve, "\n", NULL); + } + else + tmpstring = xstrdup ("\n"); + for (i=0; result[i]; i++) + { + rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, NULL, result[i]); + if (rc) + die ("result %d: [error printing number: %s]\n", + i, gpg_strerror (rc)); + else + { + if (verbose > 1) + inf ("result %d: %s\n", i, tmpbuf); + snprintf (numbuf, sizeof numbuf, "%d:", i); + p = xstrconcat (tmpstring, numbuf, tmpbuf, "\n", NULL); + xfree (tmpstring); + tmpstring = p; + gcry_free (tmpbuf); + } + } + + resulthash = hash_buffer (tmpstring, strlen (tmpstring)); + xfree (tmpstring); + } + + if (verbose > 1) + { + inf ("cert(exp)=%s", certexpected); + inf ("cert(got)=%s", certstr? certstr:"[null]"); + inf ("key(exp)=%s", keyexpected); + inf ("key(got)=%s", resulthash? resulthash:"[null]"); + } + + ret = 1; + if (!result) + printresult ("FAIL: %s - error from parser\n", name); + else if (certexpected && !certstr) + printresult ("FAIL: %s - expected certs but got none\n", name); + else if (!certexpected && certstr) + printresult ("FAIL: %s - no certs expected but got one\n", name); + else if (certexpected && certstr && strcmp (certexpected, certstr)) + printresult ("FAIL: %s - certs not as expected\n", name); + else if (keyexpected && !resulthash) + printresult ("FAIL: %s - expected key but got none\n", name); + else if (!keyexpected && resulthash) + printresult ("FAIL: %s - key not expected but got one\n", name); + else if (keyexpected && resulthash && strcmp (keyexpected, resulthash)) + printresult ("FAIL: %s - keys not as expected\n", name); + else + { + printresult ("PASS: %s\n", name); + ret = 0; + } + + if (result) + { + int i; + for (i=0; result[i]; i++) + gcry_mpi_release (result[i]); + gcry_free (result); + } + xfree (certstr); + xfree (resulthash); + xfree (curve); + xfree (buf); + return ret; +} + + +/* Run a regression test using the Info take from DESCFNAME. */ +static int +run_tests_from_file (const char *descfname) +{ + FILE *fp; + char *descdir; + int lineno, ntests; + char *line; + char *name = NULL; + char *desc = NULL; + char *pass = NULL; + char *cert = NULL; + char *key = NULL; + int ret = 0; + char *p; + + inf ("Running tests from '%s'", descfname); + descdir = xstrdup (descfname); + p = strrchr (descdir, '/'); + if (p) + *p = 0; + else + { + xfree (descdir); + descdir = xstrdup ("."); + } + + fp = fopen (descfname, "r"); + if (!fp) + die ("error opening '%s': %s\n", descfname, strerror (errno)); + + lineno = ntests = 0; + while ((line = read_textline (fp, &lineno))) + { + if (!strncmp (line, "Name:", 5)) + { + if (name) + ret |= run_one_test (name, desc, pass, cert, key); + xfree (cert); cert = NULL; + xfree (desc); desc = NULL; + xfree (pass); pass = NULL; + xfree (key); key = NULL; + copy_data (&name, line, lineno); + if (name) + { + p = xstrconcat (descdir, "/", name, NULL); + xfree (name); + name = p; + } + } + else if (!strncmp (line, "Desc:", 5)) + copy_data (&desc, line, lineno); + else if (!strncmp (line, "Pass:", 5)) + copy_data (&pass, line, lineno); + else if (!strncmp (line, "Cert:", 5)) + { + p = NULL; + copy_data (&p, line, lineno); + hexdowncase (p); + if (p && cert) + { + char *save = cert; + cert = xstrconcat (save, ",", p, NULL); + xfree (save); + xfree (p); + } + else + cert = p; + } + else if (!strncmp (line, "Key:", 4)) + { + copy_data (&key, line, lineno); + hexdowncase (key); + } + else + inf ("%s:%d: unknown tag ignored", descfname, lineno); + + xfree (line); + } + if (name) + ret |= run_one_test (name, desc, pass, cert, key); + xfree (name); + xfree (desc); + xfree (pass); + xfree (cert); + xfree (key); + + fclose (fp); + xfree (descdir); + return ret; +} + + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + char const *name = NULL; + char const *pass = NULL; + int ret; + int no_extra = 0; if (argc) { argc--; argv++; } @@ -73,12 +703,20 @@ main (int argc, char **argv) else if (!strcmp (*argv, "--help")) { fputs ("usage: " PGM " <pkcs12file> [<passphrase>]\n" + "Without <pkcs12file> a regression test is run\n" "Options:\n" + " --no-extra do not run extra tests\n" " --verbose print timings etc.\n" + " given twice shows more\n" " --debug flyswatter\n" , stdout); exit (0); } + else if (!strcmp (*argv, "--no-extra")) + { + no_extra = 1; + argc--; argv++; + } else if (!strcmp (*argv, "--verbose")) { verbose++; @@ -97,7 +735,12 @@ main (int argc, char **argv) } } - if (argc == 1) + if (!argc) + { + name = NULL; + pass = NULL; + } + else if (argc == 1) { name = argv[0]; pass = ""; @@ -109,57 +752,38 @@ main (int argc, char **argv) } else { - fprintf (stderr, "usage: " PGM " <file> [<passphrase>]\n"); + fprintf (stderr, "usage: " PGM " [<file> [<passphrase>]]\n"); exit (1); } gcry_control (GCRYCTL_DISABLE_SECMEM, NULL); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL); - - fp = fopen (name, "rb"); - if (!fp) + if (name) { - fprintf (stderr, PGM": can't open '%s': %s\n", name, strerror (errno)); - return 1; + p12_set_verbosity (verbose, debug); + ret = one_file (name, pass); } - - if (fstat (fileno(fp), &st)) - { - fprintf (stderr, PGM": can't stat '%s': %s\n", name, strerror (errno)); - return 1; - } - - buflen = st.st_size; - buf = gcry_malloc (buflen+1); - if (!buf || fread (buf, buflen, 1, fp) != 1) + else { - fprintf (stderr, "error reading '%s': %s\n", name, strerror (errno)); - return 1; - } - fclose (fp); + char *descfname, *p; - result = p12_parse (buf, buflen, pass, cert_cb, NULL, &badpass, &curve); - if (result) - { - int i, rc; - unsigned char *tmpbuf; + if (verbose > 1) + p12_set_verbosity (verbose > 1? (verbose - 1):0, debug); + descfname = prepend_srcdir ("../tests/cms/samplekeys/Description-p12"); + ret = run_tests_from_file (descfname); + xfree (descfname); - if (curve) - log_info ("curve: %s\n", curve); - for (i=0; result[i]; i++) + /* Check whether we have non-public regression test cases. */ + p = no_extra? NULL:value_from_gnupg_autogen_rc ("GNUPG_EXTRA_TESTS_DIR"); + if (p) { - rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, NULL, result[i]); - if (rc) - log_error ("%d: [error printing number: %s]\n", - i, gpg_strerror (rc)); - else - { - log_info ("%d: %s\n", i, tmpbuf); - gcry_free (tmpbuf); - } + descfname = xstrconcat (p, "/pkcs12/Description", NULL); + xfree (p); + ret |= run_tests_from_file (descfname); + xfree (descfname); } } - return 0; + return ret; } diff --git a/sm/verify.c b/sm/verify.c index a07d1c9c7..c7f4492ce 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -158,6 +158,10 @@ gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp) } } + gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl); + if (ctrl->input_size_hint) + gnupg_ksba_set_total (b64writer, ctrl->input_size_hint); + rc = ksba_cms_new (&cms); if (rc) goto leave; |