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 <dkg@fifthhorseman.net>

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
This commit is contained in:
Daniel Kahn Gillmor 2016-11-11 16:49:28 +09:00 committed by Werner Koch
parent 9451faa2ee
commit cad1210fb8
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
15 changed files with 125 additions and 23 deletions

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 */);

View File

@ -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");

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;
};