From 08f1a7f3cfe87bc19ecd564711b4d2beaa603924 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 1 Feb 2013 12:02:16 +0100 Subject: Implement support for java JKS keystore format * All aliases must be lower case in order to work with the default keystore implementation. --- doc/p11-kit.xml | 4 + tools/Makefile.am | 2 +- tools/extract-jks.c | 331 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/extract.c | 4 +- 4 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 tools/extract-jks.c diff --git a/doc/p11-kit.xml b/doc/p11-kit.xml index c8735d8..162563b 100644 --- a/doc/p11-kit.xml +++ b/doc/p11-kit.xml @@ -144,6 +144,10 @@ $ p11-kit extract --format=x509-directory --filter=ca-certificates /path/to/dire Directory of OpenSSL specific PEM files + + + Java keystore 'cacerts' certificate bundle + diff --git a/tools/Makefile.am b/tools/Makefile.am index 6159c08..32cc21e 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -41,11 +41,11 @@ p11_kit_LDADD += \ p11_kit_SOURCES += \ extract.c extract.h \ extract-info.c \ + extract-jks.c \ extract-openssl.c \ extract-pem.c \ extract-x509.c \ save.c save.h \ - utf8.c utf8.h \ $(NULL) endif # WITH_ASN1 diff --git a/tools/extract-jks.c b/tools/extract-jks.c new file mode 100644 index 0000000..93a7964 --- /dev/null +++ b/tools/extract-jks.c @@ -0,0 +1,331 @@ +/* + * 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 + */ + +#include "config.h" + +#include "attrs.h" +#include "buffer.h" +#include "checksum.h" +#include "compat.h" +#include "debug.h" +#include "extract.h" +#include "library.h" +#include "save.h" + +#include +#include +#include +#include +#include + +static void +encode_msb_short (unsigned char *data, + int16_t value) +{ + uint16_t v; + + /* At this point we only support positive numbers */ + assert (value >= 0); + assert (value < INT16_MAX); + + v = (uint16_t)value; + data[0] = (v >> 8) & 0xff; + data[1] = (v >> 0) & 0xff; +} + +static void +encode_msb_int (unsigned char *data, + int32_t value) +{ + uint32_t v; + + /* At this point we only support positive numbers */ + assert (value >= 0); + assert (value < INT32_MAX); + + v = (uint32_t)value; + data[0] = (v >> 24) & 0xff; + data[1] = (v >> 16) & 0xff; + data[2] = (v >> 8) & 0xff; + data[3] = (v >> 0) & 0xff; +} + +static void +encode_msb_long (unsigned char *data, + int64_t value) +{ + uint64_t v; + + /* At this point we only support positive numbers */ + assert (value >= 0); + assert (value < INT64_MAX); + + v = (uint64_t)value; + data[0] = (v >> 56) & 0xff; + data[1] = (v >> 48) & 0xff; + data[2] = (v >> 40) & 0xff; + data[3] = (v >> 32) & 0xff; + data[4] = (v >> 24) & 0xff; + data[5] = (v >> 16) & 0xff; + data[6] = (v >> 8) & 0xff; + data[7] = (v >> 0) & 0xff; +} + +static void +add_msb_int (p11_buffer *buffer, + int32_t value) +{ + unsigned char *data = p11_buffer_append (buffer, 4); + return_if_fail (data != NULL); + encode_msb_int (data, value); +} + +static void +add_msb_long (p11_buffer *buffer, + int64_t value) +{ + unsigned char *data = p11_buffer_append (buffer, 8); + return_if_fail (data != NULL); + encode_msb_long (data, value); +} + +static void +add_string (p11_buffer *buffer, + const char *string, + size_t length) +{ + unsigned char *data; + + if (length > INT16_MAX) { + p11_message ("truncating long string"); + length = INT16_MAX; + } + + data = p11_buffer_append (buffer, 2); + return_if_fail (data != NULL); + encode_msb_short (data, length); + p11_buffer_add (buffer, string, length); +} + +static void +convert_alias (const char *input, + size_t length, + p11_buffer *buf) +{ + char ch; + size_t i; + + /* + * Java requires that the aliases are 'converted'. For the basic java + * cacerts key store this is lower case. We just do this for ASCII, since + * we don't want to have to bring in unicode case rules. Since we're + * screwing around, we also take out spaces, to make these look like + * java aliases. + */ + + for (i = 0; i < length; i++) { + ch = input[i]; + if (!isspace (ch) && (ch & 0x80) == 0) { + ch = tolower (ch); + p11_buffer_add (buf, &ch, 1); + } + } +} + +static bool +add_alias (p11_buffer *buffer, + p11_dict *aliases, + CK_ATTRIBUTE *label) +{ + const char *input; + size_t input_len; + size_t length; + p11_buffer buf; + char num[32]; + char *alias; + int i; + + p11_buffer_init_null (&buf, 64); + + if (label && label->pValue) { + input = label->pValue; + input_len = label->ulValueLen; + } else { + input = "unlabeled"; + input_len = strlen (input); + } + + convert_alias (input, input_len, &buf); + + for (i = 0; i < INT32_MAX; i++) { + if (i > 0) { + snprintf (num, sizeof (num), "-%d", i); + p11_buffer_add (&buf, num, -1); + } + + return_val_if_fail (p11_buffer_ok (&buf), false); + if (!p11_dict_get (aliases, buf.data)) { + alias = p11_buffer_steal (&buf, &length); + if (!p11_dict_set (aliases, alias, alias)) + return_val_if_reached (false); + add_string (buffer, alias, length); + return true; + } + + p11_buffer_reset (&buf, 0); + } + + return false; +} + +static bool +prepare_jks_buffer (P11KitIter *iter, + p11_extract_info *ex, + p11_buffer *buffer) +{ + const unsigned char magic[] = { 0xfe, 0xed, 0xfe, 0xed }; + const int version = 2; + size_t count_at; + unsigned char *digest; + CK_ATTRIBUTE *label; + p11_dict *aliases; + size_t length; + int64_t now; + int count; + CK_RV rv; + + enum { + private_key = 1, + trusted_cert = 2, + }; + + /* + * Documented in the java sources in the file: + * src/share/classes/sun/security/provider/JavaKeyStore.java + */ + + p11_buffer_add (buffer, magic, sizeof (magic)); + add_msb_int (buffer, version); + count_at = buffer->len; + p11_buffer_append (buffer, 4); + count = 0; + + /* + * We use the current time for each entry. Java expects the time + * when this was this certificate was added to the keystore, however + * we don't have that information. Java uses time in milliseconds + */ + now = time (NULL); + return_val_if_fail (now > 0, false); + now *= 1000; /* seconds to milliseconds */ + + /* + * The aliases in the output file need to be unique. We use a hash + * table to guarantee this. + */ + aliases = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, free, NULL); + return_val_if_fail (aliases != NULL, false); + + /* For every certificate */ + while ((rv = p11_kit_iter_next (iter)) == CKR_OK) { + count++; + + /* The type of entry */ + add_msb_int (buffer, trusted_cert); + + /* The alias */ + label = p11_attrs_find (ex->attrs, CKA_LABEL); + if (!add_alias (buffer, aliases, label)) { + p11_message ("could not generate a certificate alias name"); + p11_dict_free (aliases); + return false; + } + + /* The creation date: current time */ + add_msb_long (buffer, now); + + /* The type of the certificate */ + add_string (buffer, "X.509", 5); + + /* The DER encoding of the certificate */ + add_msb_int (buffer, ex->cert_len); + p11_buffer_add (buffer, ex->cert_der, ex->cert_len); + } + + p11_dict_free (aliases); + + if (rv != CKR_OK && rv != CKR_CANCEL) { + p11_message ("failed to find certificates: %s", p11_kit_strerror (rv)); + return false; + } + + /* Place the count in the right place */ + encode_msb_int ((unsigned char *)buffer->data + count_at, count); + + /* + * Java keystore reinvents HMAC and uses it to try and secure the + * keystore. We fill this in and use an empty string as the password + * for this keyed digest. + */ + length = buffer->len; + digest = p11_buffer_append (buffer, P11_CHECKSUM_SHA1_LENGTH); + return_val_if_fail (digest != NULL, false); + p11_checksum_sha1 (digest, + "\000c\000h\000a\000n\000g\000e\000i\000t", 16, /* empty password */ + "Mighty Aphrodite", 16, /* go figure */ + buffer->data, length, + NULL); + + return_val_if_fail (p11_buffer_ok (buffer), false); + return true; +} + +bool +p11_extract_jks_cacerts (P11KitIter *iter, + p11_extract_info *ex) +{ + p11_buffer buffer; + p11_save_file *file; + bool ret; + + p11_buffer_init (&buffer, 1024 * 10); + ret = prepare_jks_buffer (iter, ex, &buffer); + if (ret) { + file = p11_save_open_file (ex->destination, ex->flags); + ret = p11_save_write_and_finish (file, buffer.data, buffer.len); + } + + p11_buffer_uninit (&buffer); + return ret; +} diff --git a/tools/extract.c b/tools/extract.c index 19b6c21..abbb300 100644 --- a/tools/extract.c +++ b/tools/extract.c @@ -177,6 +177,7 @@ format_argument (const char *optarg, { "x509-directory", p11_extract_x509_directory, }, { "pem-bundle", p11_extract_pem_bundle, }, { "pem-directory", p11_extract_pem_directory }, + { "java-cacerts", p11_extract_jks_cacerts }, { "openssl-bundle", p11_extract_openssl_bundle }, { "openssl-directory", p11_extract_openssl_directory }, { NULL }, @@ -359,7 +360,8 @@ p11_tool_extract (int argc, " 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", + " openssl-directory directory of OpenSSL specific files\n" + " java-cacerts java keystore cacerts file", "type" }, { opt_purpose, -- cgit v1.1