You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
357 lines
7.6 KiB
357 lines
7.6 KiB
/*
|
|
* 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;
|
|
}
|