454 lines
12 KiB
C
454 lines
12 KiB
C
/* extract_v2.c - Alpine Package Keeper (APK)
|
|
*
|
|
* Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
|
|
* Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
|
|
* All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*/
|
|
|
|
#include "apk_context.h"
|
|
#include "apk_crypto.h"
|
|
#include "apk_extract.h"
|
|
#include "apk_package.h"
|
|
#include "apk_tar.h"
|
|
|
|
#define APK_SIGN_NONE 0
|
|
#define APK_SIGN_VERIFY 1
|
|
#define APK_SIGN_VERIFY_IDENTITY 2
|
|
#define APK_SIGN_GENERATE 4
|
|
#define APK_SIGN_VERIFY_AND_GENERATE 5
|
|
|
|
struct apk_sign_ctx {
|
|
struct apk_trust *trust;
|
|
int action;
|
|
int num_signatures;
|
|
int control_started : 1;
|
|
int data_started : 1;
|
|
int has_data_checksum : 1;
|
|
int control_verified : 1;
|
|
int data_verified : 1;
|
|
int allow_untrusted : 1;
|
|
char data_checksum[APK_DIGEST_MAX_LENGTH];
|
|
|
|
struct apk_checksum identity;
|
|
struct apk_digest_ctx digest_ctx;
|
|
|
|
struct {
|
|
struct apk_public_key *public_key;
|
|
apk_blob_t data;
|
|
uint16_t type;
|
|
char *identity;
|
|
} signature;
|
|
};
|
|
|
|
static void
|
|
apk_sign_ctx_init(struct apk_sign_ctx *ctx, int action, struct apk_checksum *identity, struct apk_trust *trust)
|
|
{
|
|
uint8_t digest_alg;
|
|
|
|
memset(ctx, 0, sizeof(struct apk_sign_ctx));
|
|
ctx->trust = trust;
|
|
ctx->action = action;
|
|
ctx->allow_untrusted = trust->allow_untrusted;
|
|
|
|
switch (action) {
|
|
case APK_SIGN_VERIFY:
|
|
/* If we're only verifing, we're going to start with a
|
|
* signature section, which we don't need a hash of */
|
|
digest_alg = APK_DIGEST_NONE;
|
|
break;
|
|
case APK_SIGN_VERIFY_IDENTITY:
|
|
/* If we're checking the package against a particular hash,
|
|
* we need to start with that hash, because there may not
|
|
* be a signature section to deduce it from */
|
|
digest_alg = APK_DIGEST_SHA1;
|
|
memcpy(&ctx->identity, identity, sizeof(ctx->identity));
|
|
break;
|
|
case APK_SIGN_GENERATE:
|
|
case APK_SIGN_VERIFY_AND_GENERATE:
|
|
digest_alg = APK_DIGEST_SHA1;
|
|
break;
|
|
default:
|
|
ctx->action = APK_SIGN_NONE;
|
|
ctx->control_started = 1;
|
|
ctx->data_started = 1;
|
|
|
|
digest_alg = APK_DIGEST_NONE;
|
|
break;
|
|
}
|
|
|
|
apk_digest_ctx_init(&ctx->digest_ctx, digest_alg);
|
|
}
|
|
|
|
static void apk_sign_ctx_free(struct apk_sign_ctx *ctx)
|
|
{
|
|
if (ctx->signature.data.ptr != NULL) {
|
|
free(ctx->signature.data.ptr);
|
|
}
|
|
|
|
apk_public_key_free(ctx->signature.public_key);
|
|
}
|
|
|
|
static int check_signing_key_trust(struct apk_sign_ctx *sctx)
|
|
{
|
|
switch (sctx->action) {
|
|
case APK_SIGN_VERIFY:
|
|
case APK_SIGN_VERIFY_AND_GENERATE:
|
|
if (sctx->signature.public_key == NULL) {
|
|
if (sctx->allow_untrusted) {
|
|
break;
|
|
}
|
|
return -APKE_SIGNATURE_UNTRUSTED;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int apk_sign_ctx_process_file(struct apk_sign_ctx *ctx, const struct apk_file_info *fi, struct apk_istream *is)
|
|
{
|
|
static struct {
|
|
char type[8];
|
|
uint8_t alg;
|
|
} signature_type[] = {
|
|
{"RSA256", APK_SIGNATURE_RSA256},
|
|
{"RSA512", APK_SIGNATURE_RSA512},
|
|
{"CUTE", APK_SIGNATURE_CUTE},
|
|
{"RSA", APK_SIGNATURE_RSA},
|
|
};
|
|
|
|
uint16_t signature_alg = APK_SIGNATURE_MAX;
|
|
struct apk_public_key *public_key;
|
|
const char *name = NULL;
|
|
int r, i;
|
|
|
|
if (ctx->data_started) {
|
|
return 1;
|
|
}
|
|
|
|
if (fi->name[0] != '.' || strchr(fi->name, '/') != NULL) {
|
|
/* APKv1.0 compatibility - first non-hidden file is
|
|
* considered to start the data section of the file.
|
|
* This does not make any sense if the file has v2.0
|
|
* style .PKGINFO */
|
|
if (ctx->has_data_checksum) {
|
|
return -APKE_V2PKG_FORMAT;
|
|
}
|
|
/* Error out early if identity part is missing */
|
|
if (ctx->action == APK_SIGN_VERIFY_IDENTITY) {
|
|
return -APKE_V2PKG_FORMAT;
|
|
}
|
|
ctx->data_started = 1;
|
|
ctx->control_started = 1;
|
|
|
|
r = check_signing_key_trust(ctx);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (ctx->control_started) {
|
|
return 1;
|
|
}
|
|
|
|
if (strncmp(fi->name, ".SIGN.", 6) != 0) {
|
|
ctx->control_started = 1;
|
|
return 1;
|
|
}
|
|
|
|
/* By this point, we must be handling a signature file */
|
|
ctx->num_signatures++;
|
|
|
|
/* Already found a signature by a trusted key; no need to keep searching */
|
|
if ((ctx->action != APK_SIGN_VERIFY && ctx->action != APK_SIGN_VERIFY_AND_GENERATE)
|
|
|| ctx->signature.public_key != NULL)
|
|
return 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(signature_type); i++) {
|
|
size_t slen = strlen(signature_type[i].type);
|
|
if (strncmp(&fi->name[6], signature_type[i].type, slen) == 0 && fi->name[6 + slen] == '.') {
|
|
signature_alg = signature_type[i].alg;
|
|
name = &fi->name[6 + slen + 1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (signature_alg == APK_SIGNATURE_MAX) {
|
|
return 0;
|
|
}
|
|
|
|
public_key = apk_trust_public_key_by_name(ctx->trust, name);
|
|
if (public_key != NULL) {
|
|
ctx->signature.public_key = public_key;
|
|
ctx->signature.type = signature_alg;
|
|
ctx->signature.data = apk_blob_from_istream(is, fi->size);
|
|
}
|
|
|
|
// TODO: check if its really a good idea to reset here
|
|
apk_verify_digest_start(&ctx->digest_ctx, signature_alg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* apk_sign_ctx_mpart_cb() handles hashing archives and checking signatures, but
|
|
it can't do it alone. apk_sign_ctx_process_file() must be in the loop to
|
|
actually select which signature is to be verified and load the corresponding
|
|
public key into the context object, and apk_sign_ctx_parse_pkginfo_line()
|
|
needs to be called when handling the .PKGINFO file to find any applicable
|
|
datahash and load it into the context for this function to check against. */
|
|
static int apk_sign_ctx_mpart_cb(void *ctx, int part, apk_blob_t data)
|
|
{
|
|
struct apk_sign_ctx *sctx = (struct apk_sign_ctx *) ctx;
|
|
struct apk_digest digest;
|
|
int r, end_of_control;
|
|
|
|
if ((part == APK_MPART_DATA) || (part == APK_MPART_BOUNDARY && sctx->data_started)) {
|
|
goto update_digest;
|
|
}
|
|
|
|
/* Still in signature blocks? */
|
|
if (!sctx->control_started) {
|
|
if (part == APK_MPART_END) {
|
|
return -APKE_V2PKG_FORMAT;
|
|
}
|
|
goto reset_digest;
|
|
}
|
|
|
|
/* Grab state and mark all remaining block as data */
|
|
end_of_control = (sctx->data_started == 0);
|
|
sctx->data_started = 1;
|
|
|
|
/* End of control-block and control does not have data checksum? */
|
|
if (sctx->has_data_checksum == 0 && end_of_control && part != APK_MPART_END)
|
|
goto update_digest;
|
|
|
|
/* Drool in the remainder of the digest block now, we will finish
|
|
* hashing it in all cases */
|
|
apk_digest_ctx_update(&sctx->digest_ctx, data.ptr, data.len);
|
|
|
|
if (sctx->has_data_checksum && !end_of_control) {
|
|
/* End of data-block with a checksum read from the control block */
|
|
apk_digest_ctx_final(&sctx->digest_ctx, &digest);
|
|
if (digest.len == 0 || memcmp(digest.data, sctx->data_checksum, digest.len) != 0) {
|
|
return -APKE_V2PKG_INTEGRITY;
|
|
}
|
|
sctx->data_verified = 1;
|
|
if (!sctx->allow_untrusted && !sctx->control_verified) {
|
|
return -APKE_SIGNATURE_UNTRUSTED;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Either end of control block with a data checksum or end
|
|
* of the data block following a control block without a data
|
|
* checksum. In either case, we're checking a signature. */
|
|
r = check_signing_key_trust(sctx);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
switch (sctx->action) {
|
|
case APK_SIGN_VERIFY:
|
|
case APK_SIGN_VERIFY_AND_GENERATE:
|
|
if (sctx->signature.public_key != NULL) {
|
|
r = apk_verify(sctx->signature.public_key,
|
|
&sctx->digest_ctx,
|
|
sctx->signature.data.ptr,
|
|
sctx->signature.data.len);
|
|
if (r != 0 && !sctx->allow_untrusted) {
|
|
return -APKE_SIGNATURE_INVALID;
|
|
}
|
|
} else {
|
|
r = 0;
|
|
if (!sctx->allow_untrusted) {
|
|
return -APKE_SIGNATURE_UNTRUSTED;
|
|
}
|
|
}
|
|
if (r == 1) {
|
|
sctx->control_verified = 1;
|
|
if (!sctx->has_data_checksum && part == APK_MPART_END) {
|
|
sctx->data_verified = 1;
|
|
}
|
|
}
|
|
if (sctx->action == APK_SIGN_VERIFY_AND_GENERATE) {
|
|
goto generate_identity;
|
|
}
|
|
break;
|
|
case APK_SIGN_VERIFY_IDENTITY:
|
|
/* Reset digest for hashing data */
|
|
apk_digest_ctx_final(&sctx->digest_ctx, &digest);
|
|
if (memcmp(digest.data, sctx->identity.data, sctx->identity.type) != 0) {
|
|
return -APKE_V2PKG_INTEGRITY;
|
|
}
|
|
|
|
sctx->control_verified = 1;
|
|
if (!sctx->has_data_checksum && part == APK_MPART_END) {
|
|
sctx->data_verified = 1;
|
|
}
|
|
break;
|
|
case APK_SIGN_GENERATE:
|
|
generate_identity:
|
|
/* Package identity is the checksum */
|
|
sctx->identity.type = apk_digest_alg_len(sctx->digest_ctx.alg);
|
|
apk_digest_ctx_final(&sctx->digest_ctx, &digest);
|
|
memcpy(sctx->identity.data, digest.data, sctx->identity.type);
|
|
if (!sctx->has_data_checksum) {
|
|
return -APKE_V2PKG_FORMAT;
|
|
}
|
|
break;
|
|
}
|
|
|
|
reset_digest:
|
|
apk_digest_ctx_reset(&sctx->digest_ctx, sctx->digest_ctx.alg);
|
|
return 0;
|
|
|
|
update_digest:
|
|
apk_digest_ctx_update(&sctx->digest_ctx, data.ptr, data.len);
|
|
return 0;
|
|
}
|
|
|
|
static int apk_extract_verify_v2index(struct apk_extract_ctx *ectx, apk_blob_t *desc, struct apk_istream *is)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
apk_extract_verify_v2file(struct apk_extract_ctx *ectx, const struct apk_file_info *fi, struct apk_istream *is)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct apk_extract_ops extract_v2verify_ops = {
|
|
.v2index = apk_extract_verify_v2index,
|
|
.v2meta = apk_extract_v2_meta,
|
|
.file = apk_extract_verify_v2file,
|
|
};
|
|
|
|
static int apk_extract_v2_entry(void *pctx, const struct apk_file_info *fi, struct apk_istream *is)
|
|
{
|
|
struct apk_extract_ctx *ectx = pctx;
|
|
struct apk_sign_ctx *sctx = ectx->pctx;
|
|
int r, type;
|
|
|
|
r = apk_sign_ctx_process_file(sctx, fi, is);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
if (!sctx->control_started)
|
|
return 0;
|
|
if (!sctx->data_started || !sctx->has_data_checksum) {
|
|
if (fi->name[0] == '.') {
|
|
ectx->is_package = 1;
|
|
if (ectx->is_index) {
|
|
return -APKE_V2NDX_FORMAT;
|
|
}
|
|
if (!ectx->ops->v2meta) {
|
|
return -APKE_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
if (strcmp(fi->name, ".PKGINFO") == 0) {
|
|
return ectx->ops->v2meta(ectx, is);
|
|
} else if (strcmp(fi->name, ".INSTALL") == 0) {
|
|
return -APKE_V2PKG_FORMAT;
|
|
} else if ((type = apk_script_type(&fi->name[1])) != APK_SCRIPT_INVALID) {
|
|
if (ectx->ops->script) {
|
|
return ectx->ops->script(ectx, type, fi->size, is);
|
|
}
|
|
}
|
|
} else {
|
|
ectx->is_index = 1;
|
|
if (ectx->is_package) {
|
|
return -APKE_V2PKG_FORMAT;
|
|
}
|
|
if (!ectx->ops->v2index) {
|
|
return -APKE_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
if (strcmp(fi->name, "DESCRIPTION") == 0) {
|
|
free(ectx->desc.ptr);
|
|
ectx->desc = apk_blob_from_istream(is, fi->size);
|
|
} else if (strcmp(fi->name, "APKINDEX") == 0) {
|
|
return ectx->ops->v2index(ectx, &ectx->desc, is);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (!sctx->data_started)
|
|
return 0;
|
|
if (!ectx->ops->file)
|
|
return -ECANCELED;
|
|
return ectx->ops->file(ectx, fi, is);
|
|
}
|
|
|
|
int apk_extract_v2(struct apk_extract_ctx *ectx, struct apk_istream *is)
|
|
{
|
|
struct apk_ctx *ac = ectx->ac;
|
|
struct apk_trust *trust = apk_ctx_get_trust(ac);
|
|
struct apk_sign_ctx sctx;
|
|
int r, action;
|
|
|
|
if (ectx->generate_identity) {
|
|
action = trust->allow_untrusted ? APK_SIGN_GENERATE : APK_SIGN_VERIFY_AND_GENERATE;
|
|
} else if (ectx->identity) {
|
|
action = APK_SIGN_VERIFY_IDENTITY;
|
|
} else {
|
|
action = APK_SIGN_VERIFY;
|
|
}
|
|
|
|
if (!ectx->ops) {
|
|
ectx->ops = &extract_v2verify_ops;
|
|
}
|
|
|
|
ectx->pctx = &sctx;
|
|
apk_sign_ctx_init(&sctx, action, ectx->identity, trust);
|
|
r = apk_tar_parse(apk_istream_gunzip_mpart(is, apk_sign_ctx_mpart_cb, &sctx),
|
|
apk_extract_v2_entry,
|
|
ectx,
|
|
apk_ctx_get_id_cache(ac));
|
|
if (r == -ECANCELED) {
|
|
r = 0;
|
|
}
|
|
if ((r == 0 || r == -APKE_EOF) && !ectx->is_package && !ectx->is_index) {
|
|
r = ectx->ops->v2index ? -APKE_V2NDX_FORMAT : -APKE_V2PKG_FORMAT;
|
|
}
|
|
if (ectx->generate_identity) {
|
|
*ectx->identity = sctx.identity;
|
|
}
|
|
apk_sign_ctx_free(&sctx);
|
|
free(ectx->desc.ptr);
|
|
apk_extract_reset(ectx);
|
|
|
|
return r;
|
|
}
|
|
|
|
void apk_extract_v2_control(struct apk_extract_ctx *ectx, apk_blob_t l, apk_blob_t r)
|
|
{
|
|
struct apk_sign_ctx *sctx = ectx->pctx;
|
|
|
|
if (!sctx || !sctx->control_started || sctx->data_started) {
|
|
return;
|
|
}
|
|
|
|
// TODO check if reset is ok
|
|
if (apk_blob_compare(APK_BLOB_STR("datahash"), l) == 0) {
|
|
sctx->has_data_checksum = 1;
|
|
apk_digest_ctx_reset(&sctx->digest_ctx, APK_DIGEST_SHA256);
|
|
apk_blob_pull_hexdump(&r, APK_BLOB_PTR_LEN(sctx->data_checksum, apk_digest_alg_len(APK_DIGEST_SHA256)));
|
|
}
|
|
}
|
|
|
|
int apk_extract_v2_meta(struct apk_extract_ctx *ectx, struct apk_istream *is)
|
|
{
|
|
apk_blob_t k, v, token = APK_BLOB_STRLIT("\n");
|
|
while (apk_istream_get_delim(is, token, &k) == 0) {
|
|
if (k.len < 1 || k.ptr[0] == '#') {
|
|
continue;
|
|
}
|
|
|
|
if (apk_blob_split(k, APK_BLOB_STRLIT(" = "), &k, &v)) {
|
|
apk_extract_v2_control(ectx, k, v);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|