verify: new applet (ref #46)

an utility to check package signature and integrity.
cute-signatures
Timo Teras 2009-07-17 13:07:52 +03:00
parent 50fed1063e
commit 3f4f9e9957
6 changed files with 259 additions and 79 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}

View File

@ -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) {

49
src/verify.c Normal file
View File

@ -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);