summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan McGee <dan@archlinux.org>2011-09-20 16:54:08 -0500
committerDan McGee <dan@archlinux.org>2011-09-22 11:14:31 -0500
commit765178c5bafe88e09f33ab67152b9bb3eabe7685 (patch)
tree44acad3f29e704265bd7cc0cc6004069ab1b27cb
parent0ef7129a4a7776db9d0c4dd52519e2ca21b428d5 (diff)
downloadpacman-765178c5bafe88e09f33ab67152b9bb3eabe7685.tar.xz
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 <dan@archlinux.org>
-rw-r--r--lib/libalpm/signing.c154
1 files 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
@@ -179,6 +179,127 @@ error:
}
/**
+ * 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
* @param data the decoded data; must be freed by the caller
@@ -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,