summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimo Teras <timo.teras@iki.fi>2009-07-17 13:07:52 +0300
committerTimo Teras <timo.teras@iki.fi>2009-07-17 13:07:52 +0300
commit3f4f9e9957a27be4efb29498f8fecd63eafcd16c (patch)
treef9306fef078e62423f38ab58300b98bba4c4ab08
parent50fed1063e080530c7654ebd168e833af2d98c98 (diff)
downloadapk-tools-3f4f9e9957a27be4efb29498f8fecd63eafcd16c.tar.bz2
apk-tools-3f4f9e9957a27be4efb29498f8fecd63eafcd16c.tar.xz
verify: new applet (ref #46)
an utility to check package signature and integrity.
-rw-r--r--src/Makefile2
-rw-r--r--src/add.c5
-rw-r--r--src/apk_package.h32
-rw-r--r--src/index.c5
-rw-r--r--src/package.c245
-rw-r--r--src/verify.c49
6 files changed, 259 insertions, 79 deletions
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 <unistd.h>
#include <sys/wait.h>
+#include <openssl/pem.h>
+
#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 <timo.teras@iki.fi>
+ * 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 <stdio.h>
+#include <unistd.h>
+
+#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);
+