/*
 * Copyright (C) 2012 Red Hat Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above
 *       copyright notice, this list of conditions and the
 *       following disclaimer.
 *     * Redistributions in binary form must reproduce the
 *       above copyright notice, this list of conditions and
 *       the following disclaimer in the documentation and/or
 *       other materials provided with the distribution.
 *     * The names of contributors to this software may not be
 *       used to endorse or promote products derived from this
 *       software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * Author: Stef Walter <stefw@redhat.com>
 */

#include "config.h"

#include "array.h"
#include "asn1.h"
#include "attrs.h"
#define P11_DEBUG_FLAG P11_DEBUG_TRUST
#include "debug.h"
#include "dict.h"
#include "digest.h"
#include "message.h"
#include "module.h"
#include "oid.h"
#include "parser.h"
#include "path.h"
#include "pem.h"
#include "pkcs11x.h"
#include "persist.h"
#include "types.h"
#include "x509.h"

#include <libtasn1.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef ENABLE_NLS
#include <libintl.h>
#define _(x) dgettext(PACKAGE_NAME, x)
#else
#define _(x) (x)
#endif

struct _p11_parser {
	p11_asn1_cache *asn1_cache;
	p11_dict *asn1_defs;
	bool asn1_owned;
	p11_persist *persist;
	char *basename;
	p11_array *parsed;
	p11_array *formats;
	int flags;
};

#define ID_LENGTH P11_DIGEST_SHA1_LEN

typedef int (* parser_func)   (p11_parser *parser,
                               const unsigned char *data,
                               size_t length);

static CK_ATTRIBUTE *
populate_trust (p11_parser *parser,
                CK_ATTRIBUTE *attrs)
{
	CK_BBOOL trustedv;
	CK_BBOOL distrustv;

	CK_ATTRIBUTE trusted = { CKA_TRUSTED, &trustedv, sizeof (trustedv) };
	CK_ATTRIBUTE distrust = { CKA_X_DISTRUSTED, &distrustv, sizeof (distrustv) };

	/*
	 * If we're are parsing an anchor location, then warn about any ditsrusted
	 * certificates there, but don't go ahead and automatically make them
	 * trusted anchors.
	 */
	if (parser->flags & P11_PARSE_FLAG_ANCHOR) {
		if (p11_attrs_find_bool (attrs, CKA_X_DISTRUSTED, &distrustv) && distrustv) {
			p11_message (_("certificate with distrust in location for anchors: %s"), parser->basename);
			return attrs;

		}

		trustedv = CK_TRUE;
		distrustv = CK_FALSE;

	/*
	 * If we're parsing a blocklist location, then force all certificates to
	 * be distrusted, regardless of whether they contain anchor information.
	 */
	} else if (parser->flags & P11_PARSE_FLAG_BLOCKLIST) {
		if (p11_attrs_find_bool (attrs, CKA_TRUSTED, &trustedv) && trustedv)
			p11_message (_("overriding trust for anchor in blocklist: %s"), parser->basename);

		trustedv = CK_FALSE;
		distrustv = CK_TRUE;

	/*
	 * If the location doesn't have a flag, then fill in trust attributes
	 * if they are missing: neither an anchor or blocklist.
	 */
	} else {
		trustedv = CK_FALSE;
		distrustv = CK_FALSE;

		if (p11_attrs_find_valid (attrs, CKA_TRUSTED))
			trusted.type = CKA_INVALID;
		if (p11_attrs_find_valid (attrs, CKA_X_DISTRUSTED))
			distrust.type = CKA_INVALID;
	}

	return p11_attrs_build (attrs, &trusted, &distrust, NULL);
}

static bool
sink_object (p11_parser *parser,
             CK_ATTRIBUTE *attrs)
{
	CK_OBJECT_CLASS klass;

	if (p11_attrs_find_ulong (attrs, CKA_CLASS, &klass) &&
	    klass == CKO_CERTIFICATE) {
		attrs = populate_trust (parser, attrs);
		return_val_if_fail (attrs != NULL, false);
	}

	if (!p11_array_push (parser->parsed, attrs)) {
		p11_attrs_free (attrs);
		return_val_if_reached (false);
	}

	return true;
}

