diff options
author | Stef Walter <stefw@gnome.org> | 2013-01-30 15:30:52 +0100 |
---|---|---|
committer | Stef Walter <stefw@gnome.org> | 2013-02-05 15:00:24 +0100 |
commit | 722efb88cf12261d705e2a6dfb4aceab9ff7b76f (patch) | |
tree | a58e8a82441fe33cfbb1f408b2c662d73c1f6c48 | |
parent | 9a21e6ddf9eb7bb0f13f01cddba9dedd7a6e43b3 (diff) |
Implement basic extract support
* The only formats supported are x509-file and x509-directory
Allow tool to build without extract
-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_ */ |