about scheme certificate viewer initial implementation

This commit is contained in:
Vincent Sanders 2020-02-23 21:41:39 +00:00
parent 5c205fbff0
commit f172a21df9
3 changed files with 377 additions and 1 deletions

View File

@ -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.
*
@ -395,6 +395,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
*
@ -1382,6 +1652,14 @@ struct about_handlers about_handler_list[] = {
fetch_about_blank_handler,
true
},
{
/* details about a certificate */
"certificate",
SLEN("certificate"),
NULL,
fetch_about_certificate_handler,
true
},
{
"query/auth",
SLEN("query/auth"),

View File

@ -25,6 +25,8 @@
#ifndef NETSURF_SSL_CERTS_H_
#define NETSURF_SSL_CERTS_H_
struct nsurl;
/**
* ssl certificate error status
*
@ -107,6 +109,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
*

View File

@ -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
*