From 3f4f9e9957a27be4efb29498f8fecd63eafcd16c Mon Sep 17 00:00:00 2001 From: Timo Teras Date: Fri, 17 Jul 2009 13:07:52 +0300 Subject: verify: new applet (ref #46) an utility to check package signature and integrity. --- src/Makefile | 2 +- src/add.c | 5 +- src/apk_package.h | 32 ++++++- src/index.c | 5 +- src/package.c | 245 ++++++++++++++++++++++++++++++++++++++---------------- src/verify.c | 49 +++++++++++ 6 files changed, 259 insertions(+), 79 deletions(-) create mode 100644 src/verify.c diff --git a/src/Makefile b/src/Makefile index 0cd0774..503a751 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,7 +8,7 @@ progs-y += apk apk-objs := state.o database.o package.o archive.o \ version.o io.o url.o gunzip.o blob.o hash.o apk.o \ add.o del.o update.o info.o search.o upgrade.o \ - cache.o ver.o index.o fetch.o audit.o + cache.o ver.o index.o fetch.o audit.o verify.o CFLAGS_apk.o := -DAPK_VERSION=\"$(FULL_VERSION)\" progs-$(STATIC) += apk.static diff --git a/src/add.c b/src/add.c index 8f067b7..c05d257 100644 --- a/src/add.c +++ b/src/add.c @@ -117,8 +117,11 @@ static int add_main(void *ctx, int argc, char **argv) if (strstr(argv[i], ".apk") != NULL) { struct apk_package *pkg; + struct apk_sign_ctx sctx; - pkg = apk_pkg_read(&db, argv[i], APK_SIGN_VERIFY); + apk_sign_ctx_init(&sctx, APK_SIGN_VERIFY); + pkg = apk_pkg_read(&db, argv[i], &sctx); + apk_sign_ctx_free(&sctx); if (pkg == NULL) { apk_error("Unable to read '%s'", argv[i]); goto err; diff --git a/src/apk_package.h b/src/apk_package.h index bf30350..6b7e959 100644 --- a/src/apk_package.h +++ b/src/apk_package.h @@ -34,6 +34,25 @@ struct apk_name; #define APK_SIGN_GENERATE_V1 1 #define APK_SIGN_GENERATE 2 +struct apk_sign_ctx { + int action; + const EVP_MD *md; + int num_signatures; + int control_started : 1; + int data_started : 1; + int has_data_checksum : 1; + int control_verified : 1; + int data_verified : 1; + char data_checksum[EVP_MAX_MD_SIZE]; + struct apk_checksum identity; + + struct { + apk_blob_t data; + EVP_PKEY *pkey; + char *identity; + } signature; +}; + struct apk_script { struct hlist_node script_list; unsigned int type; @@ -73,6 +92,13 @@ APK_ARRAY(apk_package_array, struct apk_package *); extern const char *apk_script_types[]; +void apk_sign_ctx_init(struct apk_sign_ctx *ctx, int action); +void apk_sign_ctx_free(struct apk_sign_ctx *ctx); +int apk_sign_ctx_process_file(struct apk_sign_ctx *ctx, + const struct apk_file_info *fi, + struct apk_istream *is); +int apk_sign_ctx_mpart_cb(void *ctx, EVP_MD_CTX *mdctx, int part); + int apk_deps_add(struct apk_dependency_array **depends, struct apk_dependency *dep); void apk_deps_del(struct apk_dependency_array **deps, @@ -84,7 +110,8 @@ int apk_deps_write(struct apk_dependency_array *deps, struct apk_ostream *os); int apk_script_type(const char *name); struct apk_package *apk_pkg_new(void); -struct apk_package *apk_pkg_read(struct apk_database *db, const char *name, int indexstyle); +struct apk_package *apk_pkg_read(struct apk_database *db, const char *name, + struct apk_sign_ctx *ctx); void apk_pkg_free(struct apk_package *pkg); int apk_pkg_parse_name(apk_blob_t apkname, apk_blob_t *name, apk_blob_t *version); @@ -103,8 +130,7 @@ int apk_pkg_write_index_entry(struct apk_package *pkg, struct apk_ostream *os); int apk_pkg_version_compare(struct apk_package *a, struct apk_package *b); -struct apk_dependency apk_dep_from_str(struct apk_database *db, - char *str); +struct apk_dependency apk_dep_from_str(struct apk_database *db, char *str); struct apk_dependency apk_dep_from_pkg(struct apk_database *db, struct apk_package *pkg); #endif diff --git a/src/index.c b/src/index.c index 86c1f44..12f21fe 100644 --- a/src/index.c +++ b/src/index.c @@ -158,8 +158,11 @@ static int index_main(void *ctx, int argc, char **argv) } while (0); if (!found) { - if (apk_pkg_read(&db, argv[i], ictx->method) != NULL) + struct apk_sign_ctx sctx; + apk_sign_ctx_init(&sctx, ictx->method); + if (apk_pkg_read(&db, argv[i], &sctx) != NULL) newpkgs++; + apk_sign_ctx_free(&sctx); } } diff --git a/src/package.c b/src/package.c index c29b501..04c7c01 100644 --- a/src/package.c +++ b/src/package.c @@ -20,6 +20,8 @@ #include #include +#include + #include "apk_defines.h" #include "apk_archive.h" #include "apk_package.h" @@ -254,16 +256,162 @@ int apk_script_type(const char *name) return APK_SCRIPT_INVALID; } +void apk_sign_ctx_init(struct apk_sign_ctx *ctx, int action) +{ + memset(ctx, 0, sizeof(struct apk_sign_ctx)); + switch (ctx->action) { + case APK_SIGN_VERIFY: + ctx->md = EVP_md_null(); + break; + case APK_SIGN_GENERATE_V1: + ctx->md = EVP_md5(); + break; + case APK_SIGN_GENERATE: + default: + action = APK_SIGN_GENERATE; + ctx->md = EVP_sha1(); + break; + } + ctx->action = action; +} + + +void apk_sign_ctx_free(struct apk_sign_ctx *ctx) +{ + if (ctx->signature.data.ptr != NULL) + free(ctx->signature.data.ptr); + if (ctx->signature.pkey != NULL) + EVP_PKEY_free(ctx->signature.pkey); +} + +int apk_sign_ctx_process_file(struct apk_sign_ctx *ctx, + const struct apk_file_info *fi, + struct apk_istream *is) +{ + if (ctx->data_started) + return 1; + + if (fi->name[0] != '.') { + ctx->data_started = 1; + ctx->control_started = 1; + return 1; + } + + if (ctx->control_started) + return 1; + + if (strncmp(fi->name, ".SIGN.", 6) != 0) { + ctx->control_started = 1; + return 1; + } + + /* A signature file */ + ctx->num_signatures++; + + /* Found already a trusted key */ + if (ctx->signature.pkey != NULL) + return 0; + + if (strncmp(&fi->name[6], "RSA.", 4) == 0 || + strncmp(&fi->name[6], "DSA.", 4) == 0) { + char file[256]; + BIO *bio = BIO_new(BIO_s_file()); + snprintf(file, sizeof(file), "/etc/apk/keys/%s", &fi->name[10]); + if (BIO_read_filename(bio, file) > 0) + ctx->signature.pkey = + PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (ctx->signature.pkey != NULL) { + if (fi->name[6] == 'R') + ctx->md = EVP_sha1(); + else + ctx->md = EVP_dss1(); + } + BIO_free(bio); + } else + return 0; + + if (ctx->signature.pkey != NULL) + ctx->signature.data = apk_blob_from_istream(is, fi->size); + + return 0; +} + +int apk_sign_ctx_mpart_cb(void *ctx, EVP_MD_CTX *mdctx, int part) +{ + struct apk_sign_ctx *sctx = (struct apk_sign_ctx *) ctx; + unsigned char calculated[EVP_MAX_MD_SIZE]; + int r; + + switch (part) { + case APK_MPART_BEGIN: + EVP_DigestInit_ex(mdctx, sctx->md, NULL); + break; + case APK_MPART_BOUNDARY: + /* We are not interested about checksums of signature, + * reset checksum if we are still in signatures */ + if (!sctx->control_started) { + EVP_DigestFinal_ex(mdctx, calculated, NULL); + EVP_DigestInit_ex(mdctx, sctx->md, NULL); + return 0; + } + + /* Are we in control part?. */ + if ((!sctx->control_started) || sctx->data_started) + return 0; + + /* End of control block, make sure rest is handled as data */ + sctx->data_started = 1; + + /* Verify the signature if we have public key */ + if (sctx->action == APK_SIGN_VERIFY && + sctx->signature.pkey != NULL) { + r = EVP_VerifyFinal(mdctx, + (unsigned char *) sctx->signature.data.ptr, + sctx->signature.data.len, + sctx->signature.pkey); + if (r != 1) + return 1; + + sctx->control_verified = 1; + EVP_DigestInit_ex(mdctx, sctx->md, NULL); + return 0; + } else if (sctx->action == APK_SIGN_GENERATE && + sctx->has_data_checksum) { + /* Package identity is checksum of control block */ + sctx->identity.type = EVP_MD_CTX_size(mdctx); + EVP_DigestFinal_ex(mdctx, sctx->identity.data, NULL); + return 1; + } else { + /* Reset digest for hashing data */ + EVP_DigestFinal_ex(mdctx, calculated, NULL); + EVP_DigestInit_ex(mdctx, sctx->md, NULL); + } + break; + case APK_MPART_END: + if (sctx->action == APK_SIGN_VERIFY) { + /* Check that data checksum matches */ + EVP_DigestFinal_ex(mdctx, calculated, NULL); + if (sctx->has_data_checksum && + EVP_MD_CTX_size(mdctx) != 0 && + memcmp(calculated, sctx->data_checksum, + EVP_MD_CTX_size(mdctx)) == 0) + sctx->data_verified = 1; + } else { + /* Package identity is checksum of all data */ + sctx->identity.type = EVP_MD_CTX_size(mdctx); + EVP_DigestFinal_ex(mdctx, sctx->identity.data, NULL); + } + return 1; + } + return 0; +} + struct read_info_ctx { struct apk_database *db; struct apk_package *pkg; - const EVP_MD *md; - int version, action; - int has_signature : 1; + struct apk_sign_ctx *sctx; + int version; int has_install : 1; - int has_data_checksum : 1; - int data_started : 1; - int in_signatures : 1; }; int apk_pkg_add_info(struct apk_database *db, struct apk_package *pkg, @@ -336,9 +484,14 @@ static int read_info_line(void *ctx, apk_blob_t line) } } - if (ri->data_started == 0 && - apk_blob_compare(APK_BLOB_STR("datahash"), l) == 0) - ri->has_data_checksum = 1; + if (ri->sctx->data_started == 0 && + apk_blob_compare(APK_BLOB_STR("datahash"), l) == 0) { + ri->sctx->has_data_checksum = 1; + ri->sctx->md = EVP_sha256(); + apk_blob_pull_hexdump( + &r, APK_BLOB_PTR_LEN(ri->sctx->data_checksum, + EVP_MD_size(ri->sctx->md))); + } return 0; } @@ -363,18 +516,16 @@ static int read_info_entry(void *ctx, const struct apk_file_info *ae, int i; /* Meta info and scripts */ - if (ri->in_signatures && strncmp(ae->name, ".SIGN.", 6) != 0) - ri->in_signatures = 0; + if (apk_sign_ctx_process_file(ri->sctx, ae, is) == 0) + return 0; - if (ri->data_started == 0 && ae->name[0] == '.') { + if (ri->sctx->data_started == 0 && ae->name[0] == '.') { /* APK 2.0 format */ if (strcmp(ae->name, ".PKGINFO") == 0) { apk_blob_t blob = apk_blob_from_istream(is, ae->size); apk_blob_for_each_segment(blob, "\n", read_info_line, ctx); free(blob.ptr); ri->version = 2; - } else if (strncmp(ae->name, ".SIGN.", 6) == 0) { - ri->has_signature = 1; } else if (strcmp(ae->name, ".INSTALL") == 0) { apk_warning("Package '%s-%s' contains deprecated .INSTALL", pkg->name->name, pkg->version); @@ -382,7 +533,6 @@ static int read_info_entry(void *ctx, const struct apk_file_info *ae, return 0; } - ri->data_started = 1; if (strncmp(ae->name, "var/db/apk/", 11) == 0) { /* APK 1.0 format */ ri->version = 1; @@ -423,35 +573,8 @@ static int read_info_entry(void *ctx, const struct apk_file_info *ae, return 0; } -static int apk_pkg_gzip_part(void *ctx, EVP_MD_CTX *mdctx, int part) -{ - struct read_info_ctx *ri = (struct read_info_ctx *) ctx; - - switch (part) { - case APK_MPART_BEGIN: - EVP_DigestInit_ex(mdctx, ri->md, NULL); - break; - case APK_MPART_BOUNDARY: - if (ri->in_signatures) { - EVP_DigestFinal_ex(mdctx, ri->pkg->csum.data, NULL); - EVP_DigestInit_ex(mdctx, ri->md, NULL); - return 0; - } - - if (ri->action == APK_SIGN_GENERATE_V1 || - !ri->has_data_checksum) - break; - /* Fallthrough to calculate checksum */ - case APK_MPART_END: - ri->pkg->csum.type = EVP_MD_CTX_size(mdctx); - EVP_DigestFinal_ex(mdctx, ri->pkg->csum.data, NULL); - return 1; - } - return 0; -} - struct apk_package *apk_pkg_read(struct apk_database *db, const char *file, - int action) + struct apk_sign_ctx *sctx) { struct read_info_ctx ctx; struct apk_file_info fi; @@ -466,22 +589,7 @@ struct apk_package *apk_pkg_read(struct apk_database *db, const char *file, return NULL; memset(&ctx, 0, sizeof(ctx)); - switch (action) { - case APK_SIGN_VERIFY: - ctx.in_signatures = 1; - ctx.md = EVP_md_null(); - break; - case APK_SIGN_GENERATE: - ctx.in_signatures = 1; - ctx.md = EVP_sha1(); - break; - case APK_SIGN_GENERATE_V1: - ctx.md = EVP_md5(); - break; - default: - return NULL; - } - + ctx.sctx = sctx; ctx.pkg = apk_pkg_new(); if (ctx.pkg == NULL) return NULL; @@ -492,27 +600,18 @@ struct apk_package *apk_pkg_read(struct apk_database *db, const char *file, ctx.db = db; ctx.has_install = 0; - ctx.action = action; ctx.pkg->size = fi.size; - tar = apk_bstream_gunzip_mpart(bs, apk_pkg_gzip_part, &ctx); + tar = apk_bstream_gunzip_mpart(bs, apk_sign_ctx_mpart_cb, sctx); r = apk_tar_parse(tar, read_info_entry, &ctx); tar->close(tar); - switch (r) { - case 0: - break; - case -2: - apk_error("File %s does not have a signature", file); + if (r < 0) goto err; - default: - apk_error("File %s is not an APK archive", file); + if (ctx.pkg->name == NULL) goto err; - } - - if (ctx.pkg->name == NULL) { - apk_error("File %s is corrupted", file); + if (sctx->action == APK_SIGN_VERIFY && !sctx->data_verified && + !(apk_flags & APK_FORCE)) goto err; - } /* Add implicit busybox dependency if there is scripts */ if (ctx.has_install) { diff --git a/src/verify.c b/src/verify.c new file mode 100644 index 0000000..2fed4bd --- /dev/null +++ b/src/verify.c @@ -0,0 +1,49 @@ +/* verify.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2009 Timo Teräs + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include +#include + +#include "apk_applet.h" +#include "apk_database.h" + +static int verify_main(void *ctx, int argc, char **argv) +{ + struct apk_database db; + struct apk_sign_ctx sctx; + int i, ok, rc = 0; + + apk_db_open(&db, NULL, APK_OPENF_NO_STATE); + for (i = 0; i < argc; i++) { + apk_sign_ctx_init(&sctx, APK_SIGN_VERIFY); + apk_pkg_read(&db, argv[i], &sctx); + ok = sctx.control_verified && sctx.data_verified; + if (apk_verbosity >= 1) + apk_message("%s: %s", argv[i], + ok ? "OK" : + sctx.data_verified ? "UNTRUSTED" : "FAILED"); + if (!ok) + rc++; + apk_sign_ctx_free(&sctx); + } + apk_db_close(&db); + + return rc; +} + +static struct apk_applet apk_verify = { + .name = "verify", + .help = "Verify package integrity and signature", + .arguments = "FILE...", + .main = verify_main, +}; + +APK_DEFINE_APPLET(apk_verify); + -- cgit v1.2.3