summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--content/fetchers/about.c280
-rw-r--r--include/netsurf/ssl_certs.h11
-rw-r--r--utils/ssl_certs.c87
3 files changed, 377 insertions, 1 deletions
diff --git a/content/fetchers/about.c b/content/fetchers/about.c
index 0e85eff10..c140fdec0 100644
--- a/content/fetchers/about.c
+++ b/content/fetchers/about.c
@@ -261,7 +261,7 @@ static bool fetch_about_licence_handler(struct fetch_about_context *ctx)
/**
- * Handler to generate about:cache page.
+ * Handler to generate about:imagecache page.
*
* Shows details of current image cache.
*
@@ -396,6 +396,276 @@ fetch_about_imagecache_handler_aborted:
/**
+ * ssl certificate information for certificate chain
+ */
+struct ssl_cert_info {
+ long version; /**< Certificate version */
+ char not_before[32]; /**< Valid from date */
+ char not_after[32]; /**< Valid to date */
+ int sig_type; /**< Signature type */
+ char serialnum[64]; /**< Serial number */
+ char issuer[256]; /**< Issuer details */
+ char subject[256]; /**< Subject details */
+ int cert_type; /**< Certificate type */
+ ssl_cert_err err; /**< Whatever is wrong with this certificate */
+};
+
+#ifdef WITH_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+static nserror
+der_to_certinfo(const uint8_t *der,
+ size_t der_length,
+ struct ssl_cert_info *info)
+{
+ BIO *mem;
+ BUF_MEM *buf;
+ const ASN1_INTEGER *asn1_num;
+ BIGNUM *bignum;
+ X509 *cert; /**< Pointer to certificate */
+
+ if (der == NULL) {
+ return NSERROR_OK;
+ }
+
+ cert = d2i_X509(NULL, &der, der_length);
+ if (cert == NULL) {
+ return NSERROR_INVALID;
+ }
+
+ /* get certificate version */
+ info->version = X509_get_version(cert);
+
+ /* not before date */
+ mem = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(mem, X509_get_notBefore(cert));
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(info->not_before,
+ buf->data,
+ min(sizeof(info->not_before) - 1, (unsigned)buf->length));
+ info->not_before[min(sizeof(info->not_before) - 1, (unsigned)buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* not after date */
+ mem = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(mem,
+ X509_get_notAfter(cert));
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(info->not_after,
+ buf->data,
+ min(sizeof(info->not_after) - 1, (unsigned)buf->length));
+ info->not_after[min(sizeof(info->not_after) - 1, (unsigned)buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* signature type */
+ info->sig_type = X509_get_signature_type(cert);
+
+ /* serial number */
+ asn1_num = X509_get_serialNumber(cert);
+ if (asn1_num != NULL) {
+ bignum = ASN1_INTEGER_to_BN(asn1_num, NULL);
+ if (bignum != NULL) {
+ char *tmp = BN_bn2hex(bignum);
+ if (tmp != NULL) {
+ strncpy(info->serialnum,
+ tmp,
+ sizeof(info->serialnum));
+ info->serialnum[sizeof(info->serialnum)-1] = '\0';
+ OPENSSL_free(tmp);
+ }
+ BN_free(bignum);
+ bignum = NULL;
+ }
+ }
+
+ /* issuer name */
+ mem = BIO_new(BIO_s_mem());
+ X509_NAME_print_ex(mem,
+ X509_get_issuer_name(cert),
+ 0, XN_FLAG_SEP_CPLUS_SPC |
+ XN_FLAG_DN_REV | XN_FLAG_FN_NONE);
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(info->issuer,
+ buf->data,
+ min(sizeof(info->issuer) - 1, (unsigned) buf->length));
+ info->issuer[min(sizeof(info->issuer) - 1, (unsigned) buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* subject */
+ mem = BIO_new(BIO_s_mem());
+ X509_NAME_print_ex(mem,
+ X509_get_subject_name(cert),
+ 0,
+ XN_FLAG_SEP_CPLUS_SPC |
+ XN_FLAG_DN_REV |
+ XN_FLAG_FN_NONE);
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(info->subject,
+ buf->data,
+ min(sizeof(info->subject) - 1, (unsigned)buf->length));
+ info->subject[min(sizeof(info->subject) - 1, (unsigned) buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* type of certificate */
+ info->cert_type = X509_certificate_type(cert, X509_get_pubkey(cert));
+
+ X509_free(cert);
+
+ return NSERROR_OK;
+}
+
+/* copy certificate data */
+static nserror
+convert_chain_to_cert_info(const struct cert_chain *chain,
+ struct ssl_cert_info **cert_info_out)
+{
+ struct ssl_cert_info *certs;
+ size_t depth;
+ nserror res;
+
+ certs = calloc(chain->depth, sizeof(struct ssl_cert_info));
+ if (certs == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ for (depth = 0; depth < chain->depth;depth++) {
+ res = der_to_certinfo(chain->certs[depth].der,
+ chain->certs[depth].der_length,
+ certs + depth);
+ if (res != NSERROR_OK) {
+ free(certs);
+ return res;
+ }
+ certs[depth].err = chain->certs[depth].err;
+ }
+
+ *cert_info_out = certs;
+ return NSERROR_OK;
+}
+
+#else
+static nserror
+convert_chain_to_cert_info(const struct cert_chain *chain,
+ struct ssl_cert_info **cert_info_out)
+{
+ return NSERROR_NOT_IMPLEMENTED;
+}
+#endif
+
+
+/**
+ * Handler to generate about:certificate page.
+ *
+ * Shows details of a certificate chain
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+static bool fetch_about_certificate_handler(struct fetch_about_context *ctx)
+{
+ int code = 200;
+ nserror res;
+ struct cert_chain *chain = NULL;
+
+
+ /* content is going to return ok */
+ fetch_set_http_code(ctx->fetchh, code);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html"))
+ goto fetch_about_certificate_handler_aborted;
+
+ /* page head */
+ res = ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>NetSurf Browser Certificate Viewer</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body id =\"certificate\">\n"
+ "<p class=\"banner\">"
+ "<a href=\"http://www.netsurf-browser.org/\">"
+ "<img src=\"resource:netsurf.png\" alt=\"NetSurf\"></a>"
+ "</p>\n"
+ "<h1>NetSurf Browser Certificate Viewer</h1>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ res = cert_chain_from_query(ctx->url, &chain);
+ if (res != NSERROR_OK) {
+ res = ssenddataf(ctx, "<p>Could not process that</p>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+ } else {
+ struct ssl_cert_info *cert_info;
+ res = convert_chain_to_cert_info(chain, &cert_info);
+ if (res == NSERROR_OK) {
+ size_t depth;
+ for (depth = 0; depth < chain->depth; depth++) {
+ res = ssenddataf(ctx,
+ "<h2>Certificate: %d</h2>\n"
+ "<p>Subject: %s</p>"
+ "<p>Serial Number: %s</p>"
+ "<p>Type: %i</p>"
+ "<p>Version: %ld</p>"
+ "<p>Issuer: %s</p>"
+ "<p>Valid From: %s</p>"
+ "<p>Valid Untill: %s</p>",
+ depth,
+ cert_info[depth].subject,
+ cert_info[depth].serialnum,
+ cert_info[depth].cert_type,
+ cert_info[depth].version,
+ cert_info[depth].issuer,
+ cert_info[depth].not_before,
+ cert_info[depth].not_after);
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ }
+ free(cert_info);
+ } else {
+ res = ssenddataf(ctx,
+ "<p>Invalid certificate data</p>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+ }
+ }
+
+
+ /* page footer */
+ res = ssenddataf(ctx, "</body>\n</html>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ cert_chain_free(chain);
+
+ return true;
+
+fetch_about_certificate_handler_aborted:
+ cert_chain_free(chain);
+ return false;
+}
+
+
+/**
* Handler to generate about scheme config page
*
* \param ctx The fetcher context.
@@ -1383,6 +1653,14 @@ struct about_handlers about_handler_list[] = {
true
},
{
+ /* details about a certificate */
+ "certificate",
+ SLEN("certificate"),
+ NULL,
+ fetch_about_certificate_handler,
+ true
+ },
+ {
"query/auth",
SLEN("query/auth"),
NULL,
diff --git a/include/netsurf/ssl_certs.h b/include/netsurf/ssl_certs.h
index b5e79abd5..e3e418848 100644
--- a/include/netsurf/ssl_certs.h
+++ b/include/netsurf/ssl_certs.h
@@ -25,6 +25,8 @@
#ifndef NETSURF_SSL_CERTS_H_
#define NETSURF_SSL_CERTS_H_
+struct nsurl;
+
/**
* ssl certificate error status
*
@@ -108,6 +110,15 @@ nserror cert_chain_dup_into(const struct cert_chain *src, struct cert_chain *dst
nserror cert_chain_dup(const struct cert_chain *src, struct cert_chain **dst_out);
/**
+ * create a certificate chain from a fetch query string
+ *
+ * \param url The url to convert the query from
+ * \param dst_out A pointer to recive the duplicated chain
+ * \return NSERROR_OK on success or NSERROR_NOMEM on memory exhaustion
+ */
+nserror cert_chain_from_query(struct nsurl *url, struct cert_chain **chain_out);
+
+/**
* free a certificate chain
*
* \param chain The certificate chain to free
diff --git a/utils/ssl_certs.c b/utils/ssl_certs.c
index 09500a4fe..94e83eba0 100644
--- a/utils/ssl_certs.c
+++ b/utils/ssl_certs.c
@@ -24,9 +24,11 @@
#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"
@@ -128,6 +130,91 @@ cert_chain_dup(const struct cert_chain *src, struct cert_chain **dst_out)
}
+#define MIN_CERT_LEN 64
+
+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;
+}
+
+
/*
* free certificate chain
*