From cad1210fb8a7402cb29e607f8f9680005314120d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 11 Nov 2016 16:49:28 +0900 Subject: [PATCH] core: Enable extraction of session keys. * src/gpgme.c (gpgme_set_export_session_keys): New function. (gpgme_get_export_session_keys): New function. * src/gpgme.h.in (struct _gpgme_op_decrypt_result): Add session_key member. (gpgme_{set,get}_export_session_keys): Declare new functions. * src/libgpgme.vers, src/gpgme.def: Export new functions in shared object. * src/engine.h: (_gpgme_engine_op_decrypt) Add export_session_key parameter. (_gpgme_engine_op_decrypt_verify): Add export_session_key parameter. * src/engine-backend.h: (struct engine_ops): Change function pointer declarations to match. * src/context.h (struct gpgme_context): Add export_session_keys member. * src/decrypt.c (release_op_data): Free result.session_key. (_gpgme_decrypt_status_handler): Store a copy of the exported session key. (decrypt_start): Pass export_session_keys from the context. * src/decrypt-verify.c (decrypt_verify_start): Pass export_session_keys from context. * src/engine.c (_gpgme_engine_op_decrypt): Pass through export_session_key flag. (_gpgme_engine_op_decrypt_verify): Pass through export_session_key flag. * src/engine-gpg.c (gpg_decrypt): If export_session_key is set, add --export-session-key to argument list. * src/engine-gpgsm.c (gpgsm_decrypt): Ignore export_session_key for now, since gpgsm offers no such mechanism. * src/engine-uiserver.c (_uiserver_decrypt): If export_session_key is set, add --export-session-key flag to cmd. * doc/gpgme.texi: Document new functions and session_key member of decrypt_result_t. * doc/uiserver.texi: Add --export-session-key flag to DECRYPT command. -- gpg(1) documents session key export as useful for key escrow, and is rightly dubious of that use case. However, session key export is also useful in other use cases. Two examples from MUA development (where this functionality would be specifically useful to me right now): * If the MUA stores a local copy of the session key upon decrypting the message, it can re-decrypt the message without expensive asymmetric operations. When rendering a thread with dozens of encrypted messages, this can represent a significant speedup. * A user may have expired encryption-capable secret key material, along with many messages encrypted to that material. If she stores the session keys for those messages she wants to keep, she can destroy her secret key material and make any messages she has deleted completely unrecoverable, even to an attacker who gets her remaining secret keys in the future. This patchset makes a two specific implementation decisions that could have gone in different ways. I welcome feedback on preferred outcomes. 0) session key representation: we currently represent the session key as an opaque textual string, rather than trying to provide any sort of in-memory structure. While it wouldn't be hard to parse the data produced by gpg's --export-session-key, I chose to use the opaque string rather than lock in a particular data format. 1) API/ABI: i've added a member to gpgme_op_decrypt_result_t. This has the potential to cause an out-of-bound memory access if someone uses code compiled against the newer verision, but linked at runtime against an older version. I've attempted to limit that risk by documenting that users must verify gpgme_get_export_session_keys() before accessing this new struct member -- this means that code expecting this capability will require the symbol at link-time, and will refuse to link against older versions. Another approach to solving this problem would be to avoid modifying gpgme_op_decrypt_result_t, and to introduce instead a new function gpgme_op_session_key(), which could be called in the same places as gpgme_op_decrypt_result(). Depending on the representation of the session key, this might introduce new memory-management burdens on the user of the library, and the session key is certainly part of a decryption result, so it seemed simpler to go with what i have here. If anyone has strong preferences that these choices should be solved in a different way, i'm happy to hear them. Additionally, I note that i'm also still pretty unclear about how the "UI Server" fits into this whole ecosystem. In particular, I don't know whether it's kosher to just add an --export-session-key flag to the DECRYPT operation without actually having implemented it anywhere, but i don't see where i would actually implement it either :/ If this patch (or some variant) is adopted, i will supply another patch that permits offering a session key during decryption (e.g. "gpg --override-session-key"), but I wanted to get these implementation choices ironed out first. Gnupg-Bug-Id: 2754 Signed-off-by: Daniel Kahn Gillmor On the concern of adding a new field to a structure: It may not be clearly documented but we don't expect that a user ever allocates such a structure - those result structure may only be created bu gpgme and are read-only for the user. Adding a new member constitutes a compatible ABI change and thus an older SO may not be used by code compiled with a header for the newer API. Unless someone tinkers with the build system, this should never happen. We have added new fields to result structure may times and I can't remember any problems. - wk --- doc/gpgme.texi | 38 ++++++++++++++++++++++++++++++++++++++ doc/uiserver.texi | 10 ++++++---- src/context.h | 3 +++ src/decrypt-verify.c | 2 +- src/decrypt.c | 11 ++++++++++- src/engine-backend.h | 4 ++-- src/engine-gpg.c | 5 ++++- src/engine-gpgsm.c | 5 ++++- src/engine-uiserver.c | 16 +++++++++------- src/engine.c | 8 ++++---- src/engine.h | 6 ++++-- src/gpgme.c | 24 ++++++++++++++++++++++++ src/gpgme.def | 2 ++ src/gpgme.h.in | 11 +++++++++++ src/libgpgme.vers | 3 +++ 15 files changed, 125 insertions(+), 23 deletions(-) diff --git a/doc/gpgme.texi b/doc/gpgme.texi index 801a53f3..7eabab48 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -191,6 +191,7 @@ Context Attributes * Text Mode:: Choosing canonical text mode. * Offline Mode:: Choosing offline mode. * Included Certificates:: Including a number of certificates. +* Exporting Session Keys:: Requesting session keys upon decryption. * Key Listing Mode:: Selecting key listing mode. * Passphrase Callback:: Getting the passphrase from the user. * Progress Meter Callback:: Being informed about the progress. @@ -2351,6 +2352,7 @@ started. In fact, these references are accessed through the * Offline Mode:: Choosing offline mode. * Pinentry Mode:: Choosing the pinentry mode. * Included Certificates:: Including a number of certificates. +* Exporting Session Keys:: Requesting session keys upon decryption. * Key Listing Mode:: Selecting key listing mode. * Passphrase Callback:: Getting the passphrase from the user. * Progress Meter Callback:: Being informed about the progress. @@ -2641,6 +2643,29 @@ certificates to include into an S/MIME signed message. @end deftypefun +@node Exporting Session Keys +@subsection Exporting Session Keys +@cindex context, exporting session keys +@cindex Exporting Session Keys +@cindex exporting session keys + +@deftypefun void gpgme_set_export_session_keys (@w{gpgme_ctx_t @var{ctx}}, @w{int @var{yes}}) +The function @code{gpgme_set_export_session_keys} specifies whether +the context should try to export the symmetric session key when +decrypting data. By default, session keys are not exported. + +Session keys are not exported if @var{yes} is zero, and +enabled otherwise. +@end deftypefun + +@deftypefun int gpgme_get_export_session_keys (@w{gpgme_ctx_t @var{ctx}}) +The function @code{gpgme_get_export_session_keys} returns @code{1} if +the context will try to export the symmetric session key when +decrypting, and @code{0} if not, or if @var{ctx} is not a valid +pointer. +@end deftypefun + + @node Key Listing Mode @subsection Key Listing Mode @cindex key listing mode @@ -4777,6 +4802,19 @@ This is a linked list of recipients to which this message was encrypted. @item char *file_name This is the filename of the original plaintext message file if it is known, otherwise this is a null pointer. + +@item char *session_key +A textual representation (null-terminated string) of the session key +used in symmetric encryption of the message, if the context has been +set to export session keys (see @code{gpgme_get_export_session_keys} +and @code{gpgme_set_export_session_keys}), and a session key was +available for the most recent decryption operation. Otherwise, this +is a null pointer. + +You should never access this member of a +@code{gpgme_op_decrypt_result_t} without first ensuring that +@code{gpgme_get_export_session_keys} returns non-zero for the +reporting context. @end table @end deftp diff --git a/doc/uiserver.texi b/doc/uiserver.texi index aae3b606..f10db01a 100644 --- a/doc/uiserver.texi +++ b/doc/uiserver.texi @@ -260,12 +260,14 @@ encoded. For details on the file descriptor, see the description of @noindent The decryption is started with the command: -@deffn Command DECRYPT -@w{}-protocol=@var{name} [-@w{}-no-verify] +@deffn Command DECRYPT -@w{}-protocol=@var{name} [-@w{}-no-verify] [-@w{}-export-session-key] @var{name} is the encryption protocol used for the message. For a description of the allowed protocols see the @code{ENCRYPT} command. -This argument is mandatory. If the option @option{--no-verify} is given, -the server should not try to verify a signature, in case the input data -is an OpenPGP combined message. +This argument is mandatory. If the option @option{--no-verify} is +given, the server should not try to verify a signature, in case the +input data is an OpenPGP combined message. If the option +@option{--export-session-key} is given and the underlying engine knows +how to export the session key, it will appear on a status line @end deffn diff --git a/src/context.h b/src/context.h index 00e2e779..94935c80 100644 --- a/src/context.h +++ b/src/context.h @@ -111,6 +111,9 @@ struct gpgme_context * unmodified string, as received form gpg, will be returned. */ unsigned int raw_description : 1; + /* True if session keys should be exported upon decryption. */ + unsigned int export_session_keys : 1; + /* Flags for keylist mode. */ gpgme_keylist_mode_t keylist_mode; diff --git a/src/decrypt-verify.c b/src/decrypt-verify.c index a334f86f..00d256a9 100644 --- a/src/decrypt-verify.c +++ b/src/decrypt-verify.c @@ -77,7 +77,7 @@ decrypt_verify_start (gpgme_ctx_t ctx, int synchronous, _gpgme_engine_set_status_handler (ctx->engine, decrypt_verify_status_handler, ctx); - return _gpgme_engine_op_decrypt_verify (ctx->engine, cipher, plain); + return _gpgme_engine_op_decrypt_verify (ctx->engine, cipher, plain, ctx->export_session_keys); } diff --git a/src/decrypt.c b/src/decrypt.c index 51e42920..49c735ca 100644 --- a/src/decrypt.c +++ b/src/decrypt.c @@ -63,6 +63,9 @@ release_op_data (void *hook) if (opd->result.file_name) free (opd->result.file_name); + if (opd->result.session_key) + free (opd->result.session_key); + while (recipient) { gpgme_recipient_t next = recipient->next; @@ -277,6 +280,12 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code, opd->last_recipient_p = &(*opd->last_recipient_p)->next; break; + case GPGME_STATUS_SESSION_KEY: + if (opd->result.session_key) + free (opd->result.session_key); + opd->result.session_key = strdup(args); + break; + case GPGME_STATUS_NO_SECKEY: { gpgme_recipient_t rec = opd->result.recipients; @@ -381,7 +390,7 @@ decrypt_start (gpgme_ctx_t ctx, int synchronous, _gpgme_engine_set_status_handler (ctx->engine, decrypt_status_handler, ctx); - return _gpgme_engine_op_decrypt (ctx->engine, cipher, plain); + return _gpgme_engine_op_decrypt (ctx->engine, cipher, plain, ctx->export_session_keys); } diff --git a/src/engine-backend.h b/src/engine-backend.h index a8b1ac60..144b1561 100644 --- a/src/engine-backend.h +++ b/src/engine-backend.h @@ -62,9 +62,9 @@ struct engine_ops gpgme_error_t (*set_locale) (void *engine, int category, const char *value); gpgme_error_t (*set_protocol) (void *engine, gpgme_protocol_t protocol); gpgme_error_t (*decrypt) (void *engine, gpgme_data_t ciph, - gpgme_data_t plain); + gpgme_data_t plain, int export_session_key); gpgme_error_t (*decrypt_verify) (void *engine, gpgme_data_t ciph, - gpgme_data_t plain); + gpgme_data_t plain, int export_session_key); gpgme_error_t (*delete) (void *engine, gpgme_key_t key, int allow_secret); gpgme_error_t (*edit) (void *engine, int type, gpgme_key_t key, gpgme_data_t out, gpgme_ctx_t ctx /* FIXME */); diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 7725a001..0e43c248 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -1550,13 +1550,16 @@ add_input_size_hint (engine_gpg_t gpg, gpgme_data_t data) static gpgme_error_t -gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain) +gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain, int export_session_key) { engine_gpg_t gpg = engine; gpgme_error_t err; err = add_arg (gpg, "--decrypt"); + if (!err && export_session_key) + err = add_arg (gpg, "--show-session-key"); + /* Tell the gpg object about the data. */ if (!err) err = add_arg (gpg, "--output"); diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index a815cf00..2ff353b9 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -1120,10 +1120,13 @@ gpgsm_reset (void *engine) static gpgme_error_t -gpgsm_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain) +gpgsm_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain, int export_session_key) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; + /* gpgsm is not capable of exporting session keys right now, so we + * will ignore this if requested. */ + (void)export_session_key; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c index 47b7dc33..26f0d18b 100644 --- a/src/engine-uiserver.c +++ b/src/engine-uiserver.c @@ -960,7 +960,8 @@ uiserver_reset (void *engine) static gpgme_error_t _uiserver_decrypt (void *engine, int verify, - gpgme_data_t ciph, gpgme_data_t plain) + gpgme_data_t ciph, gpgme_data_t plain, + int export_session_key) { engine_uiserver_t uiserver = engine; gpgme_error_t err; @@ -978,8 +979,9 @@ _uiserver_decrypt (void *engine, int verify, else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); - if (asprintf (&cmd, "DECRYPT%s%s", protocol, - verify ? "" : " --no-verify") < 0) + if (asprintf (&cmd, "DECRYPT%s%s%s", protocol, + verify ? "" : " --no-verify", + export_session_key ? " --export-session-key" : "") < 0) return gpg_error_from_syserror (); uiserver->input_cb.data = ciph; @@ -1006,16 +1008,16 @@ _uiserver_decrypt (void *engine, int verify, static gpgme_error_t -uiserver_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain) +uiserver_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain, int export_session_key) { - return _uiserver_decrypt (engine, 0, ciph, plain); + return _uiserver_decrypt (engine, 0, ciph, plain, export_session_key); } static gpgme_error_t -uiserver_decrypt_verify (void *engine, gpgme_data_t ciph, gpgme_data_t plain) +uiserver_decrypt_verify (void *engine, gpgme_data_t ciph, gpgme_data_t plain, int export_session_key) { - return _uiserver_decrypt (engine, 1, ciph, plain); + return _uiserver_decrypt (engine, 1, ciph, plain, export_session_key); } diff --git a/src/engine.c b/src/engine.c index 4e513b6d..b43f683e 100644 --- a/src/engine.c +++ b/src/engine.c @@ -653,7 +653,7 @@ _gpgme_engine_set_protocol (engine_t engine, gpgme_protocol_t protocol) gpgme_error_t _gpgme_engine_op_decrypt (engine_t engine, gpgme_data_t ciph, - gpgme_data_t plain) + gpgme_data_t plain, int export_session_key) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); @@ -661,13 +661,13 @@ _gpgme_engine_op_decrypt (engine_t engine, gpgme_data_t ciph, if (!engine->ops->decrypt) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); - return (*engine->ops->decrypt) (engine->engine, ciph, plain); + return (*engine->ops->decrypt) (engine->engine, ciph, plain, export_session_key); } gpgme_error_t _gpgme_engine_op_decrypt_verify (engine_t engine, gpgme_data_t ciph, - gpgme_data_t plain) + gpgme_data_t plain, int export_session_key) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); @@ -675,7 +675,7 @@ _gpgme_engine_op_decrypt_verify (engine_t engine, gpgme_data_t ciph, if (!engine->ops->decrypt_verify) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); - return (*engine->ops->decrypt_verify) (engine->engine, ciph, plain); + return (*engine->ops->decrypt_verify) (engine->engine, ciph, plain, export_session_key); } diff --git a/src/engine.h b/src/engine.h index 15b0b5d4..512ac19a 100644 --- a/src/engine.h +++ b/src/engine.h @@ -83,10 +83,12 @@ _gpgme_engine_set_colon_line_handler (engine_t engine, engine_colon_line_handler_t fnc, void *fnc_value); gpgme_error_t _gpgme_engine_op_decrypt (engine_t engine, gpgme_data_t ciph, - gpgme_data_t plain); + gpgme_data_t plain, + int export_session_key); gpgme_error_t _gpgme_engine_op_decrypt_verify (engine_t engine, gpgme_data_t ciph, - gpgme_data_t plain); + gpgme_data_t plain, + int export_session_key); gpgme_error_t _gpgme_engine_op_delete (engine_t engine, gpgme_key_t key, int allow_secret); gpgme_error_t _gpgme_engine_op_edit (engine_t engine, int type, diff --git a/src/gpgme.c b/src/gpgme.c index 443cb768..7b14b5e9 100644 --- a/src/gpgme.c +++ b/src/gpgme.c @@ -518,6 +518,30 @@ gpgme_get_armor (gpgme_ctx_t ctx) } +/* Enable or disable the exporting session keys upon decryption. */ +void +gpgme_set_export_session_keys (gpgme_ctx_t ctx, int export_session_keys) +{ + TRACE2 (DEBUG_CTX, "gpgme_set_export_session_keys", ctx, "export_session_keys=%i (%s)", + export_session_keys, export_session_keys ? "yes" : "no"); + + if (!ctx) + return; + + ctx->export_session_keys = !!export_session_keys; +} + + +/* Return whether this context will export session keys upon decryption. */ +int +gpgme_get_export_session_keys (gpgme_ctx_t ctx) +{ + TRACE2 (DEBUG_CTX, "gpgme_get_export_session_keys", ctx, "ctx->export_session_keys=%i (%s)", + ctx->export_session_keys, ctx->export_session_keys ? "yes" : "no"); + return ctx->export_session_keys; +} + + /* Enable or disable the use of the special textmode. Textmode is for example used for the RFC2015 signatures; note that the updated RFC 3156 mandates that the MUA does some preparations so that textmode diff --git a/src/gpgme.def b/src/gpgme.def index 2f6837da..35f43411 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -252,5 +252,7 @@ EXPORTS gpgme_op_query_swdb @189 gpgme_op_query_swdb_result @190 + gpgme_set_export_session_keys @191 + gpgme_get_export_session_keys @192 ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 4f470a03..2a0e16e3 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -1037,6 +1037,13 @@ void gpgme_set_offline (gpgme_ctx_t ctx, int yes); /* Return non-zero if offline mode is set in CTX. */ int gpgme_get_offline (gpgme_ctx_t ctx); +/* If YES is non-zero, try to return session keys during decryption, + do not otherwise. */ +void gpgme_set_export_session_keys (gpgme_ctx_t ctx, int yes); + +/* Return non-zero if export_session_keys is set in CTX. */ +int gpgme_get_export_session_keys (gpgme_ctx_t ctx); + /* Use whatever the default of the backend crypto engine is. */ #define GPGME_INCLUDE_CERTS_DEFAULT -256 @@ -1527,6 +1534,10 @@ struct _gpgme_op_decrypt_result /* The original file name of the plaintext message, if available. */ char *file_name; + + /* A textual representation of the session key used to decrypt the + * message, if available */ + char *session_key; }; typedef struct _gpgme_op_decrypt_result *gpgme_decrypt_result_t; diff --git a/src/libgpgme.vers b/src/libgpgme.vers index 5457daa4..9a3ecb2e 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -125,6 +125,9 @@ GPGME_1.1 { gpgme_op_query_swdb; gpgme_op_query_swdb_result; + + gpgme_set_export_session_keys; + gpgme_get_export_session_keys; };