* epa.el (epa-armor): New user option.
[elisp/epg.git] / epa.el
1 ;;; epa.el --- the EasyPG Assistant
2 ;; Copyright (C) 2006 Daiki Ueno
3
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
5 ;; Keywords: PGP, GnuPG
6
7 ;; This file is part of EasyPG.
8
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
23
24 ;;; Code:
25
26 (require 'epg)
27 (require 'font-lock)
28 (require 'widget)
29 (eval-when-compile (require 'wid-edit))
30
31 (defgroup epa nil
32   "The EasyPG Assistant"
33   :group 'epg)
34
35 (defcustom epa-armor nil
36   "If non-nil, epa commands create ASCII armored output."
37   :type 'boolean
38   :group 'epa)
39
40 (defcustom epa-textmode nil
41   "If non-nil, epa commands treat input files as text."
42   :type 'boolean
43   :group 'epa)
44   
45 (defgroup epa-faces nil
46   "Faces for epa-mode."
47   :group 'epa)
48
49 (defface epa-validity-high-face
50   '((((class color) (background dark))
51      (:foreground "PaleTurquoise" :bold t))
52     (t
53      (:bold t)))
54   "Face used for displaying the high validity."
55   :group 'epa-faces)
56 (defvar epa-validity-high-face 'epa-validity-high-face)
57
58 (defface epa-validity-medium-face
59   '((((class color) (background dark))
60      (:foreground "PaleTurquoise" :italic t))
61     (t
62      ()))
63   "Face used for displaying the medium validity."
64   :group 'epa-faces)
65 (defvar epa-validity-medium-face 'epa-validity-medium-face)
66
67 (defface epa-validity-low-face
68   '((t
69      (:italic t)))
70   "Face used for displaying the low validity."
71   :group 'epa-faces)
72 (defvar epa-validity-low-face 'epa-validity-low-face)
73
74 (defface epa-validity-disabled-face
75   '((t
76      (:italic t :inverse-video t)))
77   "Face used for displaying the disabled validity."
78   :group 'epa-faces)
79 (defvar epa-validity-disabled-face 'epa-validity-disabled-face)
80
81 (defface epa-string-face
82   '((((class color)
83       (background dark))
84      (:foreground "lightyellow"))
85     (((class color)
86       (background light))
87      (:foreground "blue4"))
88     (t
89      ()))
90   "Face used for displaying the string."
91   :group 'epa-faces)
92 (defvar epa-string-face 'epa-string-face)
93
94 (defface epa-mark-face
95   '((((class color) (background dark))
96      (:foreground "orange" :bold t))
97     (t
98      (:foreground "red" :bold t)))
99   "Face used for displaying the high validity."
100   :group 'epa-faces)
101 (defvar epa-mark-face 'epa-mark-face)
102
103 (defface epa-field-name-face
104   '((((class color) (background dark))
105      (:foreground "PaleTurquoise" :bold t))
106     (t (:bold t)))
107   "Face for the name of the attribute field."
108   :group 'epa)
109 (defvar epa-field-name-face 'epa-field-name-face)
110
111 (defface epa-field-body-face
112   '((((class color) (background dark))
113      (:foreground "turquoise" :italic t))
114     (t (:italic t)))
115   "Face for the body of the attribute field."
116   :group 'epa)
117 (defvar epa-field-body-face 'epa-field-body-face)
118
119 (defcustom epa-validity-face-alist
120   '((unknown . epa-validity-disabled-face)
121     (invalid . epa-validity-disabled-face)
122     (disabled . epa-validity-disabled-face)
123     (revoked . epa-validity-disabled-face)
124     (expired . epa-validity-disabled-face)
125     (none . epa-validity-low-face)
126     (undefined . epa-validity-low-face)
127     (never . epa-validity-low-face)
128     (marginal . epa-validity-medium-face)
129     (full . epa-validity-high-face)
130     (ultimate . epa-validity-high-face))
131   "An alist mapping validity values to faces."
132   :type 'list
133   :group 'epa)
134
135 (defcustom epa-font-lock-keywords
136   '(("^\\*"
137      (0 epa-mark-face))
138     ("^\t\\([^\t:]+:\\)[ \t]*\\(.*\\)$"
139      (1 epa-field-name-face)
140      (2 epa-field-body-face)))
141   "Default expressions to addon in epa-mode."
142   :type '(repeat (list string))
143   :group 'epa)
144
145 (defconst epa-pubkey-algorithm-letter-alist
146   '((1 . ?R)
147     (2 . ?r)
148     (3 . ?s)
149     (16 . ?g)
150     (17 . ?D)
151     (20 . ?G)))
152
153 (defvar epa-keys-buffer nil)
154 (defvar epa-key-buffer-alist nil)
155 (defvar epa-key nil)
156 (defvar epa-list-keys-arguments nil)
157
158 (defvar epa-keys-mode-map
159   (let ((keymap (make-sparse-keymap)))
160     (define-key keymap "m" 'epa-mark)
161     (define-key keymap "u" 'epa-unmark)
162     (define-key keymap "d" 'epa-decrypt-file)
163     (define-key keymap "v" 'epa-verify-file)
164     (define-key keymap "s" 'epa-sign-file)
165     (define-key keymap "S" 'epa-sign-keys)
166     (define-key keymap "e" 'epa-encrypt-file)
167     (define-key keymap "r" 'epa-delete-keys)
168     (define-key keymap "i" 'epa-import-keys)
169     (define-key keymap "o" 'epa-export-keys)
170     (define-key keymap "g" 'epa-list-keys)
171     (define-key keymap "n" 'next-line)
172     (define-key keymap "p" 'previous-line)
173     (define-key keymap " " 'scroll-up)
174     (define-key keymap [delete] 'scroll-down)
175     (define-key keymap "q" 'epa-exit-buffer)
176     keymap))
177
178 (defvar epa-exit-buffer-function #'bury-buffer)
179
180 (define-widget 'epa-key 'push-button
181   "Button for representing a epg-key object."
182   :format "%[%v%]"
183   :button-face-get 'epa-key-widget-button-face-get
184   :value-create 'epa-key-widget-value-create
185   :action 'epa-key-widget-action
186   :help-echo 'epa-key-widget-help-echo)
187
188 (defun epa-key-widget-action (widget &optional event)
189   (epa-show-key (widget-get widget :value)))
190
191 (defun epa-key-widget-value-create (widget)
192   (let* ((key (widget-get widget :value))
193          (primary-sub-key (car (epg-key-sub-key-list key)))
194          (primary-user-id (car (epg-key-user-id-list key))))
195     (insert (format "%c "
196                     (if (epg-sub-key-validity primary-sub-key)
197                         (car (rassq (epg-sub-key-validity primary-sub-key)
198                                     epg-key-validity-alist))
199                       ? ))
200             (epg-sub-key-id primary-sub-key)
201             " "
202             (if (stringp (epg-user-id-string primary-user-id))
203                 (epg-user-id-string primary-user-id)
204               (epg-decode-dn (epg-user-id-string primary-user-id))))))
205
206 (defun epa-key-widget-button-face-get (widget)
207   (let ((validity (epg-sub-key-validity (car (epg-key-sub-key-list
208                                               (widget-get widget :value))))))
209     (if validity
210         (cdr (assq validity epa-validity-face-alist))
211       'default)))
212
213 (defun epa-key-widget-help-echo (widget)
214   (format "Show %s"
215           (epg-sub-key-id (car (epg-key-sub-key-list
216                                 (widget-get widget :value))))))
217
218 (defun epa-keys-mode ()
219   "Major mode for `epa-list-keys'."
220   (kill-all-local-variables)
221   (buffer-disable-undo)
222   (setq major-mode 'epa-keys-mode
223         mode-name "Keys"
224         truncate-lines t
225         buffer-read-only t)
226   (use-local-map epa-keys-mode-map)
227   (set-keymap-parent (current-local-map) widget-keymap)
228   (make-local-variable 'font-lock-defaults)
229   (setq font-lock-defaults '(epa-font-lock-keywords t))
230   ;; In XEmacs, auto-initialization of font-lock is not effective
231   ;; if buffer-file-name is not set.
232   (font-lock-set-defaults)
233   (widget-setup)
234   (make-local-variable 'epa-exit-buffer-function)
235   (run-hooks 'epa-keys-mode-hook))
236
237 (defvar epa-key-mode-map
238   (let ((keymap (make-sparse-keymap)))
239     (define-key keymap "q" 'bury-buffer)
240     keymap))
241
242 (defun epa-key-mode ()
243   "Major mode for `epa-show-key'."
244   (kill-all-local-variables)
245   (buffer-disable-undo)
246   (setq major-mode 'epa-key-mode
247         mode-name "Key"
248         truncate-lines t
249         buffer-read-only t)
250   (use-local-map epa-key-mode-map)
251   (make-local-variable 'font-lock-defaults)
252   (setq font-lock-defaults '(epa-font-lock-keywords t))
253   ;; In XEmacs, auto-initialization of font-lock is not effective
254   ;; if buffer-file-name is not set.
255   (font-lock-set-defaults)
256   (make-local-variable 'epa-exit-buffer-function)
257   (run-hooks 'epa-key-mode-hook))
258
259 ;;;###autoload
260 (defun epa-list-keys (&optional name mode protocol)
261   (interactive
262    (if current-prefix-arg
263        (let ((name (read-string "Pattern: "
264                                 (if epa-list-keys-arguments
265                                     (car epa-list-keys-arguments)))))
266          (list (if (equal name "") nil name)
267                (y-or-n-p "Secret keys? ")
268                (intern (completing-read "Protocol? "
269                                         '(("OpenPGP") ("CMS"))
270                                         nil t))))
271      (or epa-list-keys-arguments (list nil nil nil))))
272   (unless (and epa-keys-buffer
273                (buffer-live-p epa-keys-buffer))
274     (setq epa-keys-buffer (generate-new-buffer "*Keys*")))
275   (set-buffer epa-keys-buffer)
276   (let ((inhibit-read-only t)
277         buffer-read-only
278         (point (point-min))
279         (context (epg-make-context protocol)))
280     (unless (get-text-property point 'epa-list-keys)
281       (setq point (next-single-property-change point 'epa-list-keys)))
282     (when point
283       (delete-region point
284                      (or (next-single-property-change point 'epa-list-keys)
285                          (point-max)))
286       (goto-char point))
287     (epa-list-keys-1 context name mode)
288     (epa-keys-mode))
289   (make-local-variable 'epa-list-keys-arguments)
290   (setq epa-list-keys-arguments (list name mode protocol))
291   (goto-char (point-min))
292   (pop-to-buffer (current-buffer)))
293
294 (defun epa-list-keys-1 (context name mode)
295   (save-restriction
296     (narrow-to-region (point) (point))
297     (let ((inhibit-read-only t)
298           buffer-read-only
299           (keys (epg-list-keys context name mode))
300           point)
301       (while keys
302         (setq point (point))
303         (insert "  ")
304         (put-text-property point (point) 'epa-key (car keys))
305         (widget-create 'epa-key :value (car keys))
306         (insert "\n")
307         (setq keys (cdr keys))))      
308     (put-text-property (point-min) (point-max) 'epa-list-keys t)))
309
310 (defun epa-marked-keys ()
311   (or (save-excursion
312         (set-buffer epa-keys-buffer)
313         (goto-char (point-min))
314         (let (keys key)
315           (while (re-search-forward "^\\*" nil t)
316             (if (setq key (get-text-property (match-beginning 0)
317                                              'epa-key))
318                 (setq keys (cons key keys))))
319           (nreverse keys)))
320       (save-excursion
321         (beginning-of-line)
322         (let ((key (get-text-property (point) 'epa-key)))
323           (if key
324               (list key))))))
325
326 ;;;###autoload
327 (defun epa-select-keys (context prompt &optional names secret)
328   "Display a user's keyring and ask him to select keys.
329 CONTEXT is an epg-context.
330 PROMPT is a string to prompt with.
331 NAMES is a list of strings to be matched with keys.  If it is nil, all
332 the keys are listed.
333 If SECRET is non-nil, list secret keys instead of public keys."
334   (save-excursion
335     (unless (and epa-keys-buffer
336                  (buffer-live-p epa-keys-buffer))
337       (setq epa-keys-buffer (generate-new-buffer "*Keys*")))
338     (let ((inhibit-read-only t)
339           buffer-read-only
340           point)
341       (set-buffer epa-keys-buffer)
342       (erase-buffer)
343       (insert prompt "\n")
344       (widget-create 'link
345                      :notify (lambda (&rest ignore) (abort-recursive-edit))
346                      :help-echo
347                      (substitute-command-keys
348                       "Click here or \\[abort-recursive-edit] to cancel")
349                      "Cancel")
350       (widget-create 'link
351                      :notify (lambda (&rest ignore) (exit-recursive-edit))
352                      :help-echo
353                      (substitute-command-keys
354                       "Click here or \\[exit-recursive-edit] to finish")
355                      "OK")
356       (insert "\n\n")
357       (if names
358           (while names
359             (setq point (point))
360             (epa-list-keys-1 context (car names) secret)
361             (goto-char point)
362             (epa-mark)
363             (goto-char (point-max))
364             (setq names (cdr names)))
365         (epa-list-keys-1 context nil secret))
366       (epa-keys-mode)
367       (setq epa-exit-buffer-function #'abort-recursive-edit)
368       (goto-char (point-min))
369       (pop-to-buffer (current-buffer)))
370     (unwind-protect
371           (progn
372             (recursive-edit)
373             (epa-marked-keys))
374         (if (get-buffer-window epa-keys-buffer)
375             (delete-window (get-buffer-window epa-keys-buffer)))
376         (kill-buffer epa-keys-buffer))))
377
378 (defun epa-show-key (key)
379   (let* ((primary-sub-key (car (epg-key-sub-key-list key)))
380          (entry (assoc (epg-sub-key-id primary-sub-key)
381                        epa-key-buffer-alist))
382          (inhibit-read-only t)
383          buffer-read-only
384          pointer)
385     (unless entry
386       (setq entry (cons (epg-sub-key-id primary-sub-key) nil)
387             epa-key-buffer-alist (cons entry epa-key-buffer-alist)))
388     (unless (and (cdr entry)
389                  (buffer-live-p (cdr entry)))
390       (setcdr entry (generate-new-buffer
391                      (format "*Key*%s" (epg-sub-key-id primary-sub-key)))))
392     (set-buffer (cdr entry))
393     (make-local-variable 'epa-key)
394     (setq epa-key key)
395     (erase-buffer)
396     (setq pointer (epg-key-user-id-list key))
397     (while pointer
398       (insert " "
399               (if (epg-user-id-validity (car pointer))
400                   (char-to-string
401                    (car (rassq (epg-user-id-validity (car pointer))
402                                epg-key-validity-alist)))
403                 " ")
404               " "
405               (if (stringp (epg-user-id-string (car pointer)))
406                   (epg-user-id-string (car pointer))
407                 (epg-decode-dn (epg-user-id-string (car pointer))))
408               "\n")
409       (setq pointer (cdr pointer)))
410     (setq pointer (epg-key-sub-key-list key))
411     (while pointer
412       (insert " "
413               (if (epg-sub-key-validity (car pointer))
414                   (char-to-string
415                    (car (rassq (epg-sub-key-validity (car pointer))
416                                epg-key-validity-alist)))
417                 " ")
418               " "
419               (epg-sub-key-id (car pointer))
420               " "
421               (format "%dbits"
422                       (epg-sub-key-length (car pointer)))
423               " "
424               (cdr (assq (epg-sub-key-algorithm (car pointer))
425                          epg-pubkey-algorithm-alist))
426               "\n\tCreated: "
427               (epg-sub-key-creation-time (car pointer))
428               (if (epg-sub-key-expiration-time (car pointer))
429                   (format "\n\tExpires: %s" (epg-sub-key-expiration-time
430                                              (car pointer)))
431                 "")
432               "\n\tCapabilities: "
433               (mapconcat #'symbol-name
434                          (epg-sub-key-capability (car pointer))
435                          " ")
436               "\n\tFingerprint: "
437               (epg-sub-key-fingerprint (car pointer))
438               "\n")
439       (setq pointer (cdr pointer)))
440     (goto-char (point-min))
441     (pop-to-buffer (current-buffer))
442     (epa-key-mode)))
443
444 (defun epa-show-key-notify (widget &rest ignore)
445   (epa-show-key (widget-get widget :value)))
446
447 (defun epa-mark (&optional arg)
448   "Mark the current line.
449 If ARG is non-nil, unmark the current line."
450   (interactive "P")
451   (let ((inhibit-read-only t)
452         buffer-read-only
453         properties)
454     (beginning-of-line)
455     (setq properties (text-properties-at (point)))
456     (delete-char 1)
457     (insert (if arg " " "*"))
458     (set-text-properties (1- (point)) (point) properties)
459     (forward-line)))
460
461 (defun epa-unmark (&optional arg)
462   "Unmark the current line.
463 If ARG is non-nil, mark the current line."
464   (interactive "P")
465   (epa-mark (not arg)))
466
467 (defun epa-exit-buffer ()
468   "Exit the current buffer.
469 `epa-exit-buffer-function' is called if it is set."
470   (interactive)
471   (funcall epa-exit-buffer-function))
472
473 ;;;###autoload
474 (defun epa-decrypt-file (file)
475   "Decrypt FILE."
476   (interactive "fFile: ")
477   (let* ((default-name (file-name-sans-extension file))
478          (plain (expand-file-name
479                  (read-file-name
480                   (concat "To file (default "
481                           (file-name-nondirectory default-name)
482                           ") ")
483                   (file-name-directory default-name)
484                   default-name)))
485          (context (epg-make-context)))
486     (message "Decrypting %s..." (file-name-nondirectory file))
487     (epg-decrypt-file context file plain)
488     (message "Decrypting %s...done" (file-name-nondirectory file))))
489
490 ;;;###autoload
491 (defun epa-verify-file (file)
492   "Verify FILE."
493   (interactive "fFile: ")
494   (let* ((context (epg-make-context))
495          (plain (if (equal (file-name-extension file) "sig")
496                     (file-name-sans-extension file))))
497     (message "Verifying %s..." (file-name-nondirectory file))
498     (epg-verify-file context file plain)
499     (message "Verifying %s...done" (file-name-nondirectory file))
500     (message "%s"
501              (epg-verify-result-to-string
502               (epg-context-result-for context 'verify)))))
503
504 ;;;###autoload
505 (defun epa-sign-file (file signers detached)
506   "Sign FILE by selected SIGNERS keys.
507 If DETACHED is non-nil, it creates a detached signature."
508   (interactive
509    (list (expand-file-name (read-file-name "File: "))
510          (epa-select-keys (epg-make-context) "Select keys for signing.
511 If no one is selected, default secret key is used.  "
512                           nil t)
513          (y-or-n-p "Make a detached signature? ")))
514   (let ((signature (concat file (if epa-armor
515                                     ".asc"
516                                   (if detached
517                                       ".sig"
518                                     ".gpg"))))
519         (context (epg-make-context)))
520     (epg-context-set-armor context epa-armor)
521     (epg-context-set-textmode context epa-textmode)
522     (message "Signing %s..." (file-name-nondirectory file))
523     (epg-context-set-signers context signers)
524     (epg-sign-file context file signature (not (null detached)))
525     (message "Signing %s...done" (file-name-nondirectory file))))
526
527 ;;;###autoload
528 (defun epa-encrypt-file (file recipients)
529   "Encrypt FILE for RECIPIENTS."
530   (interactive
531    (list (expand-file-name (read-file-name "File: "))
532          (epa-select-keys (epg-make-context) "Select recipents for encryption.
533 If no one is selected, symmetric encryption will be performed.  ")))
534   (let ((cipher (concat file (if epa-armor ".asc" ".gpg")))
535         (context (epg-make-context)))
536     (epg-context-set-armor context epa-armor)
537     (epg-context-set-textmode context epa-textmode)
538     (message "Encrypting %s..." (file-name-nondirectory file))
539     (epg-encrypt-file context file recipients cipher)
540     (message "Encrypting %s...done" (file-name-nondirectory file))))
541
542 ;;;###autoload
543 (defun epa-delete-keys (keys &optional allow-secret)
544   "Delete selected KEYS."
545   (interactive
546    (let ((keys (epa-marked-keys)))
547      (unless keys
548        (error "No keys selected"))
549      (list keys
550            (eq (nth 1 epa-list-keys-arguments) t))))
551   (let ((context (epg-make-context)))
552     (message "Deleting...")
553     (epg-delete-keys context keys allow-secret)
554     (apply #'epa-list-keys epa-list-keys-arguments)
555     (message "Deleting...done")))
556
557 ;;;###autoload
558 (defun epa-import-keys (file)
559   "Import keys from FILE."
560   (interactive "fFile: ")
561   (let ((context (epg-make-context)))
562     (message "Importing %s..." (file-name-nondirectory file))
563     (epg-import-keys-from-file context (expand-file-name file))
564     (apply #'epa-list-keys epa-list-keys-arguments)
565     (message "Importing %s...done" (file-name-nondirectory file))))
566
567 ;;;###autoload
568 (defun epa-export-keys (keys file)
569   "Export selected KEYS to FILE."
570   (interactive
571    (let ((keys (epa-marked-keys))
572          default-name)
573      (unless keys
574        (error "No keys selected"))
575      (setq default-name
576            (expand-file-name
577             (concat (epg-sub-key-id (car (epg-key-sub-key-list (car keys))))
578                     (if epa-armor ".asc" ".gpg"))
579             default-directory))
580      (list keys
581            (expand-file-name
582             (read-file-name
583              (concat "To file (default "
584                      (file-name-nondirectory default-name)
585                      ") ")
586              (file-name-directory default-name)
587              default-name)))))
588   (let ((context (epg-make-context)))
589     (epg-context-set-armor context epa-armor)
590     (message "Exporting to %s..." (file-name-nondirectory file))
591     (epg-export-keys-to-file context keys file)
592     (message "Exporting to %s...done" (file-name-nondirectory file))))
593
594 ;;;###autoload
595 (defun epa-sign-keys (keys &optional local)
596   "Sign selected KEYS.
597 If LOCAL is non-nil, the signature is marked as non exportable."
598   (interactive
599    (let ((keys (epa-marked-keys)))
600      (unless keys
601        (error "No keys selected"))
602      (list keys current-prefix-arg)))
603   (let ((context (epg-make-context)))
604     (message "Signing keys...")
605     (epg-sign-keys context keys local)
606     (message "Signing keys...done")))
607
608 (provide 'epa)
609
610 ;;; epa.el ends here