Merge v5-10 branch.
[gnus] / lisp / pgg-gpg.el
1 ;;; pgg-gpg.el --- GnuPG support for PGG.
2
3 ;; Copyright (C) 1999, 2000, 2002, 2003, 2004,
4 ;;   2005, 2006 Free Software Foundation, Inc.
5
6 ;; Author: Daiki Ueno <ueno@unixuser.org>
7 ;; Symmetric encryption added by: Sascha Wilde <wilde@sha-bang.de>
8 ;; Created: 1999/10/28
9 ;; Keywords: PGP, OpenPGP, GnuPG
10
11 ;; This file is part of GNU Emacs.
12
13 ;; GNU Emacs is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; any later version.
17
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 ;; Boston, MA 02110-1301, USA.
27
28 ;;; Code:
29
30 (eval-when-compile
31   (require 'cl)                         ; for gpg macros
32   (require 'pgg))
33
34 (defgroup pgg-gpg ()
35   "GnuPG interface."
36   :group 'pgg)
37
38 (defcustom pgg-gpg-program "gpg"
39   "The GnuPG executable."
40   :group 'pgg-gpg
41   :type 'string)
42
43 (defcustom pgg-gpg-extra-args nil
44   "Extra arguments for every GnuPG invocation."
45   :group 'pgg-gpg
46   :type '(repeat (string :tag "Argument")))
47
48 (defcustom pgg-gpg-recipient-argument "--recipient"
49   "GnuPG option to specify recipient."
50   :group 'pgg-gpg
51   :type '(choice (const :tag "New `--recipient' option" "--recipient")
52                  (const :tag "Old `--remote-user' option" "--remote-user")))
53
54 (defvar pgg-gpg-user-id nil
55   "GnuPG ID of your default identity.")
56
57 (defun pgg-gpg-process-region (start end passphrase program args)
58   (let* ((output-file-name (pgg-make-temp-file "pgg-output"))
59          (args
60           `("--status-fd" "2"
61             ,@(if passphrase '("--passphrase-fd" "0"))
62             "--yes" ; overwrite
63             "--output" ,output-file-name
64             ,@pgg-gpg-extra-args ,@args))
65          (output-buffer pgg-output-buffer)
66          (errors-buffer pgg-errors-buffer)
67          (orig-mode (default-file-modes))
68          (process-connection-type nil)
69          exit-status)
70     (with-current-buffer (get-buffer-create errors-buffer)
71       (buffer-disable-undo)
72       (erase-buffer))
73     (unwind-protect
74         (progn
75           (set-default-file-modes 448)
76           (let ((coding-system-for-write 'binary)
77                 (input (buffer-substring-no-properties start end))
78                 (default-enable-multibyte-characters nil))
79             (with-temp-buffer
80               (when passphrase
81                 (insert passphrase "\n"))
82               (insert input)
83               (setq exit-status
84                     (apply #'call-process-region (point-min) (point-max) program
85                            nil errors-buffer nil args))))
86           (with-current-buffer (get-buffer-create output-buffer)
87             (buffer-disable-undo)
88             (erase-buffer)
89             (if (file-exists-p output-file-name)
90                 (let ((coding-system-for-read 'raw-text-dos))
91                   (insert-file-contents output-file-name)))
92             (set-buffer errors-buffer)
93             (if (not (equal exit-status 0))
94                 (insert (format "\n%s exited abnormally: '%s'\n"
95                                 program exit-status)))))
96       (if (file-exists-p output-file-name)
97           (delete-file output-file-name))
98       (set-default-file-modes orig-mode))))
99
100 (defun pgg-gpg-possibly-cache-passphrase (passphrase &optional key notruncate)
101   (if (and pgg-cache-passphrase
102            (progn
103              (goto-char (point-min))
104              (re-search-forward "^\\[GNUPG:] \\(GOOD_PASSPHRASE\\>\\)\\|\\(SIG_CREATED\\)" nil t)))
105       (pgg-add-passphrase-to-cache
106        (or key
107            (progn
108              (goto-char (point-min))
109              (if (re-search-forward
110                   "^\\[GNUPG:] NEED_PASSPHRASE\\(_PIN\\)? \\w+ ?\\w*" nil t)
111                  (substring (match-string 0) -8))))
112        passphrase
113        notruncate)))
114
115 (defvar pgg-gpg-all-secret-keys 'unknown)
116
117 (defun pgg-gpg-lookup-all-secret-keys ()
118   "Return all secret keys present in secret key ring."
119   (when (eq pgg-gpg-all-secret-keys 'unknown)
120     (setq pgg-gpg-all-secret-keys '())
121     (let ((args (list "--with-colons" "--no-greeting" "--batch"
122                       "--list-secret-keys")))
123       (with-temp-buffer
124         (apply #'call-process pgg-gpg-program nil t nil args)
125         (goto-char (point-min))
126         (while (re-search-forward
127                 "^\\(sec\\|pub\\):[^:]*:[^:]*:[^:]*:\\([^:]*\\)" nil t)
128           (push (substring (match-string 2) 8)
129                 pgg-gpg-all-secret-keys)))))
130   pgg-gpg-all-secret-keys)
131
132 (defun pgg-gpg-lookup-key (string &optional type)
133   "Search keys associated with STRING."
134   (let ((args (list "--with-colons" "--no-greeting" "--batch"
135                     (if type "--list-secret-keys" "--list-keys")
136                     string)))
137     (with-temp-buffer
138       (apply #'call-process pgg-gpg-program nil t nil args)
139       (goto-char (point-min))
140       (if (re-search-forward "^\\(sec\\|pub\\):[^:]*:[^:]*:[^:]*:\\([^:]*\\)"
141                              nil t)
142           (substring (match-string 2) 8)))))
143
144 (defun pgg-gpg-lookup-key-owner (string &optional all)
145   "Search keys associated with STRING and return owner of identified key.
146
147 The value may be just the bare key id, or it may be a combination of the
148 user name associated with the key and the key id, with the key id enclosed
149 in \"<...>\" angle brackets.
150
151 Optional ALL non-nil means search all keys, including secret keys."
152   (let ((args (list "--with-colons" "--no-greeting" "--batch"
153                     (if all "--list-secret-keys" "--list-keys")
154                     string))
155         (key-regexp (concat "^\\(sec\\|pub\\)"
156                             ":[^:]*:[^:]*:[^:]*:\\([^:]*\\):[^:]*"
157                             ":[^:]*:[^:]*:[^:]*:\\([^:]*\\):")))
158     (with-temp-buffer
159       (apply #'call-process pgg-gpg-program nil t nil args)
160       (goto-char (point-min))
161       (if (re-search-forward key-regexp
162                              nil t)
163           (match-string 3)))))
164
165 (defun pgg-gpg-key-id-from-key-owner (key-owner)
166   (cond ((not key-owner) nil)
167         ;; Extract bare key id from outermost paired angle brackets, if any:
168         ((string-match "[^<]*<\\(.+\\)>[^>]*" key-owner)
169          (substring key-owner (match-beginning 1)(match-end 1)))
170         (key-owner)))
171
172 (defun pgg-gpg-encrypt-region (start end recipients &optional sign passphrase)
173   "Encrypt the current region between START and END.
174
175 If optional argument SIGN is non-nil, do a combined sign and encrypt.
176
177 If optional PASSPHRASE is not specified, it will be obtained from the
178 passphrase cache or user."
179   (let* ((pgg-gpg-user-id (or pgg-gpg-user-id pgg-default-user-id))
180          (passphrase (or passphrase
181                          (when sign
182                            (pgg-read-passphrase
183                             (format "GnuPG passphrase for %s: "
184                                     pgg-gpg-user-id)
185                             pgg-gpg-user-id))))
186          (args
187           (append
188            (list "--batch" "--textmode" "--armor" "--always-trust" "--encrypt")
189            (if sign (list "--sign" "--local-user" pgg-gpg-user-id))
190            (if recipients
191                (apply #'nconc
192                       (mapcar (lambda (rcpt)
193                                 (list pgg-gpg-recipient-argument rcpt))
194                               (append recipients
195                                       (if pgg-encrypt-for-me
196                                           (list pgg-gpg-user-id)))))))))
197     (pgg-as-lbt start end 'CRLF
198       (pgg-gpg-process-region start end passphrase pgg-gpg-program args))
199     (when sign
200       (with-current-buffer pgg-errors-buffer
201         ;; Possibly cache passphrase under, e.g. "jas", for future sign.
202         (pgg-gpg-possibly-cache-passphrase passphrase pgg-gpg-user-id)
203         ;; Possibly cache passphrase under, e.g. B565716F, for future decrypt.
204         (pgg-gpg-possibly-cache-passphrase passphrase)))
205     (pgg-process-when-success)))
206
207 (defun pgg-gpg-encrypt-symmetric-region (start end &optional passphrase)
208   "Encrypt the current region between START and END with symmetric cipher.
209
210 If optional PASSPHRASE is not specified, it will be obtained from the
211 passphrase cache or user."
212   (let* ((passphrase (or passphrase
213                          (pgg-read-passphrase
214                           "GnuPG passphrase for symmetric encryption: ")))
215          (args
216           (append (list "--batch" "--textmode" "--armor" "--symmetric" ))))
217     (pgg-as-lbt start end 'CRLF
218       (pgg-gpg-process-region start end passphrase pgg-gpg-program args))
219     (pgg-process-when-success)))
220
221 (defun pgg-gpg-decrypt-region (start end &optional passphrase)
222   "Decrypt the current region between START and END.
223
224 If optional PASSPHRASE is not specified, it will be obtained from the
225 passphrase cache or user."
226   (let* ((current-buffer (current-buffer))
227          (message-keys (with-temp-buffer
228                          (insert-buffer-substring current-buffer)
229                          (pgg-decode-armor-region (point-min) (point-max))))
230          (secret-keys (pgg-gpg-lookup-all-secret-keys))
231          ;; XXX the user is stuck if they need to use the passphrase for
232          ;;     any but the first secret key for which the message is
233          ;;     encrypted.  ideally, we would incrementally give them a
234          ;;     chance with subsequent keys each time they fail with one.
235          (key (pgg-gpg-select-matching-key message-keys secret-keys))
236          (key-owner (and key (pgg-gpg-lookup-key-owner key t)))
237          (key-id (pgg-gpg-key-id-from-key-owner key-owner))
238          (pgg-gpg-user-id (or key-id key
239                               pgg-gpg-user-id pgg-default-user-id))
240          (passphrase (or passphrase
241                          (pgg-read-passphrase
242                           (format (if (pgg-gpg-symmetric-key-p message-keys)
243                                       "Passphrase for symmetric decryption: "
244                                     "GnuPG passphrase for %s: ")
245                                   (or key-owner "??"))
246                           pgg-gpg-user-id)))
247          (args '("--batch" "--decrypt")))
248     (pgg-gpg-process-region start end passphrase pgg-gpg-program args)
249     (with-current-buffer pgg-errors-buffer
250       (pgg-gpg-possibly-cache-passphrase passphrase pgg-gpg-user-id)
251       (goto-char (point-min))
252       (re-search-forward "^\\[GNUPG:] DECRYPTION_OKAY\\>" nil t))))
253
254 ;;;###autoload
255 (defun pgg-gpg-symmetric-key-p (message-keys)
256   "True if decoded armor MESSAGE-KEYS has symmetric encryption indicator."
257   (let (result)
258     (dolist (key message-keys result)
259       (when (and (eq (car key) 3)
260                  (member '(symmetric-key-algorithm) key))
261         (setq result key)))))
262
263 (defun pgg-gpg-select-matching-key (message-keys secret-keys)
264   "Choose a key from MESSAGE-KEYS that matches one of the keys in SECRET-KEYS."
265   (loop for message-key in message-keys
266         for message-key-id = (and (equal (car message-key) 1)
267                                   (cdr (assq 'key-identifier
268                                              (cdr message-key))))
269         for key = (and message-key-id (pgg-lookup-key message-key-id 'encrypt))
270         when (and key (member key secret-keys)) return key))
271
272 (defun pgg-gpg-sign-region (start end &optional cleartext passphrase)
273   "Make detached signature from text between START and END."
274   (let* ((pgg-gpg-user-id (or pgg-gpg-user-id pgg-default-user-id))
275          (passphrase (or passphrase
276                          (pgg-read-passphrase
277                           (format "GnuPG passphrase for %s: " pgg-gpg-user-id)
278                           pgg-gpg-user-id)))
279          (args
280           (list (if cleartext "--clearsign" "--detach-sign")
281                 "--armor" "--batch" "--verbose"
282                 "--local-user" pgg-gpg-user-id))
283          (inhibit-read-only t)
284          buffer-read-only)
285     (pgg-as-lbt start end 'CRLF
286       (pgg-gpg-process-region start end passphrase pgg-gpg-program args))
287     (with-current-buffer pgg-errors-buffer
288       ;; Possibly cache passphrase under, e.g. "jas", for future sign.
289       (pgg-gpg-possibly-cache-passphrase passphrase pgg-gpg-user-id)
290       ;; Possibly cache passphrase under, e.g. B565716F, for future decrypt.
291       (pgg-gpg-possibly-cache-passphrase passphrase))
292     (pgg-process-when-success)))
293
294 (defun pgg-gpg-verify-region (start end &optional signature)
295   "Verify region between START and END as the detached signature SIGNATURE."
296   (let ((args '("--batch" "--verify")))
297     (when (stringp signature)
298       (setq args (append args (list signature))))
299     (setq args (append args '("-")))
300     (pgg-gpg-process-region start end nil pgg-gpg-program args)
301     (with-current-buffer pgg-errors-buffer
302       (goto-char (point-min))
303       (while (re-search-forward "^gpg: \\(.*\\)\n" nil t)
304         (with-current-buffer pgg-output-buffer
305           (insert-buffer-substring pgg-errors-buffer
306                                    (match-beginning 1) (match-end 0)))
307         (delete-region (match-beginning 0) (match-end 0)))
308       (goto-char (point-min))
309       (re-search-forward "^\\[GNUPG:] GOODSIG\\>" nil t))))
310
311 (defun pgg-gpg-insert-key ()
312   "Insert public key at point."
313   (let* ((pgg-gpg-user-id (or pgg-gpg-user-id pgg-default-user-id))
314          (args (list "--batch" "--export" "--armor"
315                      pgg-gpg-user-id)))
316     (pgg-gpg-process-region (point)(point) nil pgg-gpg-program args)
317     (insert-buffer-substring pgg-output-buffer)))
318
319 (defun pgg-gpg-snarf-keys-region (start end)
320   "Add all public keys in region between START and END to the keyring."
321   (let ((args '("--import" "--batch" "-")) status)
322     (pgg-gpg-process-region start end nil pgg-gpg-program args)
323     (set-buffer pgg-errors-buffer)
324     (goto-char (point-min))
325     (when (re-search-forward "^\\[GNUPG:] IMPORT_RES\\>" nil t)
326       (setq status (buffer-substring (match-end 0)
327                                      (progn (end-of-line)(point)))
328             status (vconcat (mapcar #'string-to-number (split-string status))))
329       (erase-buffer)
330       (insert (format "Imported %d key(s).
331 \tArmor contains %d key(s) [%d bad, %d old].\n"
332                       (+ (aref status 2)
333                          (aref status 10))
334                       (aref status 0)
335                       (aref status 1)
336                       (+ (aref status 4)
337                          (aref status 11)))
338               (if (zerop (aref status 9))
339                   ""
340                 "\tSecret keys are imported.\n")))
341     (append-to-buffer pgg-output-buffer (point-min)(point-max))
342     (pgg-process-when-success)))
343
344 (provide 'pgg-gpg)
345
346 ;;; arch-tag: 2aa5d5d8-93a0-4865-9312-33e29830e000
347 ;;; pgg-gpg.el ends here