/* 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_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; 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; int allow_untrusted : 1; char data_checksum[EVP_MAX_MD_SIZE]; struct apk_checksum identity; EVP_MD_CTX *mdctx; struct { apk_blob_t data; EVP_PKEY *pkey; char *identity; } signature; }; static void apk_sign_ctx_init(struct apk_sign_ctx *ctx, int action, struct apk_checksum *identity, struct apk_trust *trust) { 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 */ ctx->md = EVP_md_null(); 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 */ ctx->md = EVP_sha1(); memcpy(&ctx->identity, identity, sizeof(ctx->identity)); break; case APK_SIGN_GENERATE: case APK_SIGN_VERIFY_AND_GENERATE: ctx->md = EVP_sha1(); break; default: ctx->action = APK_SIGN_NONE; ctx->md = EVP_md_null(); ctx->control_started = 1; ctx->data_started = 1; break; } ctx->mdctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx->mdctx, ctx->md, NULL); EVP_MD_CTX_set_flags(ctx->mdctx, EVP_MD_CTX_FLAG_ONESHOT); } static void apk_sign_ctx_free(struct apk_sign_ctx *ctx) { if (ctx->signature.data.ptr != NULL) free(ctx->signature.data.ptr); EVP_MD_CTX_free(ctx->mdctx); } 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.pkey == 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]; unsigned int nid; } signature_type[] = { { "RSA512", NID_sha512 }, { "RSA256", NID_sha256 }, { "RSA", NID_sha1 }, { "DSA", NID_dsa }, }; const EVP_MD *md = NULL; const char *name = NULL; struct apk_pkey *pkey; 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.pkey != 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] == '.') { md = EVP_get_digestbynid(signature_type[i].nid); name = &fi->name[6+slen+1]; break; } } if (!md) return 0; pkey = apk_trust_key_by_name(ctx->trust, name); if (pkey) { ctx->md = md; ctx->signature.pkey = pkey->key; ctx->signature.data = apk_blob_from_istream(is, fi->size); } 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; unsigned char calculated[EVP_MAX_MD_SIZE]; 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 */ EVP_DigestUpdate(sctx->mdctx, data.ptr, data.len); if (sctx->has_data_checksum && !end_of_control) { /* End of data-block with a checksum read from the control block */ EVP_DigestFinal_ex(sctx->mdctx, calculated, NULL); if (EVP_MD_CTX_size(sctx->mdctx) == 0 || memcmp(calculated, sctx->data_checksum, EVP_MD_CTX_size(sctx->mdctx)) != 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.pkey != NULL) { r = EVP_VerifyFinal(sctx->mdctx, (unsigned char *) sctx->signature.data.ptr, sctx->signature.data.len, sctx->signature.pkey); if (r != 1 && !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 */ EVP_DigestFinal_ex(sctx->mdctx, calculated, NULL); if (memcmp(calculated, 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 = EVP_MD_CTX_size(sctx->mdctx); EVP_DigestFinal_ex(sctx->mdctx, sctx->identity.data, NULL); if (!sctx->has_data_checksum) return -APKE_V2PKG_FORMAT; break; } reset_digest: EVP_DigestInit_ex(sctx->mdctx, sctx->md, NULL); EVP_MD_CTX_set_flags(sctx->mdctx, EVP_MD_CTX_FLAG_ONESHOT); return 0; update_digest: EVP_MD_CTX_clear_flags(sctx->mdctx, EVP_MD_CTX_FLAG_ONESHOT); EVP_DigestUpdate(sctx->mdctx, 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->control_verified) 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 = trust->allow_untrusted ? APK_SIGN_NONE : APK_SIGN_VERIFY_IDENTITY; else action = trust->allow_untrusted ? APK_SIGN_NONE : 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_SIGNATURE_UNTRUSTED || 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; if (apk_blob_compare(APK_BLOB_STR("datahash"), l) == 0) { sctx->has_data_checksum = 1; sctx->md = EVP_sha256(); apk_blob_pull_hexdump( &r, APK_BLOB_PTR_LEN(sctx->data_checksum, EVP_MD_size(sctx->md))); } } 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; }