[GPGME PATCH] core: Enable extraction of session keys.

Daniel Kahn Gillmor dkg at fifthhorseman.net
Fri Nov 11 08:49:28 CET 2016


* 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 <dkg at fifthhorseman.net>
---
 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 78c173f..1f6e00b 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
 
 
+ at node Exporting Session Keys
+ at subsection Exporting Session Keys
+ at cindex context, exporting session keys
+ at cindex Exporting Session Keys
+ at cindex exporting session keys
+
+ at 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.
+ at end deftypefun
+
+ at 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.
+ at 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.
+
+ at 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
+ at code{gpgme_op_decrypt_result_t} without first ensuring that
+ at 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 16e8f85..94c378b 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:
 
- at deffn Command DECRYPT - at w{}-protocol=@var{name} [- at w{}-no-verify]
+ at deffn Command DECRYPT - at w{}-protocol=@var{name} [- at w{}-no-verify] [- at 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
+ at 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 00e2e77..94935c8 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 a334f86..00d256a 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 51e4292..49c735c 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 a8b1ac6..144b156 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 7725a00..0e43c24 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 a815cf0..2ff353b 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 47b7dc3..26f0d18 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 4e513b6..b43f683 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 15b0b5d..512ac19 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 443cb76..7b14b5e 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 2f6837d..35f4341 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 4f470a0..2a0e16e 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 5457daa..9a3ecb2 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;
 };
 
 
-- 
2.10.2




More information about the Gnupg-devel mailing list