1
0
mirror of https://github.com/profanity-im/profanity.git synced 2024-12-04 14:46:46 -05:00

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

78
docs/profanity-ox.md Normal file
View File

@ -0,0 +1,78 @@
# Profanity - OpenPGP for XMPP
Implementation of XEP-0373 - OpenPGP for XMPP (OX) in profanity.
## Overview
The current version (2020-05-23) of profanity provides *XEP-0027: Current Jabber
OpenPGP Usage* via the `/pgp` command. This XEP is *Obsolete*. We should
implement *XEP-0373 - OpenPGP for XMPP (OX)* in profanity.
## pgp
```
14:37:52 - Synopsis
14:37:52 - /pgp libver
14:37:52 - /pgp keys
14:37:52 - /pgp contacts
14:37:52 - /pgp setkey <contact> <keyid>
14:37:52 - /pgp start [<contact>]
14:37:52 - /pgp end
14:37:52 - /pgp log on|off|redact
14:37:52 - /pgp char <char>
14:37:52 -
14:37:52 - Description
14:37:52 - Open PGP commands to manage keys, and perform PGP encryption during chat sessions. See the /account command to set your own PGP key.
14:37:52 -
14:37:52 - Arguments
14:37:52 - libver : Show which version of the libgpgme library is being used.
14:37:52 - keys : List all keys known to the system.
14:37:52 - contacts : Show contacts with assigned public keys.
14:37:52 - setkey <contact> <keyid> : Manually associate a contact with a public key.
14:37:52 - start [<contact>] : Start PGP encrypted chat, current contact will be used if not specified.
14:37:52 - end : End PGP encrypted chat with the current recipient.
14:37:52 - log on|off : Enable or disable plaintext logging of PGP encrypted messages.
14:37:52 - log redact : Log PGP encrypted messages, but replace the contents with [redacted]. This is the default.
14:37:52 - char <char> : Set the character to be displayed next to PGP encrypted messages.
```
## OX
We should implement the `/ox` command which can be used for XEP-0373 instead of
XEP-0027.
```
/ox keys - List all public keys known to the system (gnupg's keyring)
/ox contacts - Shows contacts with an assigned public key.
```
The `keys` command will list all public keys of gnupg's Keyring, independent if
the key is in use for XMPP or not.
In profanity we are going to implement the key lookup with a XMPP-URI as OpenPGP
User-ID. An OpenPGP public key can only be used, if the owner of the public key
created an User-ID with the XMPP-URI as Name. https://xmpp.org/extensions/xep-0373.html#openpgp-user-ids
It's not required and possible to assign a contact to an public key.
```
sec rsa3072 2020-05-01 [SC] [verfällt: 2022-05-01]
7FA1EB8644BAC07E7F18E7C9F121E6A6F3A0C7A5
uid [ ultimativ ] Doctor Snuggles <doctor.snuggles@domain.tld>
uid [ ultimativ ] xmpp:doctor.snuggles@domain.tld
ssb rsa3072 2020-05-01 [E] [verfällt: 2022-05-01]
```
The `contacts` command will show all contacts of the roster with a public key in
the keyring, if there is a xmpp user-id within the public key.
OX provides the elements: `<signcrypt/>`, `<sign/>` and `<crypt/>`. Profanity
implements signcrypt, only.
## Keys command
The command `keys` is independent of the XEP. Should we move common commands
(e.g. /pgp keys /ox keys) to /openpgp which will will be the function which are
related to gnupg itself.
## Appendix
* https://xmpp.org/extensions/xep-0373.html - 0.4.0 (2018-07-30)

View File

