summaryrefslogtreecommitdiff
path: root/trust/extract-jks.c
diff options
context:
space:
mode:
Diffstat (limited to 'trust/extract-jks.c')
-rw-r--r--trust/extract-jks.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/trust/extract-jks.c b/trust/extract-jks.c
new file mode 100644
index 0000000..2c78a51
--- /dev/null
+++ b/trust/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 <stefw@redhat.com>
+ */
+
+#include "config.h"
+
+#include "attrs.h"
+#include "buffer.h"
+#include "compat.h"
+#include "debug.h"
+#include "extract.h"
+#include "hash.h"
+#include "message.h"
+#include "save.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+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_valid (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
+ * cacerts. We fill this in and use the default "changeit" string
+ * as the password for this keyed digest.
+ */
+ length = buffer->len;
+ digest = p11_buffer_append (buffer, P11_HASH_SHA1_LEN);
+ return_val_if_fail (digest != NULL, false);
+ p11_hash_sha1 (digest,
+ "\000c\000h\000a\000n\000g\000e\000i\000t", 16, /* default 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;
+}