diff options
| author | Ted Zlatanov | 2017-12-13 23:58:40 -0500 |
|---|---|---|
| committer | Ted Zlatanov | 2017-12-13 23:58:40 -0500 |
| commit | adebcb647abd82564f0e245974f74f05c9b4cd2e (patch) | |
| tree | 183b14151631fd769b9ac79c4a1d6646f62024bd | |
| parent | 57e2ca5c504fda014ba1971e850a26ef001a7bfd (diff) | |
| download | emacs-scratch/tzz/gnus-cloud-aead.tar.gz emacs-scratch/tzz/gnus-cloud-aead.zip | |
WIP: gnus-cloud: add native AEAD encryptionscratch/tzz/gnus-cloud-aead
| -rw-r--r-- | lisp/gnus/gnus-cloud.el | 137 |
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 | ||