summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am9
-rw-r--r--src/rexmpp.c43
-rw-r--r--src/rexmpp.h12
-rw-r--r--src/rexmpp_openpgp.c464
-rw-r--r--src/rexmpp_openpgp.h28
5 files changed, 549 insertions, 7 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index e66a276..488b96d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,10 +13,11 @@ librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \
rexmpp_socks.h rexmpp_socks.c \
rexmpp.h rexmpp.c \
rexmpp_dns.h rexmpp_dns.c \
- rexmpp_jid.h rexmpp_jid.c
+ rexmpp_jid.h rexmpp_jid.c \
+ rexmpp_openpgp.h rexmpp_openpgp.c
include_HEADERS = rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \
- rexmpp_dns.h rexmpp_jid.h
+ rexmpp_dns.h rexmpp_jid.h rexmpp_openpgp.h
librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) \
- $(LIBDANE_CFLAGS) $(GSASL_CFLAGS) $(UNBOUND_CFLAGS)
+ $(LIBDANE_CFLAGS) $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(GPGME_CFLAGS)
librexmpp_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(LIBDANE_LIBS) \
- $(GSASL_LIBS) $(UNBOUND_LIBS)
+ $(GSASL_LIBS) $(UNBOUND_LIBS) $(GPGME_LIBS)
diff --git a/src/rexmpp.c b/src/rexmpp.c
index 431ec32..c75f9b2 100644
--- a/src/rexmpp.c
+++ b/src/rexmpp.c
@@ -22,6 +22,7 @@
#include <gnutls/dane.h>
#include <gsasl.h>
#include <unbound.h>
+#include <gpgme.h>
#include "rexmpp.h"
#include "rexmpp_tcp.h"
@@ -29,6 +30,7 @@
#include "rexmpp_roster.h"
#include "rexmpp_dns.h"
#include "rexmpp_jid.h"
+#include "rexmpp_openpgp.h"
void rexmpp_sax_start_elem_ns (rexmpp_t *s,
const char *localname,
@@ -187,6 +189,9 @@ xmlNodePtr rexmpp_find_event (rexmpp_t *s,
cur != NULL;
prev = cur, cur = xmlNextElementSibling(cur)) {
char *cur_from = xmlGetProp(cur, "from");
+ if (cur_from == NULL) {
+ continue;
+ }
xmlNodePtr cur_event =
rexmpp_xml_find_child(cur,
"http://jabber.org/protocol/pubsub#event",
@@ -195,7 +200,14 @@ xmlNodePtr rexmpp_find_event (rexmpp_t *s,
rexmpp_xml_find_child(cur_event,
"http://jabber.org/protocol/pubsub#event",
"items");
+ if (cur_items == NULL) {
+ free(cur_from);
+ continue;
+ }
char *cur_node = xmlGetProp(cur_items, "node");
+ if (cur_node == NULL) {
+ continue;
+ }
int match = (strcmp(cur_from, from) == 0 && strcmp(cur_node, node) == 0);
free(cur_node);
free(cur_from);
@@ -289,6 +301,11 @@ xmlNodePtr rexmpp_disco_info (rexmpp_t *s) {
prev->next = cur;
prev = cur;
}
+ if (s->retrieve_openpgp_keys) {
+ cur = rexmpp_xml_feature("urn:xmpp:openpgp:0:public-keys+notify");
+ prev->next = cur;
+ prev = cur;
+ }
cur = rexmpp_xml_feature("urn:xmpp:ping");
prev->next = cur;
prev = cur;
@@ -333,6 +350,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid)
s->track_roster_presence = 1;
s->track_roster_events = 1;
s->nick_notifications = 1;
+ s->retrieve_openpgp_keys = 1;
s->send_buffer = NULL;
s->send_queue = NULL;
s->resolver_ctx = NULL;
@@ -440,6 +458,17 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid)
gsasl_callback_hook_set(s->sasl_ctx, s);
gsasl_callback_set(s->sasl_ctx, rexmpp_sasl_cb);
+ gpgme_check_version(NULL);
+ err = gpgme_new(&(s->pgp_ctx));
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_CRIT, "gpgme initialisation error: %s",
+ gpgme_strerror(err));
+ gsasl_done(s->sasl_ctx);
+ gnutls_certificate_free_credentials(s->gnutls_cred);
+ xmlFreeParserCtxt(s->xml_parser);
+ return REXMPP_E_PGP;
+ }
+
return REXMPP_SUCCESS;
}
@@ -508,6 +537,7 @@ void rexmpp_cleanup (rexmpp_t *s) {
/* Frees the things that persist through reconnects. */
void rexmpp_done (rexmpp_t *s) {
rexmpp_cleanup(s);
+ gpgme_release(s->pgp_ctx);
gsasl_done(s->sasl_ctx);
gnutls_certificate_free_credentials(s->gnutls_cred);
if (s->resolver_ctx != NULL) {
@@ -654,8 +684,8 @@ xmlNodePtr rexmpp_xml_set_delay (rexmpp_t *s, xmlNodePtr node) {
}
char buf[42];
time_t t = time(NULL);
- struct tm *local_time = localtime(&t);
- strftime(buf, 42, "%FT%T%z", local_time);
+ struct tm *utc_time = gmtime(&t);
+ strftime(buf, 42, "%FT%TZ", utc_time);
xmlNodePtr delay = xmlNewChild(node, NULL, "delay", NULL);
xmlNewProp(delay, "stamp", buf);
if (s != NULL && s->assigned_jid.full[0]) {
@@ -1760,12 +1790,19 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
prev->next = cur->next;
}
}
- free(node);
/* Add the new message. */
xmlNodePtr message = xmlCopyNode(elem, 1);
message->next = s->roster_events;
s->roster_events = message;
+
+ /* Process the node at once. */
+ if (s->retrieve_openpgp_keys &&
+ strcmp(node, "urn:xmpp:openpgp:0:public-keys") == 0) {
+ rexmpp_openpgp_check_keys(s, from_jid.bare, items);
+ }
+
+ free(node);
}
}
}
diff --git a/src/rexmpp.h b/src/rexmpp.h
index 53f2cba..e1cfe0e 100644
--- a/src/rexmpp.h
+++ b/src/rexmpp.h
@@ -14,6 +14,7 @@
#include <gnutls/gnutls.h>
#include <gsasl.h>
#include <libxml/tree.h>
+#include <gpgme.h>
#include "rexmpp_tcp.h"
#include "rexmpp_socks.h"
#include "rexmpp_dns.h"
@@ -177,6 +178,8 @@ enum rexmpp_err {
REXMPP_E_SEND_BUFFER_NOT_EMPTY,
/** SASL-related error. */
REXMPP_E_SASL,
+ /** OpenGPG-related error. */
+ REXMPP_E_PGP,
/** TLS-related error. */
REXMPP_E_TLS,
/** TCP-related error. */
@@ -243,6 +246,7 @@ struct rexmpp
int track_roster_presence;
int track_roster_events;
int nick_notifications;
+ int retrieve_openpgp_keys;
/* Resource limits. */
uint32_t stanza_queue_size;
@@ -336,6 +340,9 @@ struct rexmpp
/* SASL structures. */
Gsasl *sasl_ctx;
Gsasl_session *sasl_session;
+
+ /* OpenPGP structures */
+ gpgme_ctx_t pgp_ctx;
};
/**
@@ -482,4 +489,9 @@ int rexmpp_xml_match (xmlNodePtr node,
xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node,
const char *namespace,
const char *name);
+
+xmlNodePtr rexmpp_find_event (rexmpp_t *s,
+ const char *from,
+ const char *node,
+ xmlNodePtr *prev_event);
#endif
diff --git a/src/rexmpp_openpgp.c b/src/rexmpp_openpgp.c
new file mode 100644
index 0000000..ecb0fc7
--- /dev/null
+++ b/src/rexmpp_openpgp.c
@@ -0,0 +1,464 @@
+/**
+ @file rexmpp_openpgp.c
+ @brief XEP-0373 routines
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#include <syslog.h>
+#include <string.h>
+
+#include <gpgme.h>
+#include <libxml/tree.h>
+
+#include "rexmpp.h"
+#include "rexmpp_openpgp.h"
+
+
+void rexmpp_pgp_fp_reply (rexmpp_t *s,
+ xmlNodePtr req,
+ xmlNodePtr response,
+ int success)
+{
+ if (! success) {
+ rexmpp_log(s, LOG_WARNING, "Failed to retrieve an OpenpPGP key");
+ return;
+ }
+ xmlNodePtr pubsub =
+ rexmpp_xml_find_child(response, "http://jabber.org/protocol/pubsub",
+ "pubsub");
+ if (pubsub == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: not a pubsub response");
+ return;
+ }
+ xmlNodePtr items =
+ rexmpp_xml_find_child(pubsub, "http://jabber.org/protocol/pubsub",
+ "items");
+ if (items == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no items in pubsub element");
+ return;
+ }
+ xmlNodePtr item =
+ rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub", "item");
+ if (item == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no item in items");
+ return;
+ }
+ xmlNodePtr pubkey =
+ rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "pubkey");
+ if (pubkey == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no pubkey in item");
+ return;
+ }
+ xmlNodePtr data =
+ rexmpp_xml_find_child(pubkey, "urn:xmpp:openpgp:0", "data");
+ if (data == NULL) {
+ rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no data in pubkey");
+ return;
+ }
+ char *key_base64 = xmlNodeGetContent(data);
+
+ char *key_raw = NULL;
+ size_t key_raw_len = 0;
+ int sasl_err =
+ gsasl_base64_from(key_base64, strlen(key_base64), &key_raw, &key_raw_len);
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_ERR, "Base-64 key decoding failure: %s",
+ gsasl_strerror(sasl_err));
+ return;
+ }
+
+ gpgme_error_t err;
+ gpgme_data_t key_dh;
+
+ gpgme_data_new_from_mem(&key_dh, key_raw, key_raw_len, 0);
+ err = gpgme_op_import(s->pgp_ctx, key_dh);
+ /* Apparently reading GPGME results is not thread-safe. Fortunately
+ it's not critical. */
+ gpgme_import_result_t r = gpgme_op_import_result(s->pgp_ctx);
+
+ gpgme_data_release(key_dh);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_WARNING, "OpenPGP key import error: %s",
+ gpgme_strerror(err));
+ return;
+ }
+ if (r->imported == 1) {
+ rexmpp_log(s, LOG_DEBUG, "Imported a key");
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Key import failure");
+ }
+}
+
+rexmpp_err_t
+rexmpp_openpgp_check_keys (rexmpp_t *s,
+ const char *jid,
+ xmlNodePtr items)
+{
+ xmlNodePtr item =
+ rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event",
+ "item");
+ xmlNodePtr list =
+ rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "public-keys-list");
+ xmlNodePtr metadata;
+ for (metadata = xmlFirstElementChild(list);
+ metadata != NULL;
+ metadata = xmlNextElementSibling(metadata)) {
+ char *fingerprint = xmlGetProp(metadata, "v4-fingerprint");
+ gpgme_key_t key;
+ gpgme_error_t err;
+ err = gpgme_get_key(s->pgp_ctx, fingerprint, &key, 0);
+ if (key != NULL) {
+ gpgme_key_release(key);
+ }
+ if (gpg_err_code(err) == GPG_ERR_EOF) {
+ rexmpp_log(s, LOG_DEBUG,
+ "Unknown OpenPGP key fingerprint for %s: %s",
+ jid, fingerprint);
+ xmlNodePtr fp_req = xmlNewNode(NULL, "pubsub");
+ xmlNewNs(fp_req, "http://jabber.org/protocol/pubsub", NULL);
+ xmlNodePtr fp_req_items = xmlNewNode(NULL, "items");
+ xmlNewProp(fp_req_items, "max_items", "1");
+ char key_node[72];
+ snprintf(key_node, 72, "urn:xmpp:openpgp:0:public-keys:%s", fingerprint);
+ xmlNewProp(fp_req_items, "node", key_node);
+ xmlAddChild(fp_req, fp_req_items);
+ rexmpp_iq_new(s, "get", jid, fp_req, rexmpp_pgp_fp_reply);
+ } else if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_WARNING,
+ "OpenPGP error when looking for a key: %s",
+ gpgme_strerror(err));
+ }
+ free(fingerprint);
+ }
+
+ return REXMPP_SUCCESS;
+}
+
+xmlNodePtr rexmpp_published_fingerprints (rexmpp_t *s, const char *jid) {
+ xmlNodePtr published =
+ rexmpp_find_event(s, jid, "urn:xmpp:openpgp:0:public-keys", NULL);
+ if (published == NULL) {
+ return NULL;
+ }
+ xmlNodePtr event =
+ rexmpp_xml_find_child(published, "http://jabber.org/protocol/pubsub#event",
+ "event");
+ xmlNodePtr items =
+ rexmpp_xml_find_child(event, "http://jabber.org/protocol/pubsub#event",
+ "items");
+ xmlNodePtr item =
+ rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event",
+ "item");
+ xmlNodePtr list =
+ rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0",
+ "public-keys-list");
+ xmlNodePtr published_fps = xmlFirstElementChild(list);
+ return published_fps;
+}
+
+int rexmpp_openpgp_key_is_published (rexmpp_t *s, const char *fp) {
+ xmlNodePtr metadata;
+ for (metadata = rexmpp_published_fingerprints(s, s->assigned_jid.bare);
+ metadata != NULL;
+ metadata = xmlNextElementSibling(metadata)) {
+ if (! rexmpp_xml_match(metadata, "urn:xmpp:openpgp:0", "pubkey-metadata")) {
+ continue;
+ }
+ char *fingerprint = xmlGetProp(metadata, "v4-fingerprint");
+ if (fingerprint == NULL) {
+ rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata");
+ continue;
+ }
+ int matches = (strcmp(fingerprint, fp) == 0);
+ free(fingerprint);
+ if (matches) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void rexmpp_pgp_key_publish_list_iq (rexmpp_t *s,
+ xmlNodePtr req,
+ xmlNodePtr response,
+ int success)
+{
+ if (! success) {
+ rexmpp_log(s, LOG_WARNING, "Failed to publish an OpenpPGP key list");
+ return;
+ }
+ rexmpp_log(s, LOG_INFO, "Published an OpenpPGP key list");
+}
+
+void rexmpp_pgp_key_publish_iq (rexmpp_t *s,
+ xmlNodePtr req,
+ xmlNodePtr response,
+ int success)
+{
+ if (! success) {
+ rexmpp_log(s, LOG_WARNING, "Failed to publish an OpenpPGP key");
+ return;
+ }
+ rexmpp_log(s, LOG_INFO, "Uploaded an OpenpPGP key");
+ xmlNodePtr pubsub = xmlFirstElementChild(req);
+ xmlNodePtr publish = xmlFirstElementChild(pubsub);
+ char *node = xmlGetProp(publish, "node");
+ char *fingerprint = node + 31;
+
+ char time_str[42];
+ time_t t = time(NULL);
+ struct tm *utc_time = gmtime(&t);
+ strftime(time_str, 42, "%FT%TZ", utc_time);
+
+ xmlNodePtr metadata = xmlNewNode(NULL, "pubkey-metadata");
+ xmlNewNs(metadata, "urn:xmpp:openpgp:0", NULL);
+ xmlNewProp(metadata, "date", time_str);
+ xmlNewProp(metadata, "v4-fingerprint", fingerprint);
+
+ free(node);
+
+ xmlNodePtr fps = rexmpp_published_fingerprints(s, s->assigned_jid.bare);
+ if (fps != NULL) {
+ metadata->next = xmlCopyNodeList(fps);
+ }
+
+ xmlNodePtr keylist = xmlNewNode(NULL, "public-keys-list");
+ xmlNewNs(keylist, "urn:xmpp:openpgp:0", NULL);
+ xmlAddChild(keylist, metadata);
+
+ xmlNodePtr item = xmlNewNode(NULL, "item");
+ xmlNewNs(item, "http://jabber.org/protocol/pubsub", NULL);
+ xmlAddChild(item, keylist);
+
+ publish = xmlNewNode(NULL, "publish");
+ xmlNewNs(publish, "http://jabber.org/protocol/pubsub", NULL);
+ xmlNewProp(publish, "node", "urn:xmpp:openpgp:0:public-keys");
+ xmlAddChild(publish, item);
+
+ pubsub = xmlNewNode(NULL, "pubsub");
+ xmlNewNs(pubsub, "http://jabber.org/protocol/pubsub", NULL);
+ xmlAddChild(pubsub, publish);
+
+ rexmpp_iq_new(s, "set", NULL, pubsub, rexmpp_pgp_key_publish_list_iq);
+}
+
+
+rexmpp_err_t rexmpp_openpgp_publish_key (rexmpp_t *s, const char *fp) {
+ if (strlen(fp) != 40) {
+ rexmpp_log(s, LOG_ERR, "Wrong fingerprint length: %d", strlen(fp));
+ return REXMPP_E_PGP;
+ }
+ if (rexmpp_openpgp_key_is_published(s, fp)) {
+ rexmpp_log(s, LOG_DEBUG, "Key %s is already published", fp);
+ return REXMPP_SUCCESS;
+ }
+
+ gpgme_data_t key_dh;
+ gpgme_error_t err;
+
+ err = gpgme_data_new(&key_dh);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to create a gpgme data buffer: %s",
+ gpgme_strerror(err));
+ return REXMPP_E_PGP;
+ }
+ gpgme_data_set_encoding(key_dh, GPGME_DATA_ENCODING_BINARY);
+ err = gpgme_op_export(s->pgp_ctx, fp, GPGME_EXPORT_MODE_MINIMAL, key_dh);
+ if (gpg_err_code(err) == GPG_ERR_EOF) {
+ rexmpp_log(s, LOG_ERR, "No such key found: %s", fp);
+ gpgme_data_release(key_dh);
+ return REXMPP_E_PGP;
+ } else if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to read a key: %s", gpgme_strerror(err));
+ gpgme_data_release(key_dh);
+ return REXMPP_E_PGP;
+ }
+ char *key_raw, *key_base64 = NULL;
+ size_t key_raw_len, key_base64_len = 0;
+ key_raw = gpgme_data_release_and_get_mem(key_dh, &key_raw_len);
+ gsasl_base64_to(key_raw, key_raw_len, &key_base64, &key_base64_len);
+ free(key_raw);
+ xmlNodePtr data = xmlNewNode(NULL, "data");
+ xmlNewNs(data, "urn:xmpp:openpgp:0", NULL);
+ xmlNodeAddContent(data, key_base64);
+ free(key_base64);
+
+ xmlNodePtr pubkey = xmlNewNode(NULL, "pubkey");
+ xmlNewNs(pubkey, "urn:xmpp:openpgp:0", NULL);
+ xmlAddChild(pubkey, data);
+
+ char time_str[42];
+ time_t t = time(NULL);
+ struct tm *utc_time = gmtime(&t);
+ strftime(time_str, 42, "%FT%TZ", utc_time);
+
+ xmlNodePtr item = xmlNewNode(NULL, "item");
+ xmlNewNs(item, "http://jabber.org/protocol/pubsub", NULL);
+ xmlNewProp(item, "id", time_str);
+ xmlAddChild(item, pubkey);
+
+ char node_str[72];
+ snprintf(node_str, 72, "urn:xmpp:openpgp:0:public-keys:%s", fp);
+ xmlNodePtr publish = xmlNewNode(NULL, "publish");
+ xmlNewNs(publish, "http://jabber.org/protocol/pubsub", NULL);
+ xmlNewProp(publish, "node", node_str);
+ xmlAddChild(publish, item);
+
+ xmlNodePtr pubsub = xmlNewNode(NULL, "pubsub");
+ xmlNewNs(pubsub, "http://jabber.org/protocol/pubsub", NULL);
+ xmlAddChild(pubsub, publish);
+
+ rexmpp_iq_new(s, "set", NULL, pubsub, rexmpp_pgp_key_publish_iq);
+
+ return REXMPP_SUCCESS;
+}
+
+xmlNodePtr
+rexmpp_openpgp_decrypt_verify (rexmpp_t *s,
+ const char *cipher_base64)
+{
+ gpgme_error_t err;
+ gpgme_data_t cipher_dh, plain_dh;
+ char *cipher_raw = NULL, *plain;
+ size_t cipher_raw_len = 0, plain_len;
+ int sasl_err = gsasl_base64_from(cipher_base64, strlen(cipher_base64),
+ &cipher_raw, &cipher_raw_len);
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_ERR, "Base-64 cipher decoding failure: %s",
+ gsasl_strerror(sasl_err));
+ return NULL;
+ }
+ gpgme_data_new_from_mem(&cipher_dh, cipher_raw, cipher_raw_len, 0);
+ gpgme_data_new(&plain_dh);
+ err = gpgme_op_decrypt_verify (s->pgp_ctx, cipher_dh, plain_dh);
+ gpgme_data_release(cipher_dh);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to decrypt/verify: %s", gpgme_strerror(err));
+ gpgme_data_release(plain_dh);
+ return NULL;
+ }
+ plain = gpgme_data_release_and_get_mem(plain_dh, &plain_len);
+ if (plain == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to release and get memory");
+ return NULL;
+ }
+ xmlNodePtr elem = NULL;
+ xmlDocPtr doc = xmlReadMemory(plain, plain_len, "", "utf-8", XML_PARSE_NONET);
+ if (doc != NULL) {
+ elem = xmlCopyNode(xmlDocGetRootElement(doc), 1);
+ xmlFreeDoc(doc);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to parse an XML document");
+ }
+ free(plain);
+ return elem;
+}
+
+
+char *rexmpp_openpgp_encrypt_sign (rexmpp_t *s,
+ xmlNodePtr payload,
+ char **recipients)
+{
+ int i, nkeys = 0, allocated = 8;
+ gpgme_error_t err;
+
+ /* Locate keys. */
+ gpgme_key_t *keys = malloc(sizeof(gpgme_key_t *) * allocated);
+ keys[0] = NULL;
+ xmlNodePtr metadata;
+ for (i = 0; recipients[i] != NULL; i++) {
+ for (metadata = rexmpp_published_fingerprints(s, recipients[i]);
+ metadata != NULL;
+ metadata = xmlNextElementSibling(metadata)) {
+ char *fingerprint = xmlGetProp(metadata, "v4-fingerprint");
+ err = gpgme_get_key(s->pgp_ctx, fingerprint, &keys[nkeys], 0);
+ if (gpg_err_code(err) == GPG_ERR_NO_ERROR) {
+ nkeys++;
+ if (nkeys == allocated) {
+ allocated *= 2;
+ keys = realloc(keys, sizeof(gpgme_key_t *) * allocated);
+ }
+ keys[nkeys] = NULL;
+ } else if (gpg_err_code(err) == GPG_ERR_EOF) {
+ rexmpp_log(s, LOG_WARNING, "No key %s for %s found",
+ fingerprint, recipients[i]);
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s",
+ fingerprint, gpgme_strerror(err));
+ }
+ free(fingerprint);
+ }
+ }
+
+ /* Prepare a signcrypt element. */
+ xmlNodePtr signcrypt = xmlNewNode(NULL, "signcrypt");
+ xmlNewNs(signcrypt, "urn:xmpp:openpgp:0", NULL);
+
+ /* Add all the recipients. */
+ for (i = 0; recipients[i] != NULL; i++) {
+ xmlNodePtr to = xmlNewNode(NULL, "to");
+ xmlNewNs(to, "urn:xmpp:openpgp:0", NULL);
+ xmlNewProp(to, "jid", recipients[i]);
+ xmlAddChild(signcrypt, to);
+ }
+
+ /* Add timestamp. */
+ char time_str[42];
+ time_t t = time(NULL);
+ struct tm *utc_time = gmtime(&t);
+ strftime(time_str, 42, "%FT%TZ", utc_time);
+
+ xmlNodePtr time = xmlNewNode(NULL, "time");
+ xmlNewNs(time, "urn:xmpp:openpgp:0", NULL);
+ xmlNewProp(time, "stamp", time_str);
+ xmlAddChild(signcrypt, time);
+
+ /* Add a random-length random-content padding. */
+ char *rand_str, rand[256];
+ gsasl_random(rand, 1);
+ size_t rand_str_len = 0, rand_len = (unsigned char)rand[0] + 16;
+ gsasl_random(rand, rand_len);
+ gsasl_base64_to(rand, rand_len, &rand_str, &rand_str_len);
+
+ xmlNodePtr rpad = xmlNewNode(NULL, "rpad");
+ xmlNewNs(rpad, "urn:xmpp:openpgp:0", NULL);
+ xmlNodeAddContent(rpad, rand_str);
+ free(rand_str);
+ xmlAddChild(signcrypt, rpad);
+
+ /* Add the payload. */
+ xmlNodePtr pl = xmlNewNode(NULL, "payload");
+ xmlNewNs(pl, "urn:xmpp:openpgp:0", NULL);
+ xmlAddChild(pl, payload);
+ xmlAddChild(signcrypt, pl);
+
+ /* Serialize the resulting XML. */
+ char *plaintext = rexmpp_xml_serialize(signcrypt);
+ xmlFreeNode(signcrypt);
+
+ /* Encrypt, base64-encode. */
+ gpgme_data_t cipher_dh, plain_dh;
+ gpgme_data_new(&cipher_dh);
+ gpgme_data_new_from_mem(&plain_dh, plaintext, strlen(plaintext), 0);
+ err = gpgme_op_encrypt_sign(s->pgp_ctx, keys, 0, plain_dh, cipher_dh);
+ for (i = 0; i < nkeys; i++) {
+ gpgme_key_release(keys[i]);
+ }
+ free(keys);
+ gpgme_data_release(plain_dh);
+ if (gpg_err_code(err) != GPG_ERR_NO_ERROR) {
+ rexmpp_log(s, LOG_ERR, "Failed to encrypt: %s", gpgme_strerror(err));
+ gpgme_data_release(cipher_dh);
+ return NULL;
+ }
+ char *cipher_raw = NULL, *cipher_base64 = NULL;
+ size_t cipher_raw_len = 0, cipher_base64_len = 0;
+ cipher_raw = gpgme_data_release_and_get_mem(cipher_dh, &cipher_raw_len);
+ gsasl_base64_to(cipher_raw, cipher_raw_len,
+ &cipher_base64, &cipher_base64_len);
+ free(cipher_raw);
+
+ return cipher_base64;
+}
diff --git a/src/rexmpp_openpgp.h b/src/rexmpp_openpgp.h
new file mode 100644
index 0000000..9d00a6e
--- /dev/null
+++ b/src/rexmpp_openpgp.h
@@ -0,0 +1,28 @@
+/**
+ @file rexmpp_openpgp.h
+ @brief XEP-0373 routines
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+#ifndef REXMPP_OPENPGP_H
+#define REXMPP_OPENPGP_H
+
+#include "rexmpp.h"
+
+rexmpp_err_t
+rexmpp_openpgp_check_keys (rexmpp_t *s,
+ const char *jid,
+ xmlNodePtr items);
+
+rexmpp_err_t rexmpp_openpgp_publish_key (rexmpp_t *s, const char *fp);
+
+xmlNodePtr
+rexmpp_openpgp_decrypt_verify (rexmpp_t *s,
+ const char *cipher_base64);
+
+char *rexmpp_openpgp_encrypt_sign (rexmpp_t *s,
+ xmlNodePtr payload,
+ char **recipients);
+
+#endif