From 765178c5bafe88e09f33ab67152b9bb3eabe7685 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 20 Sep 2011 16:54:08 -0500 Subject: Implement PGP key search and import Add two new static methods, key_search() and key_import(), to our growing list of signing code. If we come across a key we do not have, attempt to look it up remotely and ask the user if they wish to import said key. If they do, flag the validation process as a potential 'retry', meaning it might succeed the next time it is ran. These depend on you having a 'keyserver hkp://foo.example.com' line in your gpg.conf file in your gnupg home directory to function. Signed-off-by: Dan McGee --- lib/libalpm/signing.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 5 deletions(-) diff --git a/lib/libalpm/signing.c b/lib/libalpm/signing.c index 45488104..fbe64c98 100644 --- a/lib/libalpm/signing.c +++ b/lib/libalpm/signing.c @@ -178,6 +178,127 @@ error: RET_ERR(handle, ALPM_ERR_GPGME, 1); } +/** + * Determine if we have a key is known in our local keyring. + * @param handle the context handle + * @param fpr the fingerprint key ID to look up + * @return 1 if key is known, 0 if key is unknown, -1 on error + */ +static int key_in_keychain(alpm_handle_t *handle, const char *fpr) +{ + gpgme_error_t err; + gpgme_ctx_t ctx; + gpgme_key_t key; + int ret = -1; + + memset(&ctx, 0, sizeof(ctx)); + err = gpgme_new(&ctx); + CHECK_ERR(); + + _alpm_log(handle, ALPM_LOG_DEBUG, "looking up key %s locally\n", fpr); + + err = gpgme_get_key(ctx, fpr, &key, 0); + if(gpg_err_code(err) == GPG_ERR_EOF) { + _alpm_log(handle, ALPM_LOG_DEBUG, "key lookup failed, unknown key\n"); + ret = 0; + } else if(gpg_err_code(err) == GPG_ERR_NO_ERROR) { + _alpm_log(handle, ALPM_LOG_DEBUG, "key lookup success, key exists\n"); + ret = 1; + } else { + _alpm_log(handle, ALPM_LOG_DEBUG, "gpg error: %s\n", gpgme_strerror(err)); + } + +error: + gpgme_key_unref(key); + gpgme_release(ctx); + return ret; +} + +/** + * Search for a GPG key in a remote location. + * This requires GPGME to call the gpg binary and have a keyserver previously + * defined in a gpg.conf configuration file. + * @param handle the context handle + * @param fpr the fingerprint key ID to look up + * @param pgpkey storage location for the given key if found + * @return 0 on success, 1 on error or key not found + */ +static int key_search(alpm_handle_t *handle, const char *fpr, + alpm_pgpkey_t *pgpkey) +{ + gpgme_error_t err; + gpgme_ctx_t ctx; + gpgme_keylist_mode_t mode; + gpgme_key_t key; + + memset(&ctx, 0, sizeof(ctx)); + err = gpgme_new(&ctx); + CHECK_ERR(); + + mode = gpgme_get_keylist_mode(ctx); + /* using LOCAL and EXTERN together doesn't work for GPG 1.X. Ugh. */ + mode &= ~GPGME_KEYLIST_MODE_LOCAL; + mode |= GPGME_KEYLIST_MODE_EXTERN; + err = gpgme_set_keylist_mode(ctx, mode); + CHECK_ERR(); + + _alpm_log(handle, ALPM_LOG_DEBUG, "looking up key %s remotely\n", fpr); + + err = gpgme_get_key(ctx, fpr, &key, 0); + if(gpg_err_code(err) == GPG_ERR_EOF) { + _alpm_log(handle, ALPM_LOG_DEBUG, "key lookup failed, unknown key\n"); + } else if(gpg_err_code(err) != GPG_ERR_NO_ERROR) { + _alpm_log(handle, ALPM_LOG_DEBUG, + "gpg error: %s\n", gpgme_strerror(err)); + CHECK_ERR(); + } + + /* should only get here if key actually exists */ + pgpkey->data = key; + if(key->subkeys->fpr) { + pgpkey->fingerprint = key->subkeys->fpr; + } else if(key->subkeys->keyid) { + pgpkey->fingerprint = key->subkeys->keyid; + } + pgpkey->uid = key->uids->uid; + pgpkey->name = key->uids->name; + pgpkey->email = key->uids->email; + pgpkey->created = key->subkeys->timestamp; + pgpkey->expires = key->subkeys->expires; + +error: + gpgme_release(ctx); + return gpg_err_code(err) == GPG_ERR_NO_ERROR; +} + +/** + * Import a key into the local keyring. + * @param handle the context handle + * @param key the key to import, likely retrieved from #key_search + * @return 0 on success, 1 on error + */ +static int key_import(alpm_handle_t *handle, alpm_pgpkey_t *key) +{ + gpgme_error_t err; + gpgme_ctx_t ctx; + gpgme_key_t keys[2]; + + memset(&ctx, 0, sizeof(ctx)); + err = gpgme_new(&ctx); + CHECK_ERR(); + + _alpm_log(handle, ALPM_LOG_DEBUG, "importing key\n"); + + keys[0] = key->data; + keys[1] = NULL; + err = gpgme_op_import_keys(ctx, keys); + CHECK_ERR(); + +error: + gpgme_release(ctx); + return gpg_err_code(err) != GPG_ERR_NO_ERROR; +} + /** * Decode a loaded signature in base64 form. * @param base64_data the signature to attempt to decode @@ -567,6 +688,7 @@ int _alpm_process_siglist(alpm_handle_t *handle, const char *identifier, for(i = 0; i < siglist->count; i++) { alpm_sigresult_t *result = siglist->results + i; const char *name = result->key.uid ? result->key.uid : result->key.fingerprint; + int answer; switch(result->status) { case ALPM_SIGSTATUS_VALID: case ALPM_SIGSTATUS_KEY_EXPIRED: @@ -578,6 +700,7 @@ int _alpm_process_siglist(alpm_handle_t *handle, const char *identifier, _alpm_log(handle, ALPM_LOG_ERROR, _("%s: signature from \"%s\" is marginal trust\n"), identifier, name); + /* QUESTION(handle, ALPM_QUESTION_EDIT_KEY_TRUST, &result->key, NULL, NULL, &answer); */ } break; case ALPM_SIGVALIDITY_UNKNOWN: @@ -585,6 +708,7 @@ int _alpm_process_siglist(alpm_handle_t *handle, const char *identifier, _alpm_log(handle, ALPM_LOG_ERROR, _("%s: signature from \"%s\" is unknown trust\n"), identifier, name); + /* QUESTION(handle, ALPM_QUESTION_EDIT_KEY_TRUST, &result->key, NULL, NULL, &answer); */ } break; case ALPM_SIGVALIDITY_NEVER: @@ -595,15 +719,35 @@ int _alpm_process_siglist(alpm_handle_t *handle, const char *identifier, } break; case ALPM_SIGSTATUS_KEY_UNKNOWN: - /* TODO import key here */ + /* ensure this key is still actually unknown; we may have imported it + * on an earlier call to this function. */ + if(key_in_keychain(handle, result->key.fingerprint) == 1) { + break; + } _alpm_log(handle, ALPM_LOG_ERROR, - _("%s: key \"%s\" is unknown\n"), - identifier, name); + _("%s: key \"%s\" is unknown\n"), identifier, name); + { + alpm_pgpkey_t fetch_key; + memset(&fetch_key, 0, sizeof(fetch_key)); + + if(key_search(handle, result->key.fingerprint, &fetch_key)) { + _alpm_log(handle, ALPM_LOG_DEBUG, + "unknown key, found %s on keyserver\n", fetch_key.uid); + QUESTION(handle, ALPM_QUESTION_IMPORT_KEY, + &fetch_key, NULL, NULL, &answer); + if(answer && !key_import(handle, &fetch_key)) { + retry = 1; + } + } else { + _alpm_log(handle, ALPM_LOG_DEBUG, + "key could not be looked up remotely\n"); + } + gpgme_key_unref(fetch_key.data); + } break; case ALPM_SIGSTATUS_SIG_EXPIRED: _alpm_log(handle, ALPM_LOG_ERROR, - _("%s: signature from \"%s\" is expired\n"), - identifier, name); + _("%s: signature from \"%s\" is expired\n"), identifier, name); break; case ALPM_SIGSTATUS_INVALID: _alpm_log(handle, ALPM_LOG_ERROR, -- cgit v1.2.3-24-g4f1b