diff options
Diffstat (limited to 'src/rexmpp_openpgp.c')
-rw-r--r-- | src/rexmpp_openpgp.c | 887 |
1 files changed, 887 insertions, 0 deletions
diff --git a/src/rexmpp_openpgp.c b/src/rexmpp_openpgp.c new file mode 100644 index 0000000..14e2d35 --- /dev/null +++ b/src/rexmpp_openpgp.c @@ -0,0 +1,887 @@ +/** + @file rexmpp_openpgp.c + @brief XEP-0373 routines + @author defanor <defanor@uberspace.net> + @date 2020--2021 + @copyright MIT license. + + +Implementation notes +==================== + +XEP-0373 v0.6 is implemented here. + +Intentionally omitted functionality: + +- Not including a `to` element for self, since it is redundant for + signed messages, and only useful for signed ones. + +- Private key synchronisation is not implemented, since it is + unnecessary in the presence of asynchronous cryptography and support + for multiple keys, but can be dangerous if a passphrase used for key + encryption is weaker than the key itself. + +- XEP-0374 is not implemented here, since restricting its usage to + `<signcrypt/>` is likely to be undesirable in some cases (primarily + because it introduces non-repudiation). + +Possible future improvements: + +- A setting to generate the keys if they are missing, upload them + automatically, encrypt messages opportunistically (as the XEP + suggests). + +- Maybe use alternative key retrieval methods in order to decrease + dependency on PEP/pubsub, and possibly to incorporate existing + infrastructure: e.g., retrieval by PEP-provided fingerprint from key + servers, by vCard-provided email address from WKD or DANE. + +*/ + +#include <syslog.h> +#include <string.h> +#include <time.h> + +#include "config.h" + +#ifdef HAVE_GPGME +#include <gpgme.h> +#endif +#include <gcrypt.h> + +#include "rexmpp.h" +#include "rexmpp_xml.h" +#include "rexmpp_openpgp.h" +#include "rexmpp_jid.h" +#include "rexmpp_pubsub.h" +#include "rexmpp_base64.h" +#include "rexmpp_random.h" + + +#ifdef HAVE_GPGME + +void rexmpp_pgp_fp_reply (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; /* Not of interest. */ + if (! success) { + rexmpp_log(s, LOG_WARNING, "Failed to retrieve an OpenpPGP key"); + return; + } + rexmpp_xml_t *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; + } + rexmpp_xml_t *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; + } + rexmpp_xml_t *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; + } + rexmpp_xml_t *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; + } + rexmpp_xml_t *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_raw = NULL; + size_t key_raw_len = 0; + const char *key_base64 = rexmpp_xml_text_child(data); + int base64_err = + rexmpp_base64_from(key_base64, strlen(key_base64), &key_raw, &key_raw_len); + if (base64_err != 0) { + rexmpp_log(s, LOG_ERR, "Base-64 key decoding failure"); + 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, + rexmpp_xml_t *items) +{ + rexmpp_xml_t *item = + rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event", + "item"); + rexmpp_xml_t *list = + rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "public-keys-list"); + rexmpp_xml_t *metadata; + for (metadata = rexmpp_xml_first_elem_child(list); + metadata != NULL; + metadata = rexmpp_xml_next_elem_sibling(metadata)) { + const char *fingerprint = + rexmpp_xml_find_attr_val(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_unref(key); + } + if (gpg_err_code(err) == GPG_ERR_EOF) { + rexmpp_log(s, LOG_DEBUG, + "Unknown OpenPGP key fingerprint for %s: %s", + jid, fingerprint); + rexmpp_xml_t *fp_req = + rexmpp_xml_new_elem("pubsub", "http://jabber.org/protocol/pubsub"); + rexmpp_xml_t *fp_req_items = + rexmpp_xml_new_elem("items", NULL); + rexmpp_xml_add_attr(fp_req_items, "max_items", "1"); + char key_node[72]; + snprintf(key_node, 72, "urn:xmpp:openpgp:0:public-keys:%s", fingerprint); + rexmpp_xml_add_attr(fp_req_items, "node", key_node); + rexmpp_xml_add_child(fp_req, fp_req_items); + rexmpp_iq_new(s, "get", jid, fp_req, rexmpp_pgp_fp_reply, NULL); + } 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)); + } + } + + return REXMPP_SUCCESS; +} + +rexmpp_xml_t *rexmpp_published_fingerprints (rexmpp_t *s, const char *jid) { + rexmpp_xml_t *published = + rexmpp_find_event(s, jid, "urn:xmpp:openpgp:0:public-keys", NULL); + if (published == NULL) { + return NULL; + } + rexmpp_xml_t *event = + rexmpp_xml_find_child(published, "http://jabber.org/protocol/pubsub#event", + "event"); + rexmpp_xml_t *items = + rexmpp_xml_find_child(event, "http://jabber.org/protocol/pubsub#event", + "items"); + rexmpp_xml_t *item = + rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event", + "item"); + rexmpp_xml_t *list = + rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", + "public-keys-list"); + rexmpp_xml_t *published_fps = list->alt.elem.children; + return published_fps; +} + +int rexmpp_openpgp_key_is_published (rexmpp_t *s, const char *fp) { + rexmpp_xml_t *metadata; + for (metadata = rexmpp_published_fingerprints(s, s->assigned_jid.bare); + metadata != NULL; + metadata = metadata->next) { + if (! rexmpp_xml_match(metadata, "urn:xmpp:openpgp:0", "pubkey-metadata")) { + continue; + } + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); + if (fingerprint == NULL) { + rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); + continue; + } + if (strcmp(fingerprint, fp) == 0) { + return 1; + } + } + return 0; +} + +rexmpp_xml_t * +rexmpp_openpgp_remove_key_from_list (rexmpp_t *s, + const char *fp) +{ + rexmpp_xml_t *fps = + rexmpp_xml_clone_list(rexmpp_published_fingerprints(s, s->assigned_jid.bare)); + rexmpp_xml_t *metadata, *prev = NULL; + for (metadata = fps; + metadata != NULL; + prev = metadata, metadata = rexmpp_xml_next_elem_sibling(metadata)) { + if (! rexmpp_xml_match(metadata, "urn:xmpp:openpgp:0", "pubkey-metadata")) { + continue; + } + const char *fingerprint = + rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); + if (fingerprint == NULL) { + rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); + continue; + } + int matches = (strcmp(fingerprint, fp) == 0); + if (matches) { + if (prev != NULL) { + prev->next = metadata->next; + } else { + fps = metadata->next; + } + rexmpp_xml_free(metadata); + return fps; + } + } + return fps; +} + +void rexmpp_pgp_key_publish_list_iq (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + (void)response; + 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_fp_list_upload (rexmpp_t *s, rexmpp_xml_t *metadata) { + rexmpp_xml_t *keylist = + rexmpp_xml_new_elem("public-keys-list", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_child(keylist, metadata); + rexmpp_pubsub_item_publish(s, NULL, "urn:xmpp:openpgp:0:public-keys", + NULL, keylist, rexmpp_pgp_key_publish_list_iq, NULL); +} + +void rexmpp_pgp_key_delete_iq (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)response; + if (! success) { + rexmpp_log(s, LOG_WARNING, "Failed to delete an OpenpPGP key"); + return; + } + rexmpp_xml_t *pubsub = req->alt.elem.children; + rexmpp_xml_t *publish = pubsub->alt.elem.children;; + const char *node = rexmpp_xml_find_attr_val(publish, "node"); + const char *fingerprint = node + 31; + rexmpp_log(s, LOG_INFO, "Removed OpenpPGP key %s", fingerprint); +} + +void rexmpp_pgp_key_publish_iq (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)response; + if (! success) { + rexmpp_log(s, LOG_WARNING, "Failed to publish an OpenpPGP key"); + return; + } + rexmpp_log(s, LOG_INFO, "Uploaded an OpenpPGP key"); + rexmpp_xml_t *pubsub = req->alt.elem.children; + rexmpp_xml_t *publish = pubsub->alt.elem.children;; + const char *node = rexmpp_xml_find_attr_val(publish, "node"); + const char *fingerprint = node + 31; + + char time_str[42]; + time_t t = time(NULL); + struct tm utc_time; + gmtime_r(&t, &utc_time); + strftime(time_str, 42, "%FT%TZ", &utc_time); + + rexmpp_xml_t *metadata = + rexmpp_xml_new_elem("pubkey-metadata", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_attr(metadata, "date", time_str); + rexmpp_xml_add_attr(metadata, "v4-fingerprint", fingerprint); + + rexmpp_xml_t *fps = rexmpp_openpgp_remove_key_from_list(s, fingerprint); + if (fps != NULL) { + metadata->next = fps; + } + rexmpp_pgp_key_fp_list_upload(s, metadata); +} + +void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp) { + rexmpp_xml_t *new_fp_list = rexmpp_openpgp_remove_key_from_list(s, fp); + if (new_fp_list != NULL) { + rexmpp_pgp_key_fp_list_upload(s, new_fp_list); + } + char node_str[72]; + snprintf(node_str, 72, "urn:xmpp:openpgp:0:public-keys:%s", fp); + rexmpp_pubsub_node_delete(s, NULL, node_str, rexmpp_pgp_key_delete_iq, NULL); +} + +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; + } + + 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, 0, 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); + rexmpp_base64_to(key_raw, key_raw_len, &key_base64, &key_base64_len); + free(key_raw); + rexmpp_xml_t *data = + rexmpp_xml_new_elem("data", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_text(data, key_base64); + free(key_base64); + + rexmpp_xml_t *pubkey = + rexmpp_xml_new_elem("pubkey", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_child(pubkey, data); + + char time_str[42]; + time_t t = time(NULL); + struct tm utc_time; + gmtime_r(&t, &utc_time); + strftime(time_str, 42, "%FT%TZ", &utc_time); + char node_str[72]; + snprintf(node_str, 72, "urn:xmpp:openpgp:0:public-keys:%s", fp); + rexmpp_pubsub_item_publish(s, NULL, node_str, time_str, + pubkey, rexmpp_pgp_key_publish_iq, NULL); + return REXMPP_SUCCESS; +} + +int rexmpp_openpgp_fingerprint_matches (const char *f1, const char *f2) { + int i = 0, j = 0; + + while (f1[i] || f2[j]) { + /* skip spaces */ + while (f1[i] == ' ') i++; + while (f2[j] == ' ') j++; + /* compare */ + if (f1[i] != f2[j]) { + return 0; + } + /* advance */ + i++; + j++; + } + return 1; +} + +rexmpp_xml_t * +rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, + rexmpp_xml_t *message, + int *valid) +{ + gpgme_error_t err; + struct rexmpp_jid from, to; + *valid = 0; + if (! rexmpp_xml_match(message, "jabber:client", "message")) { + rexmpp_log(s, LOG_ERR, "Not a message element"); + return NULL; + } + const char *from_str = rexmpp_xml_find_attr_val(message, "from"); + if (from_str == NULL) { + rexmpp_log(s, LOG_ERR, "No 'from' attribute"); + return NULL; + } + rexmpp_jid_parse(from_str, &from); + const char *to_str = rexmpp_xml_find_attr_val(message, "to"); + if (to_str == NULL) { + if (strcmp(from.bare, s->assigned_jid.bare) != 0) { + rexmpp_log(s, LOG_ERR, "No 'to' attribute"); + return NULL; + } + rexmpp_jid_parse(from.full, &to); + } else { + rexmpp_jid_parse(to_str, &to); + } + rexmpp_xml_t *openpgp = + rexmpp_xml_find_child(message, "urn:xmpp:openpgp:0", "openpgp"); + if (openpgp == NULL) { + rexmpp_log(s, LOG_ERR, "No 'openpgp' child element"); + return NULL; + } + const char *cipher_str = rexmpp_xml_text_child(openpgp); + rexmpp_xml_t *plain = + rexmpp_openpgp_decrypt_verify(s, cipher_str); + if (plain == NULL) { + return NULL; + } + + if (rexmpp_xml_match(plain, "urn:xmpp:openpgp:0", "crypt")) { + *valid = 1; + return plain; + } + + if (! (rexmpp_xml_match(plain, "urn:xmpp:openpgp:0", "signcrypt") || + rexmpp_xml_match(plain, "urn:xmpp:openpgp:0", "sign"))) { + rexmpp_log(s, LOG_ERR, "An unexpected element inside <openpgp/>"); + return plain; + } + + rexmpp_xml_t *child; + int found = 0; + for (child = rexmpp_xml_first_elem_child(plain); + child != NULL && ! found; + child = rexmpp_xml_next_elem_sibling(child)) + { + if (rexmpp_xml_match(child, "urn:xmpp:openpgp:0", "to")) { + const char *to_jid = rexmpp_xml_find_attr_val(child, "jid"); + if (to_jid == NULL) { + rexmpp_log(s, LOG_WARNING, + "Found a 'to' element without a 'jid' attribute"); + } else if (strcmp(to_jid, to.bare) == 0) { + found = 1; + } + } + } + if (! found) { + rexmpp_log(s, LOG_ERR, + "No recipient corresponds to outer message's recipient"); + return plain; + } + + gpgme_verify_result_t result = gpgme_op_verify_result(s->pgp_ctx); + if (result == NULL) { + rexmpp_log(s, LOG_ERR, "Signature verification failed"); + return plain; + } + + gpgme_signature_t sig = result->signatures; + if (sig->next != NULL) { + rexmpp_log(s, LOG_WARNING, + "Multiple signatures detected, verifying them all"); + } + while (sig) { + if (! sig->validity) { + rexmpp_log(s, LOG_WARNING, "Invalid signature: %s", + gpgme_strerror(sig->validity_reason)); + return plain; + } + + found = 0; + rexmpp_xml_t *metadata; + for (metadata = rexmpp_published_fingerprints(s, from.bare); + metadata != NULL && ! found; + metadata = metadata->next) { + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); + if (fingerprint == NULL) { + rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); + continue; + } + if (rexmpp_openpgp_fingerprint_matches(fingerprint, sig->fpr)) { + found = 1; + } + } + if (! found) { + rexmpp_log(s, LOG_ERR, "No %s's known key matches that of the signature", + from.bare); + return plain; + } + gpgme_key_t key; + err = gpgme_get_key(s->pgp_ctx, sig->fpr, &key, 0); + if (gpg_err_code(err) != GPG_ERR_NO_ERROR) { + rexmpp_log(s, LOG_ERR, "Key reading failure: %s", + gpgme_strerror(err)); + return plain; + } + gpgme_user_id_t uid; + found = 0; + for (uid = key->uids; uid != NULL; uid = uid->next) { + if (strlen(uid->uid) < 6) { + continue; + } + if (strncmp(uid->uid, "xmpp:", 5) == 0 && + strcmp(uid->uid + 5, from.bare) == 0) { + found = 1; + } + } + if (! found) { + rexmpp_log(s, LOG_ERR, + "No 'xmpp:%s' user ID found in the key used for signature", + from.bare); + return plain; + } + sig = sig->next; + } + + *valid = 1; + return plain; +} + +rexmpp_xml_t * +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 base64_err = rexmpp_base64_from(cipher_base64, strlen(cipher_base64), + &cipher_raw, &cipher_raw_len); + if (base64_err != 0) { + rexmpp_log(s, LOG_ERR, "Base-64 cipher decoding failure"); + 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 || + gpg_err_code(err) == GPG_ERR_NO_DATA)) { + 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; + } + rexmpp_xml_t *elem = rexmpp_xml_parse(plain, plain_len); + if(elem == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to parse an XML document"); + } + free(plain); + return elem; +} + +void rexmpp_openpgp_add_keys (rexmpp_t *s, + const char *jid, + gpgme_key_t **keys, + int *nkeys, + int *allocated) +{ + gpgme_error_t err; + rexmpp_xml_t *metadata; + for (metadata = rexmpp_published_fingerprints(s, jid); + metadata != NULL; + metadata = metadata->next) { + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); + if (fingerprint == NULL) { + rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); + continue; + } + err = gpgme_get_key(s->pgp_ctx, fingerprint, &(*keys)[*nkeys], 0); + if (gpg_err_code(err) == GPG_ERR_NO_ERROR) { + if ((*keys)[*nkeys]->can_encrypt) { + *nkeys = *nkeys + 1; + if (*nkeys == *allocated) { + *allocated = *allocated * 2; + gpgme_key_t *new_keys = + realloc(*keys, sizeof(gpgme_key_t *) * *allocated); + if (new_keys == NULL) { + rexmpp_log(s, LOG_ERR, + "Failed to reallocate the OpenPGP keys array: %s", + strerror(errno)); + continue; + } + *keys = new_keys; + } + } else { + gpgme_key_unref((*keys)[*nkeys]); + } + (*keys)[*nkeys] = NULL; + } else if (gpg_err_code(err) == GPG_ERR_EOF) { + rexmpp_log(s, LOG_WARNING, "No key %s for %s found", + fingerprint, jid); + } else { + rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s", + fingerprint, gpgme_strerror(err)); + } + } +} + +void rexmpp_openpgp_set_signers (rexmpp_t *s) { + gpgme_error_t err; + rexmpp_xml_t *metadata; + gpgme_key_t sec_key; + gpgme_signers_clear(s->pgp_ctx); + for (metadata = rexmpp_published_fingerprints(s, s->initial_jid.bare); + metadata != NULL; + metadata = metadata->next) { + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); + if (fingerprint == NULL) { + rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); + continue; + } + err = gpgme_get_key(s->pgp_ctx, fingerprint, &sec_key, 1); + if (gpg_err_code(err) == GPG_ERR_NO_ERROR) { + if (sec_key->can_sign) { + gpgme_signers_add(s->pgp_ctx, sec_key); + } + gpgme_key_unref(sec_key); + } else if (gpg_err_code(err) != GPG_ERR_EOF) { + rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s", + fingerprint, gpgme_strerror(err)); + } + } +} + +char *rexmpp_openpgp_payload (rexmpp_t *s, + rexmpp_xml_t *payload, + const char **recipients, + const char **signers, + enum rexmpp_ox_mode mode) +{ + gpgme_error_t err; + int i, nkeys = 0, allocated = 0; + gpgme_key_t *keys = NULL; + + /* Prepare an element. */ + char *elem_name = NULL; + if (mode == REXMPP_OX_SIGNCRYPT) { + elem_name = "signcrypt"; + } else if (mode == REXMPP_OX_SIGN) { + elem_name = "sign"; + } else if (mode == REXMPP_OX_CRYPT) { + elem_name = "crypt"; + } + rexmpp_xml_t *elem = + rexmpp_xml_new_elem(elem_name, "urn:xmpp:openpgp:0"); + + if (mode == REXMPP_OX_SIGN || mode == REXMPP_OX_SIGNCRYPT) { + if (signers == NULL) { + rexmpp_openpgp_set_signers(s); + } else { + gpgme_signers_clear(s->pgp_ctx); + int signer; + gpgme_key_t sec_key; + for (signer = 0; signers[signer] != NULL; signer++) { + err = gpgme_get_key(s->pgp_ctx, signers[signer], &sec_key, 1); + if (gpg_err_code(err) == GPG_ERR_NO_ERROR) { + gpgme_signers_add(s->pgp_ctx, sec_key); + gpgme_key_unref(sec_key); + } else { + rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s", + signers[signer], gpgme_strerror(err)); + } + } + } + + /* Add all the recipients. */ + for (i = 0; recipients[i] != NULL; i++) { + rexmpp_xml_t *to = + rexmpp_xml_new_elem("to", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_attr(to, "jid", recipients[i]); + rexmpp_xml_add_child(elem, to); + } + } + + /* Add timestamp. */ + char time_str[42]; + time_t t = time(NULL); + struct tm utc_time; + gmtime_r(&t, &utc_time); + strftime(time_str, 42, "%FT%TZ", &utc_time); + rexmpp_xml_t *time = + rexmpp_xml_new_elem("time", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_attr(time, "stamp", time_str); + rexmpp_xml_add_child(elem, time); + + /* Add the payload. */ + rexmpp_xml_t *pl = + rexmpp_xml_new_elem("payload", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_child(pl, payload); + rexmpp_xml_add_child(elem, pl); + + if (mode == REXMPP_OX_CRYPT || mode == REXMPP_OX_SIGNCRYPT) { + /* Add keys for encryption. */ + allocated = 8; + keys = malloc(sizeof(gpgme_key_t *) * allocated); + if (keys == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for keys"); + rexmpp_xml_free(elem); + return NULL; + } + keys[0] = NULL; + rexmpp_openpgp_add_keys(s, s->initial_jid.bare, &keys, &nkeys, &allocated); + for (i = 0; recipients[i] != NULL; i++) { + rexmpp_openpgp_add_keys(s, recipients[i], &keys, &nkeys, &allocated); + } + + /* A random-length random-content padding. */ + char *rand_str, rand[256]; + rexmpp_random_buf(rand, 1); + size_t rand_str_len = 0, rand_len = (unsigned char)rand[0] % (255 - 16) + 16; + rexmpp_random_buf(rand, rand_len); + rexmpp_base64_to(rand, rand_len, &rand_str, &rand_str_len); + + rexmpp_xml_t *rpad = + rexmpp_xml_new_elem("rpad", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_text(rpad, rand_str); + free(rand_str); + rexmpp_xml_add_child(elem, rpad); + } + + /* Serialize the resulting XML. */ + char *plaintext = rexmpp_xml_serialize(elem, 0); + rexmpp_xml_free(elem); + + /* 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); + if (mode == REXMPP_OX_SIGNCRYPT) { + err = gpgme_op_encrypt_sign(s->pgp_ctx, keys, GPGME_ENCRYPT_NO_ENCRYPT_TO, + plain_dh, cipher_dh); + } else if (mode == REXMPP_OX_CRYPT) { + err = gpgme_op_encrypt(s->pgp_ctx, keys, GPGME_ENCRYPT_NO_ENCRYPT_TO, + plain_dh, cipher_dh); + } else { /* if (mode == REXMPP_OX_SIGN) */ + err = gpgme_op_sign(s->pgp_ctx, plain_dh, cipher_dh, GPGME_SIG_MODE_NORMAL); + } + if (keys != NULL) { + for (i = 0; i < nkeys; i++) { + gpgme_key_unref(keys[i]); + } + free(keys); + keys = NULL; + } + gpgme_data_release(plain_dh); + if (gpg_err_code(err) != GPG_ERR_NO_ERROR) { + rexmpp_log(s, LOG_ERR, "Failed to %s: %s", elem_name, 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); + rexmpp_base64_to(cipher_raw, cipher_raw_len, + &cipher_base64, &cipher_base64_len); + free(cipher_raw); + + return cipher_base64; +} + +rexmpp_err_t rexmpp_openpgp_set_home_dir (rexmpp_t *s, const char *home_dir) { + gpgme_engine_info_t engine_info; + gpgme_error_t err; + engine_info = gpgme_ctx_get_engine_info(s->pgp_ctx); + err = gpgme_ctx_set_engine_info(s->pgp_ctx, engine_info->protocol, + engine_info->file_name, + home_dir); + if (gpg_err_code(err) != GPG_ERR_NO_ERROR) { + rexmpp_log(s, LOG_ERR, "Failed to set home directory: %s", + gpgme_strerror(err)); + return REXMPP_E_PGP; + } + return REXMPP_SUCCESS; +} + +#else + +/* Dummy functions for when it's built without GPGME. */ + +rexmpp_err_t gpgme_not_supported(rexmpp_t *s) { + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without GPGME support"); + return REXMPP_E_PGP; +} + +rexmpp_err_t +rexmpp_openpgp_check_keys (rexmpp_t *s, + const char *jid, + rexmpp_xml_t *items) { + (void)jid; + (void)items; + return gpgme_not_supported(s); +} + +rexmpp_err_t rexmpp_openpgp_publish_key (rexmpp_t *s, const char *fp) { + (void)fp; + return gpgme_not_supported(s); +} + +void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp) { + (void)fp; + gpgme_not_supported(s); +} + +rexmpp_xml_t * +rexmpp_openpgp_decrypt_verify (rexmpp_t *s, + const char *cipher_base64) { + (void)cipher_base64; + gpgme_not_supported(s); + return NULL; +} + +rexmpp_xml_t * +rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, + rexmpp_xml_t *message, + int *valid) { + (void)message; + (void)valid; + gpgme_not_supported(s); + return NULL; +} + +char *rexmpp_openpgp_payload (rexmpp_t *s, + rexmpp_xml_t *payload, + const char **recipients, + const char **signers, + enum rexmpp_ox_mode mode) { + (void)recipients; + (void)signers; + (void)mode; + rexmpp_xml_free(payload); + gpgme_not_supported(s); + return NULL; +} + +rexmpp_err_t rexmpp_openpgp_set_home_dir (rexmpp_t *s, const char *home_dir) { + (void)home_dir; + return gpgme_not_supported(s); +} + +#endif |