static CK_ATTRIBUTE *
certificate_attrs (p11_parser *parser,
                   const unsigned char *der,
                   size_t der_len)
{
	CK_OBJECT_CLASS klassv = CKO_CERTIFICATE;
	CK_CERTIFICATE_TYPE x509 = CKC_X_509;
	CK_BBOOL modifiablev = CK_FALSE;

	CK_ATTRIBUTE modifiable = { CKA_MODIFIABLE, &modifiablev, sizeof (modifiablev) };
	CK_ATTRIBUTE klass = { CKA_CLASS, &klassv, sizeof (klassv) };
	CK_ATTRIBUTE certificate_type = { CKA_CERTIFICATE_TYPE, &x509, sizeof (x509) };
	CK_ATTRIBUTE value = { CKA_VALUE, (void *)der, der_len };

	return p11_attrs_build (NULL, &klass, &modifiable, &certificate_type, &value, NULL);
}

int
p11_parser_format_x509 (p11_parser *parser,
                        const unsigned char *data,
                        size_t length)
{
	char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE];
	CK_ATTRIBUTE *attrs = NULL;
	CK_ATTRIBUTE *value = NULL;
	asn1_node cert = NULL;

	cert = p11_asn1_decode (parser->asn1_defs, "PKIX1.Certificate", data, length, message);
	if (cert == NULL)
		return P11_PARSE_UNRECOGNIZED;

	attrs = certificate_attrs (parser, data, length);
	if (attrs == NULL) {
		asn1_delete_structure (&cert);
		return_val_if_reached (P11_PARSE_FAILURE);
	}

	value = p11_attrs_find_valid (attrs, CKA_VALUE);
	if (value == NULL) {
		asn1_delete_structure (&cert);
		p11_attrs_free (attrs);
		return_val_if_reached (P11_PARSE_FAILURE);
	}
	p11_asn1_cache_take (parser->asn1_cache, cert, "PKIX1.Certificate",
	                     value->pValue, value->ulValueLen);

	if (!sink_object (parser, attrs)) {
		asn1_delete_structure (&cert);
		return_val_if_reached (P11_PARSE_FAILURE);
	}
	return P11_PARSE_SUCCESS;
}

static CK_ATTRIBUTE *
extension_attrs (p11_parser *parser,
                 const CK_ATTRIBUTE *public_key_info,
                 const char *oid_str,
                 const unsigned char *oid_der,
                 bool critical,
                 const unsigned char *value,
                 int length)
{
	CK_OBJECT_CLASS klassv = CKO_X_CERTIFICATE_EXTENSION;
	CK_BBOOL modifiablev = CK_FALSE;

	CK_ATTRIBUTE klass = { CKA_CLASS, &klassv, sizeof (klassv) };
	CK_ATTRIBUTE modifiable = { CKA_MODIFIABLE, &modifiablev, sizeof (modifiablev) };
	CK_ATTRIBUTE oid = { CKA_OBJECT_ID, (void *)oid_der, p11_oid_length (oid_der) };

	CK_ATTRIBUTE *attrs = NULL;
	asn1_node dest = NULL;
	unsigned char *der = NULL;
	size_t len;
	int ret;

	attrs = p11_attrs_build (NULL, public_key_info, &klass, &modifiable, &oid, NULL);
	return_val_if_fail (attrs != NULL, NULL);

	dest = p11_asn1_create (parser->asn1_defs, "PKIX1.Extension");
	if (dest == NULL) {
		warn_if_reached ();
		goto fail;
	}

	ret = asn1_write_value (dest, "extnID", oid_str, 1);
	if (ret != ASN1_SUCCESS) {
		warn_if_reached ();
		goto fail;
	}

	if (critical) {
		ret = asn1_write_value (dest, "critical", "TRUE", 1);
		if (ret != ASN1_SUCCESS) {
			warn_if_reached ();
			goto fail;
		}
	}

	ret = asn1_write_value (dest, "extnValue", value, length);
	if (ret != ASN1_SUCCESS) {
		warn_if_reached ();
		goto fail;
	}

	der = p11_asn1_encode (dest, &len);
	if (der == NULL) {
		warn_if_reached ();
		goto fail;
	}

	attrs = p11_attrs_take (attrs, CKA_VALUE, der, len);
	if (attrs == NULL) {
		warn_if_reached ();
		free (der);
		goto fail;
	}

	/* An opmitization so that the builder can get at this without parsing */
	p11_asn1_cache_take (parser->asn1_cache, dest, "PKIX1.Extension", der, len);
	return attrs;

fail:
	asn1_delete_structure (&dest);
	p11_attrs_free (attrs);
	return NULL;
}

