* epa.el (epa-sign-keys): New command.
[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 (defgroup epa-faces nil
36   "Faces for epa-mode."
37   :group 'epa)
38
39 (defface epa-validity-high-face
40   '((((class color) (background dark))
41      (:foreground "PaleTurquoise" :bold t))
42     (t
43      (:bold t)))
44   "Face used for displaying the high validity."
45   :group 'epa-faces)
46 (defvar epa-validity-high-face 'epa-validity-high-face)
47
48 (defface epa-validity-medium-face
49   '((((class color) (background dark))
50      (:foreground "PaleTurquoise" :italic t))
51     (t
52      ()))
53   "Face used for displaying the medium validity."
54   :group 'epa-faces)
55 (defvar epa-validity-medium-face 'epa-validity-medium-face)
56
57 (defface epa-validity-low-face
58   '((t
59      (:italic t)))
60   "Face used for displaying the low validity."
61   :group 'epa-faces)
62 (defvar epa-validity-low-face 'epa-validity-low-face)
63
64 (defface epa-validity-disabled-face
65   '((t
66      (:italic t :inverse-video t)))
67   "Face used for displaying the disabled validity."
68   :group 'epa-faces)
69 (defvar epa-validity-disabled-face 'epa-validity-disabled-face)
70
71 (defface epa-string-face
72   '((((class color)
73       (background dark))
74      (:foreground "lightyellow"))
75     (((class color)
76       (background light))
77      (:foreground "blue4"))
78     (t
79      ()))
80   "Face used for displaying the string."
81   :group 'epa-faces)
82 (defvar epa-string-face 'epa-string-face)
83
84 (defface epa-mark-face
85   '((((class color) (background dark))
86      (:foreground "orange" :bold t))
87     (t
88      (:foreground "red" :bold t)))
89   "Face used for displaying the high validity."
90   :group 'epa-faces)
91 (defvar epa-mark-face 'epa-mark-face)
92
93 (defface epa-field-name-face
94   '((((class color) (background dark))
95      (:foreground "PaleTurquoise" :bold t))
96     (t (:bold t)))
97   "Face for the name of the attribute field."
98   :group 'epa)
99 (defvar epa-field-name-face 'epa-field-name-face)
100
101 (defface epa-field-body-face
102   '((((class color) (background dark))
103      (:foreground "turquoise" :italic t))
104     (t (:italic t)))
105   "Face for the body of the attribute field."
106   :group 'epa)
107 (defvar epa-field-body-face 'epa-field-body-face)
108
109 (defcustom epa-validity-face-alist
110   '((unknown . epa-validity-disabled-face)
111     (invalid . epa-validity-disabled-face)
112     (disabled . epa-validity-disabled-face)
113     (revoked . epa-validity-disabled-face)
114     (expired . epa-validity-disabled-face)
115     (none . epa-validity-low-face)
116     (undefined . epa-validity-low-face)
117     (never . epa-validity-low-face)
118     (marginal . epa-validity-medium-face)
119     (full . epa-validity-high-face)
120     (ultimate . epa-validity-high-face))
121   "An alist mapping validity values to faces."
122   :type 'list
123   :group 'epa)
124
125 (defcustom epa-font-lock-keywords
126   '(("^\\*"
127      (0 epa-mark-face))
128     ("^\t\\([^\t:]+:\\)[ \t]*\\(.*\\)$"
129      (1 epa-field-name-face)
130      (2 epa-field-body-face)))
131   "Default expressions to addon in epa-mode."
132   :type '(repeat (list string))
133   :group 'epa)
134
135 (defconst epa-pubkey-algorithm-letter-alist
136   '((1 . ?R)
137     (2 . ?r)
138     (3 . ?s)
139     (16 . ?g)
140     (17 . ?D)
141     (20 . ?G)))
142
143 (defvar epa-keys-buffer nil)
144 (defvar epa-key-buffer-alist nil)
145 (defvar epa-key nil)
146 (defvar epa-list-keys-arguments nil)
147
148 (defvar epa-keys-mode-map
149   (let ((keymap (make-sparse-keymap)))
150     (define-key keymap "m" 'epa-mark)
151     (define-key keymap "u" 'epa-unmark)
152     (define-key keymap "d" 'epa-decrypt-file)
153     (define-key keymap "v" 'epa-verify-file)
154     (define-key keymap "s" 'epa-sign-file)
155     (define-key keymap "S" 'epa-sign-keys)
156     (define-key keymap "e" 'epa-encrypt-file)
157     (define-key keymap "r" 'epa-delete-keys)
158     (define-key keymap "i" 'epa-import-keys)
159     (define-key keymap "o" 'epa-export-keys)
160     (define-key keymap "g" 'epa-list-keys)
161     (define-key keymap "n" 'next-line)
162     (define-key keymap "p" 'previous-line)
163     (define-key keymap " " 'scroll-up)
164     (define-key keymap [delete] 'scroll-down)
165     (define-key keymap "q" 'epa-exit-buffer)
166     keymap))
167
168 (defvar epa-exit-buffer-function #'bury-buffer)
169
170 (define-widget 'epa-key 'push-button
171   "Button for representing a epg-key object."
172   :format "%[%v%]"
173   :button-face-get 'epa-key-widget-button-face-get
174   :value-create 'epa-key-widget-value-create
175   :action 'epa-key-widget-action
176   :help-echo 'epa-key-widget-help-echo)
177
178 (defun epa-key-widget-action (widget &optional event)
179   (epa-show-key (widget-get widget :value)))
180
181 (defun epa-key-widget-value-create (widget)
182   (let* ((key (widget-get widget :value))
183          (primary-sub-key (car (epg-key-sub-key-list key)))
184          (primary-user-id (car (epg-key-user-id-list key))))
185     (insert (format "%c "
186                     (if (epg-sub-key-validity primary-sub-key)
187                         (car (rassq (epg-sub-key-validity primary-sub-key)
188                                     epg-key-validity-alist))
189                       ? ))
190             (epg-sub-key-id primary-sub-key)
191             " "
192             (epg-user-id-name primary-user-id))))
193
194 (defun epa-key-widget-button-face-get (widget)
195   (let ((validity (epg-sub-key-validity (car (epg-key-sub-key-list
196                                               (widget-get widget :value))))))
197     (if validity
198         (cdr (assq validity epa-validity-face-alist))
199       'default)))
200
201 (defun epa-key-widget-help-echo (widget)
202   (format "Show %s"
203           (epg-sub-key-id (car (epg-key-sub-key-list
204                                 (widget-get widget :value))))))
205
206 (defun epa-keys-mode ()
207   "Major mode for `epa-list-keys'."
208   (kill-all-local-variables)
209   (buffer-disable-undo)
210   (setq major-mode 'epa-keys-mode
211         mode-name "Keys"
212         truncate-lines t
213         buffer-read-only t)
214   (use-local-map epa-keys-mode-map)
215   (set-keymap-parent (current-local-map) widget-keymap)
216   (make-local-variable 'font-lock-defaults)
217   (setq font-lock-defaults '(epa-font-lock-keywords t))
218   ;; In XEmacs, auto-initialization of font-lock is not effective
219   ;; if buffer-file-name is not set.
220   (font-lock-set-defaults)
221   (widget-setup)
222   (run-hooks 'epa-keys-mode-hook))
223
224 (defvar epa-key-mode-map
225   (let ((keymap (make-sparse-keymap)))
226     (define-key keymap "q" 'bury-buffer)
227     keymap))
228
229 (defun epa-key-mode ()
230   "Major mode for `epa-show-key'."
231   (kill-all-local-variables)
232   (buffer-disable-undo)
233   (setq major-mode 'epa-key-mode
234         mode-name "Key"
235         truncate-lines t
236         buffer-read-only t)
237   (use-local-map epa-key-mode-map)
238   (make-local-variable 'font-lock-defaults)
239   (setq font-lock-defaults '(epa-font-lock-keywords t))
240   ;; In XEmacs, auto-initialization of font-lock is not effective
241   ;; if buffer-file-name is not set.
242   (font-lock-set-defaults)
243   (make-local-variable 'epa-exit-buffer-function)
244   (run-hooks 'epa-key-mode-hook))
245
246 ;;;###autoload
247 (defun epa-list-keys (&optional name mode)
248   (interactive
249    (if current-prefix-arg
250        (let ((name (read-string "Pattern: "
251                                 (if epa-list-keys-arguments
252                                     (car epa-list-keys-arguments)))))
253          (list (if (equal name "") nil name)
254                (y-or-n-p "Secret keys? ")))
255      (or epa-list-keys-arguments (list nil nil))))
256   (unless (and epa-keys-buffer
257                (buffer-live-p epa-keys-buffer))
258     (setq epa-keys-buffer (generate-new-buffer "*Keys*")))
259   (set-buffer epa-keys-buffer)
260   (let ((inhibit-read-only t)
261         buffer-read-only
262         (point (point-min)))
263     (unless (get-text-property point 'epa-list-keys)
264       (setq point (next-single-property-change point 'epa-list-keys)))
265     (when point
266       (delete-region point
267                      (or (next-single-property-change point 'epa-list-keys)
268                          (point-max)))
269       (goto-char point))
270     (epa-list-keys-1 name mode)
271     (epa-keys-mode))
272   (make-local-variable 'epa-list-keys-arguments)
273   (setq epa-list-keys-arguments (list name mode))
274   (goto-char (point-min))
275   (pop-to-buffer (current-buffer)))
276
277 (defun epa-list-keys-1 (name mode)
278   (save-restriction
279     (narrow-to-region (point) (point))
280     (let ((inhibit-read-only t)
281           buffer-read-only
282           (keys (epg-list-keys name mode))
283           point)
284       (while keys
285         (setq point (point))
286         (insert "  ")
287         (put-text-property point (point) 'epa-key (car keys))
288         (widget-create 'epa-key :value (car keys))
289         (insert "\n")
290         (setq keys (cdr keys))))      
291     (put-text-property (point-min) (point-max) 'epa-list-keys t)))
292
293 (defun epa-marked-keys ()
294   (or (save-excursion
295         (set-buffer epa-keys-buffer)
296         (goto-char (point-min))
297         (let (keys key)
298           (while (re-search-forward "^\\*" nil t)
299             (if (setq key (get-text-property (match-beginning 0)
300                                              'epa-key))
301                 (setq keys (cons key keys))))
302           (nreverse keys)))
303       (save-excursion
304         (beginning-of-line)
305         (let ((key (get-text-property (point) 'epa-key)))
306           (if key
307               (list key))))))
308
309 (defun epa-select-keys (prompt &optional names mode)
310   (save-excursion
311     (unless (and epa-keys-buffer
312                  (buffer-live-p epa-keys-buffer))
313       (setq epa-keys-buffer (generate-new-buffer "*Keys*")))
314     (let ((inhibit-read-only t)
315           buffer-read-only
316           point)
317       (set-buffer epa-keys-buffer)
318       (erase-buffer)
319       (insert prompt "\n")
320       (widget-create 'link
321                      :notify (lambda (&rest ignore) (abort-recursive-edit))
322                      :help-echo
323                      (substitute-command-keys
324                       "Click here or \\[abort-recursive-edit] to cancel")
325                      "Cancel")
326       (widget-create 'link
327                      :notify (lambda (&rest ignore) (exit-recursive-edit))
328                      :help-echo
329                      (substitute-command-keys
330                       "Click here or \\[exit-recursive-edit] to finish")
331                      "OK")
332       (insert "\n\n")
333       (if names
334           (while names
335             (setq point (point))
336             (epa-list-keys-1 (car names) mode)
337             (goto-char point)
338             (epa-mark)
339             (goto-char (point-max))
340             (setq names (cdr names)))
341         (epa-list-keys-1 nil mode))
342       (epa-keys-mode)
343       (setq epa-exit-buffer-function #'abort-recursive-edit)
344       (goto-char (point-min))
345       (pop-to-buffer (current-buffer))
346       (unwind-protect
347           (progn
348             (recursive-edit)
349             (epa-marked-keys))
350         (if (get-buffer-window epa-keys-buffer)
351             (delete-window (get-buffer-window epa-keys-buffer)))
352         (kill-buffer epa-keys-buffer)))))
353
354 (defun epa-show-key (key)
355   (let* ((primary-sub-key (car (epg-key-sub-key-list key)))
356          (entry (assoc (epg-sub-key-id primary-sub-key)
357                        epa-key-buffer-alist))
358          (inhibit-read-only t)
359          buffer-read-only
360          pointer)
361     (unless entry
362       (setq entry (cons (epg-sub-key-id primary-sub-key) nil)
363             epa-key-buffer-alist (cons entry epa-key-buffer-alist)))
364     (unless (and (cdr entry)
365                  (buffer-live-p (cdr entry)))
366       (setcdr entry (generate-new-buffer
367                      (format "*Key*%s" (epg-sub-key-id primary-sub-key)))))
368     (set-buffer (cdr entry))
369     (make-local-variable 'epa-key)
370     (setq epa-key key)
371     (erase-buffer)
372     (setq pointer (epg-key-user-id-list key))
373     (while pointer
374       (insert " "
375               (if (epg-user-id-validity (car pointer))
376                   (char-to-string
377                    (car (rassq (epg-user-id-validity (car pointer))
378                                epg-key-validity-alist)))
379                 " ")
380               " "
381               (epg-user-id-name (car pointer))
382               "\n")
383       (setq pointer (cdr pointer)))
384     (setq pointer (epg-key-sub-key-list key))
385     (while pointer
386       (insert " "
387               (if (epg-sub-key-validity (car pointer))
388                   (char-to-string
389                    (car (rassq (epg-sub-key-validity (car pointer))
390                                epg-key-validity-alist)))
391                 " ")
392               " "
393               (epg-sub-key-id (car pointer))
394               " "
395               (format "%dbits"
396                       (epg-sub-key-length (car pointer)))
397               " "
398               (cdr (assq (epg-sub-key-algorithm (car pointer))
399                          epg-pubkey-algorithm-alist))
400               "\n\tCreated: "
401               (epg-sub-key-creation-time (car pointer))
402               (if (epg-sub-key-expiration-time (car pointer))
403                   (format "\n\tExpires: %s" (epg-sub-key-expiration-time
404                                              (car pointer)))
405                 "")
406               "\n\tCapabilities: "
407               (mapconcat #'symbol-name
408                          (epg-sub-key-capability (car pointer))
409                          " ")
410               "\n\tFingerprint: "
411               (epg-sub-key-fingerprint (car pointer))
412               "\n")
413       (setq pointer (cdr pointer)))
414     (goto-char (point-min))
415     (pop-to-buffer (current-buffer))
416     (epa-key-mode)))
417
418 (defun epa-show-key-notify (widget &rest ignore)
419   (epa-show-key (widget-get widget :value)))
420
421 (defun epa-mark (&optional arg)
422   "Mark the current line."
423   (interactive "P")
424   (let ((inhibit-read-only t)
425         buffer-read-only
426         properties)
427     (beginning-of-line)
428     (setq properties (text-properties-at (point)))
429     (delete-char 1)
430     (insert (if arg " " "*"))
431     (set-text-properties (1- (point)) (point) properties)
432     (forward-line)))
433
434 (defun epa-unmark (&optional arg)
435   "Unmark the current line."
436   (interactive "P")
437   (epa-mark (not arg)))
438
439 (defun epa-exit-buffer ()
440   (interactive)
441   (funcall epa-exit-buffer-function))
442
443 (defun epa-decrypt-file (file)
444   (interactive "fFile: ")
445   (let* ((default-name (file-name-sans-extension file))
446          (plain (expand-file-name
447                  (read-file-name
448                   (concat "To file (default "
449                           (file-name-nondirectory default-name)
450                           ") ")
451                   (file-name-directory default-name)
452                   default-name)))
453          (context (epg-make-context)))
454     (message "Decrypting %s..." (file-name-nondirectory file))
455     (epg-decrypt-file context file plain)
456     (message "Decrypting %s...done" (file-name-nondirectory file))))
457
458 (defun epa-verify-file (file)
459   (interactive "fFile: ")
460   (let* ((context (epg-make-context))
461          (plain (if (equal (file-name-extension file) "sig")
462                     (file-name-sans-extension file))))
463     (message "Verifying %s..." (file-name-nondirectory file))
464     (epg-verify-file context file plain)
465     (message "Verifying %s...done" (file-name-nondirectory file))
466     (message "%s"
467              (epg-verify-result-to-string
468               (epg-context-result-for context 'verify)))))
469
470 (defun epa-sign-file (file detached)
471   (interactive
472    (list (expand-file-name (read-file-name "File: "))
473          (y-or-n-p "Make a detached signature? ")))
474   (let ((signature (concat file (if detached ".sig" ".gpg")))
475         (context (epg-make-context)))
476     (message "Signing %s..." (file-name-nondirectory file))
477     (epg-sign-file context file signature (not (null detached)))
478     (message "Signing %s...done" (file-name-nondirectory file))))
479
480 (defun epa-encrypt-file (file recipients)
481   (interactive
482    (list (expand-file-name (read-file-name "File: "))
483          (epa-select-keys "Select recipents for encryption.
484 If no one is selected, symmetric encryption will be performed.  ")))
485   (let ((cipher (concat file ".gpg"))
486         (context (epg-make-context)))
487     (message "Encrypting %s..." (file-name-nondirectory file))
488     (epg-encrypt-file context file recipients cipher)
489     (message "Encrypting %s...done" (file-name-nondirectory file))))
490
491 (defun epa-delete-keys (keys)
492   (interactive
493    (let ((keys (epa-marked-keys)))
494      (unless keys
495        (error "No keys selected"))
496      (list keys)))
497   (let ((context (epg-make-context)))
498     (message "Deleting...")
499     (epg-delete-keys context keys)
500     (apply #'epa-list-keys epa-list-keys-arguments)
501     (message "Deleting...done")))
502
503 (defun epa-import-keys (file)
504   (interactive "fFile: ")
505   (let ((context (epg-make-context)))
506     (message "Importing %s..." (file-name-nondirectory file))
507     (epg-import-keys-from-file context (expand-file-name file))
508     (apply #'epa-list-keys epa-list-keys-arguments)
509     (message "Importing %s...done" (file-name-nondirectory file))))
510
511 (defun epa-export-keys (keys file)
512   (interactive
513    (let ((keys (epa-marked-keys))
514          default-name)
515      (unless keys
516        (error "No keys selected"))
517      (setq default-name
518            (expand-file-name
519             (concat (epg-sub-key-id (car (epg-key-sub-key-list (car keys))))
520                     ".gpg")
521             default-directory))
522      (list keys
523            (expand-file-name
524             (read-file-name
525              (concat "To file (default "
526                      (file-name-nondirectory default-name)
527                      ") ")
528              (file-name-directory default-name)
529              default-name)))))
530   (let ((context (epg-make-context)))
531     (message "Exporting to %s..." (file-name-nondirectory file))
532     (epg-export-keys-to-file context keys file)
533     (message "Exporting to %s...done" (file-name-nondirectory file))))
534
535 (defun epa-sign-keys (keys &optional local)
536   (interactive
537    (let ((keys (epa-marked-keys)))
538      (unless keys
539        (error "No keys selected"))
540      (list keys current-prefix-arg)))
541   (let ((context (epg-make-context)))
542     (message "Signing keys...")
543     (epg-sign-keys context keys local)
544     (message "Signing keys...done")))
545
546 (provide 'epa)
547
548 ;;; epa.el ends here