aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisp/gnus/gnus-cloud.el137
1 files changed, 136 insertions, 1 deletions
diff --git a/lisp/gnus/gnus-cloud.el b/lisp/gnus/gnus-cloud.el
index f3acd9e4c53..3801db89cec 100644
--- a/lisp/gnus/gnus-cloud.el
+++ b/lisp/gnus/gnus-cloud.el
@@ -27,6 +27,7 @@
27(eval-when-compile (require 'cl)) 27(eval-when-compile (require 'cl))
28(require 'parse-time) 28(require 'parse-time)
29(require 'nnimap) 29(require 'nnimap)
30(require 'hex-util)
30 31
31(eval-when-compile (require 'epg)) ;; setf-method for `epg-context-armor' 32(eval-when-compile (require 'epg)) ;; setf-method for `epg-context-armor'
32(autoload 'epg-make-context "epg") 33(autoload 'epg-make-context "epg")
@@ -55,7 +56,8 @@
55 :type '(radio (const :tag "No encoding" nil) 56 :type '(radio (const :tag "No encoding" nil)
56 (const :tag "Base64" base64) 57 (const :tag "Base64" base64)
57 (const :tag "Base64+gzip" base64-gzip) 58 (const :tag "Base64+gzip" base64-gzip)
58 (const :tag "EPG" epg))) 59 (const :tag "EPG" epg)
60 (const :tag "GnuTLS AEAD cipher" 'gnutls-aead-user))
59 61
60(defcustom gnus-cloud-interactive t 62(defcustom gnus-cloud-interactive t
61 "Whether Gnus Cloud changes should be confirmed." 63 "Whether Gnus Cloud changes should be confirmed."
@@ -63,6 +65,7 @@
63 :type 'boolean) 65 :type 'boolean)
64 66
65(defvar gnus-cloud-group-name "Emacs-Cloud") 67(defvar gnus-cloud-group-name "Emacs-Cloud")
68(defvar gnus-cloud-AEAD-auth "gnus-cloud auth")
66(defvar gnus-cloud-covered-servers nil) 69(defvar gnus-cloud-covered-servers nil)
67 70
68(defvar gnus-cloud-version 1) 71(defvar gnus-cloud-version 1)
@@ -109,6 +112,23 @@ easy interactive way to set this from the Server buffer."
109 (gnus-cloud-encode-data) 112 (gnus-cloud-encode-data)
110 (buffer-string))) 113 (buffer-string)))
111 114
115;; TODO: replace with s-pad-right please
116(defun gnus-cloud-pad-right (len padding s)
117 "If S is shorter than LEN, pad it with PADDING on the right."
118 (declare (pure t) (side-effect-free t))
119 (let ((extra (max 0 (- len (length s)))))
120 (concat s
121 (make-string extra (string-to-char padding)))))
122
123(defun gnus-cloud-pad-buffer-to-multiple (b blocksize)
124 "Pad buffer B to BLOCKSIZE numeric size and return it."
125 (let ((e (if (zerop (buffer-size b))
126 blocksize
127 (* blocksize (ceiling (buffer-size b) blocksize)))))
128 (goto-char (point-max))
129 (insert (make-string (- e (buffer-size b)) 0)))
130 b)
131
112(defun gnus-cloud-encode-data () 132(defun gnus-cloud-encode-data ()
113 (cond 133 (cond
114 ((eq gnus-cloud-storage-method 'base64-gzip) 134 ((eq gnus-cloud-storage-method 'base64-gzip)
@@ -133,6 +153,53 @@ easy interactive way to set this from the Server buffer."
133 nil))) 153 nil)))
134 (delete-region (point-min) (point-max)) 154 (delete-region (point-min) (point-max))
135 (insert data)))) 155 (insert data))))
156 ((eq gnus-cloud-storage-method 'gnutls-aead-user)
157 ;; TODO: factor this out into an external library
158 (if (memq 'AEAD-ciphers (gnutls-available-p))
159 (let* ((input (current-buffer))
160 (auth gnus-cloud-AEAD-auth)
161 (ciphers (remove-if-not
162 (lambda (c) (plist-get (cdr c) :cipher-aead-capable))
163 (gnutls-ciphers)))
164 (cipher (completing-read "Select a GnuTLS AEAD cipher"
165 ciphers nil t))
166 (cipher (and cipher (assq (intern cipher) ciphers))))
167 (when cipher
168 (let* ((cname (car cipher))
169 (cdata (cdr cipher))
170 (keysize (plist-get cdata :cipher-keysize))
171 (ivsize (plist-get cdata :cipher-ivsize))
172 (iv (list 'iv-auto ivsize))
173 (blocksize (plist-get cdata :cipher-blocksize))
174 (passwd-prompt
175 (format "Enter encryption key (max %s): " keysize))
176 ;; TODO: add check function to read-passwd for min/max etc
177 (key (read-passwd passwd-prompt)))
178 (if (and key (<= (length key) keysize))
179 (let* ((key (gnus-cloud-pad-right keysize "\000" key))
180 (payload-length (buffer-size input))
181 (input (gnus-cloud-pad-buffer-to-multiple
182 input blocksize))
183 (output (gnutls-symmetric-encrypt
184 cdata key iv input auth))
185 (data (nth 0 output))
186 (actual-iv (encode-hex-string (nth 1 output)))
187 (ep (append cipher
188 (list
189 :payload-length payload-length
190 :data-length (length data)
191 :iv actual-iv))))
192 (delete-region (point-min) (point-max))
193 (insert data)
194 (let* ((encoded-length (base64-encode-region
195 (point-min) (point-max)))
196 (ep (append ep
197 (list :encoded-length encoded-length))))
198 (goto-char (point-min))
199 (insert (format "Gnus-Cloud-Encryption %S\n\n" ep))))
200 (error "Sorry, the encryption key was invalid"))
201 (clear-string key))))
202 (error "Sorry, the available GnuTLS ciphers do not include AEAD")))
136 203
137 ((null gnus-cloud-storage-method) 204 ((null gnus-cloud-storage-method)
138 (gnus-message 5 "Leaving cloud data plaintext")) 205 (gnus-message 5 "Leaving cloud data plaintext"))
@@ -157,6 +224,74 @@ easy interactive way to set this from the Server buffer."
157 (delete-region (point-min) (point-max)) 224 (delete-region (point-min) (point-max))
158 (insert data))) 225 (insert data)))
159 226
227 ((eq gnus-cloud-storage-method 'gnutls-aead-user)
228 ;; TODO: factor this out into an external library
229 (if (memq 'AEAD-ciphers (gnutls-available-p))
230 (progn
231 (goto-char (point-min))
232 (if (looking-at "Gnus-Cloud-Encryption \\(.+\\)")
233 (let* ((input (current-buffer))
234 (auth gnus-cloud-AEAD-auth)
235 (encryption-parameter-string (match-string 1))
236 (control (read encryption-parameter-string))
237 (cipher (assq (car control) (gnutls-ciphers)))
238 (cname (car cipher))
239 (cdata (cdr cipher))
240 (ep (cdr control))
241 (payload-length (plist-get ep :payload-length))
242 (decoded-length (plist-get ep :data-length))
243 (encoded-length (plist-get ep :encoded-length))
244 (proposed-iv (plist-get ep :iv))
245 (iv (and (stringp proposed-iv)
246 (decode-hex-string proposed-iv))))
247 (if (and cipher cname cdata ep iv
248 (integerp payload-length)
249 (integerp encoded-length)
250 (integerp decoded-length))
251 (let* ((cname (car cipher))
252 (cdata (cdr cipher))
253 (keysize (plist-get cdata :cipher-keysize))
254 (blocksize (plist-get cdata :cipher-blocksize))
255 (passwd-prompt
256 (format "Enter decryption key (max %s): " keysize))
257 ;; TODO: add check function to read-passwd for min/max etc
258 (key (read-passwd passwd-prompt)))
259 ;; Advance past the data header and delete it
260 (forward-line 2)
261 (delete-region (point-min) (point))
262 ;; Delete any trailing data in the buffer
263 (when (> (buffer-size) encoded-length)
264 (delete-region (+ (point-min) encoded-length) (point-max)))
265
266 (base64-decode-region (point-min) (point-max))
267 (unless (equal (buffer-size) decoded-length)
268 (error "Sorry, the encrypted data length %d != %d"
269 (buffer-size) decoded-length))
270
271 (if (and key (<= (length key) keysize))
272 (let* ((key (gnus-cloud-pad-right keysize "\000" key))
273 (input (gnus-cloud-pad-buffer-to-multiple
274 input blocksize))
275 ;; TODO: fix docs to note this returns a list
276 (aead-output (gnutls-symmetric-decrypt
277 cdata key iv input auth))
278 (data (nth 0 aead-output)))
279 ;; trim the data back to original length
280 (when (> (length data) payload-length)
281 (setq data (substring data 0 payload-length)))
282
283 (unless (equal (length data) payload-length)
284 (error "Sorry, the decrypted data length %d != %d"
285 (length data) payload-length))
286 (delete-region (point-min) (point-max))
287 (insert data))
288 (error "Sorry, the decryption key was invalid"))
289 (clear-string key))
290 (error "Sorry, invalid decryption parameters %s"
291 encryption-parameter-string)))
292 (error "Sorry, there was no valid Gnus-Cloud-Encryption header")))
293 (error "Sorry, the available GnuTLS ciphers do not include AEAD")))
294
160 ((null gnus-cloud-storage-method) 295 ((null gnus-cloud-storage-method)
161 (gnus-message 5 "Reading cloud data as plaintext")) 296 (gnus-message 5 "Reading cloud data as plaintext"))
162 297