diff --git a/docs/profanity-ox-setup.1 b/docs/profanity-ox-setup.1 new file mode 100644 index 00000000..58f8da41 --- /dev/null +++ b/docs/profanity-ox-setup.1 @@ -0,0 +1,153 @@ +.TH man 1 "2022-05-03" "0.12.1" "Profanity XMPP client" +.SH NAME +Profanity \- a simple console based XMPP chat client. +.SH DESCRIPTION +.ie "\f[CB]x\f[]"x" \{\ +. ftr V B +. ftr VI BI +. ftr VB B +. ftr VBI BI +.\} +.el \{\ +. ftr V CR +. ftr VI CI +. ftr VB CB +. ftr VBI CBI +.\} +.PP +This man page is intended to help you set up XEP-0374: OpenPGP for XMPP +Instant Messaging. +Also known as OX. +.PP +For details on usage see man profanity-ox or \f[V]/help ox\f[R]. +.PP +Profanity wants to give the user a maximum freedom in setting up their +system. +So we won\[cq]t touch your GPG settings directly. +Which means you will need to do some manual steps. +They are described here +.SH Generate OpenPGP key materials +.PP +The first step is to create a OpenPGP key pair. +The key pair generation will be done with the \f[V]gpg\f[R] command of +GnuPG. +.IP +.nf +\f[C] +gpg --quick-generate-key xmpp:alice\[at]domain.tld future-default default 3y +\f[R] +.fi +.PP +This command will generated a OpenPGP key with a UID +\f[V]xmpp:alice\[at]domain.tld\f[R]. +The option \f[V]future-default\f[R] has been used to generate a +ed25519/cv25519 key. +The key is set to expire in threeyears. +.PP +Replace the Jabber ID with your JID and do \f[B]not\f[R] forget the URI +\f[V]xmpp:\f[R] prefix. +.PP +Example output: +.IP +.nf +\f[C] +pub ed25519 2021-09-21 [SC] [verf\[:a]llt: 2024-09-20] + 583BAE703A801095B6B71A56BD801174B1A0B84A +uid xmpp:alice\[at]domain.tld +sub cv25519 2021-09-21 [E] +\f[R] +.fi +.SH Export your public key +.PP +You need to export your public key so you can later upload it into a PEP +node. +It\[cq]s just a way how your chat partners can retrieve the public key +from you. +Use the command below to export public key: +.PP +Example command: +.IP +.nf +\f[C] +gpg --export \[rs] + --export-options export-minimal \[rs] + --export-filter \[aq]keep-uid=uid =\[ti] xmpp:alice\[at]domain.tld\[aq] \[rs] + --export-filter \[aq]drop-subkey=usage =\[ti] a\[aq] \[rs] + 583BAE703A801095B6B71A56BD801174B1A0B84A \[rs] + > /tmp/pep-key.gpg +\f[R] +.fi +.PP +The key will be exported to \f[V]/tmp/pep-key.gpg\f[R]. +You may check the key with the command below: +.PP +\f[V]gpg --show-key --with-sig-list /tmp/pep-key.gpg\f[R] +.PP +Keep in mind: Public keys may have some information (signatures, name, +e-mail address). +Be careful which data will be exported. +The \f[V]export-options\f[R] and \f[V]export-filter\f[R] option of GnuPG +will help you to filter the data. +.SH Publish your key +.PP +You can use profanity to publish your exported key into your account +(PEP). +The \f[V]/ox announce\f[R] command will publish your key. +.IP +.nf +\f[C] +/ox announce /tmp/pep-key.gpg +\f[R] +.fi +.PP +The command will create two PEP node records to store the key. +.SH Discover keys +.PP +To discover public keys of your partners use the \f[V]/ox discover\f[R] +command. +.PP +Example output: +.IP +.nf +\f[C] +/ox discover buddy\[at]domain.tld +Discovering Public Key for buddy\[at]domain.tld +1234567890ABCDEF1234567890ABCDEF12345678 +\f[R] +.fi +.PP +To request and import a key, you can use the \f[V]/ox request\f[R] +command. +.IP +.nf +\f[C] +/ox request buddy\[at]domain.tld 1234567890ABCDEF1234567890ABCDEF12345678 +Requesting Public Key 1234567890ABCDEF1234567890ABCDEF12345678 for buddy\[at]domain.tld +Public Key imported +\f[R] +.fi +.PP +The key will be imported into your gnupg keyring. +.SH Sign the imported key +.PP +The key can been shown via gpg +\f[V]gpg -k xmpp:buddy\[at]domain.tld\f[R]. +Make sure the key is the key of your buddy and sign the key with your +key. +.IP +.nf +\f[C] +gpg --ask-cert-level --default-key 583BAE703A801095B6B71A56BD801174B1A0B84A --sign-key 1234567890ABCDEF1234567890ABCDEF12345678 +\f[R] +.fi +.PP +The command \f[V]/ox contacts\f[R] will show the keys with XMPP-UID. +The command \f[V]/ox keys\f[R] will show all known OpenPGP keys. +.PP +Only once you signed the key you can actually use OX with your partner. +.SH Use OX +.PP +Within a chat window you can start OX via \f[V]/ox start\f[R] and stop +it via \f[V]/ox end\f[R]. +.PP +Messages will be send signed and encrypted. diff --git a/docs/profanity-ox.md b/docs/profanity-ox.md deleted file mode 100644 index d9513bae..00000000 --- a/docs/profanity-ox.md +++ /dev/null @@ -1,76 +0,0 @@ -# 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 -14:37:52 - /pgp start [] -14:37:52 - /pgp end -14:37:52 - /pgp log on|off|redact -14:37:52 - /pgp 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 : Manually associate a contact with a public key. -14:37:52 - start [] : 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 : 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 -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: ``, `` and ``. 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) diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index a9e45147..b816ddcc 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -1734,7 +1734,8 @@ static struct cmd_t command_defs[] = { CMD_DESC( "OpenPGP (OX) commands to manage keys, and perform OpenPGP encryption during chat sessions. " "Your OpenPGP key needs a user-id with your JID URI (xmpp:local@domain.tld). " - "A key can be generated with \"gpg --quick-gen-key xmpp:local@domain.tld future-default default 3y\".") + "A key can be generated with \"gpg --quick-gen-key xmpp:local@domain.tld future-default default 3y\". " + "See man profanity-ox-setup for details on how to set up OX the first time.") CMD_ARGS( { "keys", "List all keys known to the system." }, { "contacts", "Show contacts with assigned public keys." }, @@ -1745,7 +1746,7 @@ static struct cmd_t command_defs[] = { { "char ", "Set the character to be displayed next to PGP encrypted messages." }, { "announce ", "Announce a public key by pushing it on the XMPP Server" }, { "discover ", "Discover public keys of a jid. The OpenPGP Key IDs will be displayed" }, - { "request ", "Request public keys" }, + { "request ", "Request public key. See /ox discover to to get available key IDs." }, { "sendfile on|off", "Allow /sendfile to send unencrypted files while otherwise using PGP." }) CMD_EXAMPLES( "/ox log off", diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index e76831fc..f1fa37f1 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -7650,7 +7650,7 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args) } if (chatwin->is_ox) { - win_println(window, THEME_DEFAULT, "!", "You have already started OX encryption."); + win_println(window, THEME_DEFAULT, "!", "You have already started an OX encrypted session."); return TRUE; } @@ -7671,6 +7671,22 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args) chatwin->is_ox = TRUE; win_println(window, THEME_DEFAULT, "!", "OX encryption enabled."); return TRUE; + } else if (g_strcmp0(args[0], "end") == 0) { + if (window->type != WIN_CHAT && args[1] == NULL) { + cons_show("You must be in a regular chat window to stop OX encryption."); + return TRUE; + } + + ProfChatWin* chatwin = (ProfChatWin*)window; + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + + if (!chatwin->is_ox) { + win_println(window, THEME_DEFAULT, "!", "No OX session has been started."); + } else { + chatwin->is_ox = FALSE; + win_println(window, THEME_DEFAULT, "!", "OX encryption disabled."); + } + return TRUE; } else if (g_strcmp0(args[0], "announce") == 0) { if (args[1]) { gchar* filename = get_expanded_path(args[1]); diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c index 99d37c64..7dc57d96 100644 --- a/src/pgp/gpg.c +++ b/src/pgp/gpg.c @@ -914,6 +914,7 @@ p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const recipient // lookup own key recp[0] = _ox_key_lookup(sender_barejid, TRUE); if (error != 0) { + cons_show_error("Can't find OX key for %s", xmpp_jid_me); log_error("OX: Key not found for %s. Error: %s", xmpp_jid_me, gpgme_strerror(error)); return NULL; } @@ -927,13 +928,14 @@ p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const recipient // lookup key of recipient recp[1] = _ox_key_lookup(recipient_barejid, FALSE); if (error != 0) { + cons_show_error("Can't find OX key for %s", xmpp_jid_recipient); log_error("OX: Key not found for %s. 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); + log_debug("OX: %s <%s>", recp[0]->uids->name, recp[0]->uids->email); + log_debug("OX: %s <%s>", recp[1]->uids->name, recp[1]->uids->email); gpgme_encrypt_flags_t flags = 0; @@ -1110,9 +1112,22 @@ _ox_key_is_usable(gpgme_key_t key, const char* const barejid, gboolean secret) gboolean result = TRUE; if (key->revoked || key->expired || key->disabled) { + cons_show_error("%s's key is revoked, expired or disabled", barejid); + log_info("OX: %s's key is revoked, expired or disabled", barejid); result = FALSE; } + // This might be a nice features but AFAIK is not defined in the XEP. + // If we add this we need to expand our documentation on how to set the + // trust leven in gpg. I'll add an example to this commit body. + /* + if (key->owner_trust < GPGME_VALIDITY_MARGINAL) { + cons_show_error(" %s's key is has a trust level lower than marginal", barejid); + log_info("OX: Owner trust of %s's key is < GPGME_VALIDITY_MARGINAL", barejid); + result = FALSE; + } + */ + return result; } @@ -1180,11 +1195,13 @@ p_ox_gpg_decrypt(char* base64) 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); + memcpy(result, plain_str, len); result[len] = '\0'; + gpgme_free(plain_str); return result; } diff --git a/src/xmpp/message.c b/src/xmpp/message.c index c18f9376..fb58285e 100644 --- a/src/xmpp/message.c +++ b/src/xmpp/message.c @@ -1616,11 +1616,13 @@ _openpgp_signcrypt(xmpp_ctx_t* ctx, const char* const to, const char* const text 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'; + // build rpad + int randnr = (rand() % 100) + 1; + char rpad_data[randnr]; + for (int i = 0; i < randnr; i++) { + int rchar = (rand() % 52) + 65; + rpad_data[i] = rchar; } rpad_data[randnr - 1] = '\0'; diff --git a/src/xmpp/ox.c b/src/xmpp/ox.c index 0fa5cece..fab3d675 100644 --- a/src/xmpp/ox.c +++ b/src/xmpp/ox.c @@ -42,6 +42,7 @@ #include "ui/ui.h" #include "xmpp/connection.h" #include "xmpp/stanza.h" +#include "xmpp/iq.h" #include "pgp/gpg.h" #ifdef HAVE_LIBGPGME @@ -49,19 +50,18 @@ #define KEYID_LENGTH 40 static void _ox_metadata_node__public_key(const char* const fingerprint); -static int _ox_metadata_result(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* const userdata); +static int _ox_metadata_result(xmpp_stanza_t* const stanza, void* const userdata); static void _ox_request_public_key(const char* const jid, const char* const fingerprint); -static int _ox_public_key_result(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* const userdata); +static int _ox_public_key_result(xmpp_stanza_t* const stanza, void* const userdata); -/*! - * \brief Current Date and Time. +/* Return Current Date and Time. * * XEP-0082: XMPP Date and Time Profiles * https://xmpp.org/extensions/xep-0082.html * - * \return YYYY-MM-DDThh:mm:ssZ - * + * According to ISO8601 + * YYYY-MM-DDThh:mm:ssZ */ static char* _gettimestamp(); @@ -106,7 +106,7 @@ ox_announce_public_key(const char* const filename) log_info("[OX] Announce OpenPGP Key for Fingerprint: %s", fp); xmpp_ctx_t* const ctx = connection_get_ctx(); - char* id = xmpp_uuid_gen(ctx); + char* id = connection_create_stanza_id(); xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id); xmpp_stanza_set_from(iq, xmpp_conn_get_jid(connection_get_conn())); @@ -143,7 +143,15 @@ ox_announce_public_key(const char* const filename) xmpp_stanza_add_child(publish, item); xmpp_stanza_add_child(pubsub, publish); xmpp_stanza_add_child(iq, pubsub); - xmpp_send(connection_get_conn(), iq); + + if (connection_supports(XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS)) { + stanza_attach_publish_options(ctx, iq, "pubsub#access_model", "open"); + } else { + log_debug("[OX] Cannot publish public key: no PUBSUB feature announced"); + } + + iq_send_stanza(iq); + xmpp_stanza_release(iq); _ox_metadata_node__public_key(fp); @@ -174,7 +182,7 @@ ox_discover_public_key(const char* const jid) cons_show("Discovering Public Key for %s", jid); // iq xmpp_ctx_t* const ctx = connection_get_ctx(); - char* id = xmpp_uuid_gen(ctx); + char* id = connection_create_stanza_id(); xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_GET, id); xmpp_stanza_set_from(iq, xmpp_conn_get_jid(connection_get_conn())); xmpp_stanza_set_to(iq, jid); @@ -190,8 +198,9 @@ ox_discover_public_key(const char* const jid) xmpp_stanza_add_child(pubsub, items); xmpp_stanza_add_child(iq, pubsub); - xmpp_id_handler_add(connection_get_conn(), _ox_metadata_result, id, strdup(jid)); - xmpp_send(connection_get_conn(), iq); + iq_id_handler_add(xmpp_stanza_get_id(iq), _ox_metadata_result, NULL, NULL); + iq_send_stanza(iq); + xmpp_stanza_release(iq); } @@ -236,7 +245,7 @@ _ox_metadata_node__public_key(const char* const fingerprint) assert(strlen(fingerprint) == KEYID_LENGTH); // iq xmpp_ctx_t* const ctx = connection_get_ctx(); - char* id = xmpp_uuid_gen(ctx); + char* id = connection_create_stanza_id(); xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id); xmpp_stanza_set_from(iq, xmpp_conn_get_jid(connection_get_conn())); // pubsub @@ -258,18 +267,22 @@ _ox_metadata_node__public_key(const char* const fingerprint) xmpp_stanza_t* pubkeymetadata = xmpp_stanza_new(ctx); xmpp_stanza_set_name(pubkeymetadata, STANZA_NAME_PUBKEY_METADATA); xmpp_stanza_set_attribute(pubkeymetadata, STANZA_ATTR_V4_FINGERPRINT, fingerprint); - xmpp_stanza_set_attribute(pubkeymetadata, STANZA_ATTR_DATE, _gettimestamp()); + char* timestamp = _gettimestamp(); + xmpp_stanza_set_attribute(pubkeymetadata, STANZA_ATTR_DATE, timestamp); + free(timestamp); xmpp_stanza_add_child(publickeyslist, pubkeymetadata); xmpp_stanza_add_child(item, publickeyslist); xmpp_stanza_add_child(publish, item); xmpp_stanza_add_child(pubsub, publish); xmpp_stanza_add_child(iq, pubsub); - xmpp_send(connection_get_conn(), iq); + + iq_send_stanza(iq); + xmpp_stanza_release(iq); } static int -_ox_metadata_result(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* const userdata) +_ox_metadata_result(xmpp_stanza_t* const stanza, void* const userdata) { log_debug("[OX] Processing result %s's metadata.", (char*)userdata); @@ -349,7 +362,7 @@ _ox_request_public_key(const char* const jid, const char* const fingerprint) log_info("[OX] Request %s's public key %s.", jid, fingerprint); // iq xmpp_ctx_t* const ctx = connection_get_ctx(); - char* id = xmpp_uuid_gen(ctx); + char* id = connection_create_stanza_id(); xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_GET, id); xmpp_stanza_set_from(iq, xmpp_conn_get_jid(connection_get_conn())); xmpp_stanza_set_to(iq, jid); @@ -370,9 +383,10 @@ _ox_request_public_key(const char* const jid, const char* const fingerprint) xmpp_stanza_add_child(pubsub, items); xmpp_stanza_add_child(iq, pubsub); - xmpp_id_handler_add(connection_get_conn(), _ox_public_key_result, id, NULL); + iq_id_handler_add(xmpp_stanza_get_id(iq), _ox_public_key_result, NULL, NULL); - xmpp_send(connection_get_conn(), iq); + iq_send_stanza(iq); + xmpp_stanza_release(iq); } /*! @@ -400,7 +414,7 @@ _ox_request_public_key(const char* const jid, const char* const fingerprint) */ int -_ox_public_key_result(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* const userdata) +_ox_public_key_result(xmpp_stanza_t* const stanza, void* const userdata) { log_debug("[OX] Processing result public key"); @@ -463,13 +477,10 @@ _ox_public_key_result(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void char* _gettimestamp() { - time_t now = time(NULL); - struct tm* tm = localtime(&now); - char buf[255]; - strftime(buf, sizeof(buf), "%FT%T", tm); - GString* d = g_string_new(buf); - g_string_append(d, "Z"); - return strdup(d->str); + GDateTime* dt = g_date_time_new_now_local(); + gchar* datestr = g_date_time_format(dt, "%FT%TZ"); + g_date_time_unref(dt); + return datestr; } #endif // HAVE_LIBGPGME