@ -78,6 +78,7 @@ static char* _otr_autocomplete(ProfWin *window, const char *const input, gboolea
#endif
#ifdef HAVE_LIBGPGME
static char* _pgp_autocomplete(ProfWin *window, const char *const input, gboolean previous);
static char* _ox_autocomplete(ProfWin *window, const char *const input, gboolean previous);
#endif
#ifdef HAVE_OMEMO
static char* _omemo_autocomplete(ProfWin *window, const char *const input, gboolean previous);
@ -224,6 +225,9 @@ static Autocomplete receipts_ac;
static Autocomplete pgp_ac;
static Autocomplete pgp_log_ac;
static Autocomplete pgp_sendfile_ac;
static Autocomplete ox_ac;
static Autocomplete ox_log_ac;
static Autocomplete ox_sendfile_ac;
#endif
static Autocomplete tls_ac;
static Autocomplete titlebar_ac;
@ -261,6 +265,13 @@ static Autocomplete correction_ac;
static Autocomplete avatar_ac;
static Autocomplete executable_ac;
/*!
* \brief Initialization of auto completion for commands.
*
* This function implements the auto completion for profanity's commands.
*
*/
void
cmd_ac_init(void)
{
@ -842,6 +853,29 @@ cmd_ac_init(void)
pgp_sendfile_ac = autocomplete_new();
autocomplete_add(pgp_sendfile_ac, "on");
autocomplete_add(pgp_sendfile_ac, "off");
// XEP-0373: OX
ox_ac = autocomplete_new();
autocomplete_add(ox_ac, "keys");
autocomplete_add(ox_ac, "contacts");
autocomplete_add(ox_ac, "start");
autocomplete_add(ox_ac, "end");
autocomplete_add(ox_ac, "log");
autocomplete_add(ox_ac, "char");
autocomplete_add(ox_ac, "sendfile");
autocomplete_add(ox_ac, "announce");
autocomplete_add(ox_ac, "discover");
autocomplete_add(ox_ac, "request");
pgp_log_ac = autocomplete_new();
autocomplete_add(ox_log_ac, "on");
autocomplete_add(ox_log_ac, "off");
autocomplete_add(ox_log_ac, "redact");
pgp_sendfile_ac = autocomplete_new();
autocomplete_add(ox_sendfile_ac, "on");
autocomplete_add(ox_sendfile_ac, "off");
#endif
tls_ac = autocomplete_new();
@ -1707,6 +1741,7 @@ _cmd_ac_complete_params(ProfWin *window, const char *const input, gboolean previ
#endif
#ifdef HAVE_LIBGPGME
g_hash_table_insert(ac_funcs, "/pgp", _pgp_autocomplete);
g_hash_table_insert(ac_funcs, "/ox", _ox_autocomplete);
#endif
#ifdef HAVE_OMEMO
g_hash_table_insert(ac_funcs, "/omemo", _omemo_autocomplete);
@ -2420,6 +2455,68 @@ _pgp_autocomplete(ProfWin *window, const char *const input, gboolean previous)
return NULL;
}
/*!
* \brief Auto completion for XEP-0373: OpenPGP for XMPP command.
*
*
*/
static char*
_ox_autocomplete(ProfWin *window, const char *const input, gboolean previous)
{
char *found = NULL;
jabber_conn_status_t conn_status = connection_get_status();
if (conn_status == JABBER_CONNECTED) {
found = autocomplete_param_with_func(input, "/ox start", roster_contact_autocomplete, previous, NULL);
if (found) {
return found;
}
}
found = autocomplete_param_with_ac(input, "/ox log", ox_log_ac, TRUE, previous);
if (found) {
return found;
}
found = autocomplete_param_with_ac(input, "/ox sendfile", ox_sendfile_ac, TRUE, previous);
if (found) {
return found;
}
gboolean result;
gchar **args = parse_args(input, 2, 3, &result);
if ((strncmp(input, "/ox", 4) == 0) && (result == TRUE)) {
GString *beginning = g_string_new("/ox ");
g_string_append(beginning, args[0]);
if (args[1]) {
g_string_append(beginning, " ");
g_string_append(beginning, args[1]);
}
found = autocomplete_param_with_func(input, beginning->str, p_gpg_autocomplete_key, previous, NULL);
g_string_free(beginning, TRUE);
if (found) {
g_strfreev(args);
return found;
}
}
g_strfreev(args);
if (conn_status == JABBER_CONNECTED) {
found = autocomplete_param_with_func(input, "/ox setkey", roster_barejid_autocomplete, previous, NULL);
if (found) {
return found;
}
}
found = autocomplete_param_with_ac(input, "/ox", ox_ac, TRUE, previous);
if (found) {
return found;
}
return NULL;
}
#endif
#ifdef HAVE_OMEMO

View File

@ -1691,6 +1691,48 @@ static struct cmd_t command_defs[] =
"/pgp char P")
},
// XEP-0373: OpenPGP for XMPP
#ifdef HAVE_LIBGPGME
{ "/ox",
parse_args, 1, 3, NULL,
CMD_NOSUBFUNCS
CMD_MAINFUNC(cmd_ox)
CMD_TAGS(
CMD_TAG_CHAT,
CMD_TAG_UI)
CMD_SYN(
"/ox keys",
"/ox contacts",
"/ox start [<contact>]",
"/ox end",
"/ox log on|off|redact",
"/ox char <char>",
"/ox sendfile on|off",
"/ox announce <file>",
"/ox discover",
"/ox request <jid>")
CMD_DESC(
"OpenPGP (OX) commands to manage keys, and perform PGP encryption during chat sessions. ")
CMD_ARGS(
{ "keys", "List all keys known to the system." },
{ "contacts", "Show contacts with assigned public keys." },
{ "start [<contact>]", "Start PGP encrypted chat, current contact will be used if not specified." },
{ "end", "End PGP encrypted chat with the current recipient." },
{ "log on|off", "Enable or disable plaintext logging of PGP encrypted messages." },
{ "log redact", "Log PGP encrypted messages, but replace the contents with [redacted]. This is the default." },
{ "char <char>", "Set the character to be displayed next to PGP encrypted messages." },
{ "announce <file>", "Announce a public key by pushing it on the XMPP Server"},
{ "discover <jid>", "Discover public keys of a jid "},
{ "request <jid>", "Request public keys"},
{ "sendfile on|off", "Allow /sendfile to send unencrypted files while otherwise using PGP."})
CMD_EXAMPLES(
"/ox log off",
"/ox start odin@valhalla.edda",
"/ox end",
"/ox char X")
},
#endif // HAVE_LIBGPGME
{ "/otr",
parse_args, 1, 3, NULL,
CMD_SUBFUNCS(

View File

@ -7418,6 +7418,169 @@ cmd_pgp(ProfWin *window, const char *const command, gchar **args)
#endif
}
#ifdef HAVE_LIBGPGME
/*!
* \brief Command for XEP-0373: OpenPGP for XMPP
*
*/
gboolean
cmd_ox(ProfWin *window, const char *const command, gchar **args)
{
if (args[0] == NULL) {
cons_bad_cmd_usage(command);
return TRUE;
}
// The '/ox keys' command - same like in pgp
// Should we move this to a common command
// e.g. '/openpgp keys'?.
else if (g_strcmp0(args[0], "keys") == 0) {
GHashTable *keys = p_gpg_list_keys();
if (!keys || g_hash_table_size(keys) == 0) {
cons_show("No keys found");
return TRUE;
}
cons_show("OpenPGP keys:");
GList *keylist = g_hash_table_get_keys(keys);
GList *curr = keylist;
while (curr) {
ProfPGPKey *key = g_hash_table_lookup(keys, curr->data);
cons_show(" %s", key->name);
cons_show(" ID : %s", key->id);
char *format_fp = p_gpg_format_fp_str(key->fp);
cons_show(" Fingerprint : %s", format_fp);
free(format_fp);
if (key->secret) {
cons_show(" Type : PUBLIC, PRIVATE");
} else {
cons_show(" Type : PUBLIC");
}
curr = g_list_next(curr);
}
g_list_free(keylist);
p_gpg_free_keys(keys);
return TRUE;
}
else if (g_strcmp0(args[0], "contacts") == 0) {
GHashTable *keys = ox_gpg_public_keys();
cons_show("OpenPGP keys:");
GList *keylist = g_hash_table_get_keys(keys);
GList *curr = keylist;
GSList *roster_list = NULL;
jabber_conn_status_t conn_status = connection_get_status();
if (conn_status != JABBER_CONNECTED) {
cons_show("You are not currently connected.");
} else {
roster_list = roster_get_contacts(ROSTER_ORD_NAME);
}
while (curr) {
ProfPGPKey *key = g_hash_table_lookup(keys, curr->data);
PContact contact = NULL;
if (roster_list) {
GSList *curr_c = roster_list;
while ( !contact && curr_c){
contact = curr_c->data;
const char *jid = p_contact_barejid(contact);
GString* xmppuri = g_string_new("xmpp:");
g_string_append(xmppuri, jid);
if( g_strcmp0(key->name, xmppuri->str)) {
contact = NULL;
}
curr_c = g_slist_next(curr_c);
}
}
if(contact) {
cons_show("%s - %s", key->fp, key->name);
} else {
cons_show("%s - %s (not in roster)", key->fp, key->name);
}
curr = g_list_next(curr);
}
} else if (g_strcmp0(args[0], "start") == 0) {
jabber_conn_status_t conn_status = connection_get_status();
if (conn_status != JABBER_CONNECTED) {
cons_show("You must be connected to start OX encrpytion.");
return TRUE;
}
if (window->type != WIN_CHAT && args[1] == NULL) {
cons_show("You must be in a regular chat window to start OX encrpytion.");
return TRUE;
}
ProfChatWin *chatwin = NULL;
if (args[1]) {
char *contact = args[1];
char *barejid = roster_barejid_from_name(contact);
if (barejid == NULL) {
barejid = contact;
}
chatwin = wins_get_chat(barejid);
if (!chatwin) {
chatwin = chatwin_new(barejid);
}
ui_focus_win((ProfWin*)chatwin);
} else {
chatwin = (ProfChatWin*)window;
assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
}
if (chatwin->is_otr) {
win_println(window, THEME_DEFAULT, "!", "You must end the OTR session to start OX encryption.");
return TRUE;
}
if (chatwin->pgp_send) {
win_println(window, THEME_DEFAULT, "!", "You must end the PGP session to start OX encryption.");
return TRUE;
}
if (chatwin->is_ox) {
win_println(window, THEME_DEFAULT, "!", "You have already started OX encryption.");
return TRUE;
}
ProfAccount *account = accounts_get_account(session_get_account_name());
if ( !ox_is_private_key_available(account->jid) ) {
win_println(window, THEME_DEFAULT, "!", "No private OpenPGP found, cannot start OX encryption.");
account_free(account);
return TRUE;
}
account_free(account);
if (!ox_is_public_key_available(chatwin->barejid)) {
win_println(window, THEME_DEFAULT, "!", "No OX-OpenPGP key found for %s.", chatwin->barejid);
return TRUE;
}
chatwin->is_ox = TRUE;
win_println(window, THEME_DEFAULT, "!", "OX encryption enabled.");
return TRUE;
} else if (g_strcmp0(args[0], "push") == 0) {
if( args[1] ) {
cons_show("Push file...%s ", args[1] );
} else {
cons_show("Filename is required");
}
} else {
cons_show("OX not implemented");
}
return TRUE;
}
#endif // HAVE_LIBGPGME
gboolean
cmd_otr_char(ProfWin *window, const char *const command, gchar **args)
{

View File

@ -107,6 +107,9 @@ gboolean cmd_msg(ProfWin *window, const char *const command, gchar **args);
gboolean cmd_nick(ProfWin *window, const char *const command, gchar **args);
gboolean cmd_notify(ProfWin *window, const char *const command, gchar **args);
gboolean cmd_pgp(ProfWin *window, const char *const command, gchar **args);
#ifdef HAVE_LIBGPGME
gboolean cmd_ox(ProfWin *window, const char *const command, gchar **args);
#endif // HAVE_LIBGPGME
gboolean cmd_outtype(ProfWin *window, const char *const command, gchar **args);
gboolean cmd_prefs(ProfWin *window, const char *const command, gchar **args);
gboolean cmd_priority(ProfWin *window, const char *const command, gchar **args);

View File

@ -62,6 +62,7 @@
#define PREF_GROUP_OTR "otr"
#define PREF_GROUP_PGP "pgp"
#define PREF_GROUP_OMEMO "omemo"
#define PREF_GROUP_OX "ox"
#define PREF_GROUP_MUC "muc"
#define PREF_GROUP_PLUGINS "plugins"
@ -942,6 +943,18 @@ prefs_set_pgp_char(char *ch)
return _prefs_set_encryption_char(ch, PREF_GROUP_PGP, "pgp.char");
}
char*
prefs_get_ox_char(void)
{
return _prefs_get_encryption_char("%", PREF_GROUP_OX, "ox.char");
}
gboolean
prefs_set_ox_char(char *ch)
{
return _prefs_set_encryption_char(ch, PREF_GROUP_OX, "ox.char");
}
char*
prefs_get_omemo_char(void)
{

View File

@ -244,6 +244,9 @@ char* prefs_get_pgp_char(void);
gboolean prefs_set_pgp_char(char *ch);
char* prefs_get_omemo_char(void);
gboolean prefs_set_omemo_char(char *ch);
// XEP-0373: OpenPGP for XMPP
char* prefs_get_ox_char(void);
gboolean prefs_set_ox_char(char *ch);
char prefs_get_roster_header_char(void);
void prefs_set_roster_header_char(char ch);

View File

@ -288,6 +288,8 @@ static prof_msg_type_t _get_message_type_type(const char *const type) {
static const char* _get_message_enc_str(prof_enc_t enc) {
switch (enc) {
case PROF_MSG_ENC_OX:
return "ox";
case PROF_MSG_ENC_PGP:
return "pgp";
case PROF_MSG_ENC_OTR:
@ -298,7 +300,7 @@ static const char* _get_message_enc_str(prof_enc_t enc) {
return "none";
}
return "none";
return "none"; // do not return none - return NULL
}
static void

View File

@ -138,6 +138,15 @@ cl_ev_send_msg_correct(ProfChatWin *chatwin, const char *const msg, const char *
log_database_add_outgoing_chat(id, chatwin->barejid, plugin_msg, replace_id, PROF_MSG_ENC_OMEMO);
chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OMEMO, request_receipt, replace_id);
free(id);
#endif
} else if (chatwin->is_ox) {
#ifdef HAVE_LIBGPGME
// XEP-0373: OpenPGP for XMPP
char *id = message_send_chat_ox(chatwin->barejid, plugin_msg, request_receipt, replace_id);
chat_log_pgp_msg_out(chatwin->barejid, plugin_msg, NULL);
log_database_add_outgoing_chat(id, chatwin->barejid, plugin_msg, replace_id, PROF_MSG_ENC_OX);
chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_ENC_OX, request_receipt, replace_id);
free(id);
#endif
} else if (chatwin->pgp_send) {
#ifdef HAVE_LIBGPGME

View File

@ -516,6 +516,22 @@ _sv_ev_incoming_pgp(ProfChatWin *chatwin, gboolean new_win, ProfMessage *message
#endif
}
static void
_sv_ev_incoming_ox(ProfChatWin *chatwin, gboolean new_win, ProfMessage *message, gboolean logit)
{
#ifdef HAVE_LIBGPGME
//_clean_incoming_message(message);
chatwin_incoming_msg(chatwin, message, new_win);
log_database_add_incoming(message);
if (logit) {
chat_log_pgp_msg_in(message);
}
chatwin->pgp_recv = TRUE;
//p_gpg_free_decrypted(message->plain);
message->plain = NULL;
}
#endif
static void
_sv_ev_incoming_otr(ProfChatWin *chatwin, gboolean new_win, ProfMessage *message)
{
@ -604,7 +620,9 @@ sv_ev_incoming_message(ProfMessage *message)
#endif
}
if (message->encrypted) {
if( message->enc == PROF_MSG_ENC_OX) {
_sv_ev_incoming_ox(chatwin, new_win, message, TRUE);
} else if (message->encrypted) {
if (chatwin->is_otr) {
win_println((ProfWin*)chatwin, THEME_DEFAULT, "-", "PGP encrypted message received whilst in OTR session.");
} else {
@ -638,7 +656,9 @@ sv_ev_incoming_carbon(ProfMessage *message)
#endif
}
if (message->encrypted) {
if (message->enc == PROF_MSG_ENC_OX) {
_sv_ev_incoming_ox(chatwin, new_win, message, FALSE);
} else if (message->encrypted) {
_sv_ev_incoming_pgp(chatwin, new_win, message, FALSE);
} else if (message->enc == PROF_MSG_ENC_OMEMO) {
_sv_ev_incoming_omemo(chatwin, new_win, message, FALSE);

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

View File

@ -90,6 +90,9 @@ chatwin_new(const char *const barejid)
if (prefs_get_boolean(PREF_MAM)) {
iq_mam_request(chatwin);
}
// XEP-0373: OpenPGP for XMPP
chatwin->is_ox = FALSE;
return chatwin;
}
@ -344,6 +347,8 @@ chatwin_outgoing_msg(ProfChatWin *chatwin, const char *const message, char *id,
enc_char = prefs_get_pgp_char();
} else if (enc_mode == PROF_MSG_ENC_OMEMO) {
enc_char = prefs_get_omemo_char();
} else if (enc_mode == PROF_MSG_ENC_OX) {
enc_char = prefs_get_ox_char();
} else {
enc_char = strdup("-");
}

View File

@ -503,6 +503,7 @@ mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *c
ProfWin *window = (ProfWin*)mucwin;
char *mynick = muc_nick(mucwin->roomjid);
// displayed message char
char *ch;
if (mucwin->message_char) {
ch = strdup(mucwin->message_char);
@ -512,6 +513,8 @@ mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *c
ch = prefs_get_pgp_char();
} else if (enc_mode == PROF_MSG_ENC_OMEMO) {
ch = prefs_get_omemo_char();
} else if (enc_mode == PROF_MSG_ENC_OX) {
ch = prefs_get_omemo_char();
} else {
ch = strdup("-");
}

View File

@ -439,6 +439,20 @@ _show_privacy(ProfChatWin *chatwin)
return;
}
// XEP-0373: OpenPGP for XMPP
if (chatwin->is_ox) {
wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
wattron(win, encrypted_attrs);
wprintw(win, "OX");
wattroff(win, encrypted_attrs);
wattron(win, bracket_attrs);
wprintw(win, "]");
wattroff(win, bracket_attrs);
}
if (chatwin->is_otr) {
wprintw(win, " ");
wattron(win, bracket_attrs);

View File

@ -155,6 +155,7 @@ typedef struct prof_chat_win_t {
gboolean pgp_send;
gboolean pgp_recv;
gboolean is_omemo;
gboolean is_ox; // XEP-0373: OpenPGP for XMPP
char *resource_override;
gboolean history_shown;
unsigned long memcheck;

View File

@ -1144,6 +1144,8 @@ win_print_incoming(ProfWin *window, const char *const display_name_from, ProfMes
enc_char = prefs_get_otr_char();
} else if (message->enc == PROF_MSG_ENC_PGP) {
enc_char = prefs_get_pgp_char();
} else if (message->enc == PROF_MSG_ENC_OX) { // XEP-0373: OpenPGP for XMPP
enc_char = prefs_get_ox_char();
} else if (message->enc == PROF_MSG_ENC_OMEMO) {
enc_char = prefs_get_omemo_char();
} else {

View File

@ -85,10 +85,13 @@ static void _handle_conference(xmpp_stanza_t *const stanza);
static void _handle_captcha(xmpp_stanza_t *const stanza);
static void _handle_receipt_received(xmpp_stanza_t *const stanza);
static void _handle_chat(xmpp_stanza_t *const stanza, gboolean is_mam);
static void _handle_ox_chat(xmpp_stanza_t *const stanza, ProfMessage *message, gboolean is_mam);
static gboolean _handle_mam(xmpp_stanza_t *const stanza);
static void _send_message_stanza(xmpp_stanza_t *const stanza);
static xmpp_stanza_t* _openpgp_signcrypt(xmpp_ctx_t* ctx, const char* const to, const char* const text);
static GHashTable *pubsub_event_handlers;
static int
@ -353,6 +356,7 @@ message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean
}
account_free(account);
#else
// ?
message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
xmpp_message_set_body(message, msg);
#endif
@ -376,6 +380,70 @@ message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean
return id;
}
// XEP-0373: OpenPGP for XMPP
char*
message_send_chat_ox(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id)
{
xmpp_ctx_t * const ctx = connection_get_ctx();
char *state = chat_session_get_state(barejid);
char *jid = chat_session_get_jid(barejid);
char *id = connection_create_stanza_id();
xmpp_stanza_t *message = NULL;
Jid *jidp = jid_create(jid);
char *account_name = session_get_account_name();
ProfAccount *account = accounts_get_account(account_name);
message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
xmpp_message_set_body(message, "This message is encrypted (XEP-0373: OpenPGP for XMPP).");
xmpp_stanza_t *openpgp = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(openpgp, STANZA_NAME_OPENPGP);
xmpp_stanza_set_ns(openpgp, STANZA_NS_OPENPGP_0);
xmpp_stanza_t * signcrypt = _openpgp_signcrypt(ctx, barejid, msg);
char* c;
size_t s;
xmpp_stanza_to_text(signcrypt, &c,&s);
char* signcrypt_e = p_ox_gpg_signcrypt(account->jid, barejid, c);
if( signcrypt_e == NULL ) {
log_error("Message not signcrypted.");
return NULL;
}
// BASE64_OPENPGP_MESSAGE
xmpp_stanza_t* base64_openpgp_message = xmpp_stanza_new(ctx);
xmpp_stanza_set_text(base64_openpgp_message,signcrypt_e);
xmpp_stanza_add_child(openpgp, base64_openpgp_message);
xmpp_stanza_add_child(message, openpgp);
xmpp_stanza_to_text(message, &c,&s);
account_free(account);
jid_destroy(jidp);
free(jid);
if (state) {
stanza_attach_state(ctx, message, state);
}
if (request_receipt) {
stanza_attach_receipt_request(ctx, message);
}
if (replace_id) {
stanza_attach_correction(ctx, message, replace_id);
}
_send_message_stanza(message);
xmpp_stanza_release(message);
return id;
}
char*
message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id)
{
@ -1140,6 +1208,12 @@ _handle_carbons(xmpp_stanza_t *const stanza)
if (x) {
message->encrypted = xmpp_stanza_get_text(x);
}
// OX
xmpp_stanza_t *ox = xmpp_stanza_get_child_by_ns(message_stanza, STANZA_NS_OPENPGP_0);
if( ox ) {
message->enc=PROF_MSG_ENC_OX;
_handle_ox_chat(message_stanza,message, FALSE);
}
//TODO: maybe also add is_carbon maybe even an enum with outgoing/incoming
//could be that then we can have sv_ev_carbon no incoming/outgoing
@ -1260,6 +1334,12 @@ _handle_chat(xmpp_stanza_t *const stanza, gboolean is_mam)
message->encrypted = xmpp_stanza_get_text(encrypted);
}
xmpp_stanza_t *ox = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_OPENPGP_0);
if( ox ) {
message->enc=PROF_MSG_ENC_OX;
_handle_ox_chat(stanza,message, FALSE);
}
if (message->plain || message->body || message->encrypted) {
sv_ev_incoming_message(message);
@ -1290,6 +1370,35 @@ _handle_chat(xmpp_stanza_t *const stanza, gboolean is_mam)
message_free(message);
}
/*!
* @brief Handle incoming XMMP-OX chat message.
*
*
*/
static void _handle_ox_chat(xmpp_stanza_t *const stanza, ProfMessage *message, gboolean is_mam) {
xmpp_stanza_t *ox = stanza_get_child_by_name_and_ns(stanza, "openpgp", STANZA_NS_OPENPGP_0);
message->plain = p_ox_gpg_decrypt(xmpp_stanza_get_text(ox));
// Implementation for libstrophe 0.10.
/*
xmpp_stanza_t *x = xmpp_stanza_new_from_string(connection_get_ctx(), message->plain);
xmpp_stanza_t *p = xmpp_stanza_get_child_by_name(x, "payload");
xmpp_stanza_t *b = xmpp_stanza_get_child_by_name(p, "body");
message->plain = xmpp_stanza_get_text(b);
if(message->plain == NULL ) {
message->plain = xmpp_stanza_get_text(stanza);
}
message->encrypted = xmpp_stanza_get_text(ox);
*/
if(message->plain == NULL ) {
message->plain = xmpp_stanza_get_text(stanza);
}
message->encrypted = xmpp_stanza_get_text(ox);
}
static gboolean
_handle_mam(xmpp_stanza_t *const stanza)
{
@ -1372,3 +1481,64 @@ message_is_sent_by_us(const ProfMessage *const message, bool checkOID) {
return ret;
}
xmpp_stanza_t* _openpgp_signcrypt(xmpp_ctx_t* ctx, const char* const to, const char* const text) {
time_t now = time(NULL);
struct tm* tm = localtime(&now);
char buf[255];
strftime(buf, sizeof(buf), "%FT%T%z", tm);
int randnr = rand() % 5;
char rpad_data[randnr];
for(int i = 0; i < randnr-1; i++) {
rpad_data[i] = 'c';
}
rpad_data[randnr-1] = '\0';
// signcrypt
xmpp_stanza_t *signcrypt = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(signcrypt, "signcrypt");
xmpp_stanza_set_ns(signcrypt, "urn:xmpp:openpgp:0");
// to
xmpp_stanza_t *s_to = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(s_to, "to");
xmpp_stanza_set_attribute(s_to, "jid", to);
// time
xmpp_stanza_t *time = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(time, "time");
xmpp_stanza_set_attribute(time, "stamp", buf);
xmpp_stanza_set_name(time, "time");
// rpad
xmpp_stanza_t *rpad = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(rpad, "rpad");
xmpp_stanza_t *rpad_text = xmpp_stanza_new(ctx);
xmpp_stanza_set_text(rpad_text, rpad_data);
// payload
xmpp_stanza_t *payload= xmpp_stanza_new(ctx);
xmpp_stanza_set_name(payload, "payload");
// body
xmpp_stanza_t *body = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(body, "body");
xmpp_stanza_set_ns(body, "jabber:client");
// text
xmpp_stanza_t *body_text = xmpp_stanza_new(ctx);
xmpp_stanza_set_text(body_text, text);
xmpp_stanza_add_child(signcrypt,s_to);
xmpp_stanza_add_child(signcrypt,time);
xmpp_stanza_add_child(signcrypt,rpad);
xmpp_stanza_add_child(rpad,rpad_text);
xmpp_stanza_add_child(signcrypt,payload);
xmpp_stanza_add_child(payload, body);
xmpp_stanza_add_child(body, body_text);
xmpp_stanza_release(body_text);
xmpp_stanza_release(body);
xmpp_stanza_release(rpad_text);
xmpp_stanza_release(rpad);
xmpp_stanza_release(time);
xmpp_stanza_release(s_to);
return signcrypt;
}

View File

@ -62,6 +62,8 @@
#define STANZA_NAME_PRESENCE "presence"
#define STANZA_NAME_PRIORITY "priority"
#define STANZA_NAME_X "x"
// XEP-0373: OpenPGP for XMPP
#define STANZA_NAME_OPENPGP "openpgp"
#define STANZA_NAME_SHOW "show"
#define STANZA_NAME_STATUS "status"
#define STANZA_NAME_IQ "iq"
@ -192,6 +194,8 @@
#define STANZA_NS_RECEIPTS "urn:xmpp:receipts"
#define STANZA_NS_SIGNED "jabber:x:signed"
#define STANZA_NS_ENCRYPTED "jabber:x:encrypted"
// XEP-0373: OpenPGP for XMPP
#define STANZA_NS_OPENPGP_0 "urn:xmpp:openpgp:0"
#define STANZA_NS_HTTP_UPLOAD "urn:xmpp:http:upload"
#define STANZA_NS_X_OOB "jabber:x:oob"
#define STANZA_NS_BLOCKING "urn:xmpp:blocking"

View File

@ -126,7 +126,8 @@ typedef enum {
PROF_MSG_ENC_NONE,
PROF_MSG_ENC_OTR,
PROF_MSG_ENC_PGP,
PROF_MSG_ENC_OMEMO
PROF_MSG_ENC_OMEMO,
PROF_MSG_ENC_OX
} prof_enc_t;
typedef enum {
@ -192,6 +193,8 @@ const char* connection_get_profanity_identifier(void);
char* message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url, gboolean request_receipt, const char *const replace_id);
char* message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id);
char* message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id);
// XEP-0373: OpenPGP for XMPP
char* message_send_chat_ox(const char *const barejid, const char *const msg, gboolean request_receipt, const char *const replace_id);
char* message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys, const unsigned char *const iv, size_t iv_len, const unsigned char *const ciphertext, size_t ciphertext_len, gboolean request_receipt, gboolean muc, const char *const replace_id);
char* message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url);
char* message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url, const char *const replace_id);