2001-01-21 00:00:00 ShengHuo ZHU <zsh@cs.rochester.edu>
[gnus] / lisp / smime.el
1 ;;; smime.el --- S/MIME support library
2 ;; Copyright (c) 2000, 2001 Free Software Foundation, Inc.
3
4 ;; Author: Simon Josefsson <simon@josefsson.org>
5 ;; Keywords: SMIME X.509 PEM OpenSSL
6
7 ;; This file is not a part of GNU Emacs, but the same permissions apply.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published
11 ;; by the Free Software Foundation; either version 2, or (at your
12 ;; option) any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 ;; 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., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;; This library perform S/MIME operations from within Emacs.
27 ;;
28 ;; Functions for fetching certificates from public repositories are
29 ;; provided, currently only from DNS.  LDAP support (via EUDC) is planned.
30 ;;
31 ;; It uses OpenSSL (tested with version 0.9.5a and 0.9.6) for signing,
32 ;; encryption and decryption.
33 ;;
34 ;; Some general knowledge of S/MIME, X.509, PKCS#12, PEM etc is
35 ;; probably required to use this library in any useful way.
36 ;; Especially, don't expect this library to buy security for you.  If
37 ;; you don't understand what you are doing, you're as likely to lose
38 ;; security than gain any by using this library.
39 ;;
40 ;; This library is not intended to provide a "raw" API for S/MIME,
41 ;; PKCSx or similar, it's intended to perform common operations
42 ;; done on messages encoded in these formats.  The terminology chosen
43 ;; reflect this.
44
45 ;;; Quick introduction:
46
47 ;; Get your S/MIME certificate from VeriSign or someplace.  I used
48 ;; Netscape to generate the key and certificate request and stuff, and
49 ;; Netscape can export the key into PKCS#12 format.
50 ;;
51 ;; Enter OpenSSL.  To be able to use this library, it need to have the
52 ;; SMIME key readable in PEM format.  OpenSSL is used to convert the
53 ;; key:
54 ;;
55 ;; $ openssl pkcs12 -in mykey.p12 -clcerts -nodes > mykey.pem
56 ;; ...
57 ;;
58 ;; Now, use M-x customize-variable smime-keys and add mykey.pem as
59 ;; a key.
60 ;;
61 ;; Now you should be able to sign messages!  Create a buffer and write
62 ;; something and run M-x smime-sign-buffer RET RET and you should see
63 ;; your message MIME armoured and a signature.  Encryption, M-x
64 ;; smime-encrypt-buffer, should also work.
65 ;;
66 ;; To be able to verify messages you need to build up trust with
67 ;; someone.  Perhaps you trust the CA that issued your certificate, at
68 ;; least I did, so I export it's certificates from my PKCS#12
69 ;; certificate with:
70 ;;
71 ;; $ openssl pkcs12 -in mykey.p12 -cacerts -nodes > cacert.pem
72 ;; ...
73 ;;
74 ;; Now, use M-x customize-variable smime-CAs and add cacert.pem as a
75 ;; CA certificate.
76 ;;
77 ;; You should now be able to sign messages, and even verify messages
78 ;; sent by others that use the same CA as you.
79
80 ;; Bugs:
81 ;;
82 ;; Don't complain that this package doesn't do encrypted PEM files,
83 ;; submit a patch instead.  I store my keys in a safe place, so I
84 ;; didn't need the encryption.  Also, programming was made easier by
85 ;; that decision.  One might think that this even influenced were I
86 ;; store my keys, and one would probably be right. :-)
87 ;;
88 ;; Suggestions and comments are appreciated, mail me at simon@josefsson.org.
89
90 ;; <rant>
91 ;;
92 ;; I would include pointers to introductory text on concepts used in
93 ;; this library here, but the material I've read are so horrible I
94 ;; don't want to recomend them.
95 ;;
96 ;; Why can't someone write a simple introduction to all this stuff?
97 ;; Until then, much of this resemble security by obscurity.
98 ;;
99 ;; Also, I'm not going to mention anything about the wonders of
100 ;; cryptopolitics.  Oops, I just did.
101 ;;
102 ;; </rant>
103
104 ;;; Revision history:
105
106 ;; version 0 not released
107
108 ;;; Code:
109
110 (require 'dig)
111 (eval-when-compile (require 'cl))
112
113 (defgroup smime nil
114   "S/MIME configuration.")
115
116 (defcustom smime-keys nil
117   "Map mail addresses to a file containing Certificate (and private key).
118 The file is assumed to be in PEM format and not encrypted."
119   :type '(repeat (list (string :tag "Mail address")
120                        (file :tag "File name")))
121   :group 'smime)
122
123 (defcustom smime-CA-directory nil
124   "Directory containing certificates for CAs you trust.
125 Directory should contain files (in PEM format) named to the X.509
126 hash of the certificate.  This can be done using OpenSSL such as:
127
128 $ ln -s ca.pem `openssl x509 -noout -hash -in ca.pem`
129
130 where `ca.pem' is the file containing a PEM encoded X.509 CA
131 certificate."
132   :type '(choice (const :tag "none" nil)
133                  directory)
134   :group 'smime)
135
136 (defcustom smime-CA-file nil
137   "Files containing certificates for CAs you trust.
138 File should contain certificates in PEM format."
139   :type '(choice (const :tag "none" nil)
140                  file)
141   :group 'smime)
142
143 (defcustom smime-certificate-directory "~/Mail/certs/"
144   "Directory containing other people's certificates.
145 It should contain files named to the X.509 hash of the certificate,
146 and the files themself should be in PEM format."
147 ;The S/MIME library provide simple functionality for fetching
148 ;certificates into this directory, so there is no need to populate it
149 ;manually.
150   :type 'directory
151   :group 'smime)
152
153 (defcustom smime-openssl-program
154   (and (condition-case ()
155            (eq 0 (call-process "openssl" nil nil nil "version"))
156          (error nil))
157        "openssl")
158   "Name of OpenSSL binary."
159   :type 'string
160   :group 'smime)
161
162 (defcustom smime-dns-server nil
163   "DNS server to query certificates from.
164 If nil, use system defaults."
165   :type '(choice (const :tag "System defaults")
166                  string)
167   :group 'dig)
168
169 (defvar smime-details-buffer "*OpenSSL output*")
170
171 ;; OpenSSL wrappers.
172
173 (defun smime-call-openssl-region (b e buf &rest args)
174   (case (apply 'call-process-region b e smime-openssl-program nil buf nil args)
175     (0 t)
176     (1 (message "OpenSSL: An error occurred parsing the command options.") nil)
177     (2 (message "OpenSSL: One of the input files could not be read.") nil)
178     (3 (message "OpenSSL: An error occurred creating the PKCS#7 file or when reading the MIME message.") nil)
179     (4 (message "OpenSSL: An error occurred decrypting or verifying the message.") nil)
180     (t (error "Unknown OpenSSL exitcode") nil)))
181
182 ;; Sign+encrypt region
183
184 (defun smime-sign-region (b e keyfile)
185   "Sign region with certified key in KEYFILE.
186 If signing fails, the buffer is not modified.  Region is assumed to
187 have proper MIME tags.  KEYFILE is expected to contain a PEM encoded
188 private key and certificate."
189   (let ((buffer (generate-new-buffer (generate-new-buffer-name " *smime*"))))
190     (prog1
191         (when (smime-call-openssl-region b e buffer "smime" "-sign"
192                                          "-signer" (expand-file-name keyfile))
193           (delete-region b e)
194           (insert-buffer buffer)
195           (when (looking-at "^MIME-Version: 1.0$")
196             (delete-region (point) (progn (forward-line 1) (point))))
197           t)
198       (with-current-buffer (get-buffer-create smime-details-buffer)
199         (goto-char (point-max))
200         (insert-buffer buffer))
201       (kill-buffer buffer))))
202
203 (defun smime-encrypt-region (b e certfiles)
204   "Encrypt region for recipients specified in CERTFILES.
205 If encryption fails, the buffer is not modified.  Region is assumed to
206 have proper MIME tags.  CERTFILES is a list of filenames, each file
207 is expected to contain of a PEM encoded certificate."
208   (let ((buffer (generate-new-buffer (generate-new-buffer-name " *smime*"))))
209     (prog1
210         (when (apply 'smime-call-openssl-region b e buffer "smime" "-encrypt"
211                      (mapcar 'expand-file-name certfiles))
212           (delete-region b e)
213           (insert-buffer buffer)
214           (when (looking-at "^MIME-Version: 1.0$")
215             (delete-region (point) (progn (forward-line 1) (point))))
216           t)
217       (with-current-buffer (get-buffer-create smime-details-buffer)
218         (goto-char (point-max))
219         (insert-buffer buffer))
220       (kill-buffer buffer))))
221
222 ;; Sign+encrypt buffer
223
224 (defun smime-sign-buffer (&optional keyfile buffer)
225   "S/MIME sign BUFFER with key in KEYFILE.
226 KEYFILE should contain a PEM encoded key and certificate."
227   (interactive)
228   (with-current-buffer (or buffer (current-buffer))
229     (smime-sign-region
230      (point-min) (point-max)
231      (or keyfile
232          (smime-get-key-by-email
233           (completing-read "Sign using which signature? " smime-keys nil nil
234                            (and (listp (car-safe smime-keys))
235                                 (caar smime-keys))))))))
236
237 (defun smime-encrypt-buffer (&optional certfiles buffer)
238   "S/MIME encrypt BUFFER for recipients specified in CERTFILES.
239 CERTFILES is a list of filenames, each file is expected to consist of
240 a PEM encoded key and certificate.  Uses current buffer if BUFFER is
241 nil."
242   (interactive)
243   (with-current-buffer (or buffer (current-buffer))
244     (smime-encrypt-region
245      (point-min) (point-max)
246      (or certfiles
247          (list (read-file-name "Recipient's S/MIME certificate: "
248                                smime-certificate-directory nil))))))
249
250 ;; Verify+decrypt region
251
252 (defun smime-verify-region (b e)
253   (let ((buffer (get-buffer-create smime-details-buffer))
254         (CAs (cond (smime-CA-file
255                     (list "-CAfile" (expand-file-name smime-CA-file)))
256                    (smime-CA-directory
257                     (list "-CApath" (expand-file-name smime-CA-directory)))
258                    (t
259                     (error "No CA configured.")))))
260     (with-current-buffer buffer
261       (erase-buffer))
262     (if (apply 'smime-call-openssl-region b e buffer "smime" "-verify"
263                "-out" "/dev/null" CAs)
264         (message "S/MIME message verified succesfully.")
265       (message "S/MIME message NOT verified successfully.")
266       nil)))
267
268 (defun smime-noverify-region (b e)
269   (let ((buffer (get-buffer-create smime-details-buffer)))
270     (with-current-buffer buffer
271       (erase-buffer))
272     (if (apply 'smime-call-openssl-region b e buffer "smime" "-verify"
273                "-noverify" "-out" '("/dev/null"))
274         (message "S/MIME message verified succesfully.")
275       (message "S/MIME message NOT verified successfully.")
276       nil)))
277
278 (defun smime-decrypt-region (b e keyfile)
279   (let ((buffer (generate-new-buffer (generate-new-buffer-name "*smime*")))
280         CAs)
281     (when (apply 'smime-call-openssl-region b e buffer "smime" "-decrypt"
282                  "-recip" (list keyfile))
283
284       )
285     (with-current-buffer (get-buffer-create smime-details-buffer)
286       (goto-char (point-max))
287       (insert-buffer buffer))
288     (kill-buffer buffer)))
289
290 ;; Verify+Decrypt buffer
291
292 (defun smime-verify-buffer (&optional buffer)
293   "Verify integrity of S/MIME message in BUFFER.
294 Uses current buffer if BUFFER is nil."
295   (interactive)
296   (with-current-buffer (or buffer (current-buffer))
297     (smime-verify-region (point-min) (point-max))))
298
299 (defun smime-noverify-buffer (&optional buffer)
300   "Verify integrity of S/MIME message in BUFFER.
301 Uses current buffer if BUFFER is nil.
302 Does NOT verify validity of certificate."
303   (interactive)
304   (with-current-buffer (or buffer (current-buffer))
305     (smime-noverify-region (point-min) (point-max))))
306
307 (defun smime-decrypt-buffer (&optional buffer keyfile)
308   "Decrypt S/MIME message in BUFFER using KEYFILE.
309 Uses current buffer if BUFFER is nil, queries user of KEYFILE is nil."
310   (interactive)
311   (with-current-buffer (or buffer (current-buffer))
312     (smime-decrypt-region
313      (point-min) (point-max)
314      (expand-file-name
315       (or keyfile
316           (smime-get-key-by-email
317            (completing-read "Decrypt with which key? " smime-keys nil nil
318                             (and (listp (car-safe smime-keys))
319                                  (caar smime-keys)))))))))
320
321 ;; Various operations
322
323 (defun smime-pkcs7-region (b e)
324   "Convert S/MIME message between points B and E into a PKCS7 message."
325   (let ((buffer (get-buffer-create smime-details-buffer)))
326     (with-current-buffer buffer
327       (erase-buffer))
328     (when (smime-call-openssl-region b e buffer "smime" "-pk7out")
329       (delete-region b e)
330       (insert-buffer-substring buffer)
331       t)))
332
333 (defun smime-pkcs7-certificates-region (b e)
334   "Extract any certificates enclosed in PKCS7 message between points B and E."
335   (let ((buffer (get-buffer-create smime-details-buffer)))
336     (with-current-buffer buffer
337       (erase-buffer))
338     (when (smime-call-openssl-region b e buffer "pkcs7" "-print_certs" "-text")
339       (delete-region b e)
340       (insert-buffer-substring buffer)
341       t)))
342
343 (defun smime-pkcs7-email-region (b e)
344   "Get email addresses contained in certificate between points B and E.
345 A string or a list of strings is returned."
346   (let ((buffer (get-buffer-create smime-details-buffer)))
347     (with-current-buffer buffer
348       (erase-buffer))
349     (when (smime-call-openssl-region b e buffer "x509" "-email" "-noout")
350       (delete-region b e)
351       (insert-buffer-substring buffer)
352       t)))
353
354 (defalias 'smime-point-at-eol
355   (if (fboundp 'point-at-eol)
356       'point-at-eol
357     'line-end-position))
358
359 (defun smime-buffer-as-string-region (b e)
360   "Return each line in region between B and E as a list of strings."
361   (save-excursion
362     (goto-char b)
363     (let (res)
364       (while (< (point) e)
365         (let ((str (buffer-substring (point) (smime-point-at-eol))))
366           (unless (string= "" str)
367             (push str res)))
368         (forward-line))
369       res)))
370
371 ;; Find certificates
372
373 (defun smime-mail-to-domain (mailaddr)
374   (if (string-match "@" mailaddr)
375       (replace-match "." 'fixedcase 'literal mailaddr)
376     mailaddr))
377
378 (defun smime-cert-by-dns (mail)
379   (let* ((dig-dns-server smime-dns-server)
380          (digbuf (dig-invoke (smime-mail-to-domain mail) "cert" nil nil "+vc"))
381          (retbuf (generate-new-buffer (format "*certificate for %s*" mail)))
382          (certrr (with-current-buffer digbuf
383                    (dig-extract-rr (smime-mail-to-domain mail) "cert")))
384          (cert (and certrr (dig-rr-get-pkix-cert certrr))))
385       (if cert
386           (with-current-buffer retbuf
387             (insert "-----BEGIN CERTIFICATE-----\n")
388             (let ((i 0) (len (length cert)))
389               (while (> (- len 64) i)
390                 (insert (substring cert i (+ i 64)) "\n")
391                 (setq i (+ i 64)))
392               (insert (substring cert i len) "\n"))
393             (insert "-----END CERTIFICATE-----\n"))
394         (kill-buffer retbuf)
395         (setq retbuf nil))
396       (kill-buffer digbuf)
397       retbuf))
398
399 ;; User interface.
400
401 (defvar smime-buffer "*SMIME*")
402
403 (defvar smime-mode-map nil)
404 (put 'smime-mode 'mode-class 'special)
405
406 (unless smime-mode-map
407   (setq smime-mode-map (make-sparse-keymap))
408   (suppress-keymap smime-mode-map)
409
410   (define-key smime-mode-map "q" 'smime-exit)
411   (define-key smime-mode-map "f" 'smime-certificate-info))
412
413 (defun smime-mode ()
414   "Major mode for browsing, viewing and fetching certificates.
415
416 All normal editing commands are switched off.
417 \\<smime-mode-map>
418
419 The following commands are available:
420
421 \\{smime-mode-map}"
422   (interactive)
423   (kill-all-local-variables)
424   (setq major-mode 'smime-mode)
425   (setq mode-name "SMIME")
426   (setq mode-line-process nil)
427   (use-local-map smime-mode-map)
428   (buffer-disable-undo)
429   (setq truncate-lines t)
430   (setq buffer-read-only t))
431
432 (defun smime-certificate-info (certfile)
433   (interactive "fCertificate file: ")
434   (let ((buffer (get-buffer-create (format "*certificate %s*" certfile))))
435     (switch-to-buffer buffer)
436     (erase-buffer)
437     (call-process smime-openssl-program nil buffer 'display
438                   "x509" "-in" (expand-file-name certfile) "-text")
439     (fundamental-mode)
440     (set-buffer-modified-p nil)
441     (toggle-read-only t)
442     (goto-char (point-min))))
443
444 (defun smime-draw-buffer ()
445   (with-current-buffer smime-buffer
446     (let (buffer-read-only)
447       (erase-buffer)
448       (insert "\nYour keys:\n")
449       (dolist (key smime-keys)
450         (insert
451          (format "\t\t%s: %s\n" (car key) (cadr key))))
452       (insert "\nTrusted Certificate Authoritys:\n")
453       (insert "\nKnown Certificates:\n"))))
454
455 (defun smime ()
456   "Go to the SMIME buffer."
457   (interactive)
458   (unless (get-buffer smime-buffer)
459     (save-excursion
460       (set-buffer (get-buffer-create smime-buffer))
461       (smime-mode)))
462   (smime-draw-buffer)
463   (switch-to-buffer smime-buffer))
464
465 (defun smime-exit ()
466   "Quit the S/MIME buffer."
467   (interactive)
468   (kill-buffer (current-buffer)))
469
470 ;; Other functions
471
472 (defun smime-get-key-by-email (email)
473   (cadr (assoc email smime-keys)))
474
475 (provide 'smime)
476
477 ;;; smime.el ends here