-/* $OpenBSD: main.c,v 1.186 2022/01/26 14:42:39 claudio Exp $ */
+/* $OpenBSD: main.c,v 1.187 2022/01/28 15:30:23 claudio Exp $ */
/*
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
entity_read_req(struct ibuf *b, struct entity *ent)
{
io_read_buf(b, &ent->type, sizeof(ent->type));
+ io_read_buf(b, &ent->location, sizeof(ent->location));
io_read_buf(b, &ent->repoid, sizeof(ent->repoid));
io_read_buf(b, &ent->talid, sizeof(ent->talid));
io_read_str(b, &ent->path);
b = io_new_buffer();
io_simple_buffer(b, &ent->type, sizeof(ent->type));
+ io_simple_buffer(b, &ent->location, sizeof(ent->location));
io_simple_buffer(b, &ent->repoid, sizeof(ent->repoid));
io_simple_buffer(b, &ent->talid, sizeof(ent->talid));
io_str_buffer(b, ent->path);
{
struct ibuf *b;
enum rtype type = RTYPE_REPO;
+ enum location loc = DIR_UNKNOWN;
unsigned int repoid;
char *path, *altpath;
int talid = 0;
altpath = repo_basedir(rp, 1);
b = io_new_buffer();
io_simple_buffer(b, &type, sizeof(type));
+ io_simple_buffer(b, &loc, sizeof(loc));
io_simple_buffer(b, &repoid, sizeof(repoid));
io_simple_buffer(b, &talid, sizeof(talid));
io_str_buffer(b, path);
* Add the heap-allocated file to the queue for processing.
*/
static void
-entityq_add(char *path, char *file, enum rtype type, struct repo *rp,
- unsigned char *data, size_t datasz, int talid)
+entityq_add(char *path, char *file, enum rtype type, enum location loc,
+ struct repo *rp, unsigned char *data, size_t datasz, int talid)
{
struct entity *p;
err(1, NULL);
p->type = type;
+ p->location = loc;
p->talid = talid;
p->path = path;
if (rp != NULL)
if ((nfile = strdup(file->file)) == NULL)
err(1, NULL);
- entityq_add(npath, nfile, file->type, rp, NULL, 0, -1);
+ entityq_add(npath, nfile, file->type, file->location, rp, NULL, 0, -1);
}
/*
if ((nfile = strdup(file)) == NULL)
err(1, NULL);
/* Not in a repository, so directly add to queue. */
- entityq_add(NULL, nfile, type, NULL, buf, len, talid);
+ entityq_add(NULL, nfile, type, DIR_UNKNOWN, NULL, buf, len, talid);
}
/*
/* steal the pkey from the tal structure */
data = tal->pkey;
tal->pkey = NULL;
- entityq_add(NULL, nfile, RTYPE_CER, repo, data, tal->pkeysz, tal->id);
+ entityq_add(NULL, nfile, RTYPE_CER, DIR_VALID, repo, data,
+ tal->pkeysz, tal->id);
}
/*
err(1, NULL);
}
- entityq_add(npath, nfile, RTYPE_MFT, repo, NULL, 0, -1);
+ entityq_add(npath, nfile, RTYPE_MFT, DIR_UNKNOWN, repo, NULL, 0, -1);
}
/*
-/* $OpenBSD: mft.c,v 1.51 2022/01/24 17:29:37 claudio Exp $ */
+/* $OpenBSD: mft.c,v 1.52 2022/01/28 15:30:23 claudio Exp $ */
/*
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
*
extern ASN1_OBJECT *mft_oid;
-static const char *
-gentime2str(const ASN1_GENERALIZEDTIME *time)
-{
- static char buf[64];
- BIO *mem;
-
- if ((mem = BIO_new(BIO_s_mem())) == NULL)
- cryptoerrx("BIO_new");
- if (!ASN1_GENERALIZEDTIME_print(mem, time))
- cryptoerrx("ASN1_GENERALIZEDTIME_print");
- if (BIO_gets(mem, buf, sizeof(buf)) < 0)
- cryptoerrx("BIO_gets");
-
- BIO_free(mem);
- return buf;
-}
-
/*
* Convert an ASN1_GENERALIZEDTIME to a struct tm.
* Returns 1 on success, 0 on failure.
/*
* Validate and verify the time validity of the mft.
- * Returns 1 if all is good, 0 if mft is stale, any other case -1.
+ * Returns 1 if all is good and for any other case 0.
*/
static int
-check_validity(const ASN1_GENERALIZEDTIME *from,
- const ASN1_GENERALIZEDTIME *until, const char *fn)
+mft_parse_time(const ASN1_GENERALIZEDTIME *from,
+ const ASN1_GENERALIZEDTIME *until, struct parse *p)
{
- time_t now = time(NULL);
- struct tm tm_from, tm_until, tm_now;
-
- if (gmtime_r(&now, &tm_now) == NULL) {
- warnx("%s: could not get current time", fn);
- return -1;
- }
+ struct tm tm_from, tm_until;
if (!generalizedtime_to_tm(from, &tm_from)) {
- warnx("%s: embedded from time format invalid", fn);
- return -1;
+ warnx("%s: embedded from time format invalid", p->fn);
+ return 0;
}
if (!generalizedtime_to_tm(until, &tm_until)) {
- warnx("%s: embedded until time format invalid", fn);
- return -1;
+ warnx("%s: embedded until time format invalid", p->fn);
+ return 0;
}
/* check that until is not before from */
if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) {
- warnx("%s: bad update interval", fn);
- return -1;
- }
- /* check that now is not before from */
- if (ASN1_time_tm_cmp(&tm_from, &tm_now) > 0) {
- warnx("%s: mft not yet valid %s", fn, gentime2str(from));
- return -1;
- }
- /* check that now is not after until */
- if (ASN1_time_tm_cmp(&tm_until, &tm_now) < 0) {
- warnx("%s: mft expired on %s", fn, gentime2str(until));
+ warnx("%s: bad update interval", p->fn);
return 0;
}
+ if ((p->res->valid_from = mktime(&tm_from)) == -1 ||
+ (p->res->valid_until = mktime(&tm_until)) == -1)
+ errx(1, "%s: mktime failed", p->fn);
+
return 1;
}
}
until = t->value.generalizedtime;
- switch (check_validity(from, until, p->fn)) {
- case 0:
- p->res->stale = 1;
- /* FALLTHROUGH */
- case 1:
- break;
- case -1:
+ if (!mft_parse_time(from, until, p))
goto out;
- }
/* File list algorithm. */
io_str_buffer(b, p->files[i].file);
io_simple_buffer(b, &p->files[i].type,
sizeof(p->files[i].type));
+ io_simple_buffer(b, &p->files[i].location,
+ sizeof(p->files[i].location));
io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
}
}
for (i = 0; i < p->filesz; i++) {
io_read_str(b, &p->files[i].file);
io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type));
+ io_read_buf(b, &p->files[i].location,
+ sizeof(p->files[i].location));
io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
}
return p;
}
+
+/*
+ * Compare two MFT files, returns 1 if first MFT is preferred and 0 if second
+ * MFT should be used.
+ */
+int
+mft_compare(const struct mft *a, const struct mft *b)
+{
+ int r;
+
+ if (b == NULL)
+ return 1;
+ if (a == NULL)
+ return 0;
+
+ r = strlen(a->seqnum) - strlen(b->seqnum);
+ if (r > 0) /* seqnum in a is longer -> higher */
+ return 1;
+ if (r < 0) /* seqnum in a is shorter -> smaller */
+ return 0;
+
+ r = strcmp(a->seqnum, b->seqnum);
+ if (r >= 0) /* a is greater or equal, prefer a */
+ return 1;
+ return 0;
+}
-/* $OpenBSD: parser.c,v 1.57 2022/01/28 06:33:27 guenther Exp $ */
+/* $OpenBSD: parser.c,v 1.58 2022/01/28 15:30:23 claudio Exp $ */
/*
* Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
errx(1, "repository already added: id %d, %s", id, path);
}
+static char *
+time2str(time_t t)
+{
+ static char buf[64];
+ struct tm tm;
+
+ if (gmtime_r(&t, &tm) == NULL)
+ return "could not convert time";
+
+ strftime(buf, sizeof(buf), "%h %d %T %Y %Z", &tm);
+ return buf;
+}
+
/*
- * Build access path to file based on repoid, path and file values.
- * If wantalt == 1 the function can return NULL, if wantalt == 0 it
- * can not fail.
+ * Build access path to file based on repoid, path, location and file values.
*/
static char *
parse_filepath(unsigned int repoid, const char *path, const char *file,
- int wantalt)
+ enum location loc)
{
struct parse_repo *rp;
char *fn, *repopath;
/* build file path based on repoid, entity path and filename */
rp = repo_get(repoid);
- if (rp == NULL) {
- /* no repo so no alternative path. */
- if (wantalt)
- return NULL;
-
- if (path == NULL) {
- if ((fn = strdup(file)) == NULL)
- err(1, NULL);
- } else {
- if (asprintf(&fn, "%s/%s", path, file) == -1)
- err(1, NULL);
- }
- } else {
- if (wantalt || rp->path == NULL)
- repopath = rp->validpath;
- else
- repopath = rp->path;
+ if (rp == NULL)
+ return NULL;
+
+ if (loc == DIR_VALID)
+ repopath = rp->validpath;
+ else
+ repopath = rp->path;
- if (repopath == NULL)
- return NULL;
+ if (repopath == NULL)
+ return NULL;
- if (path == NULL) {
- if (asprintf(&fn, "%s/%s", repopath, file) == -1)
- err(1, NULL);
- } else {
- if (asprintf(&fn, "%s/%s/%s", repopath, path,
- file) == -1)
- err(1, NULL);
- }
+ if (path == NULL) {
+ if (asprintf(&fn, "%s/%s", repopath, file) == -1)
+ err(1, NULL);
+ } else {
+ if (asprintf(&fn, "%s/%s/%s", repopath, path, file) == -1)
+ err(1, NULL);
}
return fn;
}
*/
static int
valid_x509(char *file, X509 *x509, struct auth *a, struct crl *crl,
- unsigned long flags)
+ unsigned long flags, int nowarn)
{
STACK_OF(X509) *chain;
STACK_OF(X509_CRL) *crls = NULL;
if (X509_verify_cert(ctx) <= 0) {
c = X509_STORE_CTX_get_error(ctx);
- warnx("%s: %s", file, X509_verify_cert_error_string(c));
+ if (!nowarn || verbose > 1)
+ warnx("%s: %s", file, X509_verify_cert_error_string(c));
X509_STORE_CTX_cleanup(ctx);
sk_X509_free(chain);
sk_X509_CRL_free(crls);
a = valid_ski_aki(file, &auths, roa->ski, roa->aki);
crl = get_crl(a);
- if (!valid_x509(file, x509, a, crl, X509_V_FLAG_CRL_CHECK)) {
+ if (!valid_x509(file, x509, a, crl, X509_V_FLAG_CRL_CHECK, 0)) {
X509_free(x509);
roa_free(roa);
return NULL;
static int
proc_parser_mft_check(const char *fn, struct mft *p)
{
- size_t i;
- int rc = 1;
+ const enum location loc[2] = { DIR_TEMP, DIR_VALID };
+ size_t i;
+ int rc = 1;
char *path;
for (i = 0; i < p->filesz; i++) {
- const struct mftfile *m = &p->files[i];
- int fd = -1, try = 0;
-
- path = NULL;
- do {
- free(path);
+ struct mftfile *m = &p->files[i];
+ int try, fd = -1, noent = 0, valid = 0;
+ for (try = 0; try < 2 && !valid; try++) {
if ((path = parse_filepath(p->repoid, p->path, m->file,
- try++)) == NULL)
- break;
+ loc[try])) == NULL)
+ continue;
fd = open(path, O_RDONLY);
- } while (fd == -1 && try < 2);
+ if (fd == -1 && errno == ENOENT)
+ noent++;
+ free(path);
- free(path);
+ /* remember which path was checked */
+ m->location = loc[try];
+ valid = valid_filehash(fd, m->hash, sizeof(m->hash));
+ }
- if (!valid_filehash(fd, m->hash, sizeof(m->hash))) {
+ if (!valid) {
+ /* silently skip not-existing unknown files */
+ if (m->type == RTYPE_INVALID && noent == 2)
+ continue;
warnx("%s: bad message digest for %s", fn, m->file);
rc = 0;
+ continue;
}
}
}
/*
- * Parse and validate a manifest file.
+ * Parse and validate a manifest file. Skip checking the fileandhash
+ * this is done in the post check. After this step we know the mft is
+ * valid and can be compared.
* Here we *don't* validate against the list of CRLs, because the
* certificate used to sign the manifest may specify a CRL that the root
* certificate didn't, and we haven't scanned for it yet.
* Return the mft on success or NULL on failure.
*/
static struct mft *
-proc_parser_mft(char *file, const unsigned char *der, size_t len,
- const char *path, unsigned int repoid)
+proc_parser_mft_pre(char *file, const unsigned char *der, size_t len)
{
struct mft *mft;
X509 *x509;
a = valid_ski_aki(file, &auths, mft->ski, mft->aki);
/* CRL checks disabled here because CRL is referenced from mft */
- if (!valid_x509(file, x509, a, NULL, 0)) {
+ if (!valid_x509(file, x509, a, NULL, 0, 1)) {
mft_free(mft);
X509_free(x509);
return NULL;
}
X509_free(x509);
+ return mft;
+}
+
+/*
+ * Do the end of manifest validation.
+ * Return the mft on success or NULL on failure.
+ */
+static struct mft *
+proc_parser_mft_post(char *file, struct mft *mft, const char *path,
+ unsigned int repoid)
+{
+ /* check that now is not before from */
+ time_t now = time(NULL);
+
+ if (mft == NULL) {
+ warnx("%s: no valid mft available", file);
+ return NULL;
+ }
+
+ /* check that now is not before from */
+ if (now < mft->valid_from) {
+ warnx("%s: mft not yet valid %s", file,
+ time2str(mft->valid_from));
+ mft->stale = 1;
+ }
+ /* check that now is not after until */
+ if (now > mft->valid_until) {
+ warnx("%s: mft expired on %s", file,
+ time2str(mft->valid_until));
+ mft->stale = 1;
+ }
+
mft->repoid = repoid;
if (path != NULL)
if ((mft->path = strdup(path)) == NULL)
a = valid_ski_aki(file, &auths, cert->ski, cert->aki);
crl = get_crl(a);
- if (!valid_x509(file, cert->x509, a, crl, X509_V_FLAG_CRL_CHECK)) {
+ if (!valid_x509(file, cert->x509, a, crl, X509_V_FLAG_CRL_CHECK, 0)) {
cert_free(cert);
return NULL;
}
crl = get_crl(a);
/* return value can be ignored since nothing happens here */
- valid_x509(file, x509, a, crl, X509_V_FLAG_CRL_CHECK);
+ valid_x509(file, x509, a, crl, X509_V_FLAG_CRL_CHECK, 0);
X509_free(x509);
gbr_free(gbr);
err(1, "sk_X509_CRL_push");
}
+/*
+ * Load the file specified by the entity information.
+ */
static char *
parse_load_file(struct entity *entp, unsigned char **f, size_t *flen)
{
- char *file, *nfile;
+ char *file;
- file = parse_filepath(entp->repoid, entp->path, entp->file, 0);
-
- /* TAL files include the data already */
- if (entp->type == RTYPE_TAL) {
- *f = NULL;
- *flen = 0;
- return file;
- }
+ file = parse_filepath(entp->repoid, entp->path, entp->file,
+ entp->location);
+ if (file == NULL)
+ errx(1, "no path to file");
*f = load_file(file, flen);
- if (*f != NULL)
- return file;
+ if (*f == NULL)
+ warn("parse file %s", file);
+
+ return file;
+}
- if (errno != ENOENT)
- goto fail;
+static char *
+parse_load_mft(struct entity *entp, struct mft **mft)
+{
+ struct mft *mft1 = NULL, *mft2 = NULL;
+ char *f, *file1, *file2;
+ size_t flen;
- /* try alternate file location */
- nfile = parse_filepath(entp->repoid, entp->path, entp->file, 1);
- if (nfile == NULL)
- goto fail;
+ file1 = parse_filepath(entp->repoid, entp->path, entp->file, DIR_VALID);
+ file2 = parse_filepath(entp->repoid, entp->path, entp->file, DIR_TEMP);
- free(file);
- file = nfile;
+ if (file1 != NULL) {
+ f = load_file(file1, &flen);
+ if (f == NULL && errno != ENOENT)
+ warn("parse file %s", file1);
+ mft1 = proc_parser_mft_pre(file1, f, flen);
+ free(f);
+ }
- *f = load_file(file, flen);
- if (*f != NULL)
- return file;
+ if (file2 != NULL) {
+ f = load_file(file2, &flen);
+ if (f == NULL && errno != ENOENT)
+ warn("parse file %s", file2);
+ mft2 = proc_parser_mft_pre(file2, f, flen);
+ free(f);
+ }
-fail:
- warn("parse file %s", file);
- return file;
+ if (mft_compare(mft1, mft2) == 1) {
+ mft_free(mft2);
+ free(file2);
+ *mft = mft1;
+ return file1;
+ } else {
+ mft_free(mft1);
+ free(file1);
+ *mft = mft2;
+ return file2;
+ }
}
/*
continue;
}
- file = parse_load_file(entp, &f, &flen);
-
/* pass back at least type, repoid and filename */
b = io_new_buffer();
io_simple_buffer(b, &entp->type, sizeof(entp->type));
- io_str_buffer(b, file);
+ file = NULL;
+ f = NULL;
switch (entp->type) {
case RTYPE_TAL:
+ io_str_buffer(b, entp->file);
if ((tal = tal_parse(entp->file, entp->data,
entp->datasz)) == NULL)
errx(1, "%s: could not parse tal file",
tal_free(tal);
break;
case RTYPE_CER:
+ file = parse_load_file(entp, &f, &flen);
+ io_str_buffer(b, file);
if (entp->data != NULL)
cert = proc_parser_root_cert(file,
f, flen, entp->data, entp->datasz,
/*
* The parsed certificate data "cert" is now
* managed in the "auths" table, so don't free
- * it here (see the loop after "out").
+ * it here.
*/
break;
case RTYPE_CRL:
+ file = parse_load_file(entp, &f, &flen);
+ io_str_buffer(b, file);
proc_parser_crl(file, f, flen);
break;
case RTYPE_MFT:
- mft = proc_parser_mft(file, f, flen,
+ file = parse_load_mft(entp, &mft);
+
+ mft = proc_parser_mft_post(file, mft,
entp->path, entp->repoid);
+
+ io_str_buffer(b, file);
c = (mft != NULL);
io_simple_buffer(b, &c, sizeof(int));
if (mft != NULL)
mft_free(mft);
break;
case RTYPE_ROA:
+ file = parse_load_file(entp, &f, &flen);
+ io_str_buffer(b, file);
roa = proc_parser_roa(file, f, flen);
c = (roa != NULL);
io_simple_buffer(b, &c, sizeof(int));
roa_free(roa);
break;
case RTYPE_GBR:
+ file = parse_load_file(entp, &f, &flen);
+ io_str_buffer(b, file);
proc_parser_gbr(file, f, flen);
break;
default:
a = auth_find(&auths, aki);
crl = get_crl(a);
- if (valid_x509(file, x509, a, crl, verify_flags))
+ if (valid_x509(file, x509, a, crl, verify_flags, 0))
printf("Validation: OK\n");
else
printf("Validation: Failed\n");