From 5f694dc0be994e8cd3bc009139d1349f3b1fcf62 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 29 Jun 2023 16:33:03 +0200 Subject: [PATCH] sm: Adding missing stuff to the PKCS#12 parser rewrite. * sm/minip12.c (struct bufferlist_s): New. (struct tlv_ctx_s): Add bufferlist. (tlv_register_buffer): New. (tlv_release): Release bufferlist. (tlv_expect_object): Handle octet string cramming. (tlv_expect_octet_string): Ditto. (cram_octet_string): Changed interface. We don't need the input_consumed value anymore. * sm/minip12.c (parse_shrouded_key_bag): Also parse the attribute set. * sm/t-minip12.c (main): Add option --no-extra. (cert_collect_cb, run_tests_from_file): Fix memory leak * tests/cms/samplekeys/t5793-openssl.pfx: New from T5793. * tests/cms/samplekeys/t5793-test.pfx: Ditto. * tests/cms/samplekeys/Description-p12: Add them. * tests/cms/Makefile.am (EXTRA_DIST): Add samplekeys. -- This should finish the rewrite of the pkcsc#12 parser for now. More fun is likely to come. GnuPG-bug-id: 6536, 5793 --- sm/minip12.c | 238 ++++++++++++++++--------- sm/t-minip12.c | 22 ++- tests/cms/Makefile.am | 8 +- tests/cms/samplekeys/Description-p12 | 12 ++ tests/cms/samplekeys/t5793-openssl.pfx | Bin 0 -> 4285 bytes tests/cms/samplekeys/t5793-test.pfx | Bin 0 -> 4328 bytes 6 files changed, 190 insertions(+), 90 deletions(-) create mode 100644 tests/cms/samplekeys/t5793-openssl.pfx create mode 100644 tests/cms/samplekeys/t5793-test.pfx diff --git a/sm/minip12.c b/sm/minip12.c index 69e23455a..eafebfe67 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -148,6 +148,14 @@ struct tag_info #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 { @@ -163,6 +171,8 @@ struct tlv_ctx_s 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 { @@ -198,6 +208,12 @@ 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, int debug) { @@ -354,9 +370,36 @@ tlv_new (const unsigned char *buffer, size_t bufsize) } +/* 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; +} + + static void tlv_release (struct tlv_ctx_s *tlv) { + if (!tlv) + return; + while (tlv->bufferlist) + { + struct bufferlist_s *save = tlv->bufferlist->next; + xfree (tlv->bufferlist->buffer); + xfree (tlv->bufferlist); + tlv->bufferlist = save; + } xfree (tlv); } @@ -457,7 +500,8 @@ tlv_next (struct tlv_ctx_s *tlv) /* End tag while in ndef container. Skip the tag, and pop. */ tlv->offset += n - (tlv->bufsize - tlv->offset); err = _tlv_pop (tlv); - // FIXME see above and run peek again. + /* 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); } @@ -558,24 +602,42 @@ tlv_expect_set (struct tlv_ctx_s *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 are not allocated but point into + * 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 - && !tlv->ti.is_constructed)) + 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)); - *r_data = p; - *r_datalen = tlv->ti.length; + 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) + { + xfree (newbuffer); + return (tlv->lasterr = err); + } + *r_data = newbuffer; + } + else + { + *r_data = p; + *r_datalen = tlv->ti.length; + } tlv->offset += tlv->ti.length; return 0; @@ -591,21 +653,40 @@ 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)) + && (!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 (r_data) - *r_data = p; - if (r_datalen) - *r_datalen = tlv->ti.length; + 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) + { + xfree (newbuffer); + return (tlv->lasterr = err); + } + *r_data = newbuffer; + } + else + { + if (r_data) + *r_data = p; + if (r_datalen) + *r_datalen = tlv->ti.length; + } if (encapsulates) return _tlv_push (tlv); @@ -716,37 +797,37 @@ tlv_expect_object_id (struct tlv_ctx_s *tlv, /* 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. - - 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. */ + * + * 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; @@ -769,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) @@ -1331,38 +1407,6 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) 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 */ - /* goto bailout; */ if (tlv_expect_object (tlv, CLASS_CONTEXT, 0, &data, &datalen)) goto bailout; @@ -1538,7 +1582,8 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) keyelem_count, gpg_strerror (err)); goto bailout; } - log_debug ("RSA key parameter %d found\n", keyelem_count); + if (opt_verbose > 1) + log_debug ("RSA key parameter %d found\n", keyelem_count); keyelem_count++; } if (err && gpg_err_code (err) != GPG_ERR_EOF) @@ -1683,6 +1728,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) size_t saltlen; char iv[16]; unsigned int iter; + struct tlv_ctx_s *saved_tlv = NULL; int renewed_tlv = 0; /* True if the TLV must be released. */ unsigned char *plain = NULL; int is_pbes2 = 0; @@ -1865,8 +1911,10 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) : GCRY_CIPHER_3DES, bag_data_p); + /* We do not need the TLV anymore and allocated a new one. */ where = "shrouded_key_bag.decrypted-text"; + saved_tlv = tlv; tlv = tlv_new (plain, datalen); if (!tlv) { @@ -1874,6 +1922,8 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; } renewed_tlv = 1; + if (opt_verbose > 1) + log_debug ("new parser context\n"); if (tlv_next (tlv)) { @@ -2037,10 +2087,39 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) err = 0; } + if (opt_verbose > 1) + log_debug ("restoring parser context\n"); + tlv_release (tlv); + renewed_tlv = 0; + tlv = saved_tlv; + + 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) + { /* 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 (plain); if (renewed_tlv) - tlv_release (tlv); + { + tlv_release (tlv); + if (opt_verbose > 1) + log_debug ("parser context released\n"); + } return err; bailout: @@ -2322,17 +2401,6 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, if (tlv_next (tlv)) goto bailout; - if (tlv->ti.is_constructed && tlv->ti.ndef) - { - log_debug ("FIXME Put this into our TLV machinery.\n"); - /* /\* 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; */ - } if (tlv_expect_octet_string (tlv, 1, NULL, NULL)) goto bailout; diff --git a/sm/t-minip12.c b/sm/t-minip12.c index 80ba4c69e..de6b7e5cc 100644 --- a/sm/t-minip12.c +++ b/sm/t-minip12.c @@ -444,12 +444,14 @@ static void cert_collect_cb (void *opaque, const unsigned char *cert, size_t certlen) { char **certstr = opaque; - char *hash; + char *hash, *save; hash = hash_buffer (cert, certlen); if (*certstr) { - *certstr = xstrconcat (*certstr, ",", hash, NULL); + save = *certstr; + *certstr = xstrconcat (save, ",", hash, NULL); + xfree (save); xfree (hash); } else @@ -645,7 +647,12 @@ run_tests_from_file (const char *descfname) copy_data (&p, line, lineno); hexdowncase (p); if (p && cert) - cert = xstrconcat (cert, ",", p, NULL); + { + char *save = cert; + cert = xstrconcat (save, ",", p, NULL); + xfree (save); + xfree (p); + } else cert = p; } @@ -681,6 +688,7 @@ main (int argc, char **argv) char const *name = NULL; char const *pass = NULL; int ret; + int no_extra = 0; if (argc) { argc--; argv++; } @@ -697,12 +705,18 @@ main (int argc, char **argv) fputs ("usage: " PGM " []\n" "Without 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++; @@ -761,7 +775,7 @@ main (int argc, char **argv) xfree (descfname); /* Check whether we have non-public regression test cases. */ - p = value_from_gnupg_autogen_rc ("GNUPG_EXTRA_TESTS_DIR"); + p = no_extra? NULL:value_from_gnupg_autogen_rc ("GNUPG_EXTRA_TESTS_DIR"); if (p) { descfname = xstrconcat (p, "/pkcs12/Description", NULL); diff --git a/tests/cms/Makefile.am b/tests/cms/Makefile.am index 60fdf0281..7efdf37b1 100644 --- a/tests/cms/Makefile.am +++ b/tests/cms/Makefile.am @@ -86,13 +86,19 @@ TEST_FILES = plain-1.cms.asc \ testscripts = sm-sign+verify sm-verify EXTRA_DIST = $(XTESTS) $(KEYS) $(CERTS) $(TEST_FILES) \ + samplemsgs/README \ + samplekeys/Description-p12 \ samplekeys/steed-self-signing-nonthority.pem \ samplekeys/68A638998DFABAC510EA645CE34F9686B2EDF7EA.key \ samplekeys/32100C27173EF6E9C4E9A25D3D69F86D37A4F939.key \ samplekeys/cert_g10code_pete1.pem \ samplekeys/cert_g10code_test1.pem \ samplekeys/cert_g10code_theo1.pem \ - samplemsgs/README \ + samplekeys/ov-user.p12 \ + samplekeys/ov-server.p12 \ + samplekeys/opensc-test.p12 \ + samplekeys/t5793-openssl.pfx \ + samplekeys/t5793-test.pfx \ samplemsgs/pwri-sample.cbc.p7m \ samplemsgs/pwri-sample.cbc-2.p7m \ samplemsgs/pwri-sample.gcm.p7m \ diff --git a/tests/cms/samplekeys/Description-p12 b/tests/cms/samplekeys/Description-p12 index bd1e35c91..f882de9ea 100644 --- a/tests/cms/samplekeys/Description-p12 +++ b/tests/cms/samplekeys/Description-p12 @@ -18,3 +18,15 @@ Pass: password Cert: 115abfc3ae554092a57ade74177fedf9459af5d2 Cert: a0d6d318952c313ff8c33cd3f629647ff1de76b3 Key: 5a36c61706367ecdb52e8779e3a32bbac1069fa1 + +Name: t5793-openssl.pfx +Desc: self-signed key issued keys +Pass: test +Cert: 80348a438e4b803b99e708da0b7fdd0659dedd15 +Key: c271e44ab4fb19ca1aae71102ea4d7292ccc981d + +Name: t5793-test.pfx +Desc: QuaVadis format of t5793-openssl +Pass: test +Cert: 80348a438e4b803b99e708da0b7fdd0659dedd15 +Key: c271e44ab4fb19ca1aae71102ea4d7292ccc981d diff --git a/tests/cms/samplekeys/t5793-openssl.pfx b/tests/cms/samplekeys/t5793-openssl.pfx new file mode 100644 index 0000000000000000000000000000000000000000..0f1beed0fb3edbcec8a8298096fac4c333606496 GIT binary patch literal 4285 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