sm: Rework the PKCS#12 parser to support DFN issued keys.

* sm/minip12.c (struct p12_parse_ctx_s): New.  Use this instead of
passing several parameters to most functions.
(parse_pag_data): Factor things out to  ...
parse_shrouded_key_bag): new.
(parse_cert_bag): New.
(parse_bag_data): New.
(p12_parse): Setup the parse context.
--

To support newer pkcs#12 files like those issued by the DFN we need to
support another ordering of data elements.  This rework reflects the
P12 data structure a bit better than our old ad-hoc hacks.  Tests could
only be done with the certificate parts and not the encrypted private
keys.

GnuPG-bug-id: 6037
This commit is contained in:
Werner Koch 2022-06-20 16:45:42 +02:00
parent be5d06dae2
commit a4e04375e8
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
1 changed files with 343 additions and 179 deletions

View File

@ -1,6 +1,7 @@
/* minip12.c - A minimal pkcs-12 implementation. /* minip12.c - A minimal pkcs-12 implementation.
* Copyright (C) 2002, 2003, 2004, 2006, 2011 Free Software Foundation, Inc. * Copyright (C) 2002, 2003, 2004, 2006, 2011 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch * Copyright (C) 2014 Werner Koch
* Copyright (C) 2022 g10 Code GmbH
* *
* This file is part of GnuPG. * This file is part of GnuPG.
* *
@ -16,6 +17,7 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>. * along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/ */
/* References: /* References:
@ -141,6 +143,26 @@ struct tag_info
int ndef; /* It is an indefinite length */ int ndef; /* It is an indefinite length */
}; };
/* Parser communication object. */
struct p12_parse_ctx_s
{
/* The callback for parsed certificates and its arg. */
void (*certcb)(void*, const unsigned char*, size_t);
void *certcbarg;
/* The supplied parseword. */
const char *password;
/* Set to true if the password was wrong. */
int badpass;
/* Malloced name of the curve. */
char *curve;
/* The private key as an MPI array. */
gcry_mpi_t *privatekey;
};
static int opt_verbose; static int opt_verbose;
@ -152,6 +174,16 @@ p12_set_verbosity (int verbose)
} }
/* 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":""); */
/* } */
/* Wrapper around tlv_builder_add_ptr to add an OID. When we /* Wrapper around tlv_builder_add_ptr to add an OID. When we
* eventually put the whole tlv_builder stuff into Libksba, we can add * eventually put the whole tlv_builder stuff into Libksba, we can add
* such a function there. Right now we don't do this to avoid a * such a function there. Right now we don't do this to avoid a
@ -727,14 +759,11 @@ bag_decrypted_data_p (const void *plaintext, size_t length)
return 1; return 1;
} }
/* Note: If R_RESULT is passed as NULL, a key object as already be
processed and thus we need to skip it here. */
static int static int
parse_bag_encrypted_data (const unsigned char *buffer, size_t length, parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx,
int startoffset, size_t *r_consumed, const char *pw, const unsigned char *buffer, size_t length,
void (*certcb)(void*, const unsigned char*, size_t), int startoffset, size_t *r_consumed)
void *certcbarg, gcry_mpi_t **r_result,
int *r_badpass)
{ {
struct tag_info ti; struct tag_info ti;
const unsigned char *p = buffer; const unsigned char *p = buffer;
@ -746,16 +775,12 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
char iv[16]; char iv[16];
unsigned int iter; unsigned int iter;
unsigned char *plain = NULL; unsigned char *plain = NULL;
int bad_pass = 0;
unsigned char *cram_buffer = NULL; unsigned char *cram_buffer = NULL;
size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ size_t consumed = 0; /* Number of bytes consumed from the original buffer. */
int is_3des = 0; int is_3des = 0;
int is_pbes2 = 0; int is_pbes2 = 0;
gcry_mpi_t *result = NULL; int keyelem_count;
int result_count;
if (r_result)
*r_result = NULL;
where = "start"; where = "start";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
goto bailout; goto bailout;
@ -930,7 +955,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
p = p_start = cram_buffer; p = p_start = cram_buffer;
if (r_consumed) if (r_consumed)
*r_consumed = consumed; *r_consumed = consumed;
r_consumed = NULL; /* Ugly hack to not update that value any further. */ r_consumed = NULL; /* Donot update that value on return. */
ti.length = n; ti.length = n;
} }
else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed) else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed)
@ -943,7 +968,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
p = p_start = cram_buffer; p = p_start = cram_buffer;
if (r_consumed) if (r_consumed)
*r_consumed = consumed; *r_consumed = consumed;
r_consumed = NULL; /* Ugly hack to not update that value any further. */ r_consumed = NULL; /* Do not update that value on return. */
ti.length = n; ti.length = n;
} }
else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.length ) else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.length )
@ -962,7 +987,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
goto bailout; goto bailout;
} }
decrypt_block (p, plain, ti.length, salt, saltlen, iter, decrypt_block (p, plain, ti.length, salt, saltlen, iter,
iv, is_pbes2?16:0, pw, iv, is_pbes2?16:0, ctx->password,
is_pbes2 ? GCRY_CIPHER_AES128 : is_pbes2 ? GCRY_CIPHER_AES128 :
is_3des ? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, is_3des ? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40,
bag_decrypted_data_p); bag_decrypted_data_p);
@ -973,18 +998,18 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
where = "outer.outer.seq"; where = "outer.outer.seq";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
{ {
bad_pass = 1; ctx->badpass = 1;
goto bailout; goto bailout;
} }
if (ti.class || ti.tag != TAG_SEQUENCE) if (ti.class || ti.tag != TAG_SEQUENCE)
{ {
bad_pass = 1; ctx->badpass = 1;
goto bailout; goto bailout;
} }
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
{ {
bad_pass = 1; ctx->badpass = 1;
goto bailout; goto bailout;
} }
@ -1040,7 +1065,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
p += ti.length; p += ti.length;
n -= ti.length; n -= ti.length;
} }
else if (iskeybag && (result || !r_result)) else if (iskeybag && ctx->privatekey)
{ {
log_info ("one keyBag already processed; skipping this one\n"); log_info ("one keyBag already processed; skipping this one\n");
p += ti.length; p += ti.length;
@ -1090,16 +1115,17 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
goto bailout; goto bailout;
len = ti.length; len = ti.length;
result = gcry_calloc (10, sizeof *result); log_assert (!ctx->privatekey);
if (!result) ctx->privatekey = gcry_calloc (10, sizeof *ctx->privatekey);
if (!ctx->privatekey)
{ {
log_error ( "error allocating result array\n"); log_error ("error allocating private key element array\n");
goto bailout; goto bailout;
} }
result_count = 0; keyelem_count = 0;
where = "reading.keybag.key-parameters"; where = "reading.keybag.key-parameters";
for (result_count = 0; len && result_count < 9;) for (keyelem_count = 0; len && keyelem_count < 9;)
{ {
if ( parse_tag (&p, &n, &ti) if ( parse_tag (&p, &n, &ti)
|| ti.class || ti.tag != TAG_INTEGER) || ti.class || ti.tag != TAG_INTEGER)
@ -1110,13 +1136,14 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
if (len < ti.length) if (len < ti.length)
goto bailout; goto bailout;
len -= ti.length; len -= ti.length;
if (!result_count && ti.length == 1 && !*p) if (!keyelem_count && ti.length == 1 && !*p)
; /* ignore the very first one if it is a 0 */ ; /* ignore the very first one if it is a 0 */
else else
{ {
int rc; int rc;
rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, rc = gcry_mpi_scan (ctx->privatekey+keyelem_count,
GCRYMPI_FMT_USG, p,
ti.length, NULL); ti.length, NULL);
if (rc) if (rc)
{ {
@ -1124,7 +1151,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
gpg_strerror (rc)); gpg_strerror (rc));
goto bailout; goto bailout;
} }
result_count++; keyelem_count++;
} }
p += ti.length; p += ti.length;
n -= ti.length; n -= ti.length;
@ -1161,8 +1188,8 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
goto bailout; goto bailout;
/* Return the certificate. */ /* Return the certificate. */
if (certcb) if (ctx->certcb)
certcb (certcbarg, p, ti.length); ctx->certcb (ctx->certcbarg, p, ti.length);
p += ti.length; p += ti.length;
n -= ti.length; n -= ti.length;
@ -1201,32 +1228,21 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
*r_consumed = consumed; *r_consumed = consumed;
gcry_free (plain); gcry_free (plain);
gcry_free (cram_buffer); gcry_free (cram_buffer);
if (r_result)
*r_result = result;
return 0; return 0;
bailout: bailout:
if (result)
{
int i;
for (i=0; result[i]; i++)
gcry_mpi_release (result[i]);
gcry_free (result);
}
if (r_consumed) if (r_consumed)
*r_consumed = consumed; *r_consumed = consumed;
gcry_free (plain); gcry_free (plain);
gcry_free (cram_buffer); gcry_free (cram_buffer);
log_error ("encryptedData error at \"%s\", offset %u\n", log_error ("encryptedData error at \"%s\", offset %u\n",
where, (unsigned int)((p - p_start)+startoffset)); where, (unsigned int)((p - p_start)+startoffset));
if (bad_pass) if (ctx->badpass)
{ {
/* Note, that the following string might be used by other programs /* Note, that the following string might be used by other programs
to check for a bad passphrase; it should therefore not be to check for a bad passphrase; it should therefore not be
translated or changed. */ translated or changed. */
log_error ("possibly bad passphrase given\n"); log_error ("possibly bad passphrase given\n");
*r_badpass = 1;
} }
return -1; return -1;
} }
@ -1259,11 +1275,13 @@ bag_data_p (const void *plaintext, size_t length)
} }
static gcry_mpi_t * static gpg_error_t
parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx,
size_t *r_consumed, char **r_curve, const char *pw) const unsigned char *buffer, size_t length,
int startoffset,
size_t *r_consumed)
{ {
int rc; gpg_error_t err = 0;
struct tag_info ti; struct tag_info ti;
const unsigned char *p = buffer; const unsigned char *p = buffer;
const unsigned char *p_start = buffer; const unsigned char *p_start = buffer;
@ -1275,61 +1293,12 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
unsigned int iter; unsigned int iter;
int len; int len;
unsigned char *plain = NULL; unsigned char *plain = NULL;
gcry_mpi_t *result = NULL;
int result_count, i;
unsigned char *cram_buffer = NULL; unsigned char *cram_buffer = NULL;
size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ size_t consumed = 0; /* Number of bytes consumed from the original buffer. */
int is_pbes2 = 0; int is_pbes2 = 0;
char *curve = NULL; int keyelem_count = 0;
where = "start"; where = "shrouded_key_bag";
if (parse_tag (&p, &n, &ti))
goto bailout;
if (ti.class != CLASS_CONTEXT || ti.tag)
goto bailout;
if (parse_tag (&p, &n, &ti))
goto bailout;
if (ti.class || ti.tag != TAG_OCTET_STRING)
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 = "cram-data.outersegs";
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 any further. */
}
where = "data.outerseqs";
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;
where = "data.objectidentifier";
if (parse_tag (&p, &n, &ti))
goto bailout;
if (ti.class || ti.tag != TAG_OBJECT_ID
|| ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)
|| memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag,
DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)))
goto bailout;
p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
where = "shrouded,outerseqs";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
goto bailout; goto bailout;
if (ti.class != CLASS_CONTEXT || ti.tag) if (ti.class != CLASS_CONTEXT || ti.tag)
@ -1365,7 +1334,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
if (is_pbes2) if (is_pbes2)
{ {
where = "pkcs5PBES2-params"; where = "shrouded_key_bag.pkcs5PBES2-params";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
goto bailout; goto bailout;
if (ti.class || ti.tag != TAG_SEQUENCE) if (ti.class || ti.tag != TAG_SEQUENCE)
@ -1430,7 +1399,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
} }
else else
{ {
where = "3des-params"; where = "shrouded_key_bag.3des-params";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
goto bailout; goto bailout;
if (ti.class || ti.tag != TAG_SEQUENCE) if (ti.class || ti.tag != TAG_SEQUENCE)
@ -1456,7 +1425,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
} }
} }
where = "3desoraes-ciphertext"; where = "shrouded_key_bag.3desoraes-ciphertext";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
goto bailout; goto bailout;
if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length ) if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length )
@ -1474,14 +1443,14 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
} }
consumed += p - p_start + ti.length; consumed += p - p_start + ti.length;
decrypt_block (p, plain, ti.length, salt, saltlen, iter, decrypt_block (p, plain, ti.length, salt, saltlen, iter,
iv, is_pbes2? 16:0, pw, iv, is_pbes2? 16:0, ctx->password,
is_pbes2? GCRY_CIPHER_AES128 : GCRY_CIPHER_3DES, is_pbes2? GCRY_CIPHER_AES128 : GCRY_CIPHER_3DES,
bag_data_p); bag_data_p);
n = ti.length; n = ti.length;
startoffset = 0; startoffset = 0;
p_start = p = plain; p_start = p = plain;
where = "decrypted-text"; where = "shrouded_key_bag.decrypted-text";
if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
goto bailout; goto bailout;
if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
@ -1524,8 +1493,9 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
len -= ti.nhdr; len -= ti.nhdr;
if (ti.class || ti.tag != TAG_OBJECT_ID) if (ti.class || ti.tag != TAG_OBJECT_ID)
goto bailout; goto bailout;
curve = ksba_oid_to_str (p, ti.length); ksba_free (ctx->curve);
if (!curve) ctx->curve = ksba_oid_to_str (p, ti.length);
if (!ctx->curve)
goto bailout; goto bailout;
/* log_debug ("OID of curve is: %s\n", curve); */ /* log_debug ("OID of curve is: %s\n", curve); */
p += ti.length; p += ti.length;
@ -1546,16 +1516,22 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
goto bailout; goto bailout;
len = ti.length; len = ti.length;
result = gcry_calloc (10, sizeof *result); if (ctx->privatekey)
if (!result)
{ {
log_error ( "error allocating result array\n"); log_error ("a key has already been received\n");
goto bailout; goto bailout;
} }
result_count = 0; ctx->privatekey = gcry_calloc (10, sizeof *ctx->privatekey);
if (!ctx->privatekey)
{
where = "reading.key-parameters"; log_error ("error allocating privatekey element array\n");
if (curve) /* ECC case. */ 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 (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER)
goto bailout; goto bailout;
@ -1582,10 +1558,11 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
goto bailout; goto bailout;
len -= ti.length; len -= ti.length;
/* log_printhex (p, ti.length, "ecc q="); */ /* log_printhex (p, ti.length, "ecc q="); */
rc = gcry_mpi_scan (result, GCRYMPI_FMT_USG, p, ti.length, NULL); err = gcry_mpi_scan (ctx->privatekey, GCRYMPI_FMT_USG,
if (rc) p, ti.length, NULL);
if (err)
{ {
log_error ("error parsing key parameter: %s\n", gpg_strerror (rc)); log_error ("error parsing key parameter: %s\n", gpg_strerror (err));
goto bailout; goto bailout;
} }
p += ti.length; p += ti.length;
@ -1595,7 +1572,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
} }
else /* RSA case */ else /* RSA case */
{ {
for (result_count=0; len && result_count < 9;) for (keyelem_count=0; len && keyelem_count < 9;)
{ {
if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER)
goto bailout; goto bailout;
@ -1605,19 +1582,19 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
if (len < ti.length) if (len < ti.length)
goto bailout; goto bailout;
len -= ti.length; len -= ti.length;
if (!result_count && ti.length == 1 && !*p) if (!keyelem_count && ti.length == 1 && !*p)
; /* ignore the very first one if it is a 0 */ ; /* ignore the very first one if it is a 0 */
else else
{ {
rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, err = gcry_mpi_scan (ctx->privatekey+keyelem_count,
ti.length, NULL); GCRYMPI_FMT_USG, p, ti.length, NULL);
if (rc) if (err)
{ {
log_error ("error parsing key parameter: %s\n", log_error ("error parsing key parameter: %s\n",
gpg_strerror (rc)); gpg_strerror (err));
goto bailout; goto bailout;
} }
result_count++; keyelem_count++;
} }
p += ti.length; p += ti.length;
n -= ti.length; n -= ti.length;
@ -1630,29 +1607,215 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
bailout: bailout:
gcry_free (plain); gcry_free (plain);
if (result) log_error ("data error at \"%s\", offset %u\n",
{
for (i=0; result[i]; i++)
gcry_mpi_release (result[i]);
gcry_free (result);
}
log_error ( "data error at \"%s\", offset %u\n",
where, (unsigned int)((p - buffer) + startoffset)); where, (unsigned int)((p - buffer) + startoffset));
result = NULL; if (!err)
err = gpg_error (GPG_ERR_GENERAL);
leave: leave:
if (r_curve && result)
{
*r_curve = curve;
curve = NULL;
}
else if (r_curve)
*r_curve = NULL;
ksba_free (curve);
gcry_free (cram_buffer); gcry_free (cram_buffer);
if (r_consumed) if (r_consumed)
*r_consumed = consumed; *r_consumed = consumed;
return result; return err;
}
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)
{
gpg_error_t err = 0;
struct tag_info ti;
const unsigned char *p = buffer;
size_t n = length;
const char *where;
size_t consumed = 0; /* Number of bytes consumed from the original buffer. */
if (opt_verbose)
log_info ("processing certBag\n");
/* Expect:
* [0]
* SEQUENCE
* OBJECT IDENTIFIER pkcs-12-certBag
*/
where = "certbag.before.certheader";
if (parse_tag (&p, &n, &ti))
goto bailout;
if (ti.class != CLASS_CONTEXT || ti.tag)
goto bailout;
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_OBJECT_ID
|| ti.length != DIM(oid_x509Certificate_for_pkcs_12)
|| memcmp (p, oid_x509Certificate_for_pkcs_12,
DIM(oid_x509Certificate_for_pkcs_12)))
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))
goto bailout;
if (ti.class != CLASS_CONTEXT || ti.tag)
goto bailout;
if (parse_tag (&p, &n, &ti))
goto bailout;
if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef)
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;
if (!n)
goto leave; /* ready. */
/* Expect:
* SET
* SEQUENCE -- we actually ignore this.
*/
where = "certbag.attribute_set";
if (parse_tag (&p, &n, &ti))
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;
}
goto leave;
bailout:
log_error ( "data error at \"%s\", offset %u\n",
where, (unsigned int)((p - buffer) + startoffset));
err = gpg_error (GPG_ERR_GENERAL);
leave:
if (r_consumed)
*r_consumed = consumed;
return err;
}
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)
{
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. */
/* Expect:
* [0]
* OCTET STRING, encapsulates
*/
where = "data";
if (parse_tag (&p, &n, &ti))
goto bailout;
if (ti.class != CLASS_CONTEXT || ti.tag)
goto bailout;
if (parse_tag (&p, &n, &ti))
goto bailout;
if (ti.class || ti.tag != TAG_OCTET_STRING)
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))
goto bailout;
if (ti.class || ti.tag != TAG_OBJECT_ID)
goto bailout;
log_printhex (p, ti.length, "oid");
/* 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)))
{
p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
if (parse_shrouded_key_bag (ctx, p, n, 0, r_consumed))
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, 0, r_consumed))
goto bailout;
}
else
goto bailout;
goto leave;
bailout:
log_error ( "data error at \"%s\", offset %u\n",
where, (unsigned int)((p - buffer) + startoffset));
err = gpg_error (GPG_ERR_GENERAL);
leave:
gcry_free (cram_buffer);
if (r_consumed) /* Store the number of consumed bytes unless already done. */
*r_consumed = consumed;
return err;
} }
@ -1661,7 +1824,8 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
that it is only able to look for 3DES encoded encryptedData and 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 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 CERRTCBARG are used to pass
X.509 certificates back to the caller. */ 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 * gcry_mpi_t *
p12_parse (const unsigned char *buffer, size_t length, const char *pw, p12_parse (const unsigned char *buffer, size_t length, const char *pw,
void (*certcb)(void*, const unsigned char*, size_t), void (*certcb)(void*, const unsigned char*, size_t),
@ -1674,11 +1838,17 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
const char *where; const char *where;
int bagseqlength, len; int bagseqlength, len;
int bagseqndef, lenndef; int bagseqndef, lenndef;
gcry_mpi_t *result = NULL;
unsigned char *cram_buffer = NULL; unsigned char *cram_buffer = NULL;
char *curve = NULL; size_t consumed;
struct p12_parse_ctx_s ctx = { NULL };
*r_badpass = 0; *r_badpass = 0;
ctx.certcb = certcb;
ctx.certcbarg = certcbarg;
ctx.password = pw;
where = "pfx"; where = "pfx";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
goto bailout; goto bailout;
@ -1734,7 +1904,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
bagseqlength = ti.length; bagseqlength = ti.length;
while (bagseqlength || bagseqndef) while (bagseqlength || bagseqndef)
{ {
/* log_debug ( "at offset %u\n", (p - p_start)); */ /* log_debug ("p12_parse: at offset %ld\n", (p - p_start)); */
where = "bag-sequence"; where = "bag-sequence";
if (parse_tag (&p, &n, &ti)) if (parse_tag (&p, &n, &ti))
goto bailout; goto bailout;
@ -1766,16 +1936,14 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData)
&& !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData)))
{ {
size_t consumed = 0;
p += DIM(oid_encryptedData); p += DIM(oid_encryptedData);
n -= DIM(oid_encryptedData); n -= DIM(oid_encryptedData);
if (!lenndef) if (!lenndef)
len -= DIM(oid_encryptedData); len -= DIM(oid_encryptedData);
where = "bag.encryptedData"; where = "bag.encryptedData";
if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw, consumed = 0;
certcb, certcbarg, if (parse_bag_encrypted_data (&ctx, p, n, (p - p_start), &consumed))
result? NULL : &result, r_badpass))
goto bailout; goto bailout;
if (lenndef) if (lenndef)
len += consumed; len += consumed;
@ -1783,31 +1951,21 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data)
&& !memcmp (p, oid_data, DIM(oid_data))) && !memcmp (p, oid_data, DIM(oid_data)))
{ {
if (result) p += DIM(oid_data);
{ n -= DIM(oid_data);
log_info ("already got an key object, skipping this one\n"); if (!lenndef)
p += ti.length; len -= DIM(oid_data);
n -= ti.length;
}
else
{
size_t consumed = 0;
p += DIM(oid_data); where = "bag.data";
n -= DIM(oid_data); consumed = 0;
if (!lenndef) if (parse_bag_data (&ctx, p, n, (p - p_start), &consumed))
len -= DIM(oid_data); goto bailout;
result = parse_bag_data (p, n, (p - p_start), if (lenndef)
&consumed, &curve, pw); len += consumed;
if (!result)
goto bailout;
if (lenndef)
len += consumed;
}
} }
else else
{ {
log_info ("unknown bag type - skipped\n"); log_info ("unknown outer bag type - skipped\n");
p += ti.length; p += ti.length;
n -= ti.length; n -= ti.length;
} }
@ -1827,23 +1985,29 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
} }
gcry_free (cram_buffer); gcry_free (cram_buffer);
*r_curve = curve; if (r_curve)
return result; *r_curve = ctx.curve;
else
gcry_free (ctx.curve);
return ctx.privatekey;
bailout: bailout:
log_error ("error at \"%s\", offset %u\n", log_error ("error at \"%s\", offset %u\n",
where, (unsigned int)(p - p_start)); where, (unsigned int)(p - p_start));
if (result) if (ctx.privatekey)
{ {
int i; int i;
for (i=0; result[i]; i++) for (i=0; ctx.privatekey[i]; i++)
gcry_mpi_release (result[i]); gcry_mpi_release (ctx.privatekey[i]);
gcry_free (result); gcry_free (ctx.privatekey);
ctx.privatekey = NULL;
} }
gcry_free (cram_buffer); gcry_free (cram_buffer);
gcry_free (curve); gcry_free (ctx.curve);
*r_curve = NULL; if (r_curve)
*r_curve = NULL;
return NULL; return NULL;
} }
@ -2035,7 +2199,7 @@ create_final (struct buffer_s *sequences, const char *pw, size_t *r_length)
/* Ready. */ /* Ready. */
resultlen = p - result; resultlen = p - result;
if (needed != resultlen) if (needed != resultlen)
log_debug ("length mismatch: %lu, %lu\n", log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)resultlen); (unsigned long)needed, (unsigned long)resultlen);
*r_length = resultlen; *r_length = resultlen;
@ -2476,7 +2640,7 @@ build_key_bag (unsigned char *buffer, size_t buflen, char *salt,
keybaglen = p - keybag; keybaglen = p - keybag;
if (needed != keybaglen) if (needed != keybaglen)
log_debug ("length mismatch: %lu, %lu\n", log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)keybaglen); (unsigned long)needed, (unsigned long)keybaglen);
*r_length = keybaglen; *r_length = keybaglen;
@ -2575,7 +2739,7 @@ build_cert_bag (unsigned char *buffer, size_t buflen, char *salt,
certbaglen = p - certbag; certbaglen = p - certbag;
if (needed != certbaglen) if (needed != certbaglen)
log_debug ("length mismatch: %lu, %lu\n", log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)certbaglen); (unsigned long)needed, (unsigned long)certbaglen);
*r_length = certbaglen; *r_length = certbaglen;
@ -2686,7 +2850,7 @@ build_cert_sequence (const unsigned char *buffer, size_t buflen,
certseqlen = p - certseq; certseqlen = p - certseq;
if (needed != certseqlen) if (needed != certseqlen)
log_debug ("length mismatch: %lu, %lu\n", log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)certseqlen); (unsigned long)needed, (unsigned long)certseqlen);
/* Append some pad characters; we already allocated extra space. */ /* Append some pad characters; we already allocated extra space. */
@ -2852,8 +3016,8 @@ p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen,
} }
/* This is actually not a pkcs#12 function but one which creates an /* This is actually not a PKCS#12 function but one which creates an
unencrypted a pkcs#1 private key. */ * unencrypted PKCS#1 private key. */
unsigned char * unsigned char *
p12_raw_build (gcry_mpi_t *kparms, int rawmode, size_t *r_length) p12_raw_build (gcry_mpi_t *kparms, int rawmode, size_t *r_length)
{ {