summaryrefslogtreecommitdiff
path: root/utils/ssl_certs.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/ssl_certs.c')
-rw-r--r--utils/ssl_certs.c356
1 files changed, 356 insertions, 0 deletions
diff --git a/utils/ssl_certs.c b/utils/ssl_certs.c
new file mode 100644
index 000000000..8546165ac
--- /dev/null
+++ b/utils/ssl_certs.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf 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; version 2 of the License.
+ *
+ * NetSurf 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * helpers for X509 certificate chains
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <nsutils/base64.h>
+
+#include "utils/errors.h"
+#include "utils/log.h"
+#include "utils/nsurl.h"
+
+#include "netsurf/ssl_certs.h"
+
+/*
+ * create new certificate chain
+ *
+ * exported interface documented in netsurf/ssl_certs.h
+ */
+nserror
+cert_chain_alloc(size_t depth, struct cert_chain **chain_out)
+{
+ struct cert_chain* chain;
+
+ chain = calloc(1, sizeof(struct cert_chain));
+ if (chain == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ chain->depth = depth;
+
+ *chain_out = chain;
+
+ return NSERROR_OK;
+}
+
+
+/*
+ * duplicate certificate chain into existing chain
+ *
+ * exported interface documented in netsurf/ssl_certs.h
+ */
+nserror
+cert_chain_dup_into(const struct cert_chain *src, struct cert_chain *dst)
+{
+ size_t depth;
+ for (depth = 0; depth < dst->depth; depth++) {
+ if (dst->certs[depth].der != NULL) {
+ free(dst->certs[depth].der);
+ dst->certs[depth].der = NULL;
+ }
+ }
+
+ dst->depth = src->depth;
+
+ for (depth = 0; depth < src->depth; depth++) {
+ dst->certs[depth].err = src->certs[depth].err;
+ dst->certs[depth].der_length = src->certs[depth].der_length;
+ if (src->certs[depth].der != NULL) {
+ dst->certs[depth].der = malloc(src->certs[depth].der_length);
+ if (dst->certs[depth].der == NULL) {
+ return NSERROR_NOMEM;
+ }
+ memcpy(dst->certs[depth].der,
+ src->certs[depth].der,
+ src->certs[depth].der_length);
+ }
+
+ }
+
+ return NSERROR_OK;
+}
+
+
+/*
+ * duplicate certificate chain
+ *
+ * exported interface documented in netsurf/ssl_certs.h
+ */
+nserror
+cert_chain_dup(const struct cert_chain *src, struct cert_chain **dst_out)
+{
+ struct cert_chain* dst;
+ size_t depth;
+ nserror res;
+
+ res = cert_chain_alloc(src->depth, &dst);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ for (depth = 0; depth < src->depth; depth++) {
+ dst->certs[depth].err = src->certs[depth].err;
+ dst->certs[depth].der_length = src->certs[depth].der_length;
+ if (src->certs[depth].der != NULL) {
+ dst->certs[depth].der = malloc(src->certs[depth].der_length);
+ if (dst->certs[depth].der == NULL) {
+ cert_chain_free(dst);
+ return NSERROR_NOMEM;
+ }
+ memcpy(dst->certs[depth].der,
+ src->certs[depth].der,
+ src->certs[depth].der_length);
+ }
+
+ }
+
+ *dst_out = dst;
+ return NSERROR_OK;
+}
+
+
+#define MIN_CERT_LEN 64
+
+/**
+ * process a part of a query extracting the certificate of an error code
+ */
+static nserror
+process_query_section(const char *str, size_t len, struct cert_chain* chain)
+{
+ nsuerror nsures;
+
+ if ((len > (5 + MIN_CERT_LEN)) &&
+ (strncmp(str, "cert=", 5) == 0)) {
+ /* possible certificate entry */
+ nsures = nsu_base64_decode_alloc_url(
+ (const uint8_t *)str + 5,
+ len - 5,
+ &chain->certs[chain->depth].der,
+ &chain->certs[chain->depth].der_length);
+ if (nsures == NSUERROR_OK) {
+ chain->depth++;
+ }
+ } else if ((len > 8) &&
+ (strncmp(str, "certerr=", 8) == 0)) {
+ /* certificate entry error code */
+ if (chain->depth > 0) {
+ chain->certs[chain->depth - 1].err = strtoul(str + 8, NULL, 10);
+ }
+ }
+ return NSERROR_OK;
+}
+
+/*
+ * create a certificate chain from a fetch query string
+ *
+ * exported interface documented in netsurf/ssl_certs.h
+ */
+nserror cert_chain_from_query(struct nsurl *url, struct cert_chain **chain_out)
+{
+ struct cert_chain* chain;
+ nserror res;
+ char *querystr;
+ size_t querylen;
+ size_t kvstart;
+ size_t kvlen;
+
+ res = nsurl_get(url, NSURL_QUERY, &querystr, &querylen);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ if (querylen < MIN_CERT_LEN) {
+ free(querystr);
+ return NSERROR_NEED_DATA;
+ }
+
+ res = cert_chain_alloc(0, &chain);
+ if (res != NSERROR_OK) {
+ free(querystr);
+ return res;
+ }
+
+ for (kvlen = 0, kvstart = 0; kvstart < querylen; kvstart += kvlen) {
+ /* get query section length */
+ kvlen = 0;
+ while (((kvstart + kvlen) < querylen) &&
+ (querystr[kvstart + kvlen] != '&')) {
+ kvlen++;
+ }
+
+ res = process_query_section(querystr + kvstart, kvlen, chain);
+ if (res != NSERROR_OK) {
+ break;
+ }
+ kvlen++; /* account for & separator */
+ }
+ free(querystr);
+
+ if (chain->depth > 0) {
+ *chain_out = chain;
+ } else {
+ free(chain);
+ return NSERROR_INVALID;
+ }
+
+ return NSERROR_OK;
+}
+
+
+/*
+ * create a fetch query string from a certificate chain
+ *
+ * exported interface documented in netsurf/ssl_certs.h
+ */
+nserror cert_chain_to_query(struct cert_chain *chain, struct nsurl **url_out )
+{
+ nserror res;
+ nsurl *url;
+ size_t allocsize;
+ size_t urlstrlen;
+ uint8_t *urlstr;
+ size_t depth;
+
+ allocsize = 20;
+ for (depth = 0; depth < chain->depth; depth++) {
+ allocsize += 7; /* allow for &cert= */
+ allocsize += 4 * ((chain->certs[depth].der_length + 2) / 3);
+ if (chain->certs[depth].err != SSL_CERT_ERR_OK) {
+ allocsize += 20; /* allow for &certerr=4000000000 */
+ }
+ }
+
+ urlstr = malloc(allocsize);
+ if (urlstr == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ urlstrlen = snprintf((char *)urlstr, allocsize, "about:certificate");
+ for (depth = 0; depth < chain->depth; depth++) {
+ int written;
+ nsuerror nsures;
+ size_t output_length;
+
+ written = snprintf((char *)urlstr + urlstrlen,
+ allocsize - urlstrlen,
+ "&cert=");
+ if (written < 0) {
+ free(urlstr);
+ return NSERROR_UNKNOWN;
+ }
+ if ((size_t)written >= allocsize - urlstrlen) {
+ free(urlstr);
+ return NSERROR_UNKNOWN;
+ }
+
+ urlstrlen += (size_t)written;
+
+ output_length = allocsize - urlstrlen;
+ nsures = nsu_base64_encode_url(
+ chain->certs[depth].der,
+ chain->certs[depth].der_length,
+ (uint8_t *)urlstr + urlstrlen,
+ &output_length);
+ if (nsures != NSUERROR_OK) {
+ free(urlstr);
+ return (nserror)nsures;
+ }
+ urlstrlen += output_length;
+
+ if (chain->certs[depth].err != SSL_CERT_ERR_OK) {
+ written = snprintf((char *)urlstr + urlstrlen,
+ allocsize - urlstrlen,
+ "&certerr=%d",
+ chain->certs[depth].err);
+ if (written < 0) {
+ free(urlstr);
+ return NSERROR_UNKNOWN;
+ }
+ if ((size_t)written >= allocsize - urlstrlen) {
+ free(urlstr);
+ return NSERROR_UNKNOWN;
+ }
+
+ urlstrlen += (size_t)written;
+ }
+
+ }
+ urlstr[17] = '?';
+ urlstr[urlstrlen] = 0;
+
+ res = nsurl_create((const char *)urlstr, &url);
+ free(urlstr);
+
+ if (res == NSERROR_OK) {
+ *url_out = url;
+ }
+
+ return res;
+}
+
+/*
+ * free certificate chain
+ *
+ * exported interface documented in netsurf/ssl_certs.h
+ */
+nserror cert_chain_free(struct cert_chain* chain)
+{
+ size_t depth;
+
+ if (chain != NULL) {
+ for (depth = 0; depth < chain->depth; depth++) {
+ if (chain->certs[depth].der != NULL) {
+ free(chain->certs[depth].der);
+ }
+ }
+
+ free(chain);
+ }
+
+ return NSERROR_OK;
+}
+
+
+/*
+ * calculate storage used of certificate chain
+ *
+ * exported interface documented in netsurf/ssl_certs.h
+ */
+size_t cert_chain_size(const struct cert_chain *chain)
+{
+ size_t size = 0;
+ size_t depth;
+
+ if (chain != NULL) {
+ size += sizeof(struct cert_chain);
+
+ for (depth = 0; depth < chain->depth; depth++) {
+ if (chain->certs[depth].der != NULL) {
+ size += chain->certs[depth].der_length;
+ }
+ }
+ }
+
+ return size;
+}