diff options
Diffstat (limited to 'src/adb_trust.c')
-rw-r--r-- | src/adb_trust.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/src/adb_trust.c b/src/adb_trust.c new file mode 100644 index 0000000..41c904f --- /dev/null +++ b/src/adb_trust.c @@ -0,0 +1,266 @@ +#include <errno.h> +#include <stdio.h> +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include "apk_defines.h" +#include "adb.h" + +struct adb_trust_key { + struct list_head key_node; + struct adb_pkey key; + +}; + +/* Trust */ +int adb_pkey_init(struct adb_pkey *pkey, EVP_PKEY *key) +{ + unsigned char dig[EVP_MAX_MD_SIZE], *pub = NULL; + unsigned int dlen = sizeof dig; + int len; + + if ((len = i2d_PublicKey(key, &pub)) < 0) return -EIO; + EVP_Digest(pub, len, dig, &dlen, EVP_sha512(), NULL); + memcpy(pkey->id, dig, sizeof pkey->id); + OPENSSL_free(pub); + + pkey->key = key; + return 0; +} + +void adb_pkey_free(struct adb_pkey *pkey) +{ + EVP_PKEY_free(pkey->key); +} + +int adb_pkey_load(struct adb_pkey *pkey, int dirfd, const char *fn) +{ + EVP_PKEY *key; + BIO *bio; + int fd; + + fd = openat(dirfd, fn, O_RDONLY|O_CLOEXEC); + if (fd < 0) return -errno; + + bio = BIO_new_fp(fdopen(fd, "r"), BIO_CLOSE); + if (!bio) return -ENOMEM; + + key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (!key) { + BIO_reset(bio); + key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + } + ERR_clear_error(); + + BIO_free(bio); + if (!key) return -EBADMSG; + + adb_pkey_init(pkey, key); + return 0; +} + +static struct adb_trust_key *adb_trust_load_key(int dirfd, const char *filename) +{ + struct adb_trust_key *key; + int r; + + key = calloc(1, sizeof *key); + if (!key) return ERR_PTR(-ENOMEM); + + r = adb_pkey_load(&key->key, dirfd, filename); + if (r) { + free(key); + return ERR_PTR(-ENOKEY); + } + + list_init(&key->key_node); + return key; +} + +static int __adb_trust_load_pubkey(void *pctx, int dirfd, const char *filename) +{ + struct adb_trust *trust = pctx; + struct adb_trust_key *key = adb_trust_load_key(dirfd, filename); + + if (!IS_ERR(key)) + list_add_tail(&key->key_node, &trust->trusted_key_list); + + return 0; +} + +int adb_trust_init(struct adb_trust *trust, int dirfd, struct apk_string_array *pkey_files) +{ + char **fn; + + *trust = (struct adb_trust){ + .mdctx = EVP_MD_CTX_new(), + }; + if (!trust->mdctx) return -ENOMEM; + EVP_MD_CTX_set_flags(trust->mdctx, EVP_MD_CTX_FLAG_FINALISE); + list_init(&trust->trusted_key_list); + list_init(&trust->private_key_list); + apk_dir_foreach_file(dirfd, __adb_trust_load_pubkey, trust); + + foreach_array_item(fn, pkey_files) { + struct adb_trust_key *key = adb_trust_load_key(AT_FDCWD, *fn); + if (IS_ERR(key)) return PTR_ERR(key); + list_add_tail(&key->key_node, &trust->private_key_list); + } + + return 0; +} + +static void __adb_trust_free_keys(struct list_head *h) +{ + struct adb_trust_key *tkey, *n; + + list_for_each_entry_safe(tkey, n, h, key_node) { + list_del(&tkey->key_node); + adb_pkey_free(&tkey->key); + free(tkey); + } +} + +void adb_trust_free(struct adb_trust *trust) +{ + if (!trust->mdctx) return; + __adb_trust_free_keys(&trust->trusted_key_list); + __adb_trust_free_keys(&trust->private_key_list); + EVP_MD_CTX_free(trust->mdctx); +} + +static int adb_verify_ctx_calc(struct adb_verify_ctx *vfy, unsigned int hash_alg, apk_blob_t data, apk_blob_t *pmd) +{ + const EVP_MD *evp; + apk_blob_t md; + + switch (hash_alg) { + case ADB_HASH_SHA512: + evp = EVP_sha512(); + *pmd = md = APK_BLOB_BUF(vfy->sha512); + break; + default: + return -ENOTSUP; + } + + if (!(vfy->calc & (1 << hash_alg))) { + unsigned int sz = md.len; + if (APK_BLOB_IS_NULL(data)) return -ENOMSG; + if (EVP_Digest(data.ptr, data.len, (unsigned char*) md.ptr, &sz, evp, NULL) != 1 || + sz != md.len) + return -EIO; + vfy->calc |= (1 << hash_alg); + } + return 0; +} + +int adb_trust_write_signatures(struct adb_trust *trust, struct adb *db, struct adb_verify_ctx *vfy, struct apk_ostream *os) +{ + union { + struct adb_sign_hdr hdr; + struct adb_sign_v0 v0; + unsigned char buf[8192]; + } sig; + struct adb_trust_key *tkey; + apk_blob_t md; + size_t siglen; + int r; + + if (!vfy) { + vfy = alloca(sizeof *vfy); + memset(vfy, 0, sizeof *vfy); + } + + r = adb_verify_ctx_calc(vfy, ADB_HASH_SHA512, db->adb, &md); + if (r) return r; + + list_for_each_entry(tkey, &trust->private_key_list, key_node) { + sig.v0 = (struct adb_sign_v0) { + .hdr.sign_ver = 0, + .hdr.hash_alg = ADB_HASH_SHA512, + }; + memcpy(sig.v0.id, tkey->key.id, sizeof(sig.v0.id)); + + siglen = sizeof sig.buf - sizeof sig.v0; + EVP_MD_CTX_set_pkey_ctx(trust->mdctx, NULL); + if (EVP_DigestSignInit(trust->mdctx, NULL, EVP_sha512(), NULL, tkey->key.key) != 1 || + EVP_DigestUpdate(trust->mdctx, &db->hdr, sizeof db->hdr) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig.hdr.sign_ver, sizeof sig.hdr.sign_ver) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig.hdr.hash_alg, sizeof sig.hdr.hash_alg) != 1 || + EVP_DigestUpdate(trust->mdctx, md.ptr, md.len) != 1 || + EVP_DigestSignFinal(trust->mdctx, sig.v0.sig, &siglen) != 1) { + ERR_print_errors_fp(stdout); + goto err_io; + } + + r = adb_c_block(os, ADB_BLOCK_SIG, APK_BLOB_PTR_LEN((char*) &sig, sizeof(sig.v0) + siglen)); + if (r < 0) goto err; + } + return 0; +err_io: + r = -EIO; +err: + apk_ostream_cancel(os, r); + return r; +} + +int adb_trust_verify_signature(struct adb_trust *trust, struct adb *db, struct adb_verify_ctx *vfy, apk_blob_t sigb) +{ + struct adb_trust_key *tkey; + struct adb_sign_hdr *sig; + struct adb_sign_v0 *sig0; + apk_blob_t md; + + if (APK_BLOB_IS_NULL(db->adb)) return -ENOMSG; + if (sigb.len < sizeof(struct adb_sign_hdr)) return -EBADMSG; + + sig = (struct adb_sign_hdr *) sigb.ptr; + sig0 = (struct adb_sign_v0 *) sigb.ptr; + if (sig->sign_ver != 0) return -ENOSYS; + + list_for_each_entry(tkey, &trust->trusted_key_list, key_node) { + if (memcmp(sig0->id, tkey->key.id, sizeof sig0->id) != 0) continue; + if (adb_verify_ctx_calc(vfy, sig->hash_alg, db->adb, &md) != 0) continue; + + EVP_MD_CTX_set_pkey_ctx(trust->mdctx, NULL); + if (EVP_DigestVerifyInit(trust->mdctx, NULL, EVP_sha512(), NULL, tkey->key.key) != 1 || + EVP_DigestUpdate(trust->mdctx, &db->hdr, sizeof db->hdr) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig->sign_ver, sizeof sig->sign_ver) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig->hash_alg, sizeof sig->hash_alg) != 1 || + EVP_DigestUpdate(trust->mdctx, md.ptr, md.len) != 1 || + EVP_DigestVerifyFinal(trust->mdctx, sig0->sig, sigb.len - sizeof(*sig0)) != 1) { + ERR_clear_error(); + continue; + } + + return 0; + } + + return -EKEYREJECTED; +} + +/* Command group for signing */ + +#include "apk_applet.h" + +#define SIGNING_OPTIONS(OPT) \ + OPT(OPT_SIGN_sign_key, APK_OPT_ARG "sign-key") + +APK_OPT_GROUP(options_signing, "Signing", SIGNING_OPTIONS); + +static int option_parse_signing(void *ctx, struct apk_db_options *dbopts, int optch, const char *optarg) +{ + switch (optch) { + case OPT_SIGN_sign_key: + *apk_string_array_add(&dbopts->private_keys) = (char*) optarg; + break; + default: + return -ENOTSUP; + } + return 0; +} + +const struct apk_option_group optgroup_signing = { + .desc = options_signing, + .parse = option_parse_signing, +}; |