summaryrefslogtreecommitdiff
path: root/src/util/convert_transport_security.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/convert_transport_security.c')
-rw-r--r--src/util/convert_transport_security.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/src/util/convert_transport_security.c b/src/util/convert_transport_security.c
new file mode 100644
index 00000000..34dd849a
--- /dev/null
+++ b/src/util/convert_transport_security.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2012 Adam Ehlers Nyholm Thomsen
+ * Copyright (c) 2013 Stefan Bolte <portix@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define _XOPEN_SOURCE 700
+#define _POSIX_C_SOURCE 200809L
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <json.h>
+
+/* Converts the static .certs and .json whitelist to a header file of the
+ * apropriate type.
+ *
+ * Warning: This file is slightly non portable as it uses getline. */
+
+
+/* Indicates whether a given pinset included a list of good certificates and/or
+ * a list of bad certificates. */
+typedef enum _has_certs {
+ HAS_GOOD_CERTS = 1,
+ HAS_BAD_CERTS = 2,
+} has_certs;
+
+/* Whether a certificate is a good certificate or a bad certificate */
+typedef enum _cert_type {
+ GOOD_CERT,
+ BAD_CERT,
+} cert_type;
+
+/* Maps pinset name to has_certs.
+ */
+GHashTable *pins;
+
+#define cert_filename "transport_security_state_static.certs"
+#define json_filename "transport_security_state_static.json"
+#define certificate_begin "-----BEGIN CERTIFICATE-----"
+#define certificate_end "-----END CERTIFICATE-----"
+#define sha1_prefix "sha1/"
+#define cert_template "static const char s_hsts_cert_hash_%s[] = \"%s\";\n"
+
+#define cert_list_template_begin "static const char * const s_hsts_cert_list_%s_%s[] = {\n"
+#define cert_list_template_entry " s_hsts_cert_hash_%s,\n"
+#define cert_list_template_end " NULL,\n};\n\n"
+
+#define entry_list_begin "static const HSTSPreloadEntry s_hsts_preload[] = {\n"
+#define entry_list_end "};\n"
+#define entry_list_length "static const size_t s_hsts_preload_length = %zu\n;"
+
+const char *gboolean_to_string(gboolean val){
+ return val ? "true" : "false";
+}
+const char *cert_type_to_string(cert_type val){
+ return val == GOOD_CERT ? "good" : "bad";
+}
+void print_has_certs(const char *name, has_certs cert_status, cert_type val){
+ if(cert_status & ((val == GOOD_CERT) ? HAS_GOOD_CERTS : HAS_BAD_CERTS)) {
+ printf("s_hsts_cert_list_%s_%s", cert_type_to_string(val), name);
+ } else
+ printf("NULL");
+}
+void print_entry_list_entry(const char *host, const char *pin_name, gboolean hsts, gboolean sub_domains){
+ has_certs cert_status = pin_name != NULL ? *((has_certs *)g_hash_table_lookup(pins, pin_name)) : 0;
+ char *host_safe = g_strescape(host, "");
+ printf(" {\"%s\", ", host);
+ g_free(host_safe);
+ print_has_certs(pin_name, cert_status, GOOD_CERT);
+ printf(", ");
+ print_has_certs(pin_name, cert_status, BAD_CERT);
+ printf(", ");
+ printf("%s, %s},\n", gboolean_to_string(hsts), gboolean_to_string(sub_domains));
+}
+
+/* The ID size should be 20, but give some room for changes */
+#define MAX_ID_SIZE 4096
+
+/* Parse the certificate file and for each certificate print the base64 encoded
+ * certificate key id, to be used in pinsets */
+gboolean parse_certs(const char *filename)
+{
+ FILE *file = g_fopen(filename, "r");
+ char *line = NULL;
+ size_t line_size = 0;
+ size_t buffer_size = 4096;
+ size_t buffer_used = 0;
+ unsigned char *buffer = g_malloc(sizeof(unsigned char)*buffer_size);
+ while(getline(&line, &line_size, file) >= 0)
+ {
+ g_strstrip(line);
+ size_t len = strlen(line);
+ if(len == 0 || line[0] == '#')
+ continue; /* Ignore comments and pure whitespace lines */
+ char *name = g_strdup(line);
+ char *key_id_base64;
+
+ if(getline(&line, &line_size, file) < 0)
+ {
+ fprintf(stderr, "Unexpected end of file while parsing %s\n", name);
+ return FALSE;
+ }
+ g_strstrip(line);
+ if(g_str_has_prefix(line, certificate_begin))
+ {
+ /* If it is a certificate entry: base64 decode the certificate,
+ * load it using gnutls, and compute the base64 encoded key id.
+ */
+ gint state = 0;
+ guint save = 0;
+ buffer_used = 0;
+ ssize_t read;
+ while((read = getline(&line, &line_size, file)) >= 0 && !g_str_has_prefix(line, certificate_end))
+ {
+ /* Read certificate line by line and base64 decode */
+ g_strstrip(line);
+ size_t len = strlen(line);
+ gboolean to_realloc = FALSE;
+ while(len > buffer_size - buffer_used - 3)
+ {
+ to_realloc = TRUE;
+ buffer_size *= 2;
+ }
+ if(to_realloc)
+ {
+ /* Increase buffer_size if it is a long certificate -- this
+ * should never really happen */
+ fprintf(stderr, "Warning: Increasing buffer size to %zd\n", buffer_size);
+ buffer = g_realloc(buffer, buffer_size);
+ }
+ buffer_used += g_base64_decode_step(line, len, &buffer[buffer_used], &state, &save);
+ }
+ if(read < 0)
+ {
+ fprintf(stderr, "Unexpected end of file while parsing base64 certificate of %s\n", name);
+ return FALSE;
+ }
+ gnutls_datum_t binary;
+ binary.data = buffer;
+ binary.size = buffer_used;
+
+ /* Load the certificate and compute the key id */
+ gnutls_x509_crt_t cert;
+ gnutls_x509_crt_init(&cert);
+ int err;
+ if((err = gnutls_x509_crt_import(cert, &binary, GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS)
+ {
+ fprintf(stderr, "Error while decoding certificate of %s, error was %d, is it perhaps PEM encoded?\n", name, err);
+ return FALSE;
+ }
+ unsigned char key_id[MAX_ID_SIZE];
+ size_t key_id_size = MAX_ID_SIZE;
+ if((err = gnutls_x509_crt_get_key_id(cert, 0, key_id, &key_id_size)) != GNUTLS_E_SUCCESS)
+ {
+ fprintf(stderr, "Couldn't retrieve the key id for the certificate of %s, error was %d\n", name, err);
+ return FALSE;
+ }
+ if(key_id_size != 20)
+ {
+ /* This might be problematic, I don't know */
+ fprintf(stderr, "Warning: Key id for %s isn't 20 bytes long, this means it probably isn't a sha-1 hash...\n", name);
+ }
+ key_id_base64 = g_base64_encode(key_id, key_id_size);
+ gnutls_x509_crt_deinit(cert);
+ }
+ else if(g_str_has_prefix(line, sha1_prefix))
+ {
+ /* If it is given as a sha-1 hash directly */
+ key_id_base64 = g_strdup(&line[strlen(sha1_prefix)]);
+ }
+ else
+ {
+ fprintf(stderr, "Unrecognised line: %s\n", line);
+ return FALSE;
+ }
+
+ printf(cert_template, name, key_id_base64);
+
+ g_free(name);
+ g_free(key_id_base64);
+ }
+ printf("\n");
+ g_free(buffer);
+ free(line);
+ fclose(file);
+ return TRUE;
+}
+
+
+/* Writes a list of certificate id names
+ * Params:
+ * name - The name of the pinset
+ * type - Whether it is a list of good or bad certificates
+ * list - The json_array of certificate id names
+ */
+gboolean write_cert_list(const char *name, cert_type type, has_certs *certs, json_object *list)
+{
+ if(list == NULL)
+ return TRUE;
+ if(!json_object_is_type(list, json_type_array))
+ return FALSE;
+ int len = json_object_array_length(list);
+ printf(cert_list_template_begin, cert_type_to_string(type), name);
+ int i;
+ for(i = 0; i < len; i++)
+ {
+ printf(cert_list_template_entry, json_object_get_string(json_object_array_get_idx(list, i)));
+ }
+ printf(cert_list_template_end);
+ *certs |= (type == GOOD_CERT) ? HAS_GOOD_CERTS : HAS_BAD_CERTS;
+ return TRUE;
+}
+
+/* Allocates a new has_certs enum and initializes it to 0(No certificates) */
+has_certs *has_certs_new()
+{
+ has_certs *var = g_malloc(sizeof(has_certs));
+ *var = 0;
+ return var;
+}
+
+/* For each pinset check whether it has a list of good certificates and if so
+ * print, and do likewise for the bad certificates */
+gboolean handle_pinsets(json_object *pinsets)
+{
+ int len = json_object_array_length(pinsets), i;
+ for(i = 0; i < len; i++)
+ {
+ json_object *pin_list = json_object_array_get_idx(pinsets, i);
+ if(pin_list == NULL || !json_object_is_type(pin_list, json_type_object))
+ {
+ fprintf(stderr, "pinset %d is not of type object\n", i);
+ return FALSE;
+ }
+ json_object *name_obj, *good_hashes, *bad_hashes;
+ if((name_obj = json_object_object_get(pin_list, "name")) == NULL || !json_object_is_type(name_obj, json_type_string))
+ {
+ fprintf(stderr, "Couldn't get name from pinset %d\n", i);
+ return FALSE;
+ }
+ const char *name = json_object_get_string(name_obj);
+
+ good_hashes = json_object_object_get(pin_list, "static_spki_hashes");
+ bad_hashes = json_object_object_get(pin_list, "bad_static_spki_hashes");
+ has_certs *certs = has_certs_new();
+ if(!write_cert_list(name, GOOD_CERT, certs, good_hashes) ||
+ !write_cert_list(name, BAD_CERT, certs, bad_hashes))
+ {
+ fprintf(stderr, "Couldn't parse hash lists for pinset %s\n", name);
+ return FALSE;
+ }
+
+ g_hash_table_insert(pins, g_strdup(name), certs);
+ }
+ return TRUE;
+}
+
+/* For each entry convert it into the structure of an HSTSPreloadEntry and
+ * print it as c code on stdout.
+ */
+gboolean handle_entries(json_object *entries)
+{
+ int len = json_object_array_length(entries);
+ printf(entry_list_begin);
+ int i;
+ for(i = 0; i < len; i++)
+ {
+ json_object *entry = json_object_array_get_idx(entries, i);
+ if(entry == NULL || !json_object_is_type(entry, json_type_object))
+ {
+ fprintf(stderr, "Entry %d wasn't a json object\n", i);
+ return FALSE;
+ }
+
+ /* Get hostname */
+ json_object *name_obj;
+ if((name_obj = json_object_object_get(entry, "name")) == NULL ||
+ !json_object_is_type(name_obj, json_type_string))
+ {
+ fprintf(stderr, "Couldn't process name from entry %d\n", i);
+ return FALSE;
+ }
+ const char *name = json_object_get_string(name_obj);
+ char *host = g_hostname_to_unicode(name);
+
+ /* Get whether to enable hsts for host */
+ json_object *mode = json_object_object_get(entry, "mode");
+ gboolean hsts = mode != NULL;
+ if(hsts && strcmp(json_object_get_string(mode), "force-https") != 0)
+ {
+ fprintf(stderr, "Unknown mode for entry %s: %s", name, json_object_get_string(mode));
+ }
+
+ /* Get sub domains directive */
+ json_object *include_subdomains = json_object_object_get(entry, "include_subdomains");
+ gboolean sub_domains = include_subdomains != NULL &&
+ json_object_get_boolean(include_subdomains);
+ if(include_subdomains != NULL && !json_object_is_type(include_subdomains, json_type_boolean))
+ {
+ fprintf(stderr, "include_subdomains for entry %s wasn't of type boolean\n", name);
+ return FALSE;
+ }
+
+ /* Get pins directive */
+ json_object *entry_pins;
+ const char *pin_name = NULL;
+ if((entry_pins = json_object_object_get(entry, "pins")) != NULL)
+ {
+ if(!json_object_is_type(entry_pins, json_type_string))
+ {
+ fprintf(stderr, "non string pins entry for %s\n", name);
+ return FALSE;
+ }
+ pin_name = json_object_get_string(entry_pins);
+ if(g_hash_table_lookup(pins, pin_name) == NULL)
+ {
+ fprintf(stderr, "unrecognised pin name in entry for %s\n", name);
+ }
+ }
+
+ print_entry_list_entry(host, pin_name, hsts, sub_domains);
+ g_free(host);
+ }
+ size_t length = len;
+ printf(entry_list_end);
+ printf(entry_list_length, length);
+ return TRUE;
+}
+
+/* Parse the json file and print the relevant c code */
+gboolean parse_json(const char *filename)
+{
+ /* Read and parse the file */
+ char *file;
+ if(!g_file_get_contents(filename, &file, NULL, NULL))
+ {
+ fprintf(stderr, "Couldn't read JSON file: %s\n", filename);
+ return FALSE;
+ }
+
+ json_object *json = json_tokener_parse(file);
+ if(json == NULL)
+ {
+ fprintf(stderr, "There was an error while parsing %s\n", filename);
+ return FALSE;
+ }
+
+ /* Parse and handle the pinsets entry */
+ json_object *pinsets;
+ pins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ if((pinsets = json_object_object_get(json, "pinsets")) == NULL || !json_object_is_type(pinsets, json_type_array) ||
+ !handle_pinsets(pinsets))
+ {
+ fprintf(stderr, "Error while handling pinsets\n");
+ return FALSE;
+ }
+
+ /* Parse and handle the list of hostnames */
+ json_object *entries;
+ if((entries = json_object_object_get(json, "entries")) == NULL || !json_object_is_type(entries, json_type_array) ||
+ !handle_entries(entries))
+ {
+ fprintf(stderr, "Error while handling entries\n");
+ return FALSE;
+ }
+
+ g_free(file);
+ g_hash_table_destroy(pins);
+ json_object_put(json);
+ return TRUE;
+}
+
+int main(){
+ gnutls_global_init();
+ if(!parse_certs(cert_filename))
+ return -1;
+ if(!parse_json(json_filename))
+ return -1;
+ gnutls_global_deinit();
+ return 0;
+}