[gmime] Added support for decypting multipart/encrypted messages using a session key



commit e80dd2594f546d0eeda0b53082d6ca34b738f9f3
Author: Jeffrey Stedfast <jestedfa microsoft com>
Date:   Mon Dec 5 10:05:05 2016 -0500

    Added support for decypting multipart/encrypted messages using a session key
    
    2016-12-05  Jeffrey Stedfast  <fejj gnome org>
    
        * gmime/gmime-multipart-encrypted.c
        (g_mime_multipart_encrypted_decrypt_session): New function to
        decrypt a multipart/encrypted using a session_key.
    
        * gmime/gmime-crypto-context.c (g_mime_crypto_context_decrypt_session): New
        function to decrypt a MIME part using a session_key.
    
        * gmime/gmime-gpg-context.c (gpg_ctx_get_argv): Added support for
        --override-session-key-id for the new decrypt_session() method.
        (gpg_ctx_op_start): Updated to create the secret_fd when passing a session_key
        as well.
        (gpg_ctx_write_session_key): New function to write the session_key to gpg.
        (gpg_decrypt_session): New function to decrypt a MIME part using a session_key.
    
        Thanks to Daniel Kahn Gillmor for this patch.

 ChangeLog                         |   18 ++++++++++
 gmime/gmime-crypto-context.c      |   67 ++++++++++++++++++++++++++++++++++++-
 gmime/gmime-crypto-context.h      |    8 ++++
 gmime/gmime-gpg-context.c         |   66 ++++++++++++++++++++++++++++++++++--
 gmime/gmime-multipart-encrypted.c |   39 +++++++++++++++++++++-
 gmime/gmime-multipart-encrypted.h |    6 +++
 6 files changed, 198 insertions(+), 6 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index d9ad9cc..a219032 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
 2016-12-05  Jeffrey Stedfast  <fejj gnome org>
 
+       * gmime/gmime-multipart-encrypted.c
+       (g_mime_multipart_encrypted_decrypt_session): New function to
+       decrypt a multipart/encrypted using a session_key.
+
+       * gmime/gmime-crypto-context.c (g_mime_crypto_context_decrypt_session): New
+       function to decrypt a MIME part using a session_key.
+
+       * gmime/gmime-gpg-context.c (gpg_ctx_get_argv): Added support for
+       --override-session-key-id for the new decrypt_session() method.
+       (gpg_ctx_op_start): Updated to create the secret_fd when passing a session_key
+       as well.
+       (gpg_ctx_write_session_key): New function to write the session_key to gpg.
+       (gpg_decrypt_session): New function to decrypt a MIME part using a session_key.
+
+       Thanks to Daniel Kahn Gillmor for this patch.
+
+2016-12-05  Jeffrey Stedfast  <fejj gnome org>
+
        * gmime/gmime-gpg-context.c (gpg_ctx_parse_status): Advance over the SESSION_KEY
        identifier before calling next_token() so that next_token() actually gets the
        sesstion key token that we want. Also fixed to free any existing session_key
diff --git a/gmime/gmime-crypto-context.c b/gmime/gmime-crypto-context.c
index 3b4171c..e610b7d 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:
@@ -443,7 +459,7 @@ crypto_decrypt (GMimeCryptoContext *ctx, GMimeStream *istream,
  * 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
+ * that recursively decrypting it will result in an endless loop, causing
  * a denial of service attack on your application.
  *
  * Returns: (transfer full): a #GMimeDecryptResult on success or %NULL
@@ -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 an 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 d8a4b2c..e39465c 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;
@@ -333,8 +338,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 *
@@ -364,6 +370,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;
@@ -625,7 +632,7 @@ gpg_ctx_get_argv (struct _GpgCtx *gpg, int status_fd, int secret_fd, char ***str
        (*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", secret_fd);
                g_ptr_array_add (args, buf);
        }
@@ -705,6 +712,11 @@ gpg_ctx_get_argv (struct _GpgCtx *gpg, int status_fd, int secret_fd, char ***str
                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", secret_fd);
+                       g_ptr_array_add (args, buf);
+               }
+               
                g_ptr_array_add (args, "--decrypt");
                g_ptr_array_add (args, "--output");
                g_ptr_array_add (args, "-");
@@ -744,10 +756,14 @@ gpg_ctx_op_start (struct _GpgCtx *gpg)
        char **argv, **strv = NULL;
        int flags;
        
-       for (i = 0; i < 10; i++)
+       maxfd = G_N_ELEMENTS (fds);
+       for (i = 0; i < maxfd; i++)
                fds[i] = -1;
        
-       maxfd = (gpg->need_passwd || gpg->sigstream) ? 10 : 8;
+       /* don't create the command-fd if we don't need it */
+       if (!(gpg->need_passwd || gpg->sigstream || gpg->override_session_key))
+               maxfd -=2;
+       
        for (i = 0; i < maxfd; i += 2) {
                if (pipe (fds + i) == -1)
                        goto exception;
@@ -1057,6 +1073,29 @@ gpg_ctx_parse_signer_info (struct _GpgCtx *gpg, char *status)
        }
 }
 
+/* write the session_key to the secret file descriptor and close
+   it.  Returns 0 on success. */
+static int
+gpg_ctx_write_session_key (struct _GpgCtx *gpg, const char *session_key)
+{
+       size_t len = strlen (session_key);
+       ssize_t w, nwritten = 0;
+       
+       do {
+               do {
+                       w = write (gpg->secret_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->secret_fd);
+       gpg->secret_fd = -1;
+       
+       return (w == -1);
+}
+
 static int
 gpg_ctx_parse_status (struct _GpgCtx *gpg, GError **err)
 {
@@ -2040,6 +2079,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;
@@ -2052,6 +2099,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,
@@ -2062,6 +2111,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__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]