static CK_ATTRIBUTE *
attached_attrs (p11_parser *parser,
                const CK_ATTRIBUTE *public_key_info,
                const char *oid_str,
                const unsigned char *oid_der,
                bool critical,
                asn1_node ext)
{
	CK_ATTRIBUTE *attrs;
	unsigned char *der;
	size_t len;

	der = p11_asn1_encode (ext, &len);
	return_val_if_fail (der != NULL, NULL);

	attrs = extension_attrs (parser, public_key_info, oid_str, oid_der,
	                         critical, der, len);
	free (der);
	return_val_if_fail (attrs != NULL, NULL);

	return attrs;
}

static p11_dict *
load_seq_of_oid_str (asn1_node node,
                     const char *seqof)
{
	p11_dict *oids;
	char field[128];
	char *oid;
	size_t len;
	int i;

	oids = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, free, NULL);
	return_val_if_fail (oids != NULL, NULL);

	for (i = 1; ; i++) {
		if (snprintf (field, sizeof (field), "%s.?%u", seqof, i) < 0) {
			p11_dict_free (oids);
			return_val_if_reached (NULL);
		}

		oid = p11_asn1_read (node, field, &len);
		if (oid == NULL)
			break;

		if (!p11_dict_set (oids, oid, oid)) {
			free (oid);
			p11_dict_free (oids);
			return_val_if_reached (NULL);
		}
	}

	return oids;
}

static CK_ATTRIBUTE *
attached_eku_attrs (p11_parser *parser,
                    const CK_ATTRIBUTE *public_key_info,
                    const char *oid_str,
                    const unsigned char *oid_der,
                    bool critical,
                    p11_dict *oid_strs)
{
	CK_ATTRIBUTE *attrs;
	p11_dictiter iter;
	asn1_node dest;
	int count = 0;
	void *value;
	int ret;

	dest = p11_asn1_create (parser->asn1_defs, "PKIX1.ExtKeyUsageSyntax");
	return_val_if_fail (dest != NULL, NULL);

	p11_dict_iterate (oid_strs, &iter);
	while (p11_dict_next (&iter, NULL, &value)) {
		ret = asn1_write_value (dest, "", "NEW", 1);
		if (ret != ASN1_SUCCESS) {
			asn1_delete_structure (&dest);
			return_val_if_reached (NULL);
		}

		ret = asn1_write_value (dest, "?LAST", value, -1);
		if (ret != ASN1_SUCCESS) {
			asn1_delete_structure (&dest);
			return_val_if_reached (NULL);
		}

		count++;
	}

	/*
	 * If no oids have been written, then we have to put in a reserved
	 * value, due to the way that ExtendedKeyUsage is defined in RFC 5280.
	 * There must be at least one purpose. This is important since *not*
	 * having an ExtendedKeyUsage is very different than having one without
	 * certain usages.
	 *
	 * We account for this in p11_parse_extended_key_usage(). However for
	 * most callers this should not matter, as they only check whether a
	 * given purpose is present, and don't make assumptions about ones
	 * that they don't know about.
	 */

	if (count == 0) {
		ret = asn1_write_value (dest, "", "NEW", 1);
		if (ret != ASN1_SUCCESS) {
			asn1_delete_structure (&dest);
			return_val_if_reached (NULL);
		}

		ret = asn1_write_value (dest, "?LAST", P11_OID_RESERVED_PURPOSE_STR, -1);
		if (ret != ASN1_SUCCESS) {
			asn1_delete_structure (&dest);
			return_val_if_reached (NULL);
		}
	}


	attrs = attached_attrs (parser, public_key_info, oid_str, oid_der, critical, dest);
	asn1_delete_structure (&dest);

	return attrs;
}

