Migrate SSL certificate storage to the browser window

* Fetchers now provide the certificates before headers
* This is propagated all the way to the browser window
* When a query occurs, we retrieve it from there and fire
  the query with those stored certificates.
* The serial number is a bignum, store it as hex.

Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
This commit is contained in:
Daniel Silverstone 2019-08-05 18:11:13 +01:00
parent bccf101714
commit bfb1bb1192
11 changed files with 226 additions and 137 deletions

View File

@ -142,6 +142,9 @@ nserror content_llcache_callback(llcache_handle *llcache,
nserror error = NSERROR_OK;
switch (event->type) {
case LLCACHE_EVENT_GOT_CERTS:
/* Will never happen: handled in hlcache */
break;
case LLCACHE_EVENT_HAD_HEADERS:
/* Will never happen: handled in hlcache */
break;

View File

@ -44,6 +44,7 @@ struct object_params;
struct rect;
struct redraw_context;
struct llcache_query_msg;
struct ssl_cert_info;
/** Status of a content */
typedef enum {
@ -59,6 +60,7 @@ typedef enum {
/** Used in callbacks to indicate what has occurred. */
typedef enum {
CONTENT_MSG_LOG, /**< Content wishes to log something */
CONTENT_MSG_SSL_CERTS, /**< Content is from SSL and this is its chain */
CONTENT_MSG_QUERY, /**< Something under the content has a query */
CONTENT_MSG_QUERY_FINISHED, /**< Something under the content finished its query */
CONTENT_MSG_LOADING, /**< fetching or converting */
@ -107,6 +109,11 @@ union content_msg_data {
size_t msglen; /**< The length of that message */
browser_window_console_flags flags; /**< The flags of the logging */
} log;
/** CONTENT_MSG_SSL_CERTS - The certificate chain from the underlying fetch */
struct {
const struct ssl_cert_info *certs; /**< The chain */
size_t num; /**< The number of certs in the chain */
} certs;
/** CONTENT_MSG_QUERY - Query from underlying object somewhere */
const struct llcache_query_msg *query_msg;
/** CONTENT_MSG_QUERY_FINISHED - Query from underlying object finished */

View File

@ -42,6 +42,7 @@ typedef enum {
FETCH_ERROR,
FETCH_REDIRECT,
FETCH_NOTMODIFIED,
FETCH_CERTS,
FETCH_AUTH,
FETCH_CERT_ERR,
FETCH_SSL_ERR
@ -70,7 +71,7 @@ typedef struct fetch_msg {
struct {
const struct ssl_cert_info *certs;
size_t num_certs;
} cert_err;
} certs;
} data;
} fetch_msg;
@ -95,12 +96,15 @@ struct ssl_cert_info {
char not_before[32]; /**< Valid from date */
char not_after[32]; /**< Valid to date */
int sig_type; /**< Signature type */
long serial; /**< Serial number */
char serialnum[64]; /**< Serial number */
char issuer[256]; /**< Issuer details */
char subject[256]; /**< Subject details */
int cert_type; /**< Certificate type */
};
/** maximum number of X509 certificates in chain for TLS connection */
#define MAX_SSL_CERTS 10
typedef void (*fetch_callback)(const fetch_msg *msg, void *p);
/**

View File

@ -64,9 +64,6 @@
/** maximum number of progress notifications per second */
#define UPDATES_PER_SECOND 2
/** maximum number of X509 certificates in chain for TLS connection */
#define MAX_CERTS 10
/* the ciphersuites we are willing to use */
#define CIPHER_LIST \
/* disable everything */ \
@ -109,7 +106,7 @@ struct curl_fetch_info {
struct curl_httppost *post_multipart; /**< Multipart post data, or 0. */
uint64_t last_progress_update; /**< Time of last progress update */
int cert_depth; /**< deepest certificate in use */
struct cert_info cert_data[MAX_CERTS]; /**< HTTPS certificate data */
struct cert_info cert_data[MAX_SSL_CERTS]; /**< HTTPS certificate data */
};
/** curl handle cache entry */
@ -445,6 +442,129 @@ failed:
}
/**
* Report the certificate information in the fetch to the users
*/
static void
fetch_curl_report_certs_upstream(struct curl_fetch_info *f)
{
int depth;
BIO *mem;
BUF_MEM *buf;
const ASN1_INTEGER *asn1_num;
BIGNUM *bignum;
struct ssl_cert_info ssl_certs[MAX_SSL_CERTS];
fetch_msg msg;
struct cert_info *certs = f->cert_data;
memset(ssl_certs, 0, sizeof(ssl_certs));
for (depth = 0; depth <= f->cert_depth; depth++) {
assert(certs[depth].cert != NULL);
/* get certificate version */
ssl_certs[depth].version = X509_get_version(certs[depth].cert);
/* not before date */
mem = BIO_new(BIO_s_mem());
ASN1_TIME_print(mem, X509_get_notBefore(certs[depth].cert));
BIO_get_mem_ptr(mem, &buf);
(void) BIO_set_close(mem, BIO_NOCLOSE);
BIO_free(mem);
memcpy(ssl_certs[depth].not_before,
buf->data,
min(sizeof(ssl_certs[depth].not_before) - 1,
(unsigned)buf->length));
ssl_certs[depth].not_before[min(sizeof(ssl_certs[depth].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(certs[depth].cert));
BIO_get_mem_ptr(mem, &buf);
(void) BIO_set_close(mem, BIO_NOCLOSE);
BIO_free(mem);
memcpy(ssl_certs[depth].not_after,
buf->data,
min(sizeof(ssl_certs[depth].not_after) - 1,
(unsigned)buf->length));
ssl_certs[depth].not_after[min(sizeof(ssl_certs[depth].not_after) - 1,
(unsigned)buf->length)] = 0;
BUF_MEM_free(buf);
/* signature type */
ssl_certs[depth].sig_type =
X509_get_signature_type(certs[depth].cert);
/* serial number */
asn1_num = X509_get0_serialNumber(certs[depth].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(ssl_certs[depth].serialnum,
tmp,
sizeof(ssl_certs[depth].serialnum));
ssl_certs[depth].serialnum[sizeof(ssl_certs[depth].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(certs[depth].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(ssl_certs[depth].issuer,
buf->data,
min(sizeof(ssl_certs[depth].issuer) - 1,
(unsigned) buf->length));
ssl_certs[depth].issuer[min(sizeof(ssl_certs[depth].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(certs[depth].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(ssl_certs[depth].subject,
buf->data,
min(sizeof(ssl_certs[depth].subject) - 1,
(unsigned)buf->length));
ssl_certs[depth].subject[min(sizeof(ssl_certs[depth].subject) - 1,
(unsigned) buf->length)] = 0;
BUF_MEM_free(buf);
/* type of certificate */
ssl_certs[depth].cert_type =
X509_certificate_type(certs[depth].cert,
X509_get_pubkey(certs[depth].cert));
}
msg.type = FETCH_CERTS;
msg.data.certs.certs = ssl_certs;
msg.data.certs.num_certs = depth;
fetch_send_callback(&msg, f->fetch_handle);
}
/**
* OpenSSL Certificate verification callback
*
@ -479,7 +599,7 @@ fetch_curl_verify_callback(int verify_ok, X509_STORE_CTX *x509_ctx)
}
/* certificate chain is excessively deep so fail verification */
if (depth >= MAX_CERTS) {
if (depth >= MAX_SSL_CERTS) {
X509_STORE_CTX_set_error(x509_ctx,
X509_V_ERR_CERT_CHAIN_TOO_LONG);
return 0;
@ -524,6 +644,7 @@ fetch_curl_verify_callback(int verify_ok, X509_STORE_CTX *x509_ctx)
*/
static int fetch_curl_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm)
{
struct curl_fetch_info *f = (struct curl_fetch_info *) parm;
int ok;
/* Store fetch struct in context for verify callback */
@ -534,6 +655,8 @@ static int fetch_curl_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm)
ok = X509_verify_cert(x509_ctx);
}
fetch_curl_report_certs_upstream(f);
return ok;
}
@ -886,7 +1009,7 @@ static void fetch_curl_free(void *vf)
curl_formfree(f->post_multipart);
}
for (i = 0; i < MAX_CERTS && f->cert_data[i].cert; i++) {
for (i = 0; i < MAX_SSL_CERTS && f->cert_data[i].cert; i++) {
ns_X509_free(f->cert_data[i].cert);
}
@ -955,114 +1078,6 @@ static bool fetch_curl_process_headers(struct curl_fetch_info *f)
return false;
}
/**
* setup callback to allow the user to examine certificates which have
* failed to validate during fetch.
*/
static void
curl_start_cert_validate(struct curl_fetch_info *f,
struct cert_info *certs)
{
int depth;
BIO *mem;
BUF_MEM *buf;
struct ssl_cert_info ssl_certs[MAX_CERTS];
fetch_msg msg;
for (depth = 0; depth <= f->cert_depth; depth++) {
assert(certs[depth].cert != NULL);
/* get certificate version */
ssl_certs[depth].version = X509_get_version(certs[depth].cert);
/* not before date */
mem = BIO_new(BIO_s_mem());
ASN1_TIME_print(mem, X509_get_notBefore(certs[depth].cert));
BIO_get_mem_ptr(mem, &buf);
(void) BIO_set_close(mem, BIO_NOCLOSE);
BIO_free(mem);
memcpy(ssl_certs[depth].not_before,
buf->data,
min(sizeof(ssl_certs[depth].not_before) - 1,
(unsigned)buf->length));
ssl_certs[depth].not_before[min(sizeof(ssl_certs[depth].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(certs[depth].cert));
BIO_get_mem_ptr(mem, &buf);
(void) BIO_set_close(mem, BIO_NOCLOSE);
BIO_free(mem);
memcpy(ssl_certs[depth].not_after,
buf->data,
min(sizeof(ssl_certs[depth].not_after) - 1,
(unsigned)buf->length));
ssl_certs[depth].not_after[min(sizeof(ssl_certs[depth].not_after) - 1,
(unsigned)buf->length)] = 0;
BUF_MEM_free(buf);
/* signature type */
ssl_certs[depth].sig_type =
X509_get_signature_type(certs[depth].cert);
/* serial number */
ssl_certs[depth].serial =
ASN1_INTEGER_get(
X509_get_serialNumber(certs[depth].cert));
/* issuer name */
mem = BIO_new(BIO_s_mem());
X509_NAME_print_ex(mem,
X509_get_issuer_name(certs[depth].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(ssl_certs[depth].issuer,
buf->data,
min(sizeof(ssl_certs[depth].issuer) - 1,
(unsigned) buf->length));
ssl_certs[depth].issuer[min(sizeof(ssl_certs[depth].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(certs[depth].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(ssl_certs[depth].subject,
buf->data,
min(sizeof(ssl_certs[depth].subject) - 1,
(unsigned)buf->length));
ssl_certs[depth].subject[min(sizeof(ssl_certs[depth].subject) - 1,
(unsigned) buf->length)] = 0;
BUF_MEM_free(buf);
/* type of certificate */
ssl_certs[depth].cert_type =
X509_certificate_type(certs[depth].cert,
X509_get_pubkey(certs[depth].cert));
/* and clean up */
ns_X509_free(certs[depth].cert);
}
msg.type = FETCH_CERT_ERR;
msg.data.cert_err.certs = ssl_certs;
msg.data.cert_err.num_certs = depth;
fetch_send_callback(&msg, f->fetch_handle);
}
/**
* Handle a completed fetch (CURLMSG_DONE from curl_multi_info_read()).
@ -1079,7 +1094,6 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
struct curl_fetch_info *f;
char **_hideous_hack = (char **) (void *) &f;
CURLcode code;
struct cert_info certs[MAX_CERTS];
/* find the structure associated with this fetch */
/* For some reason, cURL thinks CURLINFO_PRIVATE should be a string?! */
@ -1127,9 +1141,6 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
/* CURLE_SSL_PEER_CERTIFICATE renamed to
* CURLE_PEER_FAILED_VERIFICATION
*/
memset(certs, 0, sizeof(certs));
memcpy(certs, f->cert_data, sizeof(certs));
memset(f->cert_data, 0, sizeof(f->cert_data));
cert = true;
} else {
NSLOG(netsurf, INFO, "Unknown cURL response code %d", result);
@ -1146,7 +1157,9 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
fetch_send_callback(&msg, f->fetch_handle);
} else if (cert) {
/* user needs to validate certificate with issue */
curl_start_cert_validate(f, certs);
fetch_msg msg;
msg.type = FETCH_CERT_ERR;
fetch_send_callback(&msg, f->fetch_handle);
} else if (error) {
fetch_msg msg;
switch (result) {

View File

@ -433,6 +433,18 @@ static nserror hlcache_llcache_callback(llcache_handle *handle,
assert(ctx->llcache == handle);
switch (event->type) {
case LLCACHE_EVENT_GOT_CERTS:
/* Pass them on upward */
if (ctx->handle->cb != NULL) {
hlcache_event hlevent;
hlevent.type = CONTENT_MSG_SSL_CERTS;
hlevent.data.certs.certs = event->data.certs.certs;
hlevent.data.certs.num = event->data.certs.num;
ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
}
break;
case LLCACHE_EVENT_HAD_HEADERS:
error = mimesniff_compute_effective_type(llcache_handle_get_header(handle, "Content-Type"), NULL, 0,
ctx->flags & HLCACHE_RETRIEVE_SNIFF_TYPE,

View File

@ -2360,12 +2360,9 @@ static nserror llcache_fetch_auth(llcache_object *object, const char *realm)
* Handle a TLS certificate verification failure
*
* \param object Object being fetched
* \param certs Certificate chain
* \param num Number of certificates in chain
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror llcache_fetch_cert_error(llcache_object *object,
const struct ssl_cert_info *certs, size_t num)
static nserror llcache_fetch_cert_error(llcache_object *object)
{
nserror error = NSERROR_OK;
@ -2386,8 +2383,6 @@ static nserror llcache_fetch_cert_error(llcache_object *object,
/* Emit query for TLS */
query.type = LLCACHE_QUERY_SSL;
query.url = object->url;
query.data.ssl.certs = certs;
query.data.ssl.num = num;
/* Construct the query event */
event.type = LLCACHE_EVENT_QUERY;
@ -2880,7 +2875,17 @@ static void llcache_fetch_callback(const fetch_msg *msg, void *p)
error = llcache_send_event_to_users(object, &event);
break;
case FETCH_CERTS:
/* Certificate information from the fetch */
/** \todo CERTS - Should we persist this on the object and
* then catch up new users etc?
*/
event.type = LLCACHE_EVENT_GOT_CERTS;
event.data.certs.certs = msg->data.certs.certs;
event.data.certs.num = msg->data.certs.num_certs;
error = llcache_send_event_to_users(object, &event);
break;
/* Events requiring action */
case FETCH_AUTH:
/* Need Authentication */
@ -2902,9 +2907,7 @@ static void llcache_fetch_callback(const fetch_msg *msg, void *p)
object->candidate = NULL;
}
error = llcache_fetch_cert_error(object,
msg->data.cert_err.certs,
msg->data.cert_err.num_certs);
error = llcache_fetch_cert_error(object);
break;
case FETCH_SSL_ERR:
/* TLS connection setup failed */

View File

@ -103,6 +103,7 @@ typedef nserror (*llcache_query_response)(bool proceed, void *cbpw);
/** Low-level cache event types */
typedef enum {
LLCACHE_EVENT_GOT_CERTS, /**< SSL certificates arrived */
LLCACHE_EVENT_HAD_HEADERS, /**< Received all headers */
LLCACHE_EVENT_HAD_DATA, /**< Received some data */
LLCACHE_EVENT_DONE, /**< Finished fetching data */
@ -143,6 +144,10 @@ typedef struct {
nsurl *from; /**< Redirect origin */
nsurl *to; /**< Redirect target */
} redirect; /**< Fetch URL redirect occured */
struct {
const struct ssl_cert_info *certs; /**< The chain */
size_t num; /**< Number of certs in chain */
} certs;
llcache_query_msg query;/**< Query event */
} data; /**< Event data */
} llcache_event;

View File

@ -25,6 +25,7 @@
#ifndef NETSURF_DESKTOP_BROWSER_PRIVATE_H_
#define NETSURF_DESKTOP_BROWSER_PRIVATE_H_
#include "content/fetch.h"
#include "desktop/frame_types.h"
struct box;
@ -88,6 +89,14 @@ struct browser_fetch_parameters {
bool parent_quirks; /**< Optional parent quirks */
};
/**
* The SSL context for a fetch, as provided by the fetchers
*/
struct browser_ssl_info {
struct ssl_cert_info certs[MAX_SSL_CERTS]; /**< The certificate chain */
size_t num; /**< The number of certificates in the chain */
};
/**
* Browser window data.
*/
@ -103,6 +112,11 @@ struct browser_window {
*/
struct browser_fetch_parameters current_parameters;
/**
* The SSL information for the current content
*/
struct browser_ssl_info current_ssl_info;
/**
* Content handle of page in process of being loaded or NULL
* if no page is being loaded.
@ -114,6 +128,11 @@ struct browser_window {
*/
struct browser_fetch_parameters loading_parameters;
/**
* The SSL information for the loading content
*/
struct browser_ssl_info loading_ssl_info;
/**
* Favicon
*/

View File

@ -688,10 +688,16 @@ static nserror browser_window_content_ready(struct browser_window *bw)
bw->current_content = bw->loading_content;
bw->loading_content = NULL;
/* Transfer the fetch parameters */
browser_window__free_fetch_parameters(&bw->current_parameters);
bw->current_parameters = bw->loading_parameters;
memset(&bw->loading_parameters, 0, sizeof(bw->loading_parameters));
/* Transfer the SSL info */
bw->current_ssl_info = bw->loading_ssl_info;
bw->loading_ssl_info.num = 0;
/* Format the new content to the correct dimensions */
browser_window_get_dimensions(bw, &width, &height);
width /= bw->scale;
@ -825,6 +831,14 @@ browser_window_callback(hlcache_handle *c, const hlcache_event *event, void *pw)
nserror res = NSERROR_OK;
switch (event->type) {
case CONTENT_MSG_SSL_CERTS:
/* SSL certificate information has arrived, store it */
assert(event->data.certs.num < MAX_SSL_CERTS);
memcpy(&bw->loading_ssl_info.certs[0],
event->data.certs.certs,
sizeof(struct ssl_cert_info) * event->data.certs.num);
bw->loading_ssl_info.num = event->data.certs.num;
break;
case CONTENT_MSG_LOG:
browser_window_console_log(bw,
event->data.log.src,
@ -1138,16 +1152,22 @@ browser_window_callback(hlcache_handle *c, const hlcache_event *event, void *pw)
break;
case CONTENT_MSG_QUERY:
case CONTENT_MSG_QUERY: {
/** \todo QUERY - Decide what is right here */
/* For now, we directly invoke the known global handler for queries */
llcache_query query = *(event->data.query_msg->query);
if (query.type == LLCACHE_QUERY_SSL) {
query.data.ssl.certs = &bw->loading_ssl_info.certs[0];
query.data.ssl.num = bw->loading_ssl_info.num;
}
return netsurf_llcache_query_handler(
event->data.query_msg->query,
&query,
NULL,
event->data.query_msg->cb,
event->data.query_msg->cb_pw);
break;
}
case CONTENT_MSG_QUERY_FINISHED:
/** \todo QUERY - Decide what is right here */
break;
@ -2844,6 +2864,9 @@ browser_window__navigate_internal(struct browser_window *bw,
NSLOG(netsurf, INFO, "Loading '%s'", nsurl_access(params->url));
/* Clear SSL info for load */
bw->loading_ssl_info.num = 0;
/* Set up retrieval parameters */
if (!(params->flags & BW_NAVIGATE_UNVERIFIABLE)) {
fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE;

View File

@ -181,6 +181,9 @@ static nserror download_callback(llcache_handle *handle,
nserror error = NSERROR_OK;
switch (event->type) {
case LLCACHE_EVENT_GOT_CERTS:
/* Nominally not interested in these */
break;
case LLCACHE_EVENT_HAD_HEADERS:
error = download_context_process_headers(ctx);
if (error != NSERROR_OK) {

View File

@ -71,7 +71,6 @@ struct sslcert_session_data {
struct sslcert_entry {
treeview_node *entry;
char version[24];
char serial[24];
char type[24];
struct treeview_field_data data[SSLCERT_V_N_FIELDS - 1];
};
@ -134,11 +133,9 @@ sslcert_viewer_set_treeview_field_data(struct sslcert_entry *e,
&e->data[SSLCERT_V_SUBJECT],
cert->subject, ssl_d);
written = snprintf(e->serial, sizeof(e->serial), "%li", cert->serial);
assert(written < sizeof(e->serial));
sslcert_viewer_field_builder(SSLCERT_V_SERIAL,
&e->data[SSLCERT_V_SERIAL],
e->serial, ssl_d);
cert->serialnum, ssl_d);
written = snprintf(e->type, sizeof(e->type), "%i", cert->cert_type);
assert(written < sizeof(e->type));