/* extract_v2.c - Alpine Package Keeper (APK) * * Copyright (C) 2005-2008 Natanael Copa * Copyright (C) 2008-2011 Timo Teräs * 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; }