/* Call to this function always invalidates pointer to `cert`
 */
static CK_ATTRIBUTE *
build_openssl_extensions (p11_parser *parser,
                          CK_ATTRIBUTE *cert,
                          const CK_ATTRIBUTE *public_key_info,
                          asn1_node aux,
                          const unsigned char *aux_der,
                          size_t aux_len)
{
	CK_BBOOL trusted = CK_FALSE;
	CK_BBOOL distrust = CK_FALSE;

	CK_ATTRIBUTE trust_attrs[] = {
		{ CKA_TRUSTED, &trusted, sizeof (trusted) },
		{ CKA_X_DISTRUSTED, &distrust, sizeof (distrust) },
		{ CKA_INVALID },
	};

	CK_ATTRIBUTE *attrs;
	p11_dict *trust = NULL;
	p11_dict *reject = NULL;
	p11_dictiter iter;
	void *key;
	int start;
	int end;
	int ret;
	int num;

	/*
	 * This will load an empty list if there is no OPTIONAL trust field.
	 * OpenSSL assumes that for a TRUSTED CERTIFICATE a missing trust field
	 * is identical to untrusted for all purposes.
	 *
	 * This is different from ExtendedKeyUsage, where a missing certificate
	 * extension means that it is trusted for all purposes.
	 */
	trust = load_seq_of_oid_str (aux, "trust");

	ret = asn1_number_of_elements (aux, "reject", &num);
	if (ret != ASN1_SUCCESS && ret != ASN1_ELEMENT_NOT_FOUND) {
		warn_if_reached ();
		goto fail;
	}
	if (ret == ASN1_SUCCESS)
		reject = load_seq_of_oid_str (aux, "reject");

	/* Remove all rejected oids from the trust set */
	if (trust && reject) {
		p11_dict_iterate (reject, &iter);
		while (p11_dict_next (&iter, &key, NULL))
			p11_dict_remove (trust, key);
	}

	/*
	 * The trust field (or lack of it) becomes a standard ExtKeyUsageSyntax.
	 *
	 * critical: require that this is enforced
	 */

	if (trust) {
		attrs = attached_eku_attrs (parser, public_key_info,
		                            P11_OID_EXTENDED_KEY_USAGE_STR,
		                            P11_OID_EXTENDED_KEY_USAGE,
		                            true, trust);
		if (attrs == NULL) {
			warn_if_reached ();
			goto fail;
		}
		if (!sink_object (parser, attrs)) {
			warn_if_reached ();
			goto fail;
		}
	}

	/*
	 * For the reject field we use a custom defined extension. We track this
	 * for completeness, although the above ExtendedKeyUsage extension handles
	 * this data fine. See oid.h for more details. It uses ExtKeyUsageSyntax structure.
	 *
	 * non-critical: non-standard, and also covered by trusts
	 */

	if (reject && p11_dict_size (reject) > 0) {
		attrs = attached_eku_attrs (parser, public_key_info,
		                            P11_OID_OPENSSL_REJECT_STR,
		                            P11_OID_OPENSSL_REJECT,
		                            false, reject);
		if (attrs == NULL) {
			warn_if_reached ();
			goto fail;
		}
		if (!sink_object (parser, attrs)) {
			warn_if_reached ();
			goto fail;
		}
	}

	/*
	 * OpenSSL model blocklists as anchors with all purposes being removed/rejected,
	 * we account for that here. If there is an ExtendedKeyUsage without any
	 * useful purposes, then treat like a blocklist.
	 */
	if (trust && p11_dict_size (trust) == 0) {
		trusted = CK_FALSE;
		distrust = CK_TRUE;

	/*
	 * Otherwise a 'TRUSTED CERTIFICATE' in an input directory is enough to
	 * mark this as a trusted certificate.
	 */
	} else if (trust && p11_dict_size (trust) > 0) {
		trusted = CK_TRUE;
		distrust = CK_FALSE;
	}

	/*
	 * OpenSSL model blocklists as anchors with all purposes being removed/rejected,
	 * we account for that here. If there is an ExtendedKeyUsage without any
	 * useful purposes, then treat like a blocklist.
	 */

	cert = p11_attrs_merge (cert, p11_attrs_dup (trust_attrs), true);
	if (cert == NULL) {
		warn_if_reached ();
		goto fail;
	}

	p11_dict_free (trust);
	p11_dict_free (reject);
	trust = NULL;
	reject = NULL;

	/*
	 * For the keyid field we use the SubjectKeyIdentifier extension. It
	 * is already in the correct form, an OCTET STRING.
	 *
	 * non-critical: as recommended in RFC 5280
	 */

	ret = asn1_der_decoding_startEnd (aux, aux_der, aux_len, "keyid", &start, &end);
	if (ret != ASN1_SUCCESS && ret != ASN1_ELEMENT_NOT_FOUND) {
		warn_if_reached ();
		goto fail;
	}

	if (ret == ASN1_SUCCESS) {
		attrs = extension_attrs (parser, public_key_info,
		                         P11_OID_SUBJECT_KEY_IDENTIFIER_STR,
		                         P11_OID_SUBJECT_KEY_IDENTIFIER,
		                         false, aux_der + start, (end - start) + 1);
		if (attrs == NULL || !sink_object (parser, attrs)) {
			warn_if_reached ();
			goto fail;
		}
	}

	return cert;

fail:
	p11_dict_free (trust);
	p11_dict_free (reject);
	p11_attrs_free (cert);
	return NULL;
}

