Merge from emacs--devo--0
[gnus] / lisp / mml-smime.el
1 ;;; mml-smime.el --- S/MIME support for MML
2
3 ;; Copyright (C) 2000, 2001, 2002, 2003, 2004,
4 ;;   2005, 2006, 2007 Free Software Foundation, Inc.
5
6 ;; Author: Simon Josefsson <simon@josefsson.org>
7 ;; Keywords: Gnus, MIME, S/MIME, MML
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published
13 ;; by the Free Software Foundation; either version 3, or (at your
14 ;; option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 ;; General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 ;; For Emacs < 22.2.
31 (eval-and-compile
32   (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
33
34 (eval-when-compile (require 'cl))
35
36 (require 'smime)
37 (require 'mm-decode)
38 (require 'mml-sec)
39 (autoload 'message-narrow-to-headers "message")
40 (autoload 'message-fetch-field "message")
41
42 (defvar mml-smime-use 'openssl)
43
44 (defvar mml-smime-function-alist
45   '((openssl mml-smime-openssl-sign
46              mml-smime-openssl-encrypt
47              mml-smime-openssl-sign-query
48              mml-smime-openssl-encrypt-query
49              mml-smime-openssl-verify
50              mml-smime-openssl-verify-test)
51     (epg mml-smime-epg-sign
52          mml-smime-epg-encrypt
53          nil
54          nil
55          mml-smime-epg-verify
56          mml-smime-epg-verify-test)))
57
58 (defcustom mml-smime-verbose mml-secure-verbose
59   "If non-nil, ask the user about the current operation more verbosely."
60   :group 'mime-security
61   :type 'boolean)
62
63 (defcustom mml-smime-cache-passphrase mml-secure-cache-passphrase
64   "If t, cache passphrase."
65   :group 'mime-security
66   :type 'boolean)
67
68 (defcustom mml-smime-passphrase-cache-expiry mml-secure-passphrase-cache-expiry
69   "How many seconds the passphrase is cached.
70 Whether the passphrase is cached at all is controlled by
71 `mml-smime-cache-passphrase'."
72   :group 'mime-security
73   :type 'integer)
74
75 (defcustom mml-smime-signers nil
76   "A list of your own key ID which will be used to sign a message."
77   :group 'mime-security
78   :type '(repeat (string :tag "Key ID")))
79
80 (defun mml-smime-sign (cont)
81   (let ((func (nth 1 (assq mml-smime-use mml-smime-function-alist))))
82     (if func
83         (funcall func cont)
84       (error "Cannot find sign function"))))
85
86 (defun mml-smime-encrypt (cont)
87   (let ((func (nth 2 (assq mml-smime-use mml-smime-function-alist))))
88     (if func
89         (funcall func cont)
90       (error "Cannot find encrypt function"))))
91
92 (defun mml-smime-sign-query ()
93   (let ((func (nth 3 (assq mml-smime-use mml-smime-function-alist))))
94     (if func
95         (funcall func))))
96
97 (defun mml-smime-encrypt-query ()
98   (let ((func (nth 4 (assq mml-smime-use mml-smime-function-alist))))
99     (if func
100         (funcall func))))
101
102 (defun mml-smime-verify (handle ctl)
103   (let ((func (nth 5 (assq mml-smime-use mml-smime-function-alist))))
104     (if func
105         (funcall func handle ctl)
106       handle)))
107
108 (defun mml-smime-verify-test (handle ctl)
109   (let ((func (nth 6 (assq mml-smime-use mml-smime-function-alist))))
110     (if func
111         (funcall func handle ctl))))
112
113 (defun mml-smime-openssl-sign (cont)
114   (when (null smime-keys)
115     (customize-variable 'smime-keys)
116     (error "No S/MIME keys configured, use customize to add your key"))
117   (smime-sign-buffer (cdr (assq 'keyfile cont)))
118   (goto-char (point-min))
119   (while (search-forward "\r\n" nil t)
120     (replace-match "\n" t t))
121   (goto-char (point-max)))
122
123 (defun mml-smime-openssl-encrypt (cont)
124   (let (certnames certfiles tmp file tmpfiles)
125     ;; xxx tmp files are always an security issue
126     (while (setq tmp (pop cont))
127       (if (and (consp tmp) (eq (car tmp) 'certfile))
128           (push (cdr tmp) certnames)))
129     (while (setq tmp (pop certnames))
130       (if (not (and (not (file-exists-p tmp))
131                     (get-buffer tmp)))
132           (push tmp certfiles)
133         (setq file (mm-make-temp-file (expand-file-name "mml."
134                                                         mm-tmp-directory)))
135         (with-current-buffer tmp
136           (write-region (point-min) (point-max) file))
137         (push file certfiles)
138         (push file tmpfiles)))
139     (if (smime-encrypt-buffer certfiles)
140         (progn
141           (while (setq tmp (pop tmpfiles))
142             (delete-file tmp))
143           t)
144       (while (setq tmp (pop tmpfiles))
145         (delete-file tmp))
146       nil))
147   (goto-char (point-max)))
148
149 (defvar gnus-extract-address-components)
150
151 (defun mml-smime-openssl-sign-query ()
152   ;; query information (what certificate) from user when MML tag is
153   ;; added, for use later by the signing process
154   (when (null smime-keys)
155     (customize-variable 'smime-keys)
156     (error "No S/MIME keys configured, use customize to add your key"))
157   (list 'keyfile
158         (if (= (length smime-keys) 1)
159             (cadar smime-keys)
160           (or (let ((from (cadr (funcall (if (boundp
161                                               'gnus-extract-address-components)
162                                              gnus-extract-address-components
163                                            'mail-extract-address-components)
164                                          (or (save-excursion
165                                                (save-restriction
166                                                  (message-narrow-to-headers)
167                                                  (message-fetch-field "from")))
168                                              "")))))
169                 (and from (smime-get-key-by-email from)))
170               (smime-get-key-by-email
171                (completing-read "Sign this part with what signature? "
172                                 smime-keys nil nil
173                                 (and (listp (car-safe smime-keys))
174                                      (caar smime-keys))))))))
175
176 (defun mml-smime-get-file-cert ()
177   (ignore-errors
178     (list 'certfile (read-file-name
179                      "File with recipient's S/MIME certificate: "
180                      smime-certificate-directory nil t ""))))
181
182 (defun mml-smime-get-dns-cert ()
183   ;; todo: deal with comma separated multiple recipients
184   (let (result who bad cert)
185     (condition-case ()
186         (while (not result)
187           (setq who (read-from-minibuffer
188                      (format "%sLookup certificate for: " (or bad ""))
189                      (cadr (funcall (if (boundp
190                                          'gnus-extract-address-components)
191                                         gnus-extract-address-components
192                                       'mail-extract-address-components)
193                                     (or (save-excursion
194                                           (save-restriction
195                                             (message-narrow-to-headers)
196                                             (message-fetch-field "to")))
197                                         "")))))
198           (if (setq cert (smime-cert-by-dns who))
199               (setq result (list 'certfile (buffer-name cert)))
200             (setq bad (format "`%s' not found. " who))))
201       (quit))
202     result))
203
204 (defun mml-smime-get-ldap-cert ()
205   ;; todo: deal with comma separated multiple recipients
206   (let (result who bad cert)
207     (condition-case ()
208         (while (not result)
209           (setq who (read-from-minibuffer
210                      (format "%sLookup certificate for: " (or bad ""))
211                      (cadr (funcall gnus-extract-address-components
212                                     (or (save-excursion
213                                           (save-restriction
214                                             (message-narrow-to-headers)
215                                             (message-fetch-field "to")))
216                                         "")))))
217           (if (setq cert (smime-cert-by-ldap who))
218               (setq result (list 'certfile (buffer-name cert)))
219             (setq bad (format "`%s' not found. " who))))
220       (quit))
221     result))
222
223 (autoload 'gnus-completing-read-with-default "gnus-util")
224
225 (defun mml-smime-openssl-encrypt-query ()
226   ;; todo: try dns/ldap automatically first, before prompting user
227   (let (certs done)
228     (while (not done)
229       (ecase (read (gnus-completing-read-with-default
230                     "ldap" "Fetch certificate from"
231                     '(("dns") ("ldap") ("file")) nil t))
232         (dns (setq certs (append certs
233                                  (mml-smime-get-dns-cert))))
234         (ldap (setq certs (append certs
235                                   (mml-smime-get-ldap-cert))))
236         (file (setq certs (append certs
237                                   (mml-smime-get-file-cert)))))
238       (setq done (not (y-or-n-p "Add more recipients? "))))
239     certs))
240
241 (defun mml-smime-openssl-verify (handle ctl)
242   (with-temp-buffer
243     (insert-buffer-substring (mm-handle-multipart-original-buffer ctl))
244     (goto-char (point-min))
245     (insert (format "Content-Type: %s; " (mm-handle-media-type ctl)))
246     (insert (format "protocol=\"%s\"; "
247                     (mm-handle-multipart-ctl-parameter ctl 'protocol)))
248     (insert (format "micalg=\"%s\"; "
249                     (mm-handle-multipart-ctl-parameter ctl 'micalg)))
250     (insert (format "boundary=\"%s\"\n\n"
251                     (mm-handle-multipart-ctl-parameter ctl 'boundary)))
252     (when (get-buffer smime-details-buffer)
253       (kill-buffer smime-details-buffer))
254     (let ((buf (current-buffer))
255           (good-signature (smime-noverify-buffer))
256           (good-certificate (and (or smime-CA-file smime-CA-directory)
257                                  (smime-verify-buffer)))
258           addresses openssl-output)
259       (setq openssl-output (with-current-buffer smime-details-buffer
260                              (buffer-string)))
261       (if (not good-signature)
262           (progn
263             ;; we couldn't verify message, fail with openssl output as message
264             (mm-set-handle-multipart-parameter
265              mm-security-handle 'gnus-info "Failed")
266             (mm-set-handle-multipart-parameter
267              mm-security-handle 'gnus-details
268              (concat "OpenSSL failed to verify message integrity:\n"
269                      "-------------------------------------------\n"
270                      openssl-output)))
271         ;; verify mail addresses in mail against those in certificate
272         (when (and (smime-pkcs7-region (point-min) (point-max))
273                    (smime-pkcs7-certificates-region (point-min) (point-max)))
274           (with-temp-buffer
275             (insert-buffer-substring buf)
276             (goto-char (point-min))
277             (while (re-search-forward "-----END CERTIFICATE-----" nil t)
278               (when (smime-pkcs7-email-region (point-min) (point))
279                 (setq addresses (append (smime-buffer-as-string-region
280                                          (point-min) (point)) addresses)))
281               (delete-region (point-min) (point)))
282             (setq addresses (mapcar 'downcase addresses))))
283         (if (not (member (downcase (or (mm-handle-multipart-from ctl) "")) addresses))
284             (mm-set-handle-multipart-parameter
285              mm-security-handle 'gnus-info "Sender address forged")
286           (if good-certificate
287               (mm-set-handle-multipart-parameter
288                mm-security-handle 'gnus-info "Ok (sender authenticated)")
289             (mm-set-handle-multipart-parameter
290              mm-security-handle 'gnus-info "Ok (sender not trusted)")))
291         (mm-set-handle-multipart-parameter
292          mm-security-handle 'gnus-details
293          (concat "Sender claimed to be: " (mm-handle-multipart-from ctl) "\n"
294                  (if addresses
295                      (concat "Addresses in certificate: "
296                              (mapconcat 'identity addresses ", "))
297                    "No addresses found in certificate. (Requires OpenSSL 0.9.6 or later.)")
298                  "\n" "\n"
299                  "OpenSSL output:\n"
300                  "---------------\n" openssl-output "\n"
301                  "Certificate(s) inside S/MIME signature:\n"
302                  "---------------------------------------\n"
303                  (buffer-string) "\n")))))
304   handle)
305
306 (defun mml-smime-openssl-verify-test (handle ctl)
307   smime-openssl-program)
308
309 (defvar epg-user-id-alist)
310 (defvar epg-digest-algorithm-alist)
311 (defvar inhibit-redisplay)
312 (defvar password-cache-expiry)
313
314 (eval-when-compile
315   (autoload 'epg-make-context "epg")
316   (autoload 'epg-context-set-armor "epg")
317   (autoload 'epg-context-set-signers "epg")
318   (autoload 'epg-context-result-for "epg")
319   (autoload 'epg-new-signature-digest-algorithm "epg")
320   (autoload 'epg-verify-result-to-string "epg")
321   (autoload 'epg-list-keys "epg")
322   (autoload 'epg-decrypt-string "epg")
323   (autoload 'epg-verify-string "epg")
324   (autoload 'epg-sign-string "epg")
325   (autoload 'epg-encrypt-string "epg")
326   (autoload 'epg-passphrase-callback-function "epg")
327   (autoload 'epg-context-set-passphrase-callback "epg")
328   (autoload 'epg-configuration "epg-config")
329   (autoload 'epg-expand-group "epg-config")
330   (autoload 'epa-select-keys "epa"))
331
332 (defvar mml-smime-epg-secret-key-id-list nil)
333
334 (defun mml-smime-epg-passphrase-callback (context key-id ignore)
335   (if (eq key-id 'SYM)
336       (epg-passphrase-callback-function context key-id nil)
337     (let* (entry
338            (passphrase
339             (password-read
340              (if (eq key-id 'PIN)
341                  "Passphrase for PIN: "
342                (if (setq entry (assoc key-id epg-user-id-alist))
343                    (format "Passphrase for %s %s: " key-id (cdr entry))
344                  (format "Passphrase for %s: " key-id)))
345              (if (eq key-id 'PIN)
346                  "PIN"
347                key-id))))
348       (when passphrase
349         (let ((password-cache-expiry mml-smime-passphrase-cache-expiry))
350           (password-cache-add key-id passphrase))
351         (setq mml-smime-epg-secret-key-id-list
352               (cons key-id mml-smime-epg-secret-key-id-list))
353         (copy-sequence passphrase)))))
354
355 (declare-function epg-key-sub-key-list   "ext:epg" (key))
356 (declare-function epg-sub-key-capability "ext:epg" (sub-key))
357 (declare-function epg-sub-key-validity   "ext:epg" (sub-key))
358
359 (defun mml-smime-epg-find-usable-key (keys usage)
360   (catch 'found
361     (while keys
362       (let ((pointer (epg-key-sub-key-list (car keys))))
363         (while pointer
364           (if (and (memq usage (epg-sub-key-capability (car pointer)))
365                    (not (memq (epg-sub-key-validity (car pointer))
366                               '(revoked expired))))
367               (throw 'found (car keys)))
368           (setq pointer (cdr pointer))))
369       (setq keys (cdr keys)))))
370
371 (autoload 'mml-compute-boundary "mml")
372
373 ;; We require mm-decode, which requires mm-bodies, which autoloads
374 ;; message-options-get (!).
375 (declare-function message-options-set "message" (symbol value))
376
377 (defun mml-smime-epg-sign (cont)
378   (let* ((inhibit-redisplay t)
379          (context (epg-make-context 'CMS))
380          (boundary (mml-compute-boundary cont))
381          signer-key
382          (signers
383           (or (message-options-get 'mml-smime-epg-signers)
384               (message-options-set
385               'mml-smime-epg-signers
386               (if mml-smime-verbose
387                   (epa-select-keys context "\
388 Select keys for signing.
389 If no one is selected, default secret key is used.  "
390                                    mml-smime-signers t)
391                 (if mml-smime-signers
392                     (mapcar
393                      (lambda (signer)
394                        (setq signer-key (mml-smime-epg-find-usable-key
395                                          (epg-list-keys context signer t)
396                                          'sign))
397                        (unless (or signer-key
398                                    (y-or-n-p
399                                     (format "No secret key for %s; skip it? "
400                                             signer)))
401                          (error "No secret key for %s" signer))
402                        signer-key)
403                      mml-smime-signers))))))
404          signature micalg)
405     (epg-context-set-signers context signers)
406     (if mml-smime-cache-passphrase
407         (epg-context-set-passphrase-callback
408          context
409          #'mml-smime-epg-passphrase-callback))
410     (condition-case error
411         (setq signature (epg-sign-string context
412                                          (mm-replace-in-string (buffer-string)
413                                                                "\n" "\r\n")
414                                          t)
415               mml-smime-epg-secret-key-id-list nil)
416       (error
417        (while mml-smime-epg-secret-key-id-list
418          (password-cache-remove (car mml-smime-epg-secret-key-id-list))
419          (setq mml-smime-epg-secret-key-id-list
420                (cdr mml-smime-epg-secret-key-id-list)))
421        (signal (car error) (cdr error))))
422     (if (epg-context-result-for context 'sign)
423         (setq micalg (epg-new-signature-digest-algorithm
424                       (car (epg-context-result-for context 'sign)))))
425     (goto-char (point-min))
426     (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
427                     boundary))
428     (if micalg
429         (insert (format "\tmicalg=%s; "
430                         (downcase
431                          (cdr (assq micalg
432                                     epg-digest-algorithm-alist))))))
433     (insert "protocol=\"application/pkcs7-signature\"\n")
434     (insert (format "\n--%s\n" boundary))
435     (goto-char (point-max))
436     (insert (format "\n--%s\n" boundary))
437     (insert "Content-Type: application/pkcs7-signature; name=smime.p7s
438 Content-Transfer-Encoding: base64
439 Content-Disposition: attachment; filename=smime.p7s
440
441 ")
442     (insert (base64-encode-string signature) "\n")
443     (goto-char (point-max))
444     (insert (format "--%s--\n" boundary))
445     (goto-char (point-max))))
446
447 (defun mml-smime-epg-encrypt (cont)
448   (let ((inhibit-redisplay t)
449         (context (epg-make-context 'CMS))
450         (config (epg-configuration))
451         (recipients (message-options-get 'mml-smime-epg-recipients))
452         cipher signers
453         (boundary (mml-compute-boundary cont))
454         recipient-key)
455     (unless recipients
456       (setq recipients
457             (apply #'nconc
458                    (mapcar
459                     (lambda (recipient)
460                       (or (epg-expand-group config recipient)
461                           (list recipient)))
462                     (split-string
463                      (or (message-options-get 'message-recipients)
464                          (message-options-set 'message-recipients
465                                               (read-string "Recipients: ")))
466                      "[ \f\t\n\r\v,]+"))))
467       (if mml-smime-verbose
468           (setq recipients
469                 (epa-select-keys context "\
470 Select recipients for encryption.
471 If no one is selected, symmetric encryption will be performed.  "
472                                  recipients))
473         (setq recipients
474               (mapcar
475                (lambda (recipient)
476                  (setq recipient-key (mml-smime-epg-find-usable-key
477                                       (epg-list-keys context recipient)
478                                       'encrypt))
479                  (unless (or recipient-key
480                              (y-or-n-p
481                               (format "No public key for %s; skip it? "
482                                       recipient)))
483                    (error "No public key for %s" recipient))
484                  recipient-key)
485                recipients))
486         (unless recipients
487           (error "No recipient specified")))
488       (message-options-set 'mml-smime-epg-recipients recipients))
489     (if mml-smime-cache-passphrase
490         (epg-context-set-passphrase-callback
491          context
492          #'mml-smime-epg-passphrase-callback))
493     (condition-case error
494         (setq cipher
495               (epg-encrypt-string context (buffer-string) recipients)
496               mml-smime-epg-secret-key-id-list nil)
497       (error
498        (while mml-smime-epg-secret-key-id-list
499          (password-cache-remove (car mml-smime-epg-secret-key-id-list))
500          (setq mml-smime-epg-secret-key-id-list
501                (cdr mml-smime-epg-secret-key-id-list)))
502        (signal (car error) (cdr error))))
503     (delete-region (point-min) (point-max))
504     (goto-char (point-min))
505     (insert "\
506 Content-Type: application/pkcs7-mime;
507  smime-type=enveloped-data;
508  name=smime.p7m
509 Content-Transfer-Encoding: base64
510 Content-Disposition: attachment; filename=smime.p7m
511
512 ")
513     (insert (base64-encode-string cipher))
514     (goto-char (point-max))))
515
516 (defun mml-smime-epg-verify (handle ctl)
517   (catch 'error
518     (let ((inhibit-redisplay t)
519           context plain signature-file part signature)
520       (when (or (null (setq part (mm-find-raw-part-by-type
521                                   ctl (or (mm-handle-multipart-ctl-parameter
522                                            ctl 'protocol)
523                                           "application/pkcs7-signature")
524                                   t)))
525                 (null (setq signature (mm-find-part-by-type
526                                        (cdr handle)
527                                        "application/pkcs7-signature"
528                                        nil t))))
529         (mm-set-handle-multipart-parameter
530          mm-security-handle 'gnus-info "Corrupted")
531         (throw 'error handle))
532       (setq part (mm-replace-in-string part "\n" "\r\n" t)
533             context (epg-make-context 'CMS))
534       (condition-case error
535           (setq plain (epg-verify-string context (mm-get-part signature) part))
536         (error
537          (mm-set-handle-multipart-parameter
538           mm-security-handle 'gnus-info "Failed")
539          (if (eq (car error) 'quit)
540              (mm-set-handle-multipart-parameter
541               mm-security-handle 'gnus-details "Quit.")
542            (mm-set-handle-multipart-parameter
543             mm-security-handle 'gnus-details (format "%S" error)))
544          (throw 'error handle)))
545       (mm-set-handle-multipart-parameter
546        mm-security-handle 'gnus-info
547        (epg-verify-result-to-string (epg-context-result-for context 'verify)))
548       handle)))
549
550 (defun mml-smime-epg-verify-test (handle ctl)
551   t)
552
553 (provide 'mml-smime)
554
555 ;;; arch-tag: f1bf94d4-f2cd-4c6f-b059-ad69492817e2
556 ;;; mml-smime.el ends here