diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/rexmpp.c | 43 | ||||
-rw-r--r-- | src/rexmpp.h | 12 | ||||
-rw-r--r-- | src/rexmpp_openpgp.c | 464 | ||||
-rw-r--r-- | src/rexmpp_openpgp.h | 28 |
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 |