crypto: actually stash session keys when decrypt=true
[notmuch] / util / crypto.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2012 Jameson Rollins
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see https://www.gnu.org/licenses/ .
17  *
18  * Authors: Jameson Rollins <jrollins@finestructure.net>
19  */
20
21 #include "crypto.h"
22 #include <strings.h>
23 #define unused(x) x __attribute__ ((unused))
24
25 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
26
27 #if (GMIME_MAJOR_VERSION < 3)
28 /* Create or pass on a GPG context (GMime 2.6) */
29 static notmuch_status_t
30 get_gpg_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
31 {
32     if (ctx == NULL || crypto == NULL)
33         return NOTMUCH_STATUS_NULL_POINTER;
34
35     if (crypto->gpgctx) {
36         *ctx = crypto->gpgctx;
37         return NOTMUCH_STATUS_SUCCESS;
38     }
39
40     /* TODO: GMimePasswordRequestFunc */
41     crypto->gpgctx = g_mime_gpg_context_new (NULL, crypto->gpgpath ? crypto->gpgpath : "gpg");
42     if (! crypto->gpgctx) {
43         return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
44     }
45
46     g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) crypto->gpgctx, true);
47     g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) crypto->gpgctx, false);
48
49     *ctx = crypto->gpgctx;
50     return NOTMUCH_STATUS_SUCCESS;
51 }
52
53 /* Create or pass on a PKCS7 context (GMime 2.6) */
54 static notmuch_status_t
55 get_pkcs7_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
56 {
57     if (ctx == NULL || crypto == NULL)
58         return NOTMUCH_STATUS_NULL_POINTER;
59
60     if (crypto->pkcs7ctx) {
61         *ctx = crypto->pkcs7ctx;
62         return NOTMUCH_STATUS_SUCCESS;
63     }
64
65     /* TODO: GMimePasswordRequestFunc */
66     crypto->pkcs7ctx = g_mime_pkcs7_context_new (NULL);
67     if (! crypto->pkcs7ctx) {
68         return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
69     }
70
71     g_mime_pkcs7_context_set_always_trust ((GMimePkcs7Context *) crypto->pkcs7ctx,
72                                            false);
73
74     *ctx = crypto->pkcs7ctx;
75     return NOTMUCH_STATUS_SUCCESS;
76 }
77 static const struct {
78     const char *protocol;
79     notmuch_status_t (*get_context) (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx);
80 } protocols[] = {
81     {
82         .protocol = "application/pgp-signature",
83         .get_context = get_gpg_context,
84     },
85     {
86         .protocol = "application/pgp-encrypted",
87         .get_context = get_gpg_context,
88     },
89     {
90         .protocol = "application/pkcs7-signature",
91         .get_context = get_pkcs7_context,
92     },
93     {
94         .protocol = "application/x-pkcs7-signature",
95         .get_context = get_pkcs7_context,
96     },
97 };
98
99 /* for the specified protocol return the context pointer (initializing
100  * if needed) */
101 notmuch_status_t
102 _notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
103                                             const char *protocol,
104                                             GMimeCryptoContext **ctx)
105 {
106     if (! protocol)
107         return NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL;
108
109     /* As per RFC 1847 section 2.1: "the [protocol] value token is
110      * comprised of the type and sub-type tokens of the Content-Type".
111      * As per RFC 1521 section 2: "Content-Type values, subtypes, and
112      * parameter names as defined in this document are
113      * case-insensitive."  Thus, we use strcasecmp for the protocol.
114      */
115     for (size_t i = 0; i < ARRAY_SIZE (protocols); i++) {
116         if (strcasecmp (protocol, protocols[i].protocol) == 0)
117             return protocols[i].get_context (crypto, ctx);
118     }
119
120     return NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL;
121 }
122
123 void
124 _notmuch_crypto_cleanup (_notmuch_crypto_t *crypto)
125 {
126     if (crypto->gpgctx) {
127         g_object_unref (crypto->gpgctx);
128         crypto->gpgctx = NULL;
129     }
130
131     if (crypto->pkcs7ctx) {
132         g_object_unref (crypto->pkcs7ctx);
133         crypto->pkcs7ctx = NULL;
134     }
135 }
136 #else
137 void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
138 {
139 }
140 #endif
141
142 GMimeObject *
143 _notmuch_crypto_decrypt (bool *attempted,
144                          notmuch_decryption_policy_t decrypt,
145                          notmuch_message_t *message,
146                          g_mime_3_unused(GMimeCryptoContext* crypto_ctx),
147                          GMimeMultipartEncrypted *part,
148                          GMimeDecryptResult **decrypt_result,
149                          GError **err)
150 {
151     GMimeObject *ret = NULL;
152     if (decrypt == NOTMUCH_DECRYPT_FALSE)
153         return NULL;
154
155     /* the versions of notmuch that can support session key decryption */
156 #if HAVE_GMIME_SESSION_KEYS
157     if (message) {
158         notmuch_message_properties_t *list = NULL;
159
160         for (list = notmuch_message_get_properties (message, "session-key", TRUE);
161              notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
162             if (err && *err) {
163                 g_error_free (*err);
164                 *err = NULL;
165             }
166             if (attempted)
167                 *attempted = true;
168 #if (GMIME_MAJOR_VERSION < 3)
169             ret = g_mime_multipart_encrypted_decrypt_session (part,
170                                                               crypto_ctx,
171                                                               notmuch_message_properties_value (list),
172                                                               decrypt_result, err);
173 #else
174             ret = g_mime_multipart_encrypted_decrypt (part,
175                                                       GMIME_DECRYPT_NONE,
176                                                       notmuch_message_properties_value (list),
177                                                       decrypt_result, err);
178 #endif
179             if (ret)
180                 break;
181         }
182         if (list)
183             notmuch_message_properties_destroy (list);
184         if (ret)
185             return ret;
186     }
187 #endif
188
189     if (err && *err) {
190         g_error_free (*err);
191         *err = NULL;
192     }
193
194     if (decrypt == NOTMUCH_DECRYPT_AUTO)
195         return ret;
196
197     if (attempted)
198         *attempted = true;
199 #if (GMIME_MAJOR_VERSION < 3)
200 #if HAVE_GMIME_SESSION_KEYS
201     gboolean oldgetsk = g_mime_crypto_context_get_retrieve_session_key (crypto_ctx);
202     gboolean newgetsk = (decrypt_result);
203     if (newgetsk != oldgetsk)
204         /* This could return an error, but we can't do anything about it, so ignore it */
205         g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, newgetsk, NULL);
206 #endif
207     ret = g_mime_multipart_encrypted_decrypt(part, crypto_ctx,
208                                              decrypt_result, err);
209 #if HAVE_GMIME_SESSION_KEYS
210     if (newgetsk != oldgetsk)
211         g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, oldgetsk, NULL);
212 #endif
213 #else
214     GMimeDecryptFlags flags = GMIME_DECRYPT_NONE;
215     if (decrypt_result)
216         flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY;
217     ret = g_mime_multipart_encrypted_decrypt(part, flags, NULL,
218                                              decrypt_result, err);
219 #endif
220     return ret;
221 }