diff options
| -rw-r--r-- | configure.ac | 1 | ||||
| -rw-r--r-- | doc/Makefile.am | 1 | ||||
| -rw-r--r-- | doc/p11-kit.xml | 95 | ||||
| -rw-r--r-- | doc/style.css | 4 | ||||
| -rw-r--r-- | tools/Makefile.am | 20 | ||||
| -rw-r--r-- | tools/extract-info.c | 359 | ||||
| -rw-r--r-- | tools/extract-x509.c | 116 | ||||
| -rw-r--r-- | tools/extract.c | 461 | ||||
| -rw-r--r-- | tools/extract.h | 110 | ||||
| -rw-r--r-- | tools/tests/Makefile.am | 15 | ||||
| -rw-r--r-- | tools/tests/test-extract.c | 301 | ||||
| -rw-r--r-- | tools/tests/test-x509.c | 276 | ||||
| -rw-r--r-- | tools/tests/test.h | 33 | ||||
| -rw-r--r-- | tools/tool.c | 3 | ||||
| -rw-r--r-- | tools/tool.h | 3 | 
15 files changed, 1796 insertions, 2 deletions
| diff --git a/configure.ac b/configure.ac index a6bb696..e7e490a 100644 --- a/configure.ac +++ b/configure.ac @@ -135,6 +135,7 @@ if test "$with_libtasn1" != "no"; then  	AC_SUBST(LIBTASN1_CFLAGS)  	AC_SUBST(LIBTASN1_LIBS)  	with_libtasn1="yes" +	AC_DEFINE_UNQUOTED(WITH_ASN1, 1, [Build with libtasn1 and certificate support])  fi  AM_CONDITIONAL(WITH_ASN1, test "$with_libtasn1" = "yes") diff --git a/doc/Makefile.am b/doc/Makefile.am index e5befe7..4fd8d54 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -118,5 +118,4 @@ CLEANFILES += \  EXTRA_DIST += \  	version.xml.in \  	version.xml \ -	$(MAN_IN_FILES) \  	$(NULL) diff --git a/doc/p11-kit.xml b/doc/p11-kit.xml index b885dda..f2af9a6 100644 --- a/doc/p11-kit.xml +++ b/doc/p11-kit.xml @@ -32,6 +32,10 @@  	<cmdsynopsis>  		<command>p11-kit list-modules</command>  	</cmdsynopsis> +	<cmdsynopsis> +		<command>p11-kit extract</command> <arg choice="plain">--filter=<what></arg>  +			<arg choice="plain">--format=<type></arg> /path/to/destination +	</cmdsynopsis>  </refsynopsisdiv>  <refsect1> @@ -73,6 +77,97 @@ $ p11-kit list-modules  </refsect1>  <refsect1> +	<title>Extract</title> + +	<para>Extract certificates from configured PKCS#11 modules.</para> + +<programlisting> +$ p11-kit extract --format=x509-directory --filter=ca-certificates /path/to/directory +</programlisting> + +	<para>You can specify the following options to control what to extract. +	The <option>--filter</option> and <option>--format</option> arguments +	should be specified. By default this command will not overwrite the +	destination file or directory.</para> + +	<variablelist> +		<varlistentry> +			<term><option>--filter=<what></option></term> +			<listitem><para>Specifies what certificates to export. +			You can specify the following values: +			<variablelist> +				<varlistentry> +					<term><option>ca-anchors</option></term> +					<listitem><para>Certificate anchors (default)</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>blacklist</option></term> +					<listitem><para>Blacklisted certificates</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>certificates</option></term> +					<listitem><para>All certificates</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>pkcs11:object=xx</option></term> +					<listitem><para>A PKCS#11 URI</para></listitem> +				</varlistentry> +			</variablelist> +			</para></listitem> +		</varlistentry> +		<varlistentry> +			<term><option>--format=<type></option></term> +			<listitem><para>The format of the destination file or directory. +			You can specify one of the following values: +			<variablelist> +				<varlistentry> +					<term><option>x509-file</option></term> +					<listitem><para>DER X.509 certificate file</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>x509-directory</option></term> +					<listitem><para>directory of X.509 certificates</para></listitem> +				</varlistentry> +			</variablelist> +			</para></listitem> +		</varlistentry> +		<varlistentry> +			<term><option>--overwrite</option></term> +			<listitem><para>Overwrite output file or directory.</para></listitem> +		</varlistentry> +		<varlistentry> +			<term><option>--purpose=<usage></option></term> +			<listitem><para>Limit to certificates usable for the given purpose +			You can specify one of the following values: +			<variablelist> +				<varlistentry> +					<term><option>server-auth</option></term> +					<listitem><para>For authenticating servers</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>client-auth</option></term> +					<listitem><para>For authenticating clients</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>email</option></term> +					<listitem><para>For email protection</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>code-signing</option></term> +					<listitem><para>For authenticated signed code</para></listitem> +				</varlistentry> +				<varlistentry> +					<term><option>1.2.3.4.5...</option></term> +					<listitem><para>An arbitrary purpose OID</para></listitem> +				</varlistentry> +			</variablelist> +			</para></listitem> +		</varlistentry> +	</variablelist> + +</refsect1> + +<refsect1>    <title>Bugs</title>    <para>      Please send bug reports to either the distribution bug tracker diff --git a/doc/style.css b/doc/style.css index b4b8d47..3d0f951 100644 --- a/doc/style.css +++ b/doc/style.css @@ -110,3 +110,7 @@ DIV.toc DT {  TABLE.variablelist SPAN.term {  	padding-right: 1em;  } + +DIV.cmdsynopsis { +	font-family: monospace; +} diff --git a/tools/Makefile.am b/tools/Makefile.am index fab1bd9..d07eda0 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -18,13 +18,31 @@ bin_PROGRAMS = \  p11_kit_SOURCES = \  	list.c \ -	save.c save.h \  	tool.c tool.h \  	$(NULL) +p11_kit_CFLAGS = \ +	$(LIBTASN1_CFLAGS) \ +	$(NULL) +  p11_kit_LDADD = \  	$(top_builddir)/p11-kit/libp11-kit.la \  	$(top_builddir)/common/libp11-library.la \  	$(top_builddir)/common/libp11-compat.la \  	$(LTLIBINTL) \  	$(NULL) + +if WITH_ASN1 + +p11_kit_LDADD += \ +	$(top_builddir)/common/libp11-data.la \ +	$(LIBTASN1_LIBS) + +p11_kit_SOURCES += \ +	extract.c extract.h \ +	extract-info.c \ +	extract-x509.c \ +	save.c save.h \ +	$(NULL) + +endif # WITH_ASN1 diff --git a/tools/extract-info.c b/tools/extract-info.c new file mode 100644 index 0000000..aa66c83 --- /dev/null +++ b/tools/extract-info.c @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2013, 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 "attrs.h" +#include "debug.h" +#include "oid.h" +#include "dict.h" +#include "extract.h" +#include "library.h" +#include "pkcs11.h" +#include "pkcs11x.h" +#include "x509.h" + +#include <stdlib.h> +#include <string.h> + +static p11_dict * +load_stapled_extensions (CK_FUNCTION_LIST_PTR module, +                         CK_SLOT_ID slot_id, +                         CK_ATTRIBUTE *id) +{ +	CK_OBJECT_CLASS extension = CKO_X_CERTIFICATE_EXTENSION; +	CK_ATTRIBUTE *attrs; +	P11KitIter *iter; +	CK_RV rv = CKR_OK; +	p11_dict *stapled; + +	CK_ATTRIBUTE match[] = { +		{ CKA_CLASS, &extension, sizeof (extension) }, +		{ CKA_ID, id->pValue, id->ulValueLen }, +	}; + +	CK_ATTRIBUTE template[] = { +		{ CKA_OBJECT_ID, }, +		{ CKA_X_CRITICAL, }, +		{ CKA_VALUE, }, +	}; + +	stapled = p11_dict_new (p11_attr_hash, +	                        (p11_dict_equals)p11_attr_equal, +	                        NULL, p11_attrs_free); + +	/* No ID to use, just short circuit */ +	if (!id->pValue || !id->ulValueLen) +		return stapled; + +	iter = p11_kit_iter_new (NULL); +	p11_kit_iter_add_filter (iter, match, 2); +	p11_kit_iter_begin_with (iter, module, slot_id, 0); + +	while (rv == CKR_OK) { +		rv = p11_kit_iter_next (iter); +		if (rv == CKR_OK) { +			attrs = p11_attrs_buildn (NULL, template, 3); +			rv = p11_kit_iter_load_attributes (iter, attrs, 3); +			if (rv == CKR_OK || rv == CKR_ATTRIBUTE_TYPE_INVALID) { +				/* CKA_OBJECT_ID is the first attribute, use it as the key */ +				if (!p11_dict_set (stapled, attrs, attrs)) +					return_val_if_reached (NULL); +				rv = CKR_OK; +			} else { +				p11_attrs_free (attrs); +			} +		} +	} + +	if (rv != CKR_OK && rv != CKR_CANCEL) { +		p11_message ("couldn't load stapled extensions for certificate: %s", p11_kit_strerror (rv)); +		p11_dict_free (stapled); +		stapled = NULL; +	} + +	p11_kit_iter_free (iter); +	return stapled; +} + +static int +extract_purposes (p11_extract_info *ex) +{ +	CK_ATTRIBUTE oid = { CKA_OBJECT_ID, +	                     (void *)P11_OID_EXTENDED_KEY_USAGE, +	                     sizeof (P11_OID_EXTENDED_KEY_USAGE) }; +	const unsigned char *ext = NULL; +	unsigned char *alloc = NULL; +	CK_ATTRIBUTE *value; +	CK_ATTRIBUTE *attrs; +	size_t ext_len; + +	if (ex->stapled) { +		attrs = p11_dict_get (ex->stapled, &oid); +		if (attrs != NULL) { +			value = p11_attrs_find (attrs, CKA_VALUE); +			if (value) { +				ext = value->pValue; +				ext_len = value->ulValueLen; +			} +		} +	} + +	if (ext == NULL && ex->cert_asn) { +		alloc = p11_x509_find_extension (ex->cert_asn, P11_OID_EXTENDED_KEY_USAGE, +		                                 ex->cert_der, ex->cert_len, &ext_len); +		ext = alloc; +	} + +	/* No such extension, match anything */ +	if (ext == NULL) +		return 1; + +	ex->purposes = p11_x509_parse_extended_key_usage (ex->asn1_defs, ext, ext_len); + +	free (alloc); +	return ex->purposes != NULL; +} + +static int +extract_certificate (P11KitIter *iter, +                     p11_extract_info *ex) +{ +	char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; +	CK_ATTRIBUTE *attr; +	CK_ULONG type; + +	/* Don't even bother with not X.509 certificates */ +	if (!p11_attrs_find_ulong (ex->attrs, CKA_CERTIFICATE_TYPE, &type)) +		type = (CK_ULONG)-1; +	if (type != CKC_X_509) +		return 0; + +	attr = p11_attrs_find_valid (ex->attrs, CKA_VALUE); +	if (!attr || !attr->pValue) +		return 0; + +	ex->cert_der = attr->pValue; +	ex->cert_len = attr->ulValueLen; +	ex->cert_asn = p11_asn1_decode (ex->asn1_defs, "PKIX1.Certificate", +	                                ex->cert_der, ex->cert_len, message); + +	if (!ex->cert_asn) { +		p11_message ("couldn't parse certificate: %s", message); +		return 0; +	} + +	return 1; +} + +static int +extract_info (P11KitIter *iter, +              p11_extract_info *ex) +{ +	CK_ATTRIBUTE *attr; +	CK_RV rv; + +	static CK_ATTRIBUTE attr_types[] = { +		{ CKA_ID, }, +		{ CKA_CLASS, }, +		{ CKA_CERTIFICATE_TYPE, }, +		{ CKA_LABEL, }, +		{ CKA_VALUE, }, +		{ CKA_SUBJECT, }, +		{ CKA_ISSUER, }, +		{ CKA_TRUSTED, }, +		{ CKA_CERTIFICATE_CATEGORY }, +		{ CKA_X_DISTRUSTED }, +		{ CKA_INVALID, }, +	}; + +	ex->attrs = p11_attrs_dup (attr_types); +	rv = p11_kit_iter_load_attributes (iter, ex->attrs, p11_attrs_count (ex->attrs)); + +	/* The attributes couldn't be loaded */ +	if (rv != CKR_OK && rv != CKR_ATTRIBUTE_TYPE_INVALID && rv != CKR_ATTRIBUTE_SENSITIVE) { +		p11_message ("couldn't load attributes: %s", p11_kit_strerror (rv)); +		return 0; +	} + +	attr = p11_attrs_find (ex->attrs, CKA_CLASS); + +	/* No class attribute, very strange, just skip */ +	if (!attr || !attr->pValue || attr->ulValueLen != sizeof (CK_OBJECT_CLASS)) +		return 0; + +	ex->klass = *((CK_ULONG *)attr->pValue); + +	/* If a certificate then  */ +	if (ex->klass != CKO_CERTIFICATE) { +		p11_message ("skipping non-certificate object"); +		return 0; +	} + +	if (!extract_certificate (iter, ex)) +		return 0; + +	attr = p11_attrs_find (ex->attrs, CKA_ID); +	if (attr) { +		ex->stapled = load_stapled_extensions (p11_kit_iter_get_module (iter), +		                                       p11_kit_iter_get_slot (iter), +		                                       attr); +		if (!ex->stapled) +			return 0; +	} + +	if (!extract_purposes (ex)) +		return 0; + +	return 1; +} + +static void +extract_clear (p11_extract_info *ex) +{ +	ex->klass = (CK_ULONG)-1; + +	p11_attrs_free (ex->attrs); +	ex->attrs = NULL; + +	asn1_delete_structure (&ex->cert_asn); +	ex->cert_der = NULL; +	ex->cert_len = 0; + +	p11_dict_free (ex->stapled); +	ex->stapled = NULL; + +	p11_array_free (ex->purposes); +	ex->purposes = NULL; +} + +CK_RV +p11_extract_info_load_filter (P11KitIter *iter, +                              CK_BBOOL *matches, +                              void *data) +{ +	p11_extract_info *ex = data; +	int i; + +	extract_clear (ex); + +	/* Try to load the certificate and extensions */ +	if (!extract_info (iter, ex)) { +		*matches = CK_FALSE; +		return CKR_OK; +	} + +	/* +	 * Limit to certain purposes. Note that the lack of purposes noted +	 * on the certificate means they match any purpose. This is the +	 * behavior of the ExtendedKeyUsage extension. +	 */ +	if (ex->limit_to_purposes && ex->purposes) { +		*matches = CK_FALSE; +		for (i = 0; i < ex->purposes->num; i++) { +			if (p11_dict_get (ex->limit_to_purposes, ex->purposes->elem[i])) { +				*matches = CK_TRUE; +				break; +			} +		} +	} + +	return CKR_OK; +} + +void +p11_extract_info_init (p11_extract_info *ex) +{ +	memset (ex, 0, sizeof (p11_extract_info)); +	ex->asn1_defs = p11_asn1_defs_load (); +	return_if_fail (ex->asn1_defs != NULL); +} + +void +p11_extract_info_cleanup (p11_extract_info *ex) +{ +	extract_clear (ex); + +	p11_dict_free (ex->limit_to_purposes); +	ex->limit_to_purposes = NULL; + +	p11_dict_free (ex->asn1_defs); +	ex->asn1_defs = NULL; +} + +void +p11_extract_info_limit_purpose (p11_extract_info *ex, +                                const char *purpose) +{ +	if (!ex->limit_to_purposes) +		ex->limit_to_purposes = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, free, NULL); +	p11_dict_set (ex->limit_to_purposes, strdup (purpose), NULL); +} + +static char * +extract_label (p11_extract_info *extract) +{ +	CK_ATTRIBUTE *attr; + +	/* Look for a label and just use that */ +	attr = p11_attrs_find (extract->attrs, CKA_LABEL); +	if (attr && attr->pValue && attr->ulValueLen) +		return strndup (attr->pValue, attr->ulValueLen); + +	/* For extracting certificates */ +	if (extract->klass == CKO_CERTIFICATE) +		return strdup ("certificate"); + +	return strdup ("unknown"); +} + +#define FILENAME_CHARS \ +	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_" + +char * +p11_extract_info_filename (p11_extract_info *extract) +{ +	char *label; +	int i; + +	label = extract_label (extract); +	return_val_if_fail (label != NULL, NULL); + +	for (i = 0; label[i] != '\0'; i++) { +		if (strchr (FILENAME_CHARS, label[i]) == NULL) +			label[i] = '_'; +	} + +	return label; +} diff --git a/tools/extract-x509.c b/tools/extract-x509.c new file mode 100644 index 0000000..c6fe15f --- /dev/null +++ b/tools/extract-x509.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2013, 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 "compat.h" +#include "debug.h" +#include "extract.h" +#include "library.h" +#include "save.h" + +#include <stdlib.h> + +bool +p11_extract_x509_file (P11KitIter *iter, +                       p11_extract_info *ex) +{ +	bool found = false; +	p11_save_file *file; +	CK_RV rv; + +	while ((rv = p11_kit_iter_next (iter)) == CKR_OK) { +		if (found) { +			p11_message ("multiple certificates found but could only write one to file"); +			break; +		} + +		file = p11_save_open_file (ex->destination, ex->flags); +		if (!p11_save_write_and_finish (file, ex->cert_der, ex->cert_len)) +			return false; + +		/* Wrote something */ +		found = true; +	} + +	if (rv != CKR_OK && rv != CKR_CANCEL) { +		p11_message ("failed to find certificates: %s", p11_kit_strerror (rv)); +		return false; + +	/* Remember that an empty DER file is not a valid file, so complain if nothing */ +	} else if (!found) { +		p11_message ("no certificate found"); +		return false; +	} + +	return true; +} + +bool +p11_extract_x509_directory (P11KitIter *iter, +                            p11_extract_info *ex) +{ +	p11_save_file *file; +	p11_save_dir *dir; +	char *filename; +	CK_RV rv; +	bool ret; + +	dir = p11_save_open_directory (ex->destination, ex->flags); +	if (dir == NULL) +		return false; + +	while ((rv = p11_kit_iter_next (iter)) == CKR_OK) { +		filename = p11_extract_info_filename (ex); +		return_val_if_fail (filename != NULL, -1); + +		file = p11_save_open_file_in (dir, filename, ".cer", NULL); +		free (filename); + +		if (!p11_save_write_and_finish (file, ex->cert_der, ex->cert_len)) { +			p11_save_finish_directory (dir, false); +			return false; +		} +	} + +	if (rv != CKR_OK && rv != CKR_CANCEL) { +		p11_message ("failed to find certificates: %s", p11_kit_strerror (rv)); +		ret = false; +	} else { +		ret = true; +	} + +	p11_save_finish_directory (dir, ret); +	return ret; +} diff --git a/tools/extract.c b/tools/extract.c new file mode 100644 index 0000000..74d4682 --- /dev/null +++ b/tools/extract.c @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2013, 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 "attrs.h" +#include "compat.h" +#include "debug.h" +#include "extract.h" +#include "iter.h" +#include "library.h" +#include "oid.h" +#include "pkcs11.h" +#include "pkcs11x.h" +#include "save.h" +#include "tool.h" + +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static bool +filter_argument (const char *optarg, +                 P11KitUri **uri, +                 CK_ATTRIBUTE **match) +{ +	CK_ATTRIBUTE *attrs; +	int ret; + +	CK_BBOOL vtrue = CK_TRUE; +	CK_OBJECT_CLASS vcertificate = CKO_CERTIFICATE; +	CK_ULONG vauthority = 2; +	CK_CERTIFICATE_TYPE vx509 = CKC_X_509; + +	CK_ATTRIBUTE trusted = { CKA_TRUSTED, &vtrue, sizeof (vtrue) }; +	CK_ATTRIBUTE distrusted = { CKA_X_DISTRUSTED, &vtrue, sizeof (vtrue) }; +	CK_ATTRIBUTE certificate = { CKA_CLASS, &vcertificate, sizeof (vcertificate) }; +	CK_ATTRIBUTE authority = { CKA_CERTIFICATE_CATEGORY, &vauthority, sizeof (vauthority) }; +	CK_ATTRIBUTE x509 = { CKA_CERTIFICATE_TYPE, &vx509, sizeof (vx509) }; + +	if (strncmp (optarg, "pkcs11:", 7) == 0) { +		if (*uri != NULL) { +			p11_message ("only one pkcs11 uri filter may be specified"); +			return false; +		} +		*uri = p11_kit_uri_new (); +		ret = p11_kit_uri_parse (optarg, P11_KIT_URI_FOR_OBJECT_ON_TOKEN_AND_MODULE, *uri); +		if (ret != P11_KIT_URI_OK) { +			p11_message ("couldn't parse pkcs11 uri filter: %s", optarg); +			return false; +		} +		return true; +	} + +	if (strcmp (optarg, "ca-anchors") == 0) { +		attrs = p11_attrs_build (NULL, &trusted, &certificate, &authority, &x509, NULL); + +	} else if (strcmp (optarg, "blacklist") == 0) { +		attrs = p11_attrs_build (NULL, &distrusted, &certificate, &x509, NULL); + +	} else if (strcmp (optarg, "certificates") == 0) { +		attrs = p11_attrs_build (NULL, &certificate, &x509, NULL); + +	} else { +		p11_message ("unsupported or unrecognized filter: %s", optarg); +		return false; +	} + +	if (*match != NULL) { +		p11_message ("a conflicting filter has already been specified"); +		p11_attrs_free (attrs); +		return false; +	} + +	*match = attrs; +	return true; +} + +static int +is_valid_oid_rough (const char *string) +{ +	size_t len; + +	len = strlen (string); + +	/* Rough check if a valid OID */ +	return (strspn (string, "0123456789.") == len && +	        !strstr (string, "..") && string[0] != '\0' && string[0] != '.' && +	        string[len - 1] != '.'); +} + +static bool +purpose_argument (const char *optarg, +                  p11_extract_info *ex) +{ +	const char *oid; + +	if (strcmp (optarg, "server-auth") == 0) { +		oid = P11_OID_SERVER_AUTH_STR; +	} else if (strcmp (optarg, "client-auth") == 0) { +		oid = P11_OID_CLIENT_AUTH_STR; +	} else if (strcmp (optarg, "email-protection") == 0 || strcmp (optarg, "email") == 0) { +		oid = P11_OID_EMAIL_PROTECTION_STR; +	} else if (strcmp (optarg, "code-signing") == 0) { +		oid = P11_OID_CODE_SIGNING_STR; +	} else if (strcmp (optarg, "ipsec-end-system") == 0) { +		oid = P11_OID_IPSEC_END_SYSTEM_STR; +	} else if (strcmp (optarg, "ipsec-tunnel") == 0) { +		oid = P11_OID_IPSEC_TUNNEL_STR; +	} else if (strcmp (optarg, "ipsec-user") == 0) { +		oid = P11_OID_IPSEC_USER_STR; +	} else if (strcmp (optarg, "time-stamping") == 0) { +		oid = P11_OID_TIME_STAMPING_STR; +	} else if (is_valid_oid_rough (optarg)) { +		oid = optarg; +	} else { +		p11_message ("unsupported or unregonized purpose: %s", optarg); +		return false; +	} + +	p11_extract_info_limit_purpose (ex, oid); +	return true; +} + +static bool +format_argument (const char *optarg, +                 p11_extract_func *func) +{ +	int i; + +	/* +	 * Certain formats do not support expressive trust information. +	 * So the caller should limit the supported purposes when asking +	 * for trust information. +	 */ + +	static const struct { +		const char *format; +		p11_extract_func func; +	} formats[] = { +		{ "x509-file", p11_extract_x509_file, }, +		{ "x509-directory", p11_extract_x509_directory, }, +		{ NULL }, +	}; + +	if (*func != NULL) { +		p11_message ("a format was already specified"); +		return false; +	} + +	for (i = 0; formats[i].format != NULL; i++) { +		if (strcmp (optarg, formats[i].format) == 0) { +			*func = formats[i].func; +			break; +		} +	} + +	if (*func == NULL) { +		p11_message ("unsupported or unrecognized format: %s", optarg); +		return false; +	} + +	return true; +} + +static int +compar_longs (const void *v1, +              const void *v2) +{ +	const long *o1 = v1; +	const long *o2 = v2; +	return (int)(o1 - o2); +} + +static void +limit_modules_if_necessary (CK_FUNCTION_LIST_PTR *modules, +                            CK_ATTRIBUTE *match) +{ +	long policy; +	char *string; +	int i, out; +	char *endptr; + +	struct { +		long policy; +		CK_FUNCTION_LIST_PTR module; +	} *order; + +	/* +	 * We only "believe" the CKA_TRUSTED and CKA_X_DISTRUSTED attributes +	 * we get from modules explicitly marked as containing trust-policy. +	 */ + +	if (!p11_attrs_find (match, CKA_TRUSTED) && +	    !p11_attrs_find (match, CKA_X_DISTRUSTED)) +		return; + +	/* Count the number of modules */ +	for (out = 0; modules[out] != NULL; out++); + +	order = malloc (sizeof (*order) * out); +	return_if_fail (order != NULL); + +	for (i = 0, out = 0; modules[i] != NULL; i++) { +		string = p11_kit_registered_option (modules[i], "trust-policy"); +		if (string) { +			policy = strtol (string, &endptr, 10); +			if (!endptr || endptr[0] != '\0' || policy > INT16_MAX || policy < INT16_MIN) { +				p11_message ("skipping module with invalid 'trust-policy' setting: %s", string); + +			} else { +				order[out].module = modules[i]; +				order[out].policy = policy; +				out++; +			} + +			free (string); +		} +	} + +	/* Our compare function compares the first member of Order */ +	qsort (order, out, sizeof (*order), compar_longs); + +	for (i = 0; i < out; i++) +		modules[i] = order[i].module; +	modules[i] = NULL; + +	free (order); + +	if (out == 0) +		p11_message ("no modules containing trust policy are registered"); +} + +static void +limit_purposes_if_necessary (p11_extract_info *ex, +                             p11_extract_func func, +                             CK_ATTRIBUTE *match) +{ +	int i; + +	/* +	 * These are the extract functions that contain purpose information. +	 * If we're being asked to export anchors, and the extract function does +	 * not support, and the caller has not specified a purpose, then add a +	 * default purpose to limit to. +	 */ + +	static p11_extract_func format_supports_purposes[] = { +		NULL +	}; + +	/* Check if looking for anchors */ +	if (!p11_attrs_find (match, CKA_TRUSTED)) +		return; + +	/* Already limiting to one or more purposes */ +	if (ex->limit_to_purposes) +		return; + +	for (i = 0; format_supports_purposes[i] != NULL; i++) { +		if (func == format_supports_purposes[i]) +			return; +	} + +	p11_message ("format does not support trust policy, limiting to purpose server-auth"); +	p11_extract_info_limit_purpose (ex, P11_OID_SERVER_AUTH_STR); +} + +int +p11_tool_extract (int argc, +                  char **argv) +{ +	p11_extract_func format = NULL; +	CK_FUNCTION_LIST_PTR *modules; +	P11KitIter *iter; +	p11_extract_info ex; +	CK_ATTRIBUTE *match; +	P11KitUri *uri; +	int opt = 0; +	CK_RV rv; +	int ret; + +	enum { +		opt_overwrite = 'f', +		opt_verbose = 'v', +		opt_quiet = 'q', +		opt_help = 'h', +		opt_filter = 1000, +		opt_purpose, +		opt_format, +	}; + +	struct option options[] = { +		{ "filter", required_argument, NULL, opt_filter }, +		{ "format", required_argument, NULL, opt_format }, +		{ "purpose", required_argument, NULL, opt_purpose }, +		{ "overwrite", no_argument, NULL, opt_overwrite }, +		{ "verbose", no_argument, NULL, opt_verbose }, +		{ "quiet", no_argument, NULL, opt_quiet }, +		{ "help", no_argument, NULL, opt_help }, +		{ 0 }, +	}; + +	p11_tool_desc usages[] = { +		{ 0, "usage: p11-kit extract --format=<output> <destination>" }, +		{ opt_filter, +		  "filter of what to export\n" +		  "  ca-anchors        certificate anchors (default)\n" +		  "  blacklist         blacklisted certificates\n" +		  "  certificates      all certificates\n" +		  "  pkcs11:object=xx  a PKCS#11 URI", +		  "what", +		}, +		{ opt_format, +		  "format to extract to\n" +		  "  x509-file         DER X.509 certificate file\n" +		  "  x509-directory    directory of X.509 certificates\n" +		  "  pem-bundle        file containing multiple PEM blocks\n" +		  "  pem-directory     directory of PEM files\n" +		  "  openssl-bundle    OpenSSL specific PEM bundle\n" +		  "  openssl-directory directory of OpenSSL specific files", +		  "type" +		}, +		{ opt_purpose, +		  "limit to certificates usable for the purpose\n" +		  "  server-auth       for authenticating servers\n" +		  "  client-auth       for authenticating clients\n" +		  "  email             for email protection\n" +		  "  code-signing      for authenticating signed code\n" +		  "  1.2.3.4.5...      an arbitrary object id", +		  "usage" +		}, +		{ opt_overwrite, "overwrite output file or directory" }, +		{ opt_verbose, "show verbose debug output", }, +		{ opt_quiet, "supress command output", }, +		{ 0 }, +	}; + +	match = NULL; +	uri = NULL; + +	p11_extract_info_init (&ex); + +	while ((opt = p11_tool_getopt (argc, argv, options)) != -1) { +		switch (opt) { +		case opt_verbose: +		case opt_quiet: +			break; + +		case opt_overwrite: +			ex.flags |= P11_SAVE_OVERWRITE; +			break; +		case opt_filter: +			if (!filter_argument (optarg, &uri, &match)) +				return 2; +			break; +		case opt_purpose: +			if (!purpose_argument (optarg, &ex)) +				return 2; +			break; +		case opt_format: +			if (!format_argument (optarg, &format)) +				return 2; +			break; +		case 'h': +			p11_tool_usage (usages, options); +			return 0; +		case '?': +			return 2; +		default: +			assert_not_reached (); +			break; +		} +	} while (opt != -1); + +	argc -= optind; +	argv += optind; + +	if (argc != 1) { +		p11_message ("specify one destination file or directory"); +		return 2; +	} +	ex.destination = argv[0]; + +	if (!format) { +		p11_message ("no output format specified"); +		return 2; +	} + +	/* If nothing that was useful to enumerate was specified, then bail */ +	if (uri == NULL && match == NULL) { +		p11_message ("no filter specified defaulting to 'ca-anchors'"); +		filter_argument ("ca-anchors", &uri, &match); +	} + +	if (uri && p11_kit_uri_any_unrecognized (uri)) +		p11_message ("uri contained unrecognized components, nothing will be extracted"); + +	rv = p11_kit_initialize_registered (); +	if (rv != CKR_OK) { +		p11_message ("couldn't initialize registered modules: %s", p11_kit_strerror (rv)); +		return 1; +	} + +	modules = p11_kit_registered_modules (); + +	limit_purposes_if_necessary (&ex, format, match); +	limit_modules_if_necessary (modules, match); + +	iter = p11_kit_iter_new (uri); + +	p11_kit_iter_add_callback (iter, p11_extract_info_load_filter, &ex, NULL); +	p11_kit_iter_add_filter (iter, match, p11_attrs_count (match)); + +	p11_kit_iter_begin (iter, modules); + +	ret = (format) (iter, &ex) ? 0 : 1; + +	p11_extract_info_cleanup (&ex); +	p11_kit_iter_free (iter); +	p11_kit_uri_free (uri); +	free (modules); + +	p11_kit_finalize_registered (); +	return ret; +} diff --git a/tools/extract.h b/tools/extract.h new file mode 100644 index 0000000..32b4e35 --- /dev/null +++ b/tools/extract.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013, 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" + +#ifndef P11_EXTRACT_H_ +#define P11_EXTRACT_H_ + +#include "array.h" +#include "asn1.h" +#include "dict.h" +#include "iter.h" +#include "pkcs11.h" + +typedef struct { +	p11_dict *asn1_defs; +	p11_dict *limit_to_purposes; +	char *destination; +	int flags; + +	/* +	 * Stuff below is parsed info for the current iteration. +	 * Currently this information is generally all relevant +	 * just for certificates. +	 */ + +	CK_OBJECT_CLASS klass; +	CK_ATTRIBUTE *attrs; + +	/* Pre-parsed data for certificates */ +	node_asn *cert_asn; +	const unsigned char *cert_der; +	size_t cert_len; + +	/* DER OID -> CK_ATTRIBUTE list */ +	p11_dict *stapled; + +	/* Set of OID purposes as strings */ +	p11_array *purposes; +} p11_extract_info; + +void            p11_extract_info_init          (p11_extract_info *ex); + +CK_RV           p11_extract_info_load_filter   (P11KitIter *iter, +                                                CK_BBOOL *matches, +                                                void *data); + +void            p11_extract_info_limit_purpose (p11_extract_info *ex, +                                                const char *purpose); + +void            p11_extract_info_cleanup       (p11_extract_info *ex); + +char *          p11_extract_info_filename      (p11_extract_info *ex); + +typedef bool (* p11_extract_func)              (P11KitIter *iter, +                                                p11_extract_info *ex); + +bool            p11_extract_x509_file          (P11KitIter *iter, +                                                p11_extract_info *ex); + +bool            p11_extract_x509_directory     (P11KitIter *iter, +                                                p11_extract_info *ex); + +bool            p11_extract_pem_bundle         (P11KitIter *iter, +                                                p11_extract_info *ex); + +bool            p11_extract_pem_directory      (P11KitIter *iter, +                                                p11_extract_info *ex); + +bool            p11_extract_jks_cacerts        (P11KitIter *iter, +                                                p11_extract_info *ex); + +bool            p11_extract_openssl_bundle     (P11KitIter *iter, +                                                p11_extract_info *ex); + +bool            p11_extract_openssl_directory  (P11KitIter *iter, +                                                p11_extract_info *ex); + +#endif /* P11_EXTRACT_H_ */ diff --git a/tools/tests/Makefile.am b/tools/tests/Makefile.am index e4dd7ff..6996675 100644 --- a/tools/tests/Makefile.am +++ b/tools/tests/Makefile.am @@ -21,6 +21,7 @@ INCLUDES = \  LDADD = \  	$(top_builddir)/p11-kit/libp11-kit.la \  	$(top_builddir)/common/libp11-data.la \ +	$(top_builddir)/common/libp11-mock.la \  	$(top_builddir)/common/libp11-library.la \  	$(top_builddir)/common/libp11-compat.la \  	$(builddir)/libtestcommon.la \ @@ -37,6 +38,8 @@ libtestcommon_la_SOURCES = \  CHECK_PROGS = \  	test-save \ +	test-extract \ +	test-x509 \  	$(NULL)  noinst_PROGRAMS = \ @@ -49,4 +52,16 @@ test_save_SOURCES = \  	$(TOOLS)/save.c \  	$(NULL) +test_extract_SOURCES = \ +	test-extract.c \ +	$(TOOLS)/extract-info.c \ +	$(NULL) + +test_x509_SOURCES = \ +	test-x509.c \ +	$(TOOLS)/extract-info.c \ +	$(TOOLS)/extract-x509.c \ +	$(TOOLS)/save.c \ +	$(NULL) +  endif # WITH_ASN1 diff --git a/tools/tests/test-extract.c b/tools/tests/test-extract.c new file mode 100644 index 0000000..55a3524 --- /dev/null +++ b/tools/tests/test-extract.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2011, Collabora Ltd. + * + * 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@collabora.co.uk> + */ + +#include "config.h" +#include "CuTest.h" + +#include "attrs.h" +#include "compat.h" +#include "debug.h" +#include "dict.h" +#include "extract.h" +#include "library.h" +#include "mock.h" +#include "pkcs11.h" +#include "pkcs11x.h" +#include "oid.h" +#include "test.h" + +#include <stdlib.h> +#include <string.h> + +static void +test_file_name_for_label (CuTest *tc) +{ +	CK_ATTRIBUTE label = { CKA_LABEL, "The Label!", 10 }; +	p11_extract_info ex; +	char *name; + +	p11_extract_info_init (&ex); + +	ex.attrs = p11_attrs_build (NULL, &label, NULL); + +	name = p11_extract_info_filename (&ex); +	CuAssertStrEquals (tc, "The_Label_", name); +	free (name); + +	p11_extract_info_cleanup (&ex); +} + +static void +test_file_name_for_class (CuTest *tc) +{ +	p11_extract_info ex; +	char *name; + +	p11_extract_info_init (&ex); + +	ex.klass = CKO_CERTIFICATE; + +	name = p11_extract_info_filename (&ex); +	CuAssertStrEquals (tc, "certificate", name); +	free (name); + +	ex.klass = CKO_DATA; + +	name = p11_extract_info_filename (&ex); +	CuAssertStrEquals (tc, "unknown", name); +	free (name); + +	p11_extract_info_cleanup (&ex); +} + +struct { +	CK_FUNCTION_LIST module; +	P11KitIter *iter; +	p11_extract_info ex; +} test; + +static void +setup (CuTest *tc) +{ +	CK_RV rv; + +	memcpy (&test.module, &mock_module, sizeof (CK_FUNCTION_LIST)); + +	rv = p11_kit_initialize_module (&test.module); +	CuAssertIntEquals (tc, CKR_OK, rv); + +	test.iter = p11_kit_iter_new (NULL); + +	p11_extract_info_init (&test.ex); +} + +static void +teardown (CuTest *tc) +{ +	CK_RV rv; + +	p11_extract_info_cleanup (&test.ex); + +	p11_kit_iter_free (test.iter); + +	rv = p11_kit_finalize_module (&test.module); +	CuAssertIntEquals (tc, CKR_OK, rv); +} + +static CK_OBJECT_CLASS certificate_class = CKO_CERTIFICATE; +static CK_OBJECT_CLASS extension_class = CKO_X_CERTIFICATE_EXTENSION; +static CK_CERTIFICATE_TYPE x509_type = CKC_X_509; + +static CK_ATTRIBUTE cacert3_authority_attrs[] = { +	{ CKA_VALUE, (void *)test_cacert3_ca_der, sizeof (test_cacert3_ca_der) }, +	{ CKA_CLASS, &certificate_class, sizeof (certificate_class) }, +	{ CKA_CERTIFICATE_TYPE, &x509_type, sizeof (x509_type) }, +	{ CKA_LABEL, "Cacert3 Here", 11 }, +	{ CKA_SUBJECT, (void *)test_cacert3_ca_subject, sizeof (test_cacert3_ca_subject) }, +	{ CKA_ID, "ID1", 3 }, +	{ CKA_INVALID }, +}; + +static CK_ATTRIBUTE certificate_filter[] = { +	{ CKA_CLASS, &certificate_class, sizeof (certificate_class) }, +	{ CKA_INVALID }, +}; + +static CK_ATTRIBUTE extension_eku_server_client[] = { +	{ CKA_CLASS, &extension_class, sizeof (extension_class) }, +	{ CKA_ID, "ID1", 3 }, +	{ CKA_OBJECT_ID, (void *)P11_OID_EXTENDED_KEY_USAGE, sizeof (P11_OID_EXTENDED_KEY_USAGE) }, +	{ CKA_VALUE, (void *)test_eku_server_and_client, sizeof (test_eku_server_and_client) }, +	{ CKA_INVALID }, +}; + +static CK_ATTRIBUTE extension_eku_invalid[] = { +	{ CKA_CLASS, &extension_class, sizeof (extension_class) }, +	{ CKA_ID, "ID1", 3 }, +	{ CKA_OBJECT_ID, (void *)P11_OID_EXTENDED_KEY_USAGE, sizeof (P11_OID_EXTENDED_KEY_USAGE) }, +	{ CKA_VALUE, "invalid", 7 }, +	{ CKA_INVALID }, +}; + +static void +test_info_simple_certificate (CuTest *tc) +{ +	CK_ATTRIBUTE *value; +	CK_RV rv; + +	setup (tc); + +	CuAssertPtrNotNull (tc, test.ex.asn1_defs); + +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); +	mock_module_add_object (MOCK_SLOT_ONE_ID, extension_eku_server_client); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	rv = p11_kit_iter_next (test.iter); +	CuAssertIntEquals (tc, CKR_OK, rv); + +	CuAssertIntEquals (tc, CKO_CERTIFICATE, test.ex.klass); +	CuAssertPtrNotNull (tc, test.ex.attrs); +	value = p11_attrs_find_valid (test.ex.attrs, CKA_VALUE); +	CuAssertPtrNotNull (tc, value); +	CuAssertTrue (tc, memcmp (value->pValue, test_cacert3_ca_der, value->ulValueLen) == 0); +	CuAssertPtrNotNull (tc, test.ex.cert_der); +	CuAssertTrue (tc, memcmp (test.ex.cert_der, test_cacert3_ca_der, test.ex.cert_len) == 0); +	CuAssertPtrNotNull (tc, test.ex.cert_asn); + +	rv = p11_kit_iter_next (test.iter); +	CuAssertIntEquals (tc, CKR_CANCEL, rv); + +	teardown (tc); +} + +static void +test_info_limit_purposes (CuTest *tc) +{ +	CK_RV rv; + +	setup (tc); + +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); +	mock_module_add_object (MOCK_SLOT_ONE_ID, extension_eku_server_client); + +	/* This should not match the above, with the stapled certificat ext */ +	CuAssertPtrEquals (tc, NULL, test.ex.limit_to_purposes); +	p11_extract_info_limit_purpose (&test.ex, "1.1.1"); +	CuAssertPtrNotNull (tc, test.ex.limit_to_purposes); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	rv = p11_kit_iter_next (test.iter); +	CuAssertIntEquals (tc, CKR_CANCEL, rv); + +	teardown (tc); +} + +static void +test_info_invalid_purposes (CuTest *tc) +{ +	CK_RV rv; + +	setup (tc); + +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); +	mock_module_add_object (MOCK_SLOT_ONE_ID, extension_eku_invalid); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	p11_kit_be_quiet (); + +	/* No results due to invalid purpose on certificate */ +	rv = p11_kit_iter_next (test.iter); +	CuAssertIntEquals (tc, CKR_CANCEL, rv); + +	p11_kit_be_loud (); + +	teardown (tc); +} + +static void +test_info_skip_non_certificate (CuTest *tc) +{ +	CK_RV rv; + +	setup (tc); + +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	p11_message_quiet (); + +	rv = p11_kit_iter_next (test.iter); +	CuAssertIntEquals (tc, CKR_OK, rv); + +	CuAssertIntEquals (tc, CKO_CERTIFICATE, test.ex.klass); + +	rv = p11_kit_iter_next (test.iter); +	CuAssertIntEquals (tc, CKR_CANCEL, rv); + +	p11_message_loud (); + +	teardown (tc); +} + +int +main (void) +{ +	CuString *output = CuStringNew (); +	CuSuite* suite = CuSuiteNew (); +	int ret; + +	putenv ("P11_KIT_STRICT=1"); +	p11_debug_init (); + +	SUITE_ADD_TEST (suite, test_file_name_for_label); +	SUITE_ADD_TEST (suite, test_file_name_for_class); +	SUITE_ADD_TEST (suite, test_info_simple_certificate); +	SUITE_ADD_TEST (suite, test_info_limit_purposes); +	SUITE_ADD_TEST (suite, test_info_invalid_purposes); +	SUITE_ADD_TEST (suite, test_info_skip_non_certificate); + +	CuSuiteRun (suite); +	CuSuiteSummary (suite, output); +	CuSuiteDetails (suite, output); +	printf ("%s\n", output->buffer); +	ret = suite->failCount; +	CuSuiteDelete (suite); +	CuStringDelete (output); + +	return ret; +} diff --git a/tools/tests/test-x509.c b/tools/tests/test-x509.c new file mode 100644 index 0000000..0367cbd --- /dev/null +++ b/tools/tests/test-x509.c @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2011, Collabora Ltd. + * + * 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@collabora.co.uk> + */ + +#include "config.h" +#include "CuTest.h" + +#include "attrs.h" +#include "compat.h" +#include "debug.h" +#include "dict.h" +#include "extract.h" +#include "library.h" +#include "mock.h" +#include "pkcs11.h" +#include "pkcs11x.h" +#include "oid.h" +#include "test.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +struct { +	CK_FUNCTION_LIST module; +	P11KitIter *iter; +	p11_extract_info ex; +	char *directory; +} test; + +static void +setup (CuTest *tc) +{ +	CK_RV rv; + +	memcpy (&test.module, &mock_module, sizeof (CK_FUNCTION_LIST)); +	rv = p11_kit_initialize_module (&test.module); +	CuAssertIntEquals (tc, CKR_OK, rv); + +	mock_module_reset_objects (MOCK_SLOT_ONE_ID); + +	test.iter = p11_kit_iter_new (NULL); + +	p11_extract_info_init (&test.ex); + +	test.directory = strdup ("/tmp/test-extract.XXXXXX"); +	if (!mkdtemp (test.directory)) +		CuFail (tc, "mkdtemp() failed"); +} + +static void +teardown (CuTest *tc) +{ +	CK_RV rv; + +	if (rmdir (test.directory) < 0) +		CuFail (tc, "rmdir() failed"); +	free (test.directory); + +	p11_extract_info_cleanup (&test.ex); +	p11_kit_iter_free (test.iter); + +	rv = p11_kit_finalize_module (&test.module); +	CuAssertIntEquals (tc, CKR_OK, rv); +} + +static CK_OBJECT_CLASS certificate_class = CKO_CERTIFICATE; +static CK_CERTIFICATE_TYPE x509_type = CKC_X_509; + +static CK_ATTRIBUTE cacert3_authority_attrs[] = { +	{ CKA_VALUE, (void *)test_cacert3_ca_der, sizeof (test_cacert3_ca_der) }, +	{ CKA_CLASS, &certificate_class, sizeof (certificate_class) }, +	{ CKA_CERTIFICATE_TYPE, &x509_type, sizeof (x509_type) }, +	{ CKA_LABEL, "Cacert3 Here", 12 }, +	{ CKA_SUBJECT, (void *)test_cacert3_ca_subject, sizeof (test_cacert3_ca_subject) }, +	{ CKA_ID, "ID1", 3 }, +	{ CKA_INVALID }, +}; + +static CK_ATTRIBUTE certificate_filter[] = { +	{ CKA_CLASS, &certificate_class, sizeof (certificate_class) }, +	{ CKA_INVALID }, +}; + +static void +test_file (CuTest *tc) +{ +	bool ret; + +	setup (tc); + +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	if (asprintf (&test.ex.destination, "%s/%s", test.directory, "extract.cer") < 0) +		assert_not_reached (); + +	ret = p11_extract_x509_file (test.iter, &test.ex); +	CuAssertIntEquals (tc, true, ret); + +	test_check_file (tc, test.directory, "extract.cer", SRCDIR "/files/cacert3.der"); + +	teardown (tc); +} + +static void +test_file_multiple (CuTest *tc) +{ +	bool ret; + +	setup (tc); + +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	if (asprintf (&test.ex.destination, "%s/%s", test.directory, "extract.cer") < 0) +		assert_not_reached (); + +	p11_message_quiet (); + +	ret = p11_extract_x509_file (test.iter, &test.ex); +	CuAssertIntEquals (tc, true, ret); + +	CuAssertTrue (tc, strstr (p11_message_last (), "multiple certificates") != NULL); + +	p11_message_loud (); + +	test_check_file (tc, test.directory, "extract.cer", SRCDIR "/files/cacert3.der"); + +	teardown (tc); +} + +static void +test_file_without (CuTest *tc) +{ +	bool ret; + +	setup (tc); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	if (asprintf (&test.ex.destination, "%s/%s", test.directory, "extract.cer") < 0) +		assert_not_reached (); + +	p11_message_quiet (); + +	ret = p11_extract_x509_file (test.iter, &test.ex); +	CuAssertIntEquals (tc, false, ret); + +	CuAssertTrue (tc, strstr (p11_message_last (), "no certificate") != NULL); + +	p11_message_loud (); + +	teardown (tc); +} + +static void +test_directory (CuTest *tc) +{ +	bool ret; + +	setup (tc); + +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); +	mock_module_add_object (MOCK_SLOT_ONE_ID, cacert3_authority_attrs); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	/* Yes, this is a race, and why you shouldn't build software as root */ +	if (rmdir (test.directory) < 0) +		assert_not_reached (); +	test.ex.destination = test.directory; + +	ret = p11_extract_x509_directory (test.iter, &test.ex); +	CuAssertIntEquals (tc, true, ret); + +	test_check_directory (tc, test.directory, ("Cacert3_Here.cer", "Cacert3_Here.1.cer", NULL)); +	test_check_file (tc, test.directory, "Cacert3_Here.cer", SRCDIR "/files/cacert3.der"); +	test_check_file (tc, test.directory, "Cacert3_Here.1.cer", SRCDIR "/files/cacert3.der"); + +	teardown (tc); +} + +static void +test_directory_empty (CuTest *tc) +{ +	bool ret; + +	setup (tc); + +	p11_kit_iter_add_callback (test.iter, p11_extract_info_load_filter, &test.ex, NULL); +	p11_kit_iter_add_filter (test.iter, certificate_filter, 1); +	p11_kit_iter_begin_with (test.iter, &test.module, 0, 0); + +	/* Yes, this is a race, and why you shouldn't build software as root */ +	if (rmdir (test.directory) < 0) +		assert_not_reached (); +	test.ex.destination = test.directory; + +	ret = p11_extract_x509_directory (test.iter, &test.ex); +	CuAssertIntEquals (tc, true, ret); + +	test_check_directory (tc, test.directory, (NULL, NULL)); + +	teardown (tc); +} + +int +main (void) +{ +	CuString *output = CuStringNew (); +	CuSuite* suite = CuSuiteNew (); +	int ret; + +	putenv ("P11_KIT_STRICT=1"); +	p11_debug_init (); + +	SUITE_ADD_TEST (suite, test_file); +	SUITE_ADD_TEST (suite, test_file_multiple); +	SUITE_ADD_TEST (suite, test_file_without); +	SUITE_ADD_TEST (suite, test_directory); +	SUITE_ADD_TEST (suite, test_directory_empty); + +	CuSuiteRun (suite); +	CuSuiteSummary (suite, output); +	CuSuiteDetails (suite, output); +	printf ("%s\n", output->buffer); +	ret = suite->failCount; +	CuSuiteDelete (suite); +	CuStringDelete (output); + +	return ret; +} diff --git a/tools/tests/test.h b/tools/tests/test.h index c3e0d08..2cc7c31 100644 --- a/tools/tests/test.h +++ b/tools/tests/test.h @@ -164,6 +164,39 @@ static const unsigned char test_cacert3_ca_der[] = {  	0xe0, 0x61, 0x92, 0xb7, 0xf3, 0x37, 0x98, 0xc4, 0xbe, 0x96, 0xa3, 0xb7, 0x8a,  }; +static const char test_cacert3_ca_subject[] = { +	0x30, 0x54, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b, 0x43, 0x41, 0x63, +	0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, +	0x0b, 0x13, 0x15, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x43, 0x41, +	0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, +	0x03, 0x13, 0x13, 0x43, 0x41, 0x63, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, +	0x33, 0x20, 0x52, 0x6f, 0x6f, 0x74, +}; + +static const char test_cacert3_ca_issuer[] = { +	0x30, 0x79, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x52, 0x6f, 0x6f, +	0x74, 0x20, 0x43, 0x41, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x15, 0x68, +	0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, +	0x2e, 0x6f, 0x72, 0x67, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x43, +	0x41, 0x20, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x41, +	0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x09, 0x2a, 0x86, +	0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x12, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, +	0x40, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x72, 0x67, +}; + +static const char test_cacert3_ca_serial[] = { +	0x02, 0x01, 0x00, +}; + +static const char test_eku_server_and_client[] = { +	0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, +	0x01, 0x05, 0x05, 0x07, 0x03, 0x02, +}; + +static const char test_eku_none[] = { +	0x30, 0x00, +}; +  void       test_check_file_msg          (CuTest *tc,                                           const char *file,                                           int line, diff --git a/tools/tool.c b/tools/tool.c index 92c5e70..c825277 100644 --- a/tools/tool.c +++ b/tools/tool.c @@ -55,6 +55,9 @@ struct {  	int (*function) (int, char*[]);  	const char *text;  } commands[] = { +#ifdef WITH_ASN1 +	{ "extract", p11_tool_extract, "Extract certificates" }, +#endif  	{ "list-modules", p11_tool_list_modules, "List modules and tokens"},  	{ 0, }  }; diff --git a/tools/tool.h b/tools/tool.h index 709e668..e0bcf90 100644 --- a/tools/tool.h +++ b/tools/tool.h @@ -53,4 +53,7 @@ void       p11_tool_usage             (const p11_tool_desc *usages,  int        p11_tool_list_modules      (int argc,                                         char *argv[]); +int        p11_tool_extract           (int argc, +                                       char **argv); +  #endif /* P11_TOOL_H_ */ | 
