Avoid double load of password-cache.
[gnus] / lisp / smime.el
1 ;;; smime.el --- S/MIME support library
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: SMIME X.509 PEM OpenSSL
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 ;; This library perform S/MIME operations from within Emacs.
29 ;;
30 ;; Functions for fetching certificates from public repositories are
31 ;; provided, currently from DNS and LDAP.
32 ;;
33 ;; It uses OpenSSL (tested with version 0.9.5a and 0.9.6) for signing,
34 ;; encryption and decryption.
35 ;;
36 ;; Some general knowledge of S/MIME, X.509, PKCS#12, PEM etc is
37 ;; probably required to use this library in any useful way.
38 ;; Especially, don't expect this library to buy security for you.  If
39 ;; you don't understand what you are doing, you're as likely to lose
40 ;; security than gain any by using this library.
41 ;;
42 ;; This library is not intended to provide a "raw" API for S/MIME,
43 ;; PKCSx or similar, it's intended to perform common operations
44 ;; done on messages encoded in these formats.  The terminology chosen
45 ;; reflect this.
46 ;;
47 ;; The home of this file is in Gnus CVS, but also available from
48 ;; http://josefsson.org/smime.html.
49
50 ;;; Quick introduction:
51
52 ;; Get your S/MIME certificate from VeriSign or someplace.  I used
53 ;; Netscape to generate the key and certificate request and stuff, and
54 ;; Netscape can export the key into PKCS#12 format.
55 ;;
56 ;; Enter OpenSSL.  To be able to use this library, it need to have the
57 ;; SMIME key readable in PEM format.  OpenSSL is used to convert the
58 ;; key:
59 ;;
60 ;; $ openssl pkcs12 -in mykey.p12 -clcerts -nodes > mykey.pem
61 ;; ...
62 ;;
63 ;; Now, use M-x customize-variable smime-keys and add mykey.pem as
64 ;; a key.
65 ;;
66 ;; Now you should be able to sign messages!  Create a buffer and write
67 ;; something and run M-x smime-sign-buffer RET RET and you should see
68 ;; your message MIME armoured and a signature.  Encryption, M-x
69 ;; smime-encrypt-buffer, should also work.
70 ;;
71 ;; To be able to verify messages you need to build up trust with
72 ;; someone.  Perhaps you trust the CA that issued your certificate, at
73 ;; least I did, so I export it's certificates from my PKCS#12
74 ;; certificate with:
75 ;;
76 ;; $ openssl pkcs12 -in mykey.p12 -cacerts -nodes > cacert.pem
77 ;; ...
78 ;;
79 ;; Now, use M-x customize-variable smime-CAs and add cacert.pem as a
80 ;; CA certificate.
81 ;;
82 ;; You should now be able to sign messages, and even verify messages
83 ;; sent by others that use the same CA as you.
84
85 ;; Bugs:
86 ;;
87 ;; Don't complain that this package doesn't do encrypted PEM files,
88 ;; submit a patch instead.  I store my keys in a safe place, so I
89 ;; didn't need the encryption.  Also, programming was made easier by
90 ;; that decision.  One might think that this even influenced were I
91 ;; store my keys, and one would probably be right. :-)
92 ;;
93 ;; Update: Mathias Herberts sent the patch.  However, it uses
94 ;; environment variables to pass the password to OpenSSL, which is
95 ;; slightly insecure. Hence a new todo: use a better -passin method.
96 ;;
97 ;; Cache password for e.g. 1h
98 ;;
99 ;; Suggestions and comments are appreciated, mail me at simon@josefsson.org.
100
101 ;; begin rant
102 ;;
103 ;; I would include pointers to introductory text on concepts used in
104 ;; this library here, but the material I've read are so horrible I
105 ;; don't want to recomend them.
106 ;;
107 ;; Why can't someone write a simple introduction to all this stuff?
108 ;; Until then, much of this resemble security by obscurity.
109 ;;
110 ;; Also, I'm not going to mention anything about the wonders of
111 ;; cryptopolitics.  Oops, I just did.
112 ;;
113 ;; end rant
114
115 ;;; Revision history:
116
117 ;; 2000-06-05  initial version, committed to Gnus CVS contrib/
118 ;; 2000-10-28  retrieve certificates via DNS CERT RRs
119 ;; 2001-10-14  posted to gnu.emacs.sources
120 ;; 2005-02-13  retrieve certificates via LDAP
121
122 ;;; Code:
123
124 ;; For Emacs < 22.2.
125 (eval-and-compile
126   (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
127 (require 'dig)
128
129 (if (featurep 'xemacs)
130     ;; Not all XEmacs versions support `noerror' arg of `require'.
131     (or (featurep 'password-cache)
132         (load "password-cache" t)
133         (require 'password))
134   (or (require 'password-cache nil t)
135       (require 'password)))
136
137 (eval-when-compile (require 'cl))
138
139 (eval-and-compile
140   (cond
141    ((fboundp 'replace-in-string)
142     (defalias 'smime-replace-in-string 'replace-in-string))
143    ((fboundp 'replace-regexp-in-string)
144     (defun smime-replace-in-string  (string regexp newtext &optional literal)
145       "Replace all matches for REGEXP with NEWTEXT in STRING.
146 If LITERAL is non-nil, insert NEWTEXT literally.  Return a new
147 string containing the replacements.
148
149 This is a compatibility function for different Emacsen."
150       (replace-regexp-in-string regexp newtext string nil literal)))))
151
152 (defgroup smime nil
153   "S/MIME configuration."
154   :group 'mime)
155
156 (defcustom smime-keys nil
157   "*Map mail addresses to a file containing Certificate (and private key).
158 The file is assumed to be in PEM format. You can also associate additional
159 certificates to be sent with every message to each address."
160   :type '(repeat (list (string :tag "Mail address")
161                        (file :tag "File name")
162                        (repeat :tag "Additional certificate files"
163                                (file :tag "File name"))))
164   :group 'smime)
165
166 (defcustom smime-CA-directory nil
167   "*Directory containing certificates for CAs you trust.
168 Directory should contain files (in PEM format) named to the X.509
169 hash of the certificate.  This can be done using OpenSSL such as:
170
171 $ ln -s ca.pem `openssl x509 -noout -hash -in ca.pem`.0
172
173 where `ca.pem' is the file containing a PEM encoded X.509 CA
174 certificate."
175   :type '(choice (const :tag "none" nil)
176                  directory)
177   :group 'smime)
178
179 (defcustom smime-CA-file nil
180   "*Files containing certificates for CAs you trust.
181 File should contain certificates in PEM format."
182   :version "22.1"
183   :type '(choice (const :tag "none" nil)
184                  file)
185   :group 'smime)
186
187 (defcustom smime-certificate-directory "~/Mail/certs/"
188   "*Directory containing other people's certificates.
189 It should contain files named to the X.509 hash of the certificate,
190 and the files themself should be in PEM format."
191 ;The S/MIME library provide simple functionality for fetching
192 ;certificates into this directory, so there is no need to populate it
193 ;manually.
194   :type 'directory
195   :group 'smime)
196
197 (defcustom smime-openssl-program
198   (and (condition-case ()
199            (eq 0 (call-process "openssl" nil nil nil "version"))
200          (error nil))
201        "openssl")
202   "*Name of OpenSSL binary."
203   :type 'string
204   :group 'smime)
205
206 ;; OpenSSL option to select the encryption cipher
207
208 (defcustom smime-encrypt-cipher "-des3"
209   "*Cipher algorithm used for encryption."
210   :version "22.1"
211   :type '(choice (const :tag "Triple DES" "-des3")
212                  (const :tag "DES"  "-des")
213                  (const :tag "RC2 40 bits" "-rc2-40")
214                  (const :tag "RC2 64 bits" "-rc2-64")
215                  (const :tag "RC2 128 bits" "-rc2-128"))
216   :group 'smime)
217
218 (defcustom smime-crl-check nil
219   "*Check revocation status of signers certificate using CRLs.
220 Enabling this will have OpenSSL check the signers certificate
221 against a certificate revocation list (CRL).
222
223 For this to work the CRL must be up-to-date and since they are
224 normally updated quite often (ie. several times a day) you
225 probably need some tool to keep them up-to-date. Unfortunately
226 Gnus cannot do this for you.
227
228 The CRL should either be appended (in PEM format) to your
229 `smime-CA-file' or be located in a file (also in PEM format) in
230 your `smime-certificate-directory' named to the X.509 hash of the
231 certificate with .r0 as file name extension.
232
233 At least OpenSSL version 0.9.7 is required for this to work."
234   :type '(choice (const :tag "No check" nil)
235                  (const :tag "Check certificate" "-crl_check")
236                  (const :tag "Check certificate chain" "-crl_check_all"))
237   :group 'smime)
238
239 (defcustom smime-dns-server nil
240   "*DNS server to query certificates from.
241 If nil, use system defaults."
242   :version "22.1"
243   :type '(choice (const :tag "System defaults")
244                  string)
245   :group 'smime)
246
247 (defcustom smime-ldap-host-list nil
248   "A list of LDAP hosts with S/MIME user certificates.
249 If needed search base, binddn, passwd, etc. for the LDAP host
250 must be set in `ldap-host-parameters-alist'."
251   :type '(repeat (string :tag "Host name"))
252   :version "23.0" ;; No Gnus
253   :group 'smime)
254
255 (defvar smime-details-buffer "*OpenSSL output*")
256
257 ;; Use mm-util?
258 (eval-and-compile
259   (defalias 'smime-make-temp-file
260     (if (fboundp 'make-temp-file)
261         'make-temp-file
262       (lambda (prefix &optional dir-flag) ;; Simple implementation
263         (expand-file-name
264          (make-temp-name prefix)
265          (if (fboundp 'temp-directory)
266              (temp-directory)
267            temporary-file-directory))))))
268
269 ;; Password dialog function
270 (declare-function password-read-and-add "password-cache" (prompt &optional key))
271
272 (defun smime-ask-passphrase (&optional cache-key)
273   "Asks the passphrase to unlock the secret key.
274 If `cache-key' and `password-cache' is non-nil then cache the
275 password under `cache-key'."
276   (let ((passphrase
277          (password-read-and-add
278           "Passphrase for secret key (RET for no passphrase): " cache-key)))
279     (if (string= passphrase "")
280         nil
281       passphrase)))
282
283 ;; OpenSSL wrappers.
284
285 (defun smime-call-openssl-region (b e buf &rest args)
286   (case (apply 'call-process-region b e smime-openssl-program nil buf nil args)
287     (0 t)
288     (1 (message "OpenSSL: An error occurred parsing the command options.") nil)
289     (2 (message "OpenSSL: One of the input files could not be read.") nil)
290     (3 (message "OpenSSL: An error occurred creating the PKCS#7 file or when reading the MIME message.") nil)
291     (4 (message "OpenSSL: An error occurred decrypting or verifying the message.") nil)
292     (t (error "Unknown OpenSSL exitcode") nil)))
293
294 (defun smime-make-certfiles (certfiles)
295   (if certfiles
296       (append (list "-certfile" (expand-file-name (car certfiles)))
297               (smime-make-certfiles (cdr certfiles)))))
298
299 ;; Sign+encrypt region
300
301 (defun smime-sign-region (b e keyfile)
302   "Sign region with certified key in KEYFILE.
303 If signing fails, the buffer is not modified.  Region is assumed to
304 have proper MIME tags.  KEYFILE is expected to contain a PEM encoded
305 private key and certificate as its car, and a list of additional
306 certificates to include in its caar.  If no additional certificates is
307 included, KEYFILE may be the file containing the PEM encoded private
308 key and certificate itself."
309   (smime-new-details-buffer)
310   (let* ((certfiles (and (cdr-safe keyfile) (cadr keyfile)))
311          (keyfile (or (car-safe keyfile) keyfile))
312          (buffer (generate-new-buffer (generate-new-buffer-name " *smime*")))
313          (passphrase (smime-ask-passphrase (expand-file-name keyfile)))
314          (tmpfile (smime-make-temp-file "smime")))
315     (if passphrase
316         (setenv "GNUS_SMIME_PASSPHRASE" passphrase))
317     (prog1
318         (when (prog1
319                   (apply 'smime-call-openssl-region b e (list buffer tmpfile)
320                          "smime" "-sign" "-signer" (expand-file-name keyfile)
321                          (append
322                           (smime-make-certfiles certfiles)
323                           (if passphrase
324                               (list "-passin" "env:GNUS_SMIME_PASSPHRASE"))))
325                 (if passphrase
326                     (setenv "GNUS_SMIME_PASSPHRASE" "" t))
327                 (with-current-buffer smime-details-buffer
328                   (insert-file-contents tmpfile)
329                   (delete-file tmpfile)))
330           (delete-region b e)
331           (insert-buffer-substring buffer)
332           (goto-char b)
333           (when (looking-at "^MIME-Version: 1.0$")
334             (delete-region (point) (progn (forward-line 1) (point))))
335           t)
336       (with-current-buffer smime-details-buffer
337         (goto-char (point-max))
338         (insert-buffer-substring buffer))
339       (kill-buffer buffer))))
340
341 (defun smime-encrypt-region (b e certfiles)
342   "Encrypt region for recipients specified in CERTFILES.
343 If encryption fails, the buffer is not modified.  Region is assumed to
344 have proper MIME tags.  CERTFILES is a list of filenames, each file
345 is expected to contain of a PEM encoded certificate."
346   (smime-new-details-buffer)
347   (let ((buffer (generate-new-buffer (generate-new-buffer-name " *smime*")))
348         (tmpfile (smime-make-temp-file "smime")))
349     (prog1
350         (when (prog1
351                   (apply 'smime-call-openssl-region b e (list buffer tmpfile)
352                          "smime" "-encrypt" smime-encrypt-cipher
353                          (mapcar 'expand-file-name certfiles))
354                 (with-current-buffer smime-details-buffer
355                   (insert-file-contents tmpfile)
356                   (delete-file tmpfile)))
357           (delete-region b e)
358           (insert-buffer-substring buffer)
359           (goto-char b)
360           (when (looking-at "^MIME-Version: 1.0$")
361             (delete-region (point) (progn (forward-line 1) (point))))
362           t)
363       (with-current-buffer smime-details-buffer
364         (goto-char (point-max))
365         (insert-buffer-substring buffer))
366       (kill-buffer buffer))))
367
368 ;; Sign+encrypt buffer
369
370 (defun smime-sign-buffer (&optional keyfile buffer)
371   "S/MIME sign BUFFER with key in KEYFILE.
372 KEYFILE should contain a PEM encoded key and certificate."
373   (interactive)
374   (with-current-buffer (or buffer (current-buffer))
375     (unless (smime-sign-region
376              (point-min) (point-max)
377              (if keyfile
378                  keyfile
379                (smime-get-key-with-certs-by-email
380                 (completing-read
381                  (concat "Sign using key"
382                          (if smime-keys
383                              (concat " (default " (caar smime-keys) "): ")
384                            ": "))
385                  smime-keys nil nil (car-safe (car-safe smime-keys))))))
386       (error "Signing failed"))))
387
388 (defun smime-encrypt-buffer (&optional certfiles buffer)
389   "S/MIME encrypt BUFFER for recipients specified in CERTFILES.
390 CERTFILES is a list of filenames, each file is expected to consist of
391 a PEM encoded key and certificate.  Uses current buffer if BUFFER is
392 nil."
393   (interactive)
394   (with-current-buffer (or buffer (current-buffer))
395     (unless (smime-encrypt-region
396              (point-min) (point-max)
397              (or certfiles
398                  (list (read-file-name "Recipient's S/MIME certificate: "
399                                        smime-certificate-directory nil))))
400       (error "Encryption failed"))))
401
402 ;; Verify+decrypt region
403
404 (defun smime-verify-region (b e)
405   "Verify S/MIME message in region between B and E.
406 Returns non-nil on success.
407 Any details (stdout and stderr) are left in the buffer specified by
408 `smime-details-buffer'."
409   (smime-new-details-buffer)
410   (let ((CAs (append (if smime-CA-file
411                          (list "-CAfile"
412                                (expand-file-name smime-CA-file)))
413                      (if smime-CA-directory
414                          (list "-CApath"
415                                (expand-file-name smime-CA-directory))))))
416     (unless CAs
417       (error "No CA configured"))
418     (if smime-crl-check
419         (add-to-list 'CAs smime-crl-check))
420     (if (apply 'smime-call-openssl-region b e (list smime-details-buffer t)
421                "smime" "-verify" "-out" "/dev/null" CAs)
422         t
423       (insert-buffer-substring smime-details-buffer)
424       nil)))
425
426 (defun smime-noverify-region (b e)
427   "Verify integrity of S/MIME message in region between B and E.
428 Returns non-nil on success.
429 Any details (stdout and stderr) are left in the buffer specified by
430 `smime-details-buffer'."
431   (smime-new-details-buffer)
432   (if (apply 'smime-call-openssl-region b e (list smime-details-buffer t)
433              "smime" "-verify" "-noverify" "-out" '("/dev/null"))
434       t
435     (insert-buffer-substring smime-details-buffer)
436     nil))
437
438 (defvar from)
439
440 (defun smime-decrypt-region (b e keyfile)
441   "Decrypt S/MIME message in region between B and E with key in KEYFILE.
442 On success, replaces region with decrypted data and return non-nil.
443 Any details (stderr on success, stdout and stderr on error) are left
444 in the buffer specified by `smime-details-buffer'."
445   (smime-new-details-buffer)
446   (let ((buffer (generate-new-buffer (generate-new-buffer-name " *smime*")))
447         CAs (passphrase (smime-ask-passphrase (expand-file-name keyfile)))
448         (tmpfile (smime-make-temp-file "smime")))
449     (if passphrase
450         (setenv "GNUS_SMIME_PASSPHRASE" passphrase))
451     (if (prog1
452             (apply 'smime-call-openssl-region b e
453                    (list buffer tmpfile)
454                    "smime" "-decrypt" "-recip" (expand-file-name keyfile)
455                    (if passphrase
456                        (list "-passin" "env:GNUS_SMIME_PASSPHRASE")))
457           (if passphrase
458               (setenv "GNUS_SMIME_PASSPHRASE" "" t))
459           (with-current-buffer smime-details-buffer
460             (insert-file-contents tmpfile)
461             (delete-file tmpfile)))
462         (progn
463           (delete-region b e)
464           (when (boundp 'from)
465             ;; `from' is dynamically bound in mm-dissect.
466             (insert "From: " from "\n"))
467           (insert-buffer-substring buffer)
468           (kill-buffer buffer)
469           t)
470       (with-current-buffer smime-details-buffer
471         (insert-buffer-substring buffer))
472       (kill-buffer buffer)
473       (delete-region b e)
474       (insert-buffer-substring smime-details-buffer)
475       nil)))
476
477 ;; Verify+Decrypt buffer
478
479 (defun smime-verify-buffer (&optional buffer)
480   "Verify integrity of S/MIME message in BUFFER.
481 Uses current buffer if BUFFER is nil. Returns non-nil on success.
482 Any details (stdout and stderr) are left in the buffer specified by
483 `smime-details-buffer'."
484   (interactive)
485   (with-current-buffer (or buffer (current-buffer))
486     (smime-verify-region (point-min) (point-max))))
487
488 (defun smime-noverify-buffer (&optional buffer)
489   "Verify integrity of S/MIME message in BUFFER.
490 Does NOT verify validity of certificate (only message integrity).
491 Uses current buffer if BUFFER is nil. Returns non-nil on success.
492 Any details (stdout and stderr) are left in the buffer specified by
493 `smime-details-buffer'."
494   (interactive)
495   (with-current-buffer (or buffer (current-buffer))
496     (smime-noverify-region (point-min) (point-max))))
497
498 (defun smime-decrypt-buffer (&optional buffer keyfile)
499   "Decrypt S/MIME message in BUFFER using KEYFILE.
500 Uses current buffer if BUFFER is nil, and query user of KEYFILE if it's nil.
501 On success, replaces data in buffer and return non-nil.
502 Any details (stderr on success, stdout and stderr on error) are left
503 in the buffer specified by `smime-details-buffer'."
504   (interactive)
505   (with-current-buffer (or buffer (current-buffer))
506     (smime-decrypt-region
507      (point-min) (point-max)
508      (expand-file-name
509       (or keyfile
510           (smime-get-key-by-email
511            (completing-read
512             (concat "Decipher using key"
513                     (if smime-keys (concat " (default " (caar smime-keys) "): ")
514                       ": "))
515             smime-keys nil nil (car-safe (car-safe smime-keys)))))))))
516
517 ;; Various operations
518
519 (defun smime-new-details-buffer ()
520   (with-current-buffer (get-buffer-create smime-details-buffer)
521     (erase-buffer)))
522
523 (defun smime-pkcs7-region (b e)
524   "Convert S/MIME message between points B and E into a PKCS7 message."
525   (smime-new-details-buffer)
526   (when (smime-call-openssl-region b e smime-details-buffer "smime" "-pk7out")
527     (delete-region b e)
528     (insert-buffer-substring smime-details-buffer)
529     t))
530
531 (defun smime-pkcs7-certificates-region (b e)
532   "Extract any certificates enclosed in PKCS7 message between points B and E."
533   (smime-new-details-buffer)
534   (when (smime-call-openssl-region
535          b e smime-details-buffer "pkcs7" "-print_certs" "-text")
536     (delete-region b e)
537     (insert-buffer-substring smime-details-buffer)
538     t))
539
540 (defun smime-pkcs7-email-region (b e)
541   "Get email addresses contained in certificate between points B and E.
542 A string or a list of strings is returned."
543   (smime-new-details-buffer)
544   (when (smime-call-openssl-region
545          b e smime-details-buffer "x509" "-email" "-noout")
546     (delete-region b e)
547     (insert-buffer-substring smime-details-buffer)
548     t))
549
550 ;; Utility functions
551
552 (defun smime-get-certfiles (keyfile keys)
553   (if keys
554       (let ((curkey (car keys))
555             (otherkeys (cdr keys)))
556         (if (string= keyfile (cadr curkey))
557             (caddr curkey)
558           (smime-get-certfiles keyfile otherkeys)))))
559
560 (defun smime-buffer-as-string-region (b e)
561   "Return each line in region between B and E as a list of strings."
562   (save-excursion
563     (goto-char b)
564     (let (res)
565       (while (< (point) e)
566         (let ((str (buffer-substring (point) (point-at-eol))))
567           (unless (string= "" str)
568             (push str res)))
569         (forward-line))
570       res)))
571
572 ;; Find certificates
573
574 (defun smime-mail-to-domain (mailaddr)
575   (if (string-match "@" mailaddr)
576       (replace-match "." 'fixedcase 'literal mailaddr)
577     mailaddr))
578
579 (defun smime-cert-by-dns (mail)
580   "Find certificate via DNS for address MAIL."
581   (let* ((dig-dns-server smime-dns-server)
582          (digbuf (dig-invoke (smime-mail-to-domain mail) "cert" nil nil "+vc"))
583          (retbuf (generate-new-buffer (format "*certificate for %s*" mail)))
584          (certrr (with-current-buffer digbuf
585                    (dig-extract-rr (smime-mail-to-domain mail) "cert")))
586          (cert (and certrr (dig-rr-get-pkix-cert certrr))))
587       (if cert
588           (with-current-buffer retbuf
589             (insert "-----BEGIN CERTIFICATE-----\n")
590             (let ((i 0) (len (length cert)))
591               (while (> (- len 64) i)
592                 (insert (substring cert i (+ i 64)) "\n")
593                 (setq i (+ i 64)))
594               (insert (substring cert i len) "\n"))
595             (insert "-----END CERTIFICATE-----\n"))
596         (kill-buffer retbuf)
597         (setq retbuf nil))
598       (kill-buffer digbuf)
599       retbuf))
600
601 (defun smime-cert-by-ldap-1 (mail host)
602   "Get cetificate for MAIL from the ldap server at HOST."
603   (let ((ldapresult
604          (funcall
605           (if (or (featurep 'xemacs)
606                   ;; For Emacs >= 22 we don't need smime-ldap.el
607                   (< emacs-major-version 22))
608               (progn
609                 (require 'smime-ldap)
610                 'smime-ldap-search)
611             'ldap-search)
612           (concat "mail=" mail)
613           host '("userCertificate") nil))
614         (retbuf (generate-new-buffer (format "*certificate for %s*" mail)))
615         cert)
616     (if (and (>= (length ldapresult) 1)
617              (> (length (cadaar ldapresult)) 0))
618         (with-current-buffer retbuf
619           ;; Certificates on LDAP servers _should_ be in DER format,
620           ;; but there are some servers out there that distributes the
621           ;; certificates in PEM format (with or without
622           ;; header/footer) so we try to handle them anyway.
623           (if (or (string= (substring (cadaar ldapresult) 0 27)
624                            "-----BEGIN CERTIFICATE-----")
625                   (string= (substring (cadaar ldapresult) 0 3)
626                            "MII"))
627               (setq cert
628                     (smime-replace-in-string
629                      (cadaar ldapresult)
630                      (concat "\\(\n\\|\r\\|-----BEGIN CERTIFICATE-----\\|"
631                              "-----END CERTIFICATE-----\\)")
632                      "" t))
633             (setq cert (base64-encode-string (cadaar ldapresult) t)))
634           (insert "-----BEGIN CERTIFICATE-----\n")
635           (let ((i 0) (len (length cert)))
636             (while (> (- len 64) i)
637               (insert (substring cert i (+ i 64)) "\n")
638               (setq i (+ i 64)))
639             (insert (substring cert i len) "\n"))
640           (insert "-----END CERTIFICATE-----\n"))
641       (kill-buffer retbuf)
642       (setq retbuf nil))
643     retbuf))
644
645 (defun smime-cert-by-ldap (mail)
646   "Find certificate via LDAP for address MAIL."
647   (if smime-ldap-host-list
648       (catch 'certbuf
649         (dolist (host smime-ldap-host-list)
650           (let ((retbuf (smime-cert-by-ldap-1 mail host)))
651             (when retbuf
652               (throw 'certbuf retbuf)))))))
653
654 ;; User interface.
655
656 (defvar smime-buffer "*SMIME*")
657
658 (defvar smime-mode-map nil)
659 (put 'smime-mode 'mode-class 'special)
660
661 (unless smime-mode-map
662   (setq smime-mode-map (make-sparse-keymap))
663   (suppress-keymap smime-mode-map)
664
665   (define-key smime-mode-map "q" 'smime-exit)
666   (define-key smime-mode-map "f" 'smime-certificate-info))
667
668 (autoload 'gnus-run-mode-hooks "gnus-util")
669
670 (defun smime-mode ()
671   "Major mode for browsing, viewing and fetching certificates.
672
673 All normal editing commands are switched off.
674 \\<smime-mode-map>
675
676 The following commands are available:
677
678 \\{smime-mode-map}"
679   (interactive)
680   (kill-all-local-variables)
681   (setq major-mode 'smime-mode)
682   (setq mode-name "SMIME")
683   (setq mode-line-process nil)
684   (use-local-map smime-mode-map)
685   (buffer-disable-undo)
686   (setq truncate-lines t)
687   (setq buffer-read-only t)
688   (gnus-run-mode-hooks 'smime-mode-hook))
689
690 (defun smime-certificate-info (certfile)
691   (interactive "fCertificate file: ")
692   (let ((buffer (get-buffer-create (format "*certificate %s*" certfile))))
693     (switch-to-buffer buffer)
694     (erase-buffer)
695     (call-process smime-openssl-program nil buffer 'display
696                   "x509" "-in" (expand-file-name certfile) "-text")
697     (fundamental-mode)
698     (set-buffer-modified-p nil)
699     (toggle-read-only t)
700     (goto-char (point-min))))
701
702 (defun smime-draw-buffer ()
703   (with-current-buffer smime-buffer
704     (let (buffer-read-only)
705       (erase-buffer)
706       (insert "\nYour keys:\n")
707       (dolist (key smime-keys)
708         (insert
709          (format "\t\t%s: %s\n" (car key) (cadr key))))
710       (insert "\nTrusted Certificate Authoritys:\n")
711       (insert "\nKnown Certificates:\n"))))
712
713 (defun smime ()
714   "Go to the SMIME buffer."
715   (interactive)
716   (unless (get-buffer smime-buffer)
717     (save-excursion
718       (set-buffer (get-buffer-create smime-buffer))
719       (smime-mode)))
720   (smime-draw-buffer)
721   (switch-to-buffer smime-buffer))
722
723 (defun smime-exit ()
724   "Quit the S/MIME buffer."
725   (interactive)
726   (kill-buffer (current-buffer)))
727
728 ;; Other functions
729
730 (defun smime-get-key-by-email (email)
731   (cadr (assoc email smime-keys)))
732
733 (defun smime-get-key-with-certs-by-email (email)
734   (cdr (assoc email smime-keys)))
735
736 (provide 'smime)
737
738 ;;; arch-tag: e3f9b938-5085-4510-8a11-6625269c9a9e
739 ;;; smime.el ends here