From 4b448fbc77510c589cc7f367c05bef63acc07858 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Tue, 12 Feb 2019 21:03:55 +0000 Subject: [PATCH] implement trust on first use --- crypto/pem.c | 6 +- crypto/tlse.c | 6 ++ doc/zpm.8 | 3 + zpm-fetchurl.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 225 insertions(+), 15 deletions(-) diff --git a/crypto/pem.c b/crypto/pem.c index 7d273f9..490bc0c 100644 --- a/crypto/pem.c +++ b/crypto/pem.c @@ -336,10 +336,10 @@ int tls_load_root_certificates(struct TLSContext *tls, do { unsigned char *data = tls_pem_decode(pem_buffer, pem_size, idx++, &len); - if ((!data) || (!len)) + if (!data || !len) { break; - struct TLSCertificate *cert = - asn1_parse(NULL, data, len, 0); + } + struct TLSCertificate *cert = asn1_parse(NULL, data, len, 0); if (cert) { if (cert->version == 2) { if (cert->priv) { diff --git a/crypto/tlse.c b/crypto/tlse.c index 5f5f047..8920467 100644 --- a/crypto/tlse.c +++ b/crypto/tlse.c @@ -5162,6 +5162,12 @@ int _private_asn1_parse(struct TLSContext *context, pos += length; } + if (cert_len && cert_data) { + int h = find_hash("sha256"); + size_t len = sizeof cert->fp; + hash_memory(h, cert_data,cert_len, cert->fp, &len); + } + if (level == 2 && cert->sign_key && cert->sign_len && cert_len && cert_data) { free(cert->fingerprint); diff --git a/doc/zpm.8 b/doc/zpm.8 index 7c578b4..6fa512e 100644 --- a/doc/zpm.8 +++ b/doc/zpm.8 @@ -80,6 +80,9 @@ from https://sqlite.org/index.html .TP lzma from http://git.tukaani.org/xz.git +.TP +SSL root certificates +taken from http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt .PP All other code written by Nathan Wagner and also placed in the public domain. .SH LICENSE diff --git a/zpm-fetchurl.c b/zpm-fetchurl.c index 15fc36a..4ffca23 100644 --- a/zpm-fetchurl.c +++ b/zpm-fetchurl.c @@ -30,7 +30,193 @@ void tls_free_uri(struct tls_uri *); int open_tcp_connection(char *host, int port); -int verify(struct TLSContext *context, struct TLSCertificate **chain, int len) { +/* if trustpolicy is 0, we just accept anything */ +int verify_trust(struct TLSContext *context, struct TLSCertificate **chain, int + len) { + /* suppress unused */ + if (context || chain || len) { + return 0; + } + + return 0; +} + +static char hexchars[] = "0123456789abcdefABCDEF"; + +static void hex(char *dst, uint8_t *src, size_t len) { + while (len--) { + dst[0] = hexchars[(src[0]>>4)&0xf]; + dst[1] = hexchars[src[0]&0xf]; + dst+=2; + src++; + } +} + +#if 0 +static void hexbin(uint8_t *dst, unsigned char *src, size_t len) { + size_t i; + int x; + + for (i=0; ierror == 0) { + loc = memchr(b->buffer, '\n', b->len); + if (loc) { + *size = loc - b->buffer + 1; + return b->buffer; + } else { + rv = read(fd, buf, sizeof buf); + if (rv == -1) { + return 0; + } + if (rv == 0) { + break; + } + tls_buffer_append(b, buf, rv); + } + } + if (rv == 0) { + *size = b->len; + return b->buffer; + } + return 0; +} + +/* + * We use a trust on first use policy. The trust DB is a simple + * file in /var/lib/zpm/known_hosts, or ~/.zpm/known_hosts, or ZPM_KNOWNHOSTS. + * if -k is given, no verification is done + */ +int verify_first(struct TLSContext *context, struct TLSCertificate **chain, int + certs) { + int err; + char *trustfile, *homedir = 0, *host, *fp; + unsigned char certhash[65]; + int trustdb; + struct tls_buffer tbuf; + + char *line = 0; + size_t len = 0; + + if (certs == 0 || chain == 0) { + return 1; + } + + err = tls_certificate_is_valid(chain[0]); + if (err) { + return err; + } + + if (context->sni) { + err = tls_certificate_valid_subject(chain[0], context->sni); + if (err) { + return err; + } + } + + hex(certhash, chain[0]->fp, 32); + certhash[64] = 0; + + trustfile = getenv("ZPM_KNOWNHOSTS"); + if (!trustfile) { + if (geteuid() == 0) { + trustfile = "/var/lib/zpm/known_hosts"; + } else { + homedir = getenv("HOME"); + if (!homedir) { + return 1; + } + len = snprintf(homedir, 0, "%s/.zpm/known_hosts", homedir); + homedir = malloc(len+1); + if (!homedir) { + return 1; + } + len = snprintf(homedir, len+1, "%s/.zpm/known_hosts", homedir); + trustfile = homedir; + } + } + /* cert is valid on its face, so check against the trust db */ + trustdb = open(trustfile, O_RDWR|O_CREAT, 0600); + if (homedir) { + free(homedir); + } + if (trustdb == -1) { + fprintf(stderr, "cannot open trustdb: %s\n", strerror(errno)); + return 1; + } + + len = 0; + tls_buffer_init(&tbuf, 128); + do { + char *off; + + tls_buffer_shift(&tbuf, len); + line = my_getline(&tbuf, trustdb, &len); + + if (!line || !len) { + break; + } + + fp = line; + while (isspace(*fp)) { + fp++; + } + if (*fp == '#') { + continue; + } + off = strchr(line, ':'); + if (!off) { + continue; + } + host = off + 1; + *off = 0; + if (line[len-1] == '\n') { + line[len-1] = 0; + } + + if (strlen(fp) != 64) { + continue; + } + + if (len && line[len-1] == '\n') { + line[len-1] = 0; + } + + if (strcmp(context->sni, host) != 0) { + continue; + } + + int match = (memcmp(certhash, fp, 64) == 0); + + close(trustdb); + tls_buffer_free(&tbuf); + return match ? no_error : bad_certificate; + } while (!tbuf.error); + + /* got here, so we should be at EOF, so add this host to trust db */ + lseek(trustdb, 0, SEEK_END); + write(trustdb, certhash, 64); + write(trustdb, ":", 1); + write(trustdb, context->sni, strlen(context->sni)); + write(trustdb, "\n", 1); + close(trustdb); + tls_buffer_free(&tbuf); + + return no_error; +} + +int verify_roots(struct TLSContext *context, struct TLSCertificate **chain, int + len) { int i, err; if (chain) { @@ -38,10 +224,12 @@ int verify(struct TLSContext *context, struct TLSCertificate **chain, int len) { struct TLSCertificate *certificate = chain[i]; // check validity date err = tls_certificate_is_valid(certificate); - if (err) + if (err) { return err; - // check certificate in certificate->bytes of length certificate->len - // the certificate is in ASN.1 DER format + } + // check certificate in certificate->bytes of length + // certificate->len the certificate is in ASN.1 DER + // format } } // check if chain is valid @@ -277,13 +465,16 @@ int main(int ac, char *av[]) { size_t header_len; char *url = 0; int redirs = 0, redirlimit = 50, printstatus = 0; + int verifypolicy = 1; ltc_mp = tfm_desc; - while ((option = getopt(ac, av, "o:rIfz:#R:S")) != -1) { + while ((option = getopt(ac, av, "o:rIfz:#R:SkK")) != -1) { switch (option) { case 'o': outfile = optarg; break; case 'S': printstatus = 1; head = 1; break; + case 'k': verifypolicy = 0; break; + case 'K': verifypolicy = 2; break; case 'I': head = 1; case 'r': raw = 1; break; case 'f': failsilent = 1; break; @@ -368,18 +559,28 @@ int main(int ac, char *av[]) { clientssl = tls_create_context(TLS_CLIENT, TLS_V12); - rv = tls_load_root_file(clientssl, "root.pem"); - if (rv == -1) { - fprintf(stderr, "Error loading root certs\n"); - return 1; - } - /* optionally, we can set a certificate validation * callback function if set_verify is not called, and * root ca is set, `tls_default_verify` will be used * (does exactly what `verify` does in this example) */ - tls_set_verify(clientssl, verify); + if (verifypolicy == 2) { + char *cert_path = 0; + cert_path = getenv("ZPM_CERTFILE"); + if (!cert_path) { + cert_path = "/var/lib/zpm/roots.pem"; + } + rv = tls_load_root_file(clientssl, cert_path); + if (rv == -1) { + fprintf(stderr, "Error loading root certs\n"); + return 1; + } + tls_set_verify(clientssl, verify_roots); + } else if (verifypolicy == 1) { + tls_set_verify(clientssl, verify_first); + } else { + tls_set_verify(clientssl, verify_trust); + } if (!clientssl) { fprintf(stderr, "Error initializing client context\n"); -- 2.40.0