diff --git a/Makefile.am b/Makefile.am index b543b0df..a8a72ec8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,6 +18,7 @@ core_sources = \ src/xmpp/blocking.c src/xmpp/blocking.h \ src/xmpp/form.c src/xmpp/form.h \ src/xmpp/avatar.c src/xmpp/avatar.h \ + src/xmpp/ox.c src/xmpp/ox.h \ src/event/common.c src/event/common.h \ src/event/server_events.c src/event/server_events.h \ src/event/client_events.c src/event/client_events.h \ diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index f352df9d..1020db96 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -89,6 +89,7 @@ #ifdef HAVE_LIBGPGME #include "pgp/gpg.h" +#include "xmpp/ox.h" #endif #ifdef HAVE_OMEMO @@ -7570,9 +7571,9 @@ 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], "push") == 0) { + } else if (g_strcmp0(args[0], "announce") == 0) { if( args[1] ) { - cons_show("Push file...%s ", args[1] ); + ox_announce_public_key( args[1] ); } else { cons_show("Filename is required"); } diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c index 6fe2e858..b9be1d71 100644 --- a/src/pgp/gpg.c +++ b/src/pgp/gpg.c @@ -1190,4 +1190,107 @@ p_ox_gpg_decrypt(char* base64) return result; } +/*! + * \brief Read public key from file. + * + * This function is used the read a public key from a file. + * + * This function is used to read a key and push it on PEP. There are some checks + * in this function: + * + * Key is not + * - gkey->revoked + * - gkey->expired + * - gkey->disabled + * - gkey->invalid + * - gkey->secret + * + * Only one key in the file. + * + * \param filename filname to read the file. + * \param key result with base64 encode key or NULL + * \param fp result with the fingerprint or NULL + * + */ +void +p_ox_gpg_readkey(const char* const filename, char** key, char** fp){ + + log_info("Read OpenPGP Key from file %s", filename); + + GError* error = NULL; + gchar* data = NULL; + gsize size = -1; + + gboolean success = g_file_get_contents (filename, + &data, + &size, + &error); + if ( success ) { + 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 ) { + log_error("Read OpenPGP key from file: gpgme_new failed: %s", gpgme_strerror(error)); + return; + } + + error = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OPENPGP); + if( error != GPG_ERR_NO_ERROR ) { + log_error("Read OpenPGP key from file: set GPGME_PROTOCOL_OPENPGP: %s", gpgme_strerror(error)); + return; + } + + gpgme_set_armor(ctx,0); + gpgme_set_textmode(ctx,0); + gpgme_set_offline(ctx,1); + gpgme_set_keylist_mode(ctx, GPGME_KEYLIST_MODE_LOCAL); + + gpgme_data_t gpgme_data = NULL; + error = gpgme_data_new (&gpgme_data); + if ( error != GPG_ERR_NO_ERROR ) { + log_error("Read OpenPGP key from file: gpgme_data_new %s", gpgme_strerror(error)); + return; + } + + error = gpgme_data_new_from_mem(&gpgme_data, (char*)data, size,0); + if ( error != GPG_ERR_NO_ERROR ) { + log_error("Read OpenPGP key from file: gpgme_data_new_from_mem %s", gpgme_strerror(error)); + return; + } + error = gpgme_op_keylist_from_data_start ( ctx, gpgme_data, 0); + if ( error != GPG_ERR_NO_ERROR ) { + log_error("Read OpenPGP key from file: gpgme_op_keylist_from_data_start %s", gpgme_strerror(error)); + return; + } + gpgme_key_t gkey; + error = gpgme_op_keylist_next (ctx, &gkey); + if ( error != GPG_ERR_NO_ERROR ) { + log_error("Read OpenPGP key from file: gpgme_op_keylist_next %s", gpgme_strerror(error)); + return; + } + + gpgme_key_t end; + error = gpgme_op_keylist_next (ctx, &end); + if( error == GPG_ERR_NO_ERROR ) { + log_error("Read OpenPGP key from file: ambiguous key"); + return; + } + + if(gkey->revoked || gkey->expired || gkey->disabled || gkey->invalid || gkey->secret ) { + log_error("Read OpenPGP key from file: Key is not valid"); + return; + } + + gchar* keybase64 = g_base64_encode( (const guchar*) data, size ); + + *key = strdup(keybase64); + *fp = strdup(gkey->fpr); + } else { + log_error("Read OpenPGP key from file: Unable to read file: %s", error->message); + } + +} diff --git a/src/pgp/gpg.h b/src/pgp/gpg.h index 3eae6032..b3202505 100644 --- a/src/pgp/gpg.h +++ b/src/pgp/gpg.h @@ -76,6 +76,8 @@ char* p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const rec char* p_ox_gpg_decrypt(char* base64); +void p_ox_gpg_readkey(const char* const filename, char** key, char** fp); + /*! * \brief List of public keys with xmpp-URI. * diff --git a/src/xmpp/ox.c b/src/xmpp/ox.c new file mode 100644 index 00000000..c1901085 --- /dev/null +++ b/src/xmpp/ox.c @@ -0,0 +1,218 @@ +/* + * ox.c + * vim: expandtab:ts=4:sts=4:sw=4 + * + * Copyright (C) 2020 Stefan Kropp + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see . + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#include +#include + +#include "log.h" +#include "ui/ui.h" +#include "xmpp/connection.h" +#include "xmpp/stanza.h" +#include "pgp/gpg.h" + + +static void _ox_metadata_node__public_key(const char* const fingerprint); + +/*! + * \brief Current Date and Time. + * + * XEP-0082: XMPP Date and Time Profiles + * https://xmpp.org/extensions/xep-0082.html + * + * \return YYYY-MM-DDThh:mm:ssZ + * + */ + +static char* _gettimestamp(); + +/*! + * +
+
+  
+    
+      
+        
+           
+             BASE64_OPENPGP_PUBLIC_KEY
+           
+        
+      
+    
+  
+
+
+ * + */ + +gboolean +ox_announce_public_key(const char* const filename) { + assert(filename); + + cons_show("Annonuce OpenPGP Key for OX %s ...", filename); + log_info("Annonuce OpenPGP Key of OX: %s", filename); + + // key the key and the fingerprint via GnuPG from file + char* key = NULL; + char* fp = NULL; + p_ox_gpg_readkey(filename, &key, &fp); + + if( !(key && fp) ) { + cons_show("Error during OpenPGP OX announce. See log file for more information"); + return FALSE; + } else { + log_info("Annonuce OpenPGP Key for Fingerprint: %s", fp); + xmpp_ctx_t * const ctx = connection_get_ctx(); + char *id = xmpp_uuid_gen(ctx); + xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id); + xmpp_stanza_set_from(iq, xmpp_conn_get_jid(connection_get_conn())); + + xmpp_stanza_t* pubsub = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB); + xmpp_stanza_set_ns(pubsub, XMPP_FEATURE_PUBSUB); + + GString* node_name = g_string_new(STANZA_NS_OPENPGP_0_PUBLIC_KEYS); + g_string_append(node_name, ":"); + g_string_append(node_name, fp); + + xmpp_stanza_t* publish = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(publish, STANZA_NAME_PUBLISH); + xmpp_stanza_set_attribute(publish, STANZA_ATTR_NODE, node_name->str) ; + + xmpp_stanza_t* item = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(item, STANZA_NAME_ITEM); + xmpp_stanza_set_attribute(item, STANZA_ATTR_ID, _gettimestamp()) ; + + xmpp_stanza_t* pubkey = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pubkey, STANZA_NAME_PUPKEY); + xmpp_stanza_set_ns(pubkey, STANZA_NS_OPENPGP_0); + + xmpp_stanza_t* data = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(data, STANZA_NAME_DATA); + xmpp_stanza_t* keydata = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(keydata,key); + + + xmpp_stanza_add_child(data, keydata); + xmpp_stanza_add_child(pubkey, data); + xmpp_stanza_add_child(item, pubkey); + xmpp_stanza_add_child(publish, item); + xmpp_stanza_add_child(pubsub, publish); + xmpp_stanza_add_child(iq, pubsub); + xmpp_send (connection_get_conn(), iq); + + _ox_metadata_node__public_key(fp); + + } + return TRUE; +} + +/*! + * + * + * +
+
+  
+    
+      
+        
+          
+          
+        
+      
+    
+  
+    
+
+ * + */ + +void +_ox_metadata_node__public_key(const char* const fingerprint) { + log_info("Annonuce OpenPGP metadata: %s", fingerprint); + assert(fingerprint); + assert(strlen(fingerprint) == 40); + // iq + xmpp_ctx_t * const ctx = connection_get_ctx(); + char *id = xmpp_uuid_gen(ctx); + 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 + xmpp_stanza_t* pubsub = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB); + xmpp_stanza_set_ns(pubsub, XMPP_FEATURE_PUBSUB); + // publish + xmpp_stanza_t* publish = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(publish, STANZA_NAME_PUBLISH); + xmpp_stanza_set_attribute(publish, STANZA_ATTR_NODE, STANZA_NS_OPENPGP_0_PUBLIC_KEYS) ; + // item + xmpp_stanza_t* item = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(item, STANZA_NAME_ITEM); + // public-keys-list + xmpp_stanza_t* publickeyslist = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(publickeyslist, STANZA_NAME_PUBLIC_KEYS_LIST); + xmpp_stanza_set_ns(publickeyslist, STANZA_NS_OPENPGP_0); + // pubkey-metadata + 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()); + + 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); +} + +// Date and Time (XEP-0082) +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); +} + diff --git a/src/xmpp/ox.h b/src/xmpp/ox.h new file mode 100644 index 00000000..8d959eb1 --- /dev/null +++ b/src/xmpp/ox.h @@ -0,0 +1,61 @@ +/* + * ox.h + * vim: expandtab:ts=4:sts=4:sw=4 + * + * Copyright (C) 2020 Stefan Kropp + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see . + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +/*! + * \page OX OX Implementation + * + * \section OX XEP-0373: OpenPGP for XMPP + * XEP-0373: OpenPGP for XMPP (OX) is the implementation of OpenPGP for XMPP + * replace the XEP-0027. + * + * https://xmpp.org/extensions/xep-0373.html + */ + + +/*! + * \brief Announcing OpenPGP public key from file to PEP. + * + * Reads the public key from the given file. Checks the key-information and + * pushes the key on PEP. + * + * https://xmpp.org/extensions/xep-0373.html#announcing-pubkey + * + * \param filename name of the file with the public key + * \return TRUE: success; FALSE: failed + */ + +gboolean ox_announce_public_key(const char* const filename); + + diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h index 37cc8dc9..5910a7d9 100644 --- a/src/xmpp/stanza.h +++ b/src/xmpp/stanza.h @@ -64,6 +64,10 @@ #define STANZA_NAME_X "x" // XEP-0373: OpenPGP for XMPP #define STANZA_NAME_OPENPGP "openpgp" +#define STANZA_NAME_PUPKEY "pubkey" +#define STANZA_NAME_PUBLIC_KEYS_LIST "public-keys-list" +#define STANZA_NAME_PUBKEY_METADATA "pubkey-metadata" +#define STANZA_NAME_DATA "data" #define STANZA_NAME_SHOW "show" #define STANZA_NAME_STATUS "status" #define STANZA_NAME_IQ "iq" @@ -167,6 +171,9 @@ #define STANZA_ATTR_AUTOJOIN "autojoin" #define STANZA_ATTR_PASSWORD "password" #define STANZA_ATTR_STATUS "status" +#define STANZA_ATTR_DATE "date" +#define STANZA_ATTR_V4_FINGERPRINT "v4-fingerprint" + #define STANZA_TEXT_AWAY "away" #define STANZA_TEXT_DND "dnd" @@ -198,6 +205,7 @@ #define STANZA_NS_ENCRYPTED "jabber:x:encrypted" // XEP-0373: OpenPGP for XMPP #define STANZA_NS_OPENPGP_0 "urn:xmpp:openpgp:0" +#define STANZA_NS_OPENPGP_0_PUBLIC_KEYS "urn:xmpp:openpgp:0:public-keys" #define STANZA_NS_HTTP_UPLOAD "urn:xmpp:http:upload" #define STANZA_NS_X_OOB "jabber:x:oob" #define STANZA_NS_BLOCKING "urn:xmpp:blocking"