summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStef Walter <stefw@gnome.org>2013-01-30 15:30:52 +0100
committerStef Walter <stefw@gnome.org>2013-02-05 15:00:24 +0100
commit722efb88cf12261d705e2a6dfb4aceab9ff7b76f (patch)
treea58e8a82441fe33cfbb1f408b2c662d73c1f6c48
parent9a21e6ddf9eb7bb0f13f01cddba9dedd7a6e43b3 (diff)
downloadp11-kit-722efb88cf12261d705e2a6dfb4aceab9ff7b76f.tar.gz
Implement basic extract support
* The only formats supported are x509-file and x509-directory Allow tool to build without extract
-rw-r--r--configure.ac1
-rw-r--r--doc/Makefile.am1
-rw-r--r--doc/p11-kit.xml95
-rw-r--r--doc/style.css4
-rw-r--r--tools/Makefile.am20
-rw-r--r--tools/extract-info.c359
-rw-r--r--tools/extract-x509.c116
-rw-r--r--tools/extract.c461
-rw-r--r--tools/extract.h110
-rw-r--r--tools/tests/Makefile.am15
-rw-r--r--tools/tests/test-extract.c301
-rw-r--r--tools/tests/test-x509.c276
-rw-r--r--tools/tests/test.h33
-rw-r--r--tools/tool.c3
-rw-r--r--tools/tool.h3
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=&lt;what&gt;</arg>
+ <arg choice="plain">--format=&lt;type&gt;</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=&lt;what&gt;</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=&lt;type&gt;</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=&lt;usage&gt;</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_ */