[gmime-devel] [PATCH 3/5] Implement overriding session key
- From: Daniel Kahn Gillmor <dkg fifthhorseman net>
- To: Gmime Development <gmime-devel-list gnome org>
- Subject: [gmime-devel] [PATCH 3/5] Implement overriding session key
- Date: Mon, 5 Dec 2016 02:05:05 -0500
This allows the user to specify which session key to use during
decryption.
Note that we make use again of the multipurpose file descriptor.
The logic here is that you cannot simultaneously offer more than one
of:
* a user-supplied passphrase
* a session key
* a message signature stream
---
gmime/gmime-crypto-context.c | 65 +++++++++++++++++++++++++++++++++++++++
gmime/gmime-crypto-context.h | 8 +++++
gmime/gmime-gpg-context.c | 65 ++++++++++++++++++++++++++++++++++++---
gmime/gmime-multipart-encrypted.c | 39 ++++++++++++++++++++++-
gmime/gmime-multipart-encrypted.h | 6 ++++
5 files changed, 178 insertions(+), 5 deletions(-)
diff --git a/gmime/gmime-crypto-context.c b/gmime/gmime-crypto-context.c
index 3b4171c..d010a05 100644
--- a/gmime/gmime-crypto-context.c
+++ b/gmime/gmime-crypto-context.c
@@ -70,6 +70,10 @@ static int crypto_encrypt (GMimeCryptoContext *ctx, gboolean sign,
static GMimeDecryptResult *crypto_decrypt (GMimeCryptoContext *ctx, GMimeStream *istream,
GMimeStream *ostream, GError **err);
+static GMimeDecryptResult *crypto_decrypt_session (GMimeCryptoContext *ctx, const char *session_key,
+ GMimeStream *istream, GMimeStream *ostream,
+ GError **err);
+
static int crypto_import_keys (GMimeCryptoContext *ctx, GMimeStream *istream,
GError **err);
@@ -120,6 +124,7 @@ g_mime_crypto_context_class_init (GMimeCryptoContextClass *klass)
klass->verify = crypto_verify;
klass->encrypt = crypto_encrypt;
klass->decrypt = crypto_decrypt;
+ klass->decrypt_session = crypto_decrypt_session;
klass->import_keys = crypto_import_keys;
klass->export_keys = crypto_export_keys;
klass->get_signature_protocol = crypto_get_signature_protocol;
@@ -423,6 +428,17 @@ crypto_decrypt (GMimeCryptoContext *ctx, GMimeStream *istream,
return NULL;
}
+static GMimeDecryptResult *
+crypto_decrypt_session (GMimeCryptoContext *ctx, const char *session_key,
+ GMimeStream *istream, GMimeStream *ostream,
+ GError **err)
+{
+ g_set_error (err, GMIME_ERROR, GMIME_ERROR_NOT_SUPPORTED,
+ "Decryption with a session key is not supported by this crypto context");
+
+ return NULL;
+}
+
/**
* g_mime_crypto_context_decrypt:
@@ -460,6 +476,55 @@ g_mime_crypto_context_decrypt (GMimeCryptoContext *ctx, GMimeStream *istream,
return GMIME_CRYPTO_CONTEXT_GET_CLASS (ctx)->decrypt (ctx, istream, ostream, err);
}
+/**
+ * g_mime_crypto_context_decrypt_session:
+ * @ctx: a #GMimeCryptoContext
+ * @session_key: session key to use
+ * @istream: input/ciphertext stream
+ * @ostream: output/cleartext stream
+ * @err: a #GError
+ *
+ * Decrypts the ciphertext input stream using a specific session key
+ * and writes the resulting cleartext to the output stream. If
+ * @session_key is non-%NULL, but is not valid for the ciphertext, the
+ * decryption will fail even if other available secret key material
+ * may have been able to decrypt it. If @session_key is %NULL, this
+ * does the same thing as g_mime_crypto_context_decrypt().
+ *
+ * When non-%NULL, @session_key should be a %NULL-terminated string,
+ * such as the one returned by g_mime_decrypt_result_get_session_key()
+ * from a previous decryption.
+ *
+ * If the encrypted input stream was also signed, the returned
+ * #GMimeDecryptResult will have a non-%NULL list of signatures, each with a
+ * #GMimeSignatureStatus (among other details about each signature).
+ *
+ * On success, the returned #GMimeDecryptResult will contain a list of
+ * certificates, one for each recipient, that the original encrypted stream
+ * was encrypted to.
+ *
+ * Note: It *may* be possible to maliciously design an encrypted stream such
+ * that recursively decrypting it will result in ane endless loop, causing
+ * a denial of service attack on your application.
+ *
+ * Returns: (transfer full): a #GMimeDecryptResult on success or %NULL
+ * on error.
+ **/
+GMimeDecryptResult *
+g_mime_crypto_context_decrypt_session (GMimeCryptoContext *ctx, const char *session_key,
+ GMimeStream *istream, GMimeStream *ostream,
+ GError **err)
+{
+ g_return_val_if_fail (GMIME_IS_CRYPTO_CONTEXT (ctx), NULL);
+ g_return_val_if_fail (GMIME_IS_STREAM (istream), NULL);
+ g_return_val_if_fail (GMIME_IS_STREAM (ostream), NULL);
+
+ if (session_key == NULL)
+ return GMIME_CRYPTO_CONTEXT_GET_CLASS (ctx)->decrypt (ctx, istream, ostream, err);
+ else
+ return GMIME_CRYPTO_CONTEXT_GET_CLASS (ctx)->decrypt_session (ctx, session_key, istream,
ostream, err);
+}
+
static int
crypto_import_keys (GMimeCryptoContext *ctx, GMimeStream *istream, GError **err)
diff --git a/gmime/gmime-crypto-context.h b/gmime/gmime-crypto-context.h
index 72573ed..6f337c4 100644
--- a/gmime/gmime-crypto-context.h
+++ b/gmime/gmime-crypto-context.h
@@ -113,6 +113,10 @@ struct _GMimeCryptoContextClass {
int (* export_keys) (GMimeCryptoContext *ctx, GPtrArray *keys,
GMimeStream *ostream, GError **err);
+
+ GMimeDecryptResult * (* decrypt_session) (GMimeCryptoContext *ctx, const char *session_key,
+ GMimeStream *istream, GMimeStream *ostream,
+ GError **err);
};
@@ -149,6 +153,10 @@ int g_mime_crypto_context_encrypt (GMimeCryptoContext *ctx, gboolean sign,
GMimeDecryptResult *g_mime_crypto_context_decrypt (GMimeCryptoContext *ctx, GMimeStream *istream,
GMimeStream *ostream, GError **err);
+GMimeDecryptResult *g_mime_crypto_context_decrypt_session (GMimeCryptoContext *ctx, const char *session_key,
+ GMimeStream *istream, GMimeStream *ostream,
+ GError **err);
+
/* key/certificate routines */
int g_mime_crypto_context_import_keys (GMimeCryptoContext *ctx, GMimeStream *istream, GError **err);
diff --git a/gmime/gmime-gpg-context.c b/gmime/gmime-gpg-context.c
index da92ae3..aa487aa 100644
--- a/gmime/gmime-gpg-context.c
+++ b/gmime/gmime-gpg-context.c
@@ -107,6 +107,10 @@ static int gpg_encrypt (GMimeCryptoContext *ctx, gboolean sign, const char *user
static GMimeDecryptResult *gpg_decrypt (GMimeCryptoContext *ctx, GMimeStream *istream,
GMimeStream *ostream, GError **err);
+static GMimeDecryptResult *gpg_decrypt_session (GMimeCryptoContext *ctx, const char *session_key,
+ GMimeStream *istream, GMimeStream *ostream,
+ GError **err);
+
static int gpg_import_keys (GMimeCryptoContext *ctx, GMimeStream *istream,
GError **err);
@@ -158,6 +162,7 @@ g_mime_gpg_context_class_init (GMimeGpgContextClass *klass)
crypto_class->verify = gpg_verify;
crypto_class->encrypt = gpg_encrypt;
crypto_class->decrypt = gpg_decrypt;
+ crypto_class->decrypt_session = gpg_decrypt_session;
crypto_class->import_keys = gpg_import_keys;
crypto_class->export_keys = gpg_export_keys;
crypto_class->get_signature_protocol = gpg_get_signature_protocol;
@@ -332,8 +337,9 @@ struct _GpgCtx {
unsigned int need_passwd:1;
unsigned int bad_passwds:2;
unsigned int decrypt_okay:1;
+ unsigned int override_session_key:1;
- unsigned int padding:19;
+ unsigned int padding:18;
};
static struct _GpgCtx *
@@ -363,6 +369,7 @@ gpg_ctx_new (GMimeGpgContext *ctx)
gpg->always_trust = FALSE;
gpg->use_agent = FALSE;
gpg->armor = FALSE;
+ gpg->override_session_key = FALSE;
gpg->stdin_fd = -1;
gpg->stdout_fd = -1;
@@ -624,7 +631,7 @@ gpg_ctx_get_argv (struct _GpgCtx *gpg, int status_fd, int multipurpose_fd, char
(*strv)[v++] = buf = g_strdup_printf ("--status-fd=%d", status_fd);
g_ptr_array_add (args, buf);
- if (gpg->need_passwd) {
+ if (gpg->need_passwd && ! gpg->override_session_key) {
(*strv)[v++] = buf = g_strdup_printf ("--command-fd=%d", multipurpose_fd);
g_ptr_array_add (args, buf);
}
@@ -703,6 +710,11 @@ gpg_ctx_get_argv (struct _GpgCtx *gpg, int status_fd, int multipurpose_fd, char
if (gpg->ctx->retrieve_session_key)
g_ptr_array_add (args, "--show-session-key");
+
+ if (gpg->override_session_key) {
+ (*strv)[v++] = buf = g_strdup_printf ("--override-session-key-fd=%d",
multipurpose_fd);
+ g_ptr_array_add (args, buf);
+ }
g_ptr_array_add (args, "--decrypt");
g_ptr_array_add (args, "--output");
@@ -749,7 +761,10 @@ gpg_ctx_op_start (struct _GpgCtx *gpg)
* stdout
* stderr
* status-fd
- * multipurpose: use for at most one of: passphrase or signature stream
+ * multipurpose -- use for sending gpg at most one of:
+ - passphrase,
+ - signature stream, or
+ - session key
*/
for (i = 0; i < sizeof (fds)/sizeof (*fds); i++)
@@ -757,7 +772,7 @@ gpg_ctx_op_start (struct _GpgCtx *gpg)
maxfd = sizeof (fds)/sizeof (*fds);
/* don't create the multipurpose stream if we don't need it */
- if (!(gpg->need_passwd || gpg->sigstream))
+ if (!(gpg->need_passwd || gpg->sigstream || gpg->override_session_key))
maxfd -= 2;
for (i = 0; i < maxfd; i += 2) {
if (pipe (fds + i) == -1)
@@ -1068,6 +1083,29 @@ gpg_ctx_parse_signer_info (struct _GpgCtx *gpg, char *status)
}
}
+/* write the session_key to the multipurpose file descriptor and close
+ it. Returns 0 on success. */
+static int
+gpg_ctx_write_session_key (struct _GpgCtx *gpg, const char *session_key)
+{
+ ssize_t w, nwritten = 0;
+ size_t len = strlen(session_key);
+
+ do {
+ do {
+ w = write (gpg->multipurpose_fd, session_key + nwritten, len - nwritten);
+ } while (w == -1 && (errno == EINTR || errno == EAGAIN));
+
+ if (w > 0)
+ nwritten += w;
+ } while (nwritten < len && w != -1);
+
+ close (gpg->multipurpose_fd);
+ gpg->multipurpose_fd = -1;
+
+ return (w == -1);
+}
+
static int
gpg_ctx_parse_status (struct _GpgCtx *gpg, GError **err)
{
@@ -2047,6 +2085,14 @@ static GMimeDecryptResult *
gpg_decrypt (GMimeCryptoContext *context, GMimeStream *istream,
GMimeStream *ostream, GError **err)
{
+ return gpg_decrypt_session (context, NULL, istream, ostream, err);
+}
+
+static GMimeDecryptResult *
+gpg_decrypt_session (GMimeCryptoContext *context, const char *session_key,
+ GMimeStream *istream, GMimeStream *ostream,
+ GError **err)
+{
#ifdef ENABLE_CRYPTOGRAPHY
GMimeGpgContext *ctx = (GMimeGpgContext *) context;
GMimeDecryptResult *result;
@@ -2059,6 +2105,8 @@ gpg_decrypt (GMimeCryptoContext *context, GMimeStream *istream,
gpg_ctx_set_use_agent (gpg, ctx->use_agent);
gpg_ctx_set_istream (gpg, istream);
gpg_ctx_set_ostream (gpg, ostream);
+ if (session_key)
+ gpg->override_session_key = TRUE;
if (gpg_ctx_op_start (gpg) == -1) {
g_set_error (err, GMIME_ERROR, errno,
@@ -2069,6 +2117,15 @@ gpg_decrypt (GMimeCryptoContext *context, GMimeStream *istream,
return NULL;
}
+ if (session_key && gpg_ctx_write_session_key (gpg, session_key)) {
+ g_set_error (err, GMIME_ERROR, errno,
+ _("Failed to pass session key to gpg: %s"),
+ errno ? g_strerror (errno) : _("Unknown"));
+ gpg_ctx_free (gpg);
+
+ return NULL;
+ }
+
while (!gpg_ctx_op_complete (gpg)) {
if (gpg_ctx_op_step (gpg, err) == -1) {
gpg_ctx_op_cancel (gpg);
diff --git a/gmime/gmime-multipart-encrypted.c b/gmime/gmime-multipart-encrypted.c
index 35e7e1c..1cd07c3 100644
--- a/gmime/gmime-multipart-encrypted.c
+++ b/gmime/gmime-multipart-encrypted.c
@@ -292,6 +292,43 @@ GMimeObject *
g_mime_multipart_encrypted_decrypt (GMimeMultipartEncrypted *mpe, GMimeCryptoContext *ctx,
GMimeDecryptResult **result, GError **err)
{
+ return g_mime_multipart_encrypted_decrypt_session (mpe, ctx, NULL, result, err);
+}
+
+/**
+ * g_mime_multipart_encrypted_decrypt_session:
+ * @mpe: multipart/encrypted object
+ * @ctx: decryption context
+ * @session_key: session key to use
+ * @result: a #GMimeDecryptionResult
+ * @err: a #GError
+ *
+ * Attempts to decrypt the encrypted MIME part contained within the
+ * multipart/encrypted object @mpe using the @ctx decryption context
+ * trying only the supplied session key. If @session_key is
+ * non-%NULL, but is not valid for the ciphertext, the decryption will
+ * fail even if other available secret key material may have been able
+ * to decrypt it. If @session_key is %NULL, this does the same thing
+ * as g_mime_multipart_encrypted_decrypt().
+ *
+ * When non-%NULL, @session_key should be a %NULL-terminated string,
+ * such as the one returned by g_mime_decrypt_result_get_session_key()
+ * from a previous decryption.
+ *
+ * If @result is non-%NULL, then on a successful decrypt operation, it will be
+ * updated to point to a newly-allocated #GMimeDecryptResult with signature
+ * status information as well as a list of recipients that the part was
+ * encrypted to.
+ *
+ * Returns: (transfer full): the decrypted MIME part on success or
+ * %NULL on fail. If the decryption fails, an exception will be set on
+ * @err to provide information as to why the failure occured.
+ **/
+GMimeObject *
+g_mime_multipart_encrypted_decrypt_session (GMimeMultipartEncrypted *mpe, GMimeCryptoContext *ctx,
+ const char *session_key, GMimeDecryptResult **result,
+ GError **err)
+{
GMimeObject *decrypted, *version, *encrypted;
GMimeStream *stream, *ciphertext;
const char *protocol, *supported;
@@ -367,7 +404,7 @@ g_mime_multipart_encrypted_decrypt (GMimeMultipartEncrypted *mpe, GMimeCryptoCon
g_object_unref (crlf_filter);
/* get the cleartext */
- if (!(res = g_mime_crypto_context_decrypt (ctx, ciphertext, filtered_stream, err))) {
+ if (!(res = g_mime_crypto_context_decrypt_session (ctx, session_key, ciphertext, filtered_stream,
err))) {
g_object_unref (filtered_stream);
g_object_unref (ciphertext);
g_object_unref (stream);
diff --git a/gmime/gmime-multipart-encrypted.h b/gmime/gmime-multipart-encrypted.h
index 7f629e0..da154a2 100644
--- a/gmime/gmime-multipart-encrypted.h
+++ b/gmime/gmime-multipart-encrypted.h
@@ -76,6 +76,12 @@ GMimeObject *g_mime_multipart_encrypted_decrypt (GMimeMultipartEncrypted *mpe,
GMimeDecryptResult **result,
GError **err);
+GMimeObject *g_mime_multipart_encrypted_decrypt_session (GMimeMultipartEncrypted *mpe,
+ GMimeCryptoContext *ctx,
+ const char *session_key,
+ GMimeDecryptResult **result,
+ GError **err);
+
G_END_DECLS
#endif /* __GMIME_MULTIPART_ENCRYPTED_H__ */
--
2.10.2
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]