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>
This commit is contained in:
parent
0ef7129a4a
commit
765178c5ba
1 changed files with 149 additions and 5 deletions
|
@ -178,6 +178,127 @@ error:
|
||||||
RET_ERR(handle, ALPM_ERR_GPGME, 1);
|
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.
|
* Decode a loaded signature in base64 form.
|
||||||
* @param base64_data the signature to attempt to decode
|
* @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++) {
|
for(i = 0; i < siglist->count; i++) {
|
||||||
alpm_sigresult_t *result = siglist->results + i;
|
alpm_sigresult_t *result = siglist->results + i;
|
||||||
const char *name = result->key.uid ? result->key.uid : result->key.fingerprint;
|
const char *name = result->key.uid ? result->key.uid : result->key.fingerprint;
|
||||||
|
int answer;
|
||||||
switch(result->status) {
|
switch(result->status) {
|
||||||
case ALPM_SIGSTATUS_VALID:
|
case ALPM_SIGSTATUS_VALID:
|
||||||
case ALPM_SIGSTATUS_KEY_EXPIRED:
|
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,
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
||||||
_("%s: signature from \"%s\" is marginal trust\n"),
|
_("%s: signature from \"%s\" is marginal trust\n"),
|
||||||
identifier, name);
|
identifier, name);
|
||||||
|
/* QUESTION(handle, ALPM_QUESTION_EDIT_KEY_TRUST, &result->key, NULL, NULL, &answer); */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ALPM_SIGVALIDITY_UNKNOWN:
|
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,
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
||||||
_("%s: signature from \"%s\" is unknown trust\n"),
|
_("%s: signature from \"%s\" is unknown trust\n"),
|
||||||
identifier, name);
|
identifier, name);
|
||||||
|
/* QUESTION(handle, ALPM_QUESTION_EDIT_KEY_TRUST, &result->key, NULL, NULL, &answer); */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ALPM_SIGVALIDITY_NEVER:
|
case ALPM_SIGVALIDITY_NEVER:
|
||||||
|
@ -595,15 +719,35 @@ int _alpm_process_siglist(alpm_handle_t *handle, const char *identifier,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ALPM_SIGSTATUS_KEY_UNKNOWN:
|
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,
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
||||||
_("%s: key \"%s\" is unknown\n"),
|
_("%s: key \"%s\" is unknown\n"), identifier, name);
|
||||||
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;
|
break;
|
||||||
case ALPM_SIGSTATUS_SIG_EXPIRED:
|
case ALPM_SIGSTATUS_SIG_EXPIRED:
|
||||||
_alpm_log(handle, ALPM_LOG_ERROR,
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
||||||
_("%s: signature from \"%s\" is expired\n"),
|
_("%s: signature from \"%s\" is expired\n"), identifier, name);
|
||||||
identifier, name);
|
|
||||||
break;
|
break;
|
||||||
case ALPM_SIGSTATUS_INVALID:
|
case ALPM_SIGSTATUS_INVALID:
|
||||||
_alpm_log(handle, ALPM_LOG_ERROR,
|
_alpm_log(handle, ALPM_LOG_ERROR,
|
||||||
|
|
Loading…
Add table
Reference in a new issue