static int
parse_openssl_trusted_certificate (p11_parser *parser,
                                   const unsigned char *data,
                                   size_t length)
{
	char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE];
	CK_ATTRIBUTE public_key_info = { CKA_PUBLIC_KEY_INFO };
	CK_ATTRIBUTE *value = NULL;
	CK_ATTRIBUTE *attrs = NULL;
	char *label = NULL;
	asn1_node cert = NULL;
	asn1_node aux = NULL;
	ssize_t cert_len;
	size_t len;
	int start;
	int end;
	bool ok;
	int ret = P11_PARSE_FAILURE;

	/*
	 * This OpenSSL format is weird. It's just two DER structures
	 * placed end to end without any wrapping SEQ. So calculate the
	 * length of the first DER TLV we see and try to parse that as
	 * the X.509 certificate.
	 */

	cert_len = p11_asn1_tlv_length (data, length);
	if (cert_len <= 0)
		return P11_PARSE_UNRECOGNIZED;

	cert = p11_asn1_decode (parser->asn1_defs, "PKIX1.Certificate", data, cert_len, message);
	if (cert == NULL)
		return P11_PARSE_UNRECOGNIZED;

	/* OpenSSL sometimes outputs TRUSTED CERTIFICATE format without the CertAux supplement */
	if (cert_len < length) {
		aux = p11_asn1_decode (parser->asn1_defs, "OPENSSL.CertAux", data + cert_len,
		                       length - cert_len, message);
		if (aux == NULL) {
			ret = P11_PARSE_UNRECOGNIZED;
			goto cleanup;
		}
	}

	attrs = certificate_attrs (parser, data, cert_len);
	if (attrs == NULL) {
		warn_if_reached();
		goto cleanup;
	}

	/* Cache the parsed certificate ASN.1 for later use by the builder */
	value = p11_attrs_find_valid (attrs, CKA_VALUE);
	if (value == NULL) {
		warn_if_reached();
		goto cleanup;
	}

	/* Pull out the subject public key info */
	if (asn1_der_decoding_startEnd (cert, data, cert_len,
	                                "tbsCertificate.subjectPublicKeyInfo",
					&start, &end) != ASN1_SUCCESS) {
		warn_if_reached();
		goto cleanup;
	}

	public_key_info.pValue = (char *)data + start;
	public_key_info.ulValueLen = (end - start) + 1;

	p11_asn1_cache_take (parser->asn1_cache, cert, "PKIX1.Certificate",
	                     value->pValue, value->ulValueLen);
	/* cache owns cert */
	cert = NULL;

	/* Pull the label out of the CertAux */
	if (aux) {
		len = 0;
		label = p11_asn1_read (aux, "alias", &len);
		if (label != NULL) {
			attrs = p11_attrs_take (attrs, CKA_LABEL, label, strlen (label));
			if (attrs == NULL) {
				warn_if_reached();
				goto cleanup;
			}
			/* attrs owns label */
			label = NULL;
		}

		attrs = build_openssl_extensions (parser, attrs, &public_key_info, aux,
		                                  data + cert_len, length - cert_len);
		if (attrs == NULL) {
			warn_if_reached();
			goto cleanup;
		}
	}

	ok = sink_object (parser, attrs);
	/* attrs freed or owned by parser */
	attrs = NULL;
	if (!ok) {
		warn_if_reached();
		goto cleanup;
	}
	ret = P11_PARSE_SUCCESS;

cleanup:
	asn1_delete_structure (&cert);
	asn1_delete_structure (&aux);
	p11_attrs_free (attrs);
	free (label);
	return ret;
}

static void
on_pem_block (const char *type,
              const unsigned char *contents,
              size_t length,
              void *user_data)
{
	p11_parser *parser = user_data;
	int ret;

	if (strcmp (type, "CERTIFICATE") == 0) {
		ret = p11_parser_format_x509 (parser, contents, length);

	} else if (strcmp (type, "TRUSTED CERTIFICATE") == 0) {
		ret = parse_openssl_trusted_certificate (parser, contents, length);

	} else {
		p11_debug ("Saw unsupported or unrecognized PEM block of type %s", type);
		ret = P11_PARSE_SUCCESS;
	}

	if (ret != P11_PARSE_SUCCESS)
		p11_message (_("Couldn't parse PEM block of type %s"), type);
}

int
p11_parser_format_pem (p11_parser *parser,
                       const unsigned char *data,
                       size_t length)
{
	int num;

	num = p11_pem_parse ((const char *)data, length, on_pem_block, parser);

	if (num == 0)
		return P11_PARSE_UNRECOGNIZED;

	return P11_PARSE_SUCCESS;
}

int
p11_parser_format_persist (p11_parser *parser,
                           const unsigned char *data,
                           size_t length)
{
	CK_BBOOL modifiablev = CK_TRUE;
	CK_ATTRIBUTE *attrs;
	p11_array *objects;
	bool ret;
	int i;

	CK_ATTRIBUTE modifiable = { CKA_MODIFIABLE, &modifiablev, sizeof (modifiablev) };

	if (!p11_persist_magic (data, length))
		return P11_PARSE_UNRECOGNIZED;

	if (!parser->persist) {
		parser->persist = p11_persist_new ();
		return_val_if_fail (parser->persist != NULL, P11_PARSE_UNRECOGNIZED);
	}

	objects = p11_array_new (p11_attrs_free);
	return_val_if_fail (objects != NULL, P11_PARSE_FAILURE);

	ret = p11_persist_read (parser->persist, parser->basename, data, length, objects);
	if (ret) {
		if (!p11_persist_is_generated (data, length))
			modifiablev = CK_FALSE;
		for (i = 0; i < objects->num; i++) {
			attrs = p11_attrs_build (objects->elem[i], &modifiable, NULL);

			/* Prevent double-free */
			objects->elem[i] = NULL;

			if (attrs == NULL || !sink_object (parser, attrs)) {
				p11_array_free (objects);
				return_val_if_reached (P11_PARSE_FAILURE);
			}
		}
	}

	p11_array_free (objects);
	return ret ? P11_PARSE_SUCCESS : P11_PARSE_UNRECOGNIZED;
}

p11_parser *
p11_parser_new (p11_asn1_cache *asn1_cache)
{
	p11_parser parser = { 0, }, *result;

	if (asn1_cache == NULL) {
		parser.asn1_owned = true;
		parser.asn1_defs = p11_asn1_defs_load ();
	} else {
		parser.asn1_defs = p11_asn1_cache_defs (asn1_cache);
		parser.asn1_cache = asn1_cache;
		parser.asn1_owned = false;
	}

	parser.parsed = p11_array_new (p11_attrs_free);
	if (parser.parsed == NULL) {
		if (parser.asn1_owned)
			p11_dict_free (parser.asn1_defs);
		return_val_if_reached (NULL);
	}

	result = memdup (&parser, sizeof (parser));
	if (result == NULL) {
		p11_array_free (parser.parsed);
		if (parser.asn1_owned)
			p11_dict_free (parser.asn1_defs);
		return_val_if_reached (NULL);
	}
	return result;
}

void
p11_parser_free (p11_parser *parser)
{
	return_if_fail (parser != NULL);
	p11_persist_free (parser->persist);
	p11_array_free (parser->parsed);
	p11_array_free (parser->formats);
	if (parser->asn1_owned)
		p11_dict_free (parser->asn1_defs);
	free (parser);
}

p11_array *
p11_parser_parsed (p11_parser *parser)
{
	return_val_if_fail (parser != NULL, NULL);
	return parser->parsed;
}

void
p11_parser_formats (p11_parser *parser,
                    ...)
{
	p11_array *formats;
	parser_func func;
	va_list va;

	formats = p11_array_new (NULL);
	return_if_fail (formats != NULL);

	va_start (va, parser);
	for (;;) {
		func = va_arg (va, parser_func);
		if (func == NULL)
			break;
		if (!p11_array_push (formats, func)) {
			p11_array_free (formats);
			va_end (va);
			return_if_reached ();
		}
	}
	va_end (va);

	p11_array_free (parser->formats);
	parser->formats = formats;
}

int
p11_parse_memory (p11_parser *parser,
                  const char *filename,
                  int flags,
                  const unsigned char *data,
                  size_t length)
{
	int ret = P11_PARSE_UNRECOGNIZED;
	char *base;
	int i;

	return_val_if_fail (parser != NULL, P11_PARSE_FAILURE);
	return_val_if_fail (filename != NULL, P11_PARSE_FAILURE);
	return_val_if_fail (parser->formats != NULL, P11_PARSE_FAILURE);

	p11_array_clear (parser->parsed);
	base = p11_path_base (filename);
	parser->basename = base;
	parser->flags = flags;

	for (i = 0; ret == P11_PARSE_UNRECOGNIZED && i < parser->formats->num; i++)
		ret = ((parser_func)parser->formats->elem[i]) (parser, data, length);

	p11_asn1_cache_flush (parser->asn1_cache);

	free (base);
	parser->basename = NULL;
	parser->flags = 0;

	return ret;
}

int
p11_parse_file (p11_parser *parser,
                const char *filename,
                struct stat *sb,
                int flags)
{
	p11_mmap *map;
	void *data;
	size_t size;
	int ret;

	return_val_if_fail (parser != NULL, P11_PARSE_FAILURE);
	return_val_if_fail (filename != NULL, P11_PARSE_FAILURE);

	map = p11_mmap_open (filename, sb, &data, &size);
	if (map == NULL) {
		p11_message_err (errno, _("couldn't open and map file: %s"), filename);
		return P11_PARSE_FAILURE;
	}

	ret = p11_parse_memory (parser, filename, flags, data, size);

	p11_mmap_close (map);
	return ret;
}
