Feature request - XEP-0373: OpenPGP for XMPP (OX)

Basic implementation of XEP-0373: OpenPGP for XMPP.
https://xmpp.org/extensions/xep-0373.html

Command /ox

Issue: #1331
This commit is contained in:
DebXWoody
2020-06-21 09:43:42 +02:00
committed by Michael Vetter
parent 3afd854dc8
commit 2c94ee5a88
20 changed files with 991 additions and 4 deletions

View File

@@ -72,6 +72,9 @@ static char* _remove_header_footer(char *str, const char *const footer);
static char* _add_header_footer(const char *const str, const char *const header, const char *const footer);
static void _save_pubkeys(void);
static gpgme_key_t _ox_key_lookup(const char *const barejid, gboolean secret_only);
static gboolean _ox_key_is_usable(gpgme_key_t key, const char *const barejid, gboolean secret);
void
_p_gpg_free_pubkeyid(ProfPGPPubKeyId *pubkeyid)
{
@@ -787,6 +790,219 @@ p_gpg_format_fp_str(char *fp)
return result;
}
/*!
* \brief Public keys with XMPP-URI.
*
* This function will look for all public key with a XMPP-URI as UID.
*
*/
GHashTable* ox_gpg_public_keys(void){
gpgme_error_t error;
GHashTable *result = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)_p_gpg_free_key);
gpgme_ctx_t ctx;
error = gpgme_new(&ctx);
if (error) {
log_error("OX - gpgme_new failed: %s %s", gpgme_strsource(error), gpgme_strerror(error));
return NULL;
}
error = gpgme_op_keylist_start(ctx, NULL, 0); // all public keys
if (error == GPG_ERR_NO_ERROR) {
gpgme_key_t key;
error = gpgme_op_keylist_next(ctx, &key);
if ( error != GPG_ERR_EOF && error != GPG_ERR_NO_ERROR) {
log_error("OX: gpgme_op_keylist_next %s %s", gpgme_strsource(error), gpgme_strerror(error));
g_hash_table_destroy(result);
return NULL;
}
while (!error) {
// Looking for XMPP URI UID
gpgme_user_id_t uid = key->uids;
gpgme_user_id_t xmppid = NULL;
while (!xmppid && uid) {
if( uid->name && strlen(uid->name) >= 10 ) {
if( strstr(uid->name, "xmpp:") == uid->name ) {
xmppid = uid;
}
}
uid = uid->next;
}
if(xmppid) {
// Build Key information about all subkey
gpgme_subkey_t sub = key->subkeys;
ProfPGPKey *p_pgpkey = _p_gpg_key_new();
p_pgpkey->id = strdup(sub->keyid);
p_pgpkey->name = strdup(xmppid->uid);
p_pgpkey->fp = strdup(sub->fpr);
if (sub->can_encrypt) p_pgpkey->encrypt = TRUE;
if (sub->can_authenticate) p_pgpkey->authenticate = TRUE;
if (sub->can_certify) p_pgpkey->certify = TRUE;
if (sub->can_sign) p_pgpkey->sign = TRUE;
sub = sub->next;
while (sub) {
if (sub->can_encrypt) p_pgpkey->encrypt = TRUE;
if (sub->can_authenticate) p_pgpkey->authenticate = TRUE;
if (sub->can_certify) p_pgpkey->certify = TRUE;
if (sub->can_sign) p_pgpkey->sign = TRUE;
sub = sub->next;
}
g_hash_table_insert(result, strdup(p_pgpkey->name), p_pgpkey);
}
gpgme_key_unref(key);
error = gpgme_op_keylist_next(ctx, &key);
}
}
gpgme_release(ctx);
//autocomplete_clear(key_ac);
// GList *ids = g_hash_table_get_keys(result);
// GList *curr = ids;
// while (curr) {
// ProfPGPKey *key = g_hash_table_lookup(result, curr->data);
// autocomplete_add(key_ac, key->id);
// curr = curr->next;
// }
// g_list_free(ids);
return result;
}
char* p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const recipient_barejid , const char* const message) {
setlocale (LC_ALL, "");
gpgme_check_version (NULL);
gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
gpgme_ctx_t ctx;
gpgme_error_t error = gpgme_new (&ctx);
if(GPG_ERR_NO_ERROR != error ) {
printf("gpgme_new: %d\n", error);
return NULL;
}
error = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OPENPGP);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
}
gpgme_set_armor(ctx,0);
gpgme_set_textmode(ctx,0);
gpgme_set_offline(ctx,1);
gpgme_set_keylist_mode(ctx, GPGME_KEYLIST_MODE_LOCAL);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
}
gpgme_key_t recp[3];
recp[0] = NULL,
recp[1] = NULL;
char* xmpp_jid_me = alloca( (strlen(sender_barejid)+6) * sizeof(char) );
char* xmpp_jid_recipient = alloca( (strlen(recipient_barejid)+6) * sizeof(char) );
strcpy(xmpp_jid_me, "xmpp:");
strcpy(xmpp_jid_recipient, "xmpp:");
strcat(xmpp_jid_me, sender_barejid);
strcat(xmpp_jid_recipient,recipient_barejid);
gpgme_signers_clear(ctx);
// lookup own key
recp[0] = _ox_key_lookup(sender_barejid, TRUE);
if(error != 0) {
log_error("Key not found for %s. GpgME Error: %s", xmpp_jid_me, gpgme_strerror(error));
return NULL;
}
error = gpgme_signers_add(ctx,recp[0]);
if(error != 0) {
log_error("gpgme_signers_add %s. GpgME Error: %s", xmpp_jid_me, gpgme_strerror(error));
return NULL;
}
// lookup key of recipient
recp[1] = _ox_key_lookup(recipient_barejid, FALSE);
if(error != 0) {
log_error("Key not found for %s. GpgME Error: %s", xmpp_jid_recipient, gpgme_strerror(error));
return NULL;
}
recp[2] = NULL;
log_debug("%s <%s>", recp[0]->uids->name, recp[0]->uids->email);
log_debug("%s <%s>", recp[1]->uids->name, recp[1]->uids->email);
gpgme_encrypt_flags_t flags = 0;
gpgme_data_t plain;
gpgme_data_t cipher;
error = gpgme_data_new (&plain);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
return NULL;
}
error = gpgme_data_new_from_mem(&plain, message, strlen(message),0);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
return NULL;
}
error = gpgme_data_new (&cipher);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
return NULL;
}
error = gpgme_op_encrypt_sign ( ctx, recp, flags, plain, cipher);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
return NULL;
}
size_t len;
char *cipher_str = gpgme_data_release_and_get_mem(cipher, &len);
char* result = g_base64_encode( (unsigned char*) cipher_str,len);
gpgme_key_release (recp[0]);
gpgme_key_release (recp[1]);
gpgme_release (ctx);
return result;
}
gboolean ox_is_private_key_available(const char *const barejid) {
g_assert(barejid);
gboolean result = FALSE;
gpgme_key_t key = _ox_key_lookup(barejid, TRUE);
if(key) {
if (_ox_key_is_usable(key, barejid, TRUE) ) {
result = TRUE;
}
gpgme_key_unref(key);
}
return result;
}
gboolean ox_is_public_key_available(const char *const barejid) {
g_assert(barejid);
gboolean result = FALSE;
gpgme_key_t key = _ox_key_lookup(barejid, FALSE);
if(key) {
if (_ox_key_is_usable(key, barejid, FALSE) ) {
result = TRUE;
}
gpgme_key_unref(key);
}
return result;
}
static char*
_remove_header_footer(char *str, const char *const footer)
{
@@ -837,3 +1053,126 @@ _save_pubkeys(void)
g_chmod(pubsloc, S_IRUSR | S_IWUSR);
g_free(g_pubkeys_data);
}
static gpgme_key_t _ox_key_lookup(const char *const barejid, gboolean secret_only) {
g_assert(barejid);
log_debug("Looking for %s key: %s", secret_only == TRUE ? "Private" : "Public", barejid);
gpgme_key_t key = NULL;
gpgme_error_t error;
gpgme_ctx_t ctx;
error = gpgme_new(&ctx);
if (error) {
log_error("OX - gpgme_new failed: %s %s", gpgme_strsource(error), gpgme_strerror(error));
return NULL;
}
error = gpgme_op_keylist_start(ctx, NULL, secret_only);
if (error == GPG_ERR_NO_ERROR) {
error = gpgme_op_keylist_next(ctx, &key);
if ( error != GPG_ERR_EOF && error != GPG_ERR_NO_ERROR) {
log_error("OX: gpgme_op_keylist_next %s %s", gpgme_strsource(error), gpgme_strerror(error));
return NULL;
}
GString* xmppuri = g_string_new("xmpp:");
g_string_append(xmppuri,barejid);
while (!error) {
// Looking for XMPP URI UID
gpgme_user_id_t uid = key->uids;
while ( uid ) {
if( uid->name && strlen(uid->name) >= 10 ) {
if( g_strcmp0(uid->name, xmppuri->str) == 0 ) {
gpgme_release(ctx);
return key;
}
}
uid = uid->next;
}
gpgme_key_unref(key);
error = gpgme_op_keylist_next(ctx, &key);
}
}
gpgme_release(ctx);
return key;
}
static gboolean _ox_key_is_usable(gpgme_key_t key, const char *const barejid, gboolean secret) {
gboolean result = TRUE;
if(key->revoked) result = FALSE;
if(key->expired) result = FALSE;
if(key->disabled) result = FALSE;
return result;
}
/*!
* @brief XMPP-OX: Decrypt OX Message.
*
*
*
* @param base64 base64_encode OpenPGP message.
*
* @result decrypt XMPP OX Message NULL terminated C-String
*/
char* p_ox_gpg_decrypt(char* base64) {
setlocale (LC_ALL, "");
gpgme_check_version (NULL);
gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
gpgme_ctx_t ctx;
gpgme_error_t error = gpgme_new (&ctx);
if(GPG_ERR_NO_ERROR != error ) {
printf("gpgme_new: %d\n", error);
return NULL;
}
error = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OPENPGP);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
}
gpgme_set_armor(ctx,0);
gpgme_set_textmode(ctx,0);
gpgme_set_offline(ctx,1);
gpgme_set_keylist_mode(ctx, GPGME_KEYLIST_MODE_LOCAL);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
}
gpgme_data_t plain = NULL;
gpgme_data_t cipher = NULL;
gsize s;
guchar* encypted = g_base64_decode(base64, &s);
error = gpgme_data_new_from_mem(&cipher, (char*)encypted, s,0);
if(error != 0) {
log_error("GpgME Error gpgme_data_new_from_mem: %s", gpgme_strerror(error));
return NULL;
}
error = gpgme_data_new (&plain);
if(error != 0) {
log_error("GpgME Error: %s", gpgme_strerror(error));
return NULL;
}
error = gpgme_op_decrypt_verify(ctx, cipher, plain);
if(error != 0) {
log_error("GpgME Error gpgme_op_decrypt: %s", gpgme_strerror(error));
error = gpgme_op_decrypt(ctx, cipher, plain);
if ( error != 0 ) {
return NULL;
}
}
size_t len;
char *plain_str = gpgme_data_release_and_get_mem(plain, &len);
char* result = malloc(len+1);
strcpy(result, plain_str);
result[len] = '\0';
return result;
}

View File

@@ -72,4 +72,20 @@ char* p_gpg_autocomplete_key(const char *const search_str, gboolean previous, vo
void p_gpg_autocomplete_key_reset(void);
char* p_gpg_format_fp_str(char *fp);
char* p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const recipient_barejid , const char* const message);
char* p_ox_gpg_decrypt(char* base64);
/*!
* \brief List of public keys with xmpp-URI.
*
* @returns GHashTable* with GString* xmpp-uri and ProfPGPKey* value. Empty
* hash, if there is no key. NULL in case of error.
*
*/
GHashTable* ox_gpg_public_keys(void);
gboolean ox_is_private_key_available(const char *const barejid);
gboolean ox_is_public_key_available(const char *const barejid);
#endif