Some message stores can be optimized by caching session keys of
encrypted messages, rather than incurring asymmetric crypto operations
on each subsequent decryption.
This patch allows gmime to perform extraction of session keys during
message decryption to support that use case.
See the discussion starting here for more detail on the API/design
choices:
https://mail.gnome.org/archives/gmime-devel-list/2016-July/msg00005.html
---
gmime/gmime-crypto-context.c | 41 ++++++++++++++++++++++++++++++++++++
gmime/gmime-crypto-context.h | 4 ++++
gmime/gmime-gpg-context.c | 50
+++++++++++++++++++++++++++++++++++++++++++-
gmime/gmime-gpg-context.h | 4 ++++
tests/test-pgp.c | 5 +++++
tests/test-pgpmime.c | 9 +++++++-
6 files changed, 111 insertions(+), 2 deletions(-)
diff --git a/gmime/gmime-crypto-context.c b/gmime/gmime-crypto-context.c
index 5269440..90fa390 100644
--- a/gmime/gmime-crypto-context.c
+++ b/gmime/gmime-crypto-context.c
@@ -573,6 +573,7 @@ g_mime_decrypt_result_init (GMimeDecryptResult
*result, GMimeDecryptResultClass
result->mdc = GMIME_DIGEST_ALGO_DEFAULT;
result->recipients = NULL;
result->signatures = NULL;
+ result->session_key = NULL;
}
static void
@@ -586,6 +587,8 @@ g_mime_decrypt_result_finalize (GObject *object)
if (result->signatures)
g_object_unref (result->signatures);
+ g_free (result->session_key);
+
G_OBJECT_CLASS (result_parent_class)->finalize (object);
}
@@ -755,3 +758,41 @@ g_mime_decryption_result_get_mdc
(GMimeDecryptResult *result)
return result->mdc;
}
+
+
+/**
+ * g_mime_decrypt_result_set_session_key:
+ * @result: a #GMimeDecryptResult
+ * @session_key: a pointer to a null-terminated string representing
the session key
+ *
+ * Set the session_key to be returned by this decryption result.
+ **/
+void
+g_mime_decrypt_result_set_session_key (GMimeDecryptResult *result,
const char *session_key)
+{
+ g_return_if_fail (GMIME_IS_DECRYPT_RESULT (result));
+
+ g_free (result->session_key);
+ result->session_key = g_strdup(session_key);
+}
+
+
+/**
+ * g_mime_decrypt_result_get_session_key:
+ * @result: a #GMimeDecryptResult
+ *
+ * Get the session_key used for this decryption, if the underlying
+ * crypto context is capable of and (configured to) retrieve session
+ * keys during decryption. See, for example,
+ * g_mime_gpg_context_set_retrieve_session_key().
+ *
+ * Returns: the session_key digest algorithm used, or NULL if no
+ * session key was requested or found.
+ **/
+const char*
+g_mime_decryption_result_get_session_key (GMimeDecryptResult *result)
+{
+ g_return_val_if_fail (GMIME_IS_DECRYPT_RESULT (result),
GMIME_DIGEST_ALGO_DEFAULT);
+
+ return result->session_key;
+}
diff --git a/gmime/gmime-crypto-context.h b/gmime/gmime-crypto-context.h
index cd38760..72573ed 100644
--- a/gmime/gmime-crypto-context.h
+++ b/gmime/gmime-crypto-context.h
@@ -206,6 +206,7 @@ struct _GMimeDecryptResult {
GMimeSignatureList *signatures;
GMimeCipherAlgo cipher;
GMimeDigestAlgo mdc;
+ char *session_key;
};
struct _GMimeDecryptResultClass {
@@ -229,6 +230,9 @@ GMimeCipherAlgo g_mime_decrypt_result_get_cipher
(GMimeDecryptResult *result);
void g_mime_decrypt_result_set_mdc (GMimeDecryptResult *result,
GMimeDigestAlgo mdc);
GMimeDigestAlgo g_mime_decrypt_result_get_mdc (GMimeDecryptResult
*result);
+void g_mime_decrypt_result_set_session_key (GMimeDecryptResult
*result, const char *session_key);
+const char *g_mime_decrypt_result_get_session_key
(GMimeDecryptResult *result);
+
G_END_DECLS
#endif /* __GMIME_CRYPTO_CONTEXT_H__ */
diff --git a/gmime/gmime-gpg-context.c b/gmime/gmime-gpg-context.c
index 2ca280a..6f7d374 100644
--- a/gmime/gmime-gpg-context.c
+++ b/gmime/gmime-gpg-context.c
@@ -172,6 +172,7 @@ g_mime_gpg_context_init (GMimeGpgContext *ctx,
GMimeGpgContextClass *klass)
ctx->always_trust = FALSE;
ctx->use_agent = FALSE;
ctx->path = NULL;
+ ctx->retrieve_session_key = FALSE;
}
static void
@@ -311,6 +312,7 @@ struct _GpgCtx {
GMimeStream *diagnostics;
GMimeCertificateList *encrypted_to; /* full list of
encrypted-to recipients */
+ char *session_key;
GMimeSignatureList *signatures;
GMimeSignature *signature;
@@ -375,6 +377,7 @@ gpg_ctx_new (GMimeGpgContext *ctx)
gpg->need_id = NULL;
gpg->encrypted_to = NULL;
+ gpg->session_key = NULL;
gpg->signatures = NULL;
gpg->signature = NULL;
@@ -555,6 +558,8 @@ gpg_ctx_free (struct _GpgCtx *gpg)
if (gpg->encrypted_to)
g_object_unref (gpg->encrypted_to);
+ g_free (gpg->session_key);
+
if (gpg->signatures)
g_object_unref (gpg->signatures);
@@ -691,6 +696,9 @@ gpg_ctx_get_argv (struct _GpgCtx *gpg, int
status_fd, int secret_fd, char ***str
if (gpg->use_agent)
g_ptr_array_add (args, "--use-agent");
+ if (gpg->ctx->retrieve_session_key)
+ g_ptr_array_add (args, "--show-session-key");
+
g_ptr_array_add (args, "--decrypt");
g_ptr_array_add (args, "--output");
g_ptr_array_add (args, "-");
@@ -1326,6 +1334,8 @@ gpg_ctx_parse_status (struct _GpgCtx *gpg,
GError **err)
/* nothing to do... we'll grab the MDC used in
DECRYPTION_INFO */
} else if (!strncmp (status, "BADMDC", 6)) {
/* nothing to do, this will only be sent after
DECRYPTION_FAILED */
+ } else if (gpg->ctx->retrieve_session_key && !strncmp
(status, "SESSION_KEY", 11)) {
+ status = next_token (status, &gpg->session_key);
} else {
gpg_ctx_parse_signer_info (gpg, status);
}
@@ -2059,10 +2069,12 @@ gpg_decrypt (GMimeCryptoContext *context,
GMimeStream *istream,
result = g_mime_decrypt_result_new ();
result->recipients = gpg->encrypted_to;
result->signatures = gpg->signatures;
+ result->session_key = gpg->session_key;
result->cipher = gpg->cipher;
result->mdc = gpg->digest;
gpg->encrypted_to = NULL;
gpg->signatures = NULL;
+ gpg->session_key = NULL;
gpg_ctx_free (gpg);
@@ -2307,7 +2319,7 @@ g_mime_gpg_context_get_use_agent
(GMimeGpgContext *ctx)
/**
* g_mime_gpg_context_set_use_agent:
* @ctx: a #GMimeGpgContext
- * @use_agent: always trust flag
+ * @use_agent: use agent flag
*
* Sets the @use_agent flag on the gpg context, which indicates that
* GnuPG should attempt to use gpg-agent for credentials.
@@ -2319,3 +2331,39 @@ g_mime_gpg_context_set_use_agent
(GMimeGpgContext *ctx, gboolean use_agent)
ctx->use_agent = use_agent;
}
+
+/**
+ * g_mime_gpg_context_get_retrieve_session_key:
+ * @ctx: a #GMimeGpgContext
+ *
+ * Gets the retrieve_session_key flag on the gpg context.
+ *
+ * Returns: the retrieve_session_key flag on the gpg context, which
+ * indicates that GnuPG should attempt to retrieve the session key for
+ * any encrypted message.
+ **/
+gboolean
+g_mime_gpg_context_get_retrieve_session_key (GMimeGpgContext *ctx)
+{
+ g_return_val_if_fail (GMIME_IS_GPG_CONTEXT (ctx), FALSE);
+
+ return ctx->retrieve_session_key;
+}
+
+
+/**
+ * g_mime_gpg_context_set_retrieve_session_key:
+ * @ctx: a #GMimeGpgContext
+ * @retrieve_session_key: retrieve session key flag
+ *
+ * Sets the @retrieve_session_key flag on the gpg context, which
+ * indicates that GnuPG should attempt to retrieve the session key for
+ * any encrypted message.
+ **/
+void
+g_mime_gpg_context_set_retrieve_session_key (GMimeGpgContext *ctx,
gboolean retrieve_session_key)
+{
+ g_return_if_fail (GMIME_IS_GPG_CONTEXT (ctx));
+
+ ctx->retrieve_session_key = retrieve_session_key;
+}
diff --git a/gmime/gmime-gpg-context.h b/gmime/gmime-gpg-context.h
index a088e90..4f17fe0 100644
--- a/gmime/gmime-gpg-context.h
+++ b/gmime/gmime-gpg-context.h
@@ -51,6 +51,7 @@ struct _GMimeGpgContext {
gboolean always_trust;
gboolean use_agent;
char *path;
+ gboolean retrieve_session_key;
};
struct _GMimeGpgContextClass {
@@ -73,6 +74,9 @@ void g_mime_gpg_context_set_always_trust
(GMimeGpgContext *ctx, gboolean always_
gboolean g_mime_gpg_context_get_use_agent (GMimeGpgContext *ctx);
void g_mime_gpg_context_set_use_agent (GMimeGpgContext *ctx,
gboolean use_agent);
+gboolean g_mime_gpg_context_get_retrieve_session_key
(GMimeGpgContext *ctx);
+void g_mime_gpg_context_set_retrieve_session_key (GMimeGpgContext
*ctx, gboolean retrieve_session_key);
+
G_END_DECLS
#endif /* __GMIME_GPG_CONTEXT_H__ */
diff --git a/tests/test-pgp.c b/tests/test-pgp.c
index 8fc755c..452b480 100644
--- a/tests/test-pgp.c
+++ b/tests/test-pgp.c
@@ -163,6 +163,11 @@ test_decrypt (GMimeCryptoContext *ctx, gboolean
sign, GMimeStream *cleartext, GM
ex = exception_new ("unexpected signature");
}
+ /* Did not ask for session_key -- it should not be present.
+ We test asking for session_key over in test-pgpmime.c */
+ if (ex == NULL && result->session_key)
+ ex = exception_new ("got session_key when not requested");
+
g_object_unref (result);
if (ex != NULL) {
diff --git a/tests/test-pgpmime.c b/tests/test-pgpmime.c
index 4cead27..0a80906 100644
--- a/tests/test-pgpmime.c
+++ b/tests/test-pgpmime.c
@@ -351,9 +351,15 @@ test_multipart_encrypted (GMimeCryptoContext
*ctx, gboolean sign)
throw (ex);
}
+ if (!result->session_key) {
+ ex = exception_new ("No session key returned!");
+ g_object_unref (cleartext);
+ throw (ex);
+ }
+
if (result->signatures)
v(print_verify_results (result->signatures));
-
+
if (sign) {
if (!result->signatures || get_sig_status
(result->signatures) != GMIME_SIGNATURE_STATUS_GOOD)
ex = exception_new ("signature status expected to be
GOOD");
@@ -436,6 +442,7 @@ int main (int argc, char *argv[])
ctx = g_mime_gpg_context_new (request_passwd, NULL);
g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) ctx,
TRUE);
+ g_mime_gpg_context_set_retrieve_session_key ((GMimeGpgContext *)
ctx, TRUE);
testsuite_check ("GMimeGpgContext::import");
try {