mirror of
https://github.com/openbsd/src.git
synced 2025-01-10 06:47:55 -08:00
Split the mft file and hash check from the mft parsing. This makes it easier
to check all files in a mft before failing and also the check is now done after the embedded cert was checked. This refactor was triggered because of a bug in mft_parse_econtent(). check_validity() altered rc but later failure code assumed that goto out is good enough to return an error (rc == -1) but since rc was 1 success was returned. This bug is now also fixed. Bug report and OK job@
This commit is contained in:
parent
81b7cd6931
commit
630e12ade6
@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: extern.h,v 1.26 2020/03/10 14:22:26 jca Exp $ */
|
||||
/* $OpenBSD: extern.h,v 1.27 2020/04/01 14:15:49 claudio Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
|
||||
*
|
||||
@ -264,6 +264,7 @@ struct cert *cert_read(int);
|
||||
void mft_buffer(char **, size_t *, size_t *, const struct mft *);
|
||||
void mft_free(struct mft *);
|
||||
struct mft *mft_parse(X509 **, const char *, int);
|
||||
int mft_check(const char *, struct mft *);
|
||||
struct mft *mft_read(int);
|
||||
|
||||
void roa_buffer(char **, size_t *, size_t *, const struct roa *);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: main.c,v 1.60 2020/04/01 10:54:19 claudio Exp $ */
|
||||
/* $OpenBSD: main.c,v 1.61 2020/04/01 14:15:49 claudio Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
|
||||
*
|
||||
@ -884,6 +884,12 @@ proc_parser_mft(struct entity *entp, int force, X509_STORE *store,
|
||||
X509_STORE_CTX_cleanup(ctx);
|
||||
sk_X509_free(chain);
|
||||
X509_free(x509);
|
||||
|
||||
if (!mft_check(entp->uri, mft)) {
|
||||
mft_free(mft);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mft;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: mft.c,v 1.12 2020/03/30 12:12:51 claudio Exp $ */
|
||||
/* $OpenBSD: mft.c,v 1.13 2020/04/01 14:15:49 claudio Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
|
||||
*
|
||||
@ -115,27 +115,25 @@ hex_pton(u_char const *src, size_t srclen, char *target, size_t targlen)
|
||||
* Return zero on failure, non-zero on success.
|
||||
*/
|
||||
static int
|
||||
mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os, int just_check)
|
||||
mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os)
|
||||
{
|
||||
char file_hash[SHA256_DIGEST_STRING_LENGTH];
|
||||
char cert_hash[SHA256_DIGEST_STRING_LENGTH];
|
||||
ASN1_SEQUENCE_ANY *seq;
|
||||
const ASN1_TYPE *file, *hash;
|
||||
char *fn = NULL;
|
||||
const unsigned char *d = os->data;
|
||||
size_t dsz = os->length, sz;
|
||||
int rc = 0;
|
||||
struct mftfile *fent;
|
||||
char *cp, *path = NULL;
|
||||
|
||||
if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
|
||||
cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
|
||||
"failed ASN.1 sequence parse", p->fn);
|
||||
goto fail;
|
||||
goto out;
|
||||
} else if (sk_ASN1_TYPE_num(seq) != 2) {
|
||||
warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
|
||||
"want 2 elements, have %d", p->fn,
|
||||
sk_ASN1_TYPE_num(seq));
|
||||
goto fail;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* First is the filename itself. */
|
||||
@ -145,7 +143,7 @@ mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os, int just_check)
|
||||
warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
|
||||
"want ASN.1 IA5 string, have %s (NID %d)",
|
||||
p->fn, ASN1_tag2str(file->type), file->type);
|
||||
goto fail;
|
||||
goto out;
|
||||
}
|
||||
fn = strndup((const char *)file->value.ia5string->data,
|
||||
file->value.ia5string->length);
|
||||
@ -162,11 +160,11 @@ mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os, int just_check)
|
||||
if (strchr(fn, '/') != NULL) {
|
||||
warnx("%s: path components disallowed in filename: %s",
|
||||
p->fn, fn);
|
||||
goto fail;
|
||||
goto out;
|
||||
} else if ((sz = strlen(fn)) <= 4) {
|
||||
warnx("%s: filename must be large enough for suffix part: %s",
|
||||
p->fn, fn);
|
||||
goto fail;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strcasecmp(fn + sz - 4, ".roa") &&
|
||||
@ -174,51 +172,28 @@ mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os, int just_check)
|
||||
strcasecmp(fn + sz - 4, ".cer")) {
|
||||
/* ignore unknown files */
|
||||
free(fn);
|
||||
sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
|
||||
return 1;
|
||||
fn = NULL;
|
||||
rc = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Now hash value. */
|
||||
|
||||
hash = sk_ASN1_TYPE_value(seq, 1);
|
||||
if (hash->type != V_ASN1_BIT_STRING) {
|
||||
warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
|
||||
"want ASN.1 bit string, have %s (NID %d)",
|
||||
p->fn, ASN1_tag2str(hash->type), hash->type);
|
||||
goto fail;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) {
|
||||
warnx("%s: RFC 6486 section 4.2.1: hash: "
|
||||
"invalid SHA256 length, have %d",
|
||||
p->fn, hash->value.bit_string->length);
|
||||
goto fail;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (hex_pton(hash->value.bit_string->data, SHA256_DIGEST_LENGTH,
|
||||
cert_hash, sizeof(cert_hash)) == -1)
|
||||
errx(1, "hexnum conversion failed");
|
||||
|
||||
/* Check hash of file now, but first build path for it */
|
||||
cp = strrchr(p->fn, '/');
|
||||
assert(cp != NULL);
|
||||
if (asprintf(&path, "%.*s/%s", (int)(cp - p->fn), p->fn, fn) == -1)
|
||||
err(1, "asprintf");
|
||||
|
||||
if (SHA256File(path, file_hash) == NULL) {
|
||||
warn("%s: referenced file %s", p->fn, fn);
|
||||
goto fail;
|
||||
}
|
||||
free(path);
|
||||
path = NULL;
|
||||
|
||||
if (strcmp(file_hash, cert_hash) != 0) {
|
||||
warnx("%s: bad message digest for %s", p->fn, fn);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (just_check)
|
||||
goto fail;
|
||||
|
||||
/* Insert the filename and hash value. */
|
||||
|
||||
p->res->files = reallocarray(p->res->files, p->res->filesz + 1,
|
||||
@ -230,16 +205,14 @@ mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os, int just_check)
|
||||
memset(fent, 0, sizeof(struct mftfile));
|
||||
|
||||
fent->file = fn;
|
||||
fn = NULL;
|
||||
memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH);
|
||||
|
||||
sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
free(path);
|
||||
rc = 1;
|
||||
out:
|
||||
free(fn);
|
||||
sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
|
||||
return 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -253,7 +226,7 @@ mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os)
|
||||
const ASN1_TYPE *t;
|
||||
const unsigned char *d = os->data;
|
||||
size_t dsz = os->length;
|
||||
int i, rc = 0, failed = 0;
|
||||
int i, rc = 0;
|
||||
|
||||
if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
|
||||
cryptowarnx("%s: RFC 6486 section 4.2: fileList: "
|
||||
@ -268,12 +241,9 @@ mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os)
|
||||
"want ASN.1 sequence, have %s (NID %d)",
|
||||
p->fn, ASN1_tag2str(t->type), t->type);
|
||||
goto out;
|
||||
} else if (!mft_parse_filehash(p, t->value.octet_string,
|
||||
failed))
|
||||
failed = 1;
|
||||
} else if (!mft_parse_filehash(p, t->value.octet_string))
|
||||
goto out;
|
||||
}
|
||||
if (failed)
|
||||
goto out;
|
||||
|
||||
rc = 1;
|
||||
out:
|
||||
@ -291,7 +261,7 @@ mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p, int forc
|
||||
ASN1_SEQUENCE_ANY *seq;
|
||||
const ASN1_TYPE *t;
|
||||
const ASN1_GENERALIZEDTIME *from, *until;
|
||||
int i, rc = -1;
|
||||
int i, rc = -1, validity;
|
||||
|
||||
if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
|
||||
cryptowarnx("%s: RFC 6486 section 4.2: Manifest: "
|
||||
@ -360,8 +330,8 @@ mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p, int forc
|
||||
}
|
||||
until = t->value.generalizedtime;
|
||||
|
||||
rc = check_validity(from, until, p->fn, force);
|
||||
if (rc != 1)
|
||||
validity = check_validity(from, until, p->fn, force);
|
||||
if (validity != 1)
|
||||
goto out;
|
||||
|
||||
/* File list algorithm. */
|
||||
@ -391,7 +361,7 @@ mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p, int forc
|
||||
} else if (!mft_parse_flist(p, t->value.octet_string))
|
||||
goto out;
|
||||
|
||||
rc = 1;
|
||||
rc = validity;
|
||||
out:
|
||||
sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
|
||||
return rc;
|
||||
@ -463,6 +433,59 @@ out:
|
||||
return p.res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the hash value of a file.
|
||||
* Return zero on failure, non-zero on success.
|
||||
*/
|
||||
static int
|
||||
mft_validfilehash(const char *fn, const struct mftfile *m)
|
||||
{
|
||||
char file_hash[SHA256_DIGEST_STRING_LENGTH];
|
||||
char mft_hash[SHA256_DIGEST_STRING_LENGTH];
|
||||
char *cp, *path = NULL;
|
||||
|
||||
if (hex_pton(m->hash, SHA256_DIGEST_LENGTH,
|
||||
mft_hash, sizeof(mft_hash)) == -1)
|
||||
errx(1, "hexnum conversion failed");
|
||||
|
||||
/* Check hash of file now, but first build path for it */
|
||||
cp = strrchr(fn, '/');
|
||||
assert(cp != NULL);
|
||||
if (asprintf(&path, "%.*s/%s", (int)(cp - fn), fn, m->file) == -1)
|
||||
err(1, "asprintf");
|
||||
|
||||
if (SHA256File(path, file_hash) == NULL) {
|
||||
warn("%s: referenced file %s", fn, m->file);
|
||||
free(path);
|
||||
return 0;
|
||||
}
|
||||
free(path);
|
||||
|
||||
if (strcmp(file_hash, mft_hash) != 0) {
|
||||
warnx("%s: bad message digest for %s", fn, m->file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check all files and their hashes in a MFT structure.
|
||||
* Return zero on failure, non-zero on success.
|
||||
*/
|
||||
int
|
||||
mft_check(const char *fn, struct mft *p)
|
||||
{
|
||||
size_t i;
|
||||
int rc = 1;
|
||||
|
||||
for (i = 0; i < p->filesz; i++)
|
||||
if (!mft_validfilehash(fn, &p->files[i]))
|
||||
rc = 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free an MFT pointer.
|
||||
* Safe to call with NULL.
|
||||
|
Loading…
Reference in New Issue
Block a user