mml-smime.el 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. ;;; mml-smime.el --- S/MIME support for MML
  2. ;; Copyright (C) 2000-2012 Free Software Foundation, Inc.
  3. ;; Author: Simon Josefsson <simon@josefsson.org>
  4. ;; Keywords: Gnus, MIME, S/MIME, MML
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;;; Code:
  18. ;; For Emacs <22.2 and XEmacs.
  19. (eval-and-compile
  20. (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
  21. (eval-when-compile (require 'cl))
  22. (require 'smime)
  23. (require 'mm-decode)
  24. (require 'mml-sec)
  25. (autoload 'message-narrow-to-headers "message")
  26. (autoload 'message-fetch-field "message")
  27. (defcustom mml-smime-use (if (featurep 'epg) 'epg 'openssl)
  28. "Whether to use OpenSSL or EPG to decrypt S/MIME messages.
  29. Defaults to EPG if it's loaded."
  30. :group 'mime-security
  31. :type '(choice (const :tag "EPG" epg)
  32. (const :tag "OpenSSL" openssl)))
  33. (defvar mml-smime-function-alist
  34. '((openssl mml-smime-openssl-sign
  35. mml-smime-openssl-encrypt
  36. mml-smime-openssl-sign-query
  37. mml-smime-openssl-encrypt-query
  38. mml-smime-openssl-verify
  39. mml-smime-openssl-verify-test)
  40. (epg mml-smime-epg-sign
  41. mml-smime-epg-encrypt
  42. nil
  43. nil
  44. mml-smime-epg-verify
  45. mml-smime-epg-verify-test)))
  46. (defcustom mml-smime-cache-passphrase mml-secure-cache-passphrase
  47. "If t, cache passphrase."
  48. :group 'mime-security
  49. :type 'boolean)
  50. (defcustom mml-smime-passphrase-cache-expiry mml-secure-passphrase-cache-expiry
  51. "How many seconds the passphrase is cached.
  52. Whether the passphrase is cached at all is controlled by
  53. `mml-smime-cache-passphrase'."
  54. :group 'mime-security
  55. :type 'integer)
  56. (defcustom mml-smime-signers nil
  57. "A list of your own key ID which will be used to sign a message."
  58. :group 'mime-security
  59. :type '(repeat (string :tag "Key ID")))
  60. (defun mml-smime-sign (cont)
  61. (let ((func (nth 1 (assq mml-smime-use mml-smime-function-alist))))
  62. (if func
  63. (funcall func cont)
  64. (error "Cannot find sign function"))))
  65. (defun mml-smime-encrypt (cont)
  66. (let ((func (nth 2 (assq mml-smime-use mml-smime-function-alist))))
  67. (if func
  68. (funcall func cont)
  69. (error "Cannot find encrypt function"))))
  70. (defun mml-smime-sign-query ()
  71. (let ((func (nth 3 (assq mml-smime-use mml-smime-function-alist))))
  72. (if func
  73. (funcall func))))
  74. (defun mml-smime-encrypt-query ()
  75. (let ((func (nth 4 (assq mml-smime-use mml-smime-function-alist))))
  76. (if func
  77. (funcall func))))
  78. (defun mml-smime-verify (handle ctl)
  79. (let ((func (nth 5 (assq mml-smime-use mml-smime-function-alist))))
  80. (if func
  81. (funcall func handle ctl)
  82. handle)))
  83. (defun mml-smime-verify-test (handle ctl)
  84. (let ((func (nth 6 (assq mml-smime-use mml-smime-function-alist))))
  85. (if func
  86. (funcall func handle ctl))))
  87. (defun mml-smime-openssl-sign (cont)
  88. (when (null smime-keys)
  89. (customize-variable 'smime-keys)
  90. (error "No S/MIME keys configured, use customize to add your key"))
  91. (smime-sign-buffer (cdr (assq 'keyfile cont)))
  92. (goto-char (point-min))
  93. (while (search-forward "\r\n" nil t)
  94. (replace-match "\n" t t))
  95. (goto-char (point-max)))
  96. (defun mml-smime-openssl-encrypt (cont)
  97. (let (certnames certfiles tmp file tmpfiles)
  98. ;; xxx tmp files are always an security issue
  99. (while (setq tmp (pop cont))
  100. (if (and (consp tmp) (eq (car tmp) 'certfile))
  101. (push (cdr tmp) certnames)))
  102. (while (setq tmp (pop certnames))
  103. (if (not (and (not (file-exists-p tmp))
  104. (get-buffer tmp)))
  105. (push tmp certfiles)
  106. (setq file (mm-make-temp-file (expand-file-name "mml."
  107. mm-tmp-directory)))
  108. (with-current-buffer tmp
  109. (write-region (point-min) (point-max) file))
  110. (push file certfiles)
  111. (push file tmpfiles)))
  112. (if (smime-encrypt-buffer certfiles)
  113. (progn
  114. (while (setq tmp (pop tmpfiles))
  115. (delete-file tmp))
  116. t)
  117. (while (setq tmp (pop tmpfiles))
  118. (delete-file tmp))
  119. nil))
  120. (goto-char (point-max)))
  121. (defvar gnus-extract-address-components)
  122. (defun mml-smime-openssl-sign-query ()
  123. ;; query information (what certificate) from user when MML tag is
  124. ;; added, for use later by the signing process
  125. (when (null smime-keys)
  126. (customize-variable 'smime-keys)
  127. (error "No S/MIME keys configured, use customize to add your key"))
  128. (list 'keyfile
  129. (if (= (length smime-keys) 1)
  130. (cadar smime-keys)
  131. (or (let ((from (cadr (funcall (if (boundp
  132. 'gnus-extract-address-components)
  133. gnus-extract-address-components
  134. 'mail-extract-address-components)
  135. (or (save-excursion
  136. (save-restriction
  137. (message-narrow-to-headers)
  138. (message-fetch-field "from")))
  139. "")))))
  140. (and from (smime-get-key-by-email from)))
  141. (smime-get-key-by-email
  142. (gnus-completing-read "Sign this part with what signature"
  143. (mapcar 'car smime-keys) nil nil nil
  144. (and (listp (car-safe smime-keys))
  145. (caar smime-keys))))))))
  146. (defun mml-smime-get-file-cert ()
  147. (ignore-errors
  148. (list 'certfile (read-file-name
  149. "File with recipient's S/MIME certificate: "
  150. smime-certificate-directory nil t ""))))
  151. (defun mml-smime-get-dns-cert ()
  152. ;; todo: deal with comma separated multiple recipients
  153. (let (result who bad cert)
  154. (condition-case ()
  155. (while (not result)
  156. (setq who (read-from-minibuffer
  157. (format "%sLookup certificate for: " (or bad ""))
  158. (cadr (funcall (if (boundp
  159. 'gnus-extract-address-components)
  160. gnus-extract-address-components
  161. 'mail-extract-address-components)
  162. (or (save-excursion
  163. (save-restriction
  164. (message-narrow-to-headers)
  165. (message-fetch-field "to")))
  166. "")))))
  167. (if (setq cert (smime-cert-by-dns who))
  168. (setq result (list 'certfile (buffer-name cert)))
  169. (setq bad (format "`%s' not found. " who))))
  170. (quit))
  171. result))
  172. (defun mml-smime-get-ldap-cert ()
  173. ;; todo: deal with comma separated multiple recipients
  174. (let (result who bad cert)
  175. (condition-case ()
  176. (while (not result)
  177. (setq who (read-from-minibuffer
  178. (format "%sLookup certificate for: " (or bad ""))
  179. (cadr (funcall gnus-extract-address-components
  180. (or (save-excursion
  181. (save-restriction
  182. (message-narrow-to-headers)
  183. (message-fetch-field "to")))
  184. "")))))
  185. (if (setq cert (smime-cert-by-ldap who))
  186. (setq result (list 'certfile (buffer-name cert)))
  187. (setq bad (format "`%s' not found. " who))))
  188. (quit))
  189. result))
  190. (autoload 'gnus-completing-read "gnus-util")
  191. (defun mml-smime-openssl-encrypt-query ()
  192. ;; todo: try dns/ldap automatically first, before prompting user
  193. (let (certs done)
  194. (while (not done)
  195. (ecase (read (gnus-completing-read
  196. "Fetch certificate from"
  197. '("dns" "ldap" "file") t nil nil
  198. "ldap"))
  199. (dns (setq certs (append certs
  200. (mml-smime-get-dns-cert))))
  201. (ldap (setq certs (append certs
  202. (mml-smime-get-ldap-cert))))
  203. (file (setq certs (append certs
  204. (mml-smime-get-file-cert)))))
  205. (setq done (not (y-or-n-p "Add more recipients? "))))
  206. certs))
  207. (defun mml-smime-openssl-verify (handle ctl)
  208. (with-temp-buffer
  209. (insert-buffer-substring (mm-handle-multipart-original-buffer ctl))
  210. (goto-char (point-min))
  211. (insert (format "Content-Type: %s; " (mm-handle-media-type ctl)))
  212. (insert (format "protocol=\"%s\"; "
  213. (mm-handle-multipart-ctl-parameter ctl 'protocol)))
  214. (insert (format "micalg=\"%s\"; "
  215. (mm-handle-multipart-ctl-parameter ctl 'micalg)))
  216. (insert (format "boundary=\"%s\"\n\n"
  217. (mm-handle-multipart-ctl-parameter ctl 'boundary)))
  218. (when (get-buffer smime-details-buffer)
  219. (kill-buffer smime-details-buffer))
  220. (let ((buf (current-buffer))
  221. (good-signature (smime-noverify-buffer))
  222. (good-certificate (and (or smime-CA-file smime-CA-directory)
  223. (smime-verify-buffer)))
  224. addresses openssl-output)
  225. (setq openssl-output (with-current-buffer smime-details-buffer
  226. (buffer-string)))
  227. (if (not good-signature)
  228. (progn
  229. ;; we couldn't verify message, fail with openssl output as message
  230. (mm-set-handle-multipart-parameter
  231. mm-security-handle 'gnus-info "Failed")
  232. (mm-set-handle-multipart-parameter
  233. mm-security-handle 'gnus-details
  234. (concat "OpenSSL failed to verify message integrity:\n"
  235. "-------------------------------------------\n"
  236. openssl-output)))
  237. ;; verify mail addresses in mail against those in certificate
  238. (when (and (smime-pkcs7-region (point-min) (point-max))
  239. (smime-pkcs7-certificates-region (point-min) (point-max)))
  240. (with-temp-buffer
  241. (insert-buffer-substring buf)
  242. (goto-char (point-min))
  243. (while (re-search-forward "-----END CERTIFICATE-----" nil t)
  244. (when (smime-pkcs7-email-region (point-min) (point))
  245. (setq addresses (append (smime-buffer-as-string-region
  246. (point-min) (point)) addresses)))
  247. (delete-region (point-min) (point)))
  248. (setq addresses (mapcar 'downcase addresses))))
  249. (if (not (member (downcase (or (mm-handle-multipart-from ctl) "")) addresses))
  250. (mm-set-handle-multipart-parameter
  251. mm-security-handle 'gnus-info "Sender address forged")
  252. (if good-certificate
  253. (mm-set-handle-multipart-parameter
  254. mm-security-handle 'gnus-info "Ok (sender authenticated)")
  255. (mm-set-handle-multipart-parameter
  256. mm-security-handle 'gnus-info "Ok (sender not trusted)")))
  257. (mm-set-handle-multipart-parameter
  258. mm-security-handle 'gnus-details
  259. (concat "Sender claimed to be: " (mm-handle-multipart-from ctl) "\n"
  260. (if addresses
  261. (concat "Addresses in certificate: "
  262. (mapconcat 'identity addresses ", "))
  263. "No addresses found in certificate. (Requires OpenSSL 0.9.6 or later.)")
  264. "\n" "\n"
  265. "OpenSSL output:\n"
  266. "---------------\n" openssl-output "\n"
  267. "Certificate(s) inside S/MIME signature:\n"
  268. "---------------------------------------\n"
  269. (buffer-string) "\n")))))
  270. handle)
  271. (defun mml-smime-openssl-verify-test (handle ctl)
  272. smime-openssl-program)
  273. (defvar epg-user-id-alist)
  274. (defvar epg-digest-algorithm-alist)
  275. (defvar inhibit-redisplay)
  276. (defvar password-cache-expiry)
  277. (eval-when-compile
  278. (autoload 'epg-make-context "epg")
  279. (autoload 'epg-context-set-armor "epg")
  280. (autoload 'epg-context-set-signers "epg")
  281. (autoload 'epg-context-result-for "epg")
  282. (autoload 'epg-new-signature-digest-algorithm "epg")
  283. (autoload 'epg-verify-result-to-string "epg")
  284. (autoload 'epg-list-keys "epg")
  285. (autoload 'epg-decrypt-string "epg")
  286. (autoload 'epg-verify-string "epg")
  287. (autoload 'epg-sign-string "epg")
  288. (autoload 'epg-encrypt-string "epg")
  289. (autoload 'epg-passphrase-callback-function "epg")
  290. (autoload 'epg-context-set-passphrase-callback "epg")
  291. (autoload 'epg-configuration "epg-config")
  292. (autoload 'epg-expand-group "epg-config")
  293. (autoload 'epa-select-keys "epa"))
  294. (defvar mml-smime-epg-secret-key-id-list nil)
  295. (defun mml-smime-epg-passphrase-callback (context key-id ignore)
  296. (if (eq key-id 'SYM)
  297. (epg-passphrase-callback-function context key-id nil)
  298. (let* (entry
  299. (passphrase
  300. (password-read
  301. (if (eq key-id 'PIN)
  302. "Passphrase for PIN: "
  303. (if (setq entry (assoc key-id epg-user-id-alist))
  304. (format "Passphrase for %s %s: " key-id (cdr entry))
  305. (format "Passphrase for %s: " key-id)))
  306. (if (eq key-id 'PIN)
  307. "PIN"
  308. key-id))))
  309. (when passphrase
  310. (let ((password-cache-expiry mml-smime-passphrase-cache-expiry))
  311. (password-cache-add key-id passphrase))
  312. (setq mml-smime-epg-secret-key-id-list
  313. (cons key-id mml-smime-epg-secret-key-id-list))
  314. (copy-sequence passphrase)))))
  315. (declare-function epg-key-sub-key-list "ext:epg" (key))
  316. (declare-function epg-sub-key-capability "ext:epg" (sub-key))
  317. (declare-function epg-sub-key-validity "ext:epg" (sub-key))
  318. (defun mml-smime-epg-find-usable-key (keys usage)
  319. (catch 'found
  320. (while keys
  321. (let ((pointer (epg-key-sub-key-list (car keys))))
  322. (while pointer
  323. (if (and (memq usage (epg-sub-key-capability (car pointer)))
  324. (not (memq (epg-sub-key-validity (car pointer))
  325. '(revoked expired))))
  326. (throw 'found (car keys)))
  327. (setq pointer (cdr pointer))))
  328. (setq keys (cdr keys)))))
  329. (autoload 'mml-compute-boundary "mml")
  330. ;; We require mm-decode, which requires mm-bodies, which autoloads
  331. ;; message-options-get (!).
  332. (declare-function message-options-set "message" (symbol value))
  333. (defun mml-smime-epg-sign (cont)
  334. (let* ((inhibit-redisplay t)
  335. (context (epg-make-context 'CMS))
  336. (boundary (mml-compute-boundary cont))
  337. signer-key
  338. (signers
  339. (or (message-options-get 'mml-smime-epg-signers)
  340. (message-options-set
  341. 'mml-smime-epg-signers
  342. (if (eq mm-sign-option 'guided)
  343. (epa-select-keys context "\
  344. Select keys for signing.
  345. If no one is selected, default secret key is used. "
  346. mml-smime-signers t)
  347. (if mml-smime-signers
  348. (mapcar
  349. (lambda (signer)
  350. (setq signer-key (mml-smime-epg-find-usable-key
  351. (epg-list-keys context signer t)
  352. 'sign))
  353. (unless (or signer-key
  354. (y-or-n-p
  355. (format "No secret key for %s; skip it? "
  356. signer)))
  357. (error "No secret key for %s" signer))
  358. signer-key)
  359. mml-smime-signers))))))
  360. signature micalg)
  361. (epg-context-set-signers context signers)
  362. (if mml-smime-cache-passphrase
  363. (epg-context-set-passphrase-callback
  364. context
  365. #'mml-smime-epg-passphrase-callback))
  366. (condition-case error
  367. (setq signature (epg-sign-string context
  368. (mm-replace-in-string (buffer-string)
  369. "\n" "\r\n")
  370. t)
  371. mml-smime-epg-secret-key-id-list nil)
  372. (error
  373. (while mml-smime-epg-secret-key-id-list
  374. (password-cache-remove (car mml-smime-epg-secret-key-id-list))
  375. (setq mml-smime-epg-secret-key-id-list
  376. (cdr mml-smime-epg-secret-key-id-list)))
  377. (signal (car error) (cdr error))))
  378. (if (epg-context-result-for context 'sign)
  379. (setq micalg (epg-new-signature-digest-algorithm
  380. (car (epg-context-result-for context 'sign)))))
  381. (goto-char (point-min))
  382. (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
  383. boundary))
  384. (if micalg
  385. (insert (format "\tmicalg=%s; "
  386. (downcase
  387. (cdr (assq micalg
  388. epg-digest-algorithm-alist))))))
  389. (insert "protocol=\"application/pkcs7-signature\"\n")
  390. (insert (format "\n--%s\n" boundary))
  391. (goto-char (point-max))
  392. (insert (format "\n--%s\n" boundary))
  393. (insert "Content-Type: application/pkcs7-signature; name=smime.p7s
  394. Content-Transfer-Encoding: base64
  395. Content-Disposition: attachment; filename=smime.p7s
  396. ")
  397. (insert (base64-encode-string signature) "\n")
  398. (goto-char (point-max))
  399. (insert (format "--%s--\n" boundary))
  400. (goto-char (point-max))))
  401. (defun mml-smime-epg-encrypt (cont)
  402. (let ((inhibit-redisplay t)
  403. (context (epg-make-context 'CMS))
  404. (config (epg-configuration))
  405. (recipients (message-options-get 'mml-smime-epg-recipients))
  406. cipher signers
  407. (boundary (mml-compute-boundary cont))
  408. recipient-key)
  409. (unless recipients
  410. (setq recipients
  411. (apply #'nconc
  412. (mapcar
  413. (lambda (recipient)
  414. (or (epg-expand-group config recipient)
  415. (list recipient)))
  416. (split-string
  417. (or (message-options-get 'message-recipients)
  418. (message-options-set 'message-recipients
  419. (read-string "Recipients: ")))
  420. "[ \f\t\n\r\v,]+"))))
  421. (if (eq mm-encrypt-option 'guided)
  422. (setq recipients
  423. (epa-select-keys context "\
  424. Select recipients for encryption.
  425. If no one is selected, symmetric encryption will be performed. "
  426. recipients))
  427. (setq recipients
  428. (mapcar
  429. (lambda (recipient)
  430. (setq recipient-key (mml-smime-epg-find-usable-key
  431. (epg-list-keys context recipient)
  432. 'encrypt))
  433. (unless (or recipient-key
  434. (y-or-n-p
  435. (format "No public key for %s; skip it? "
  436. recipient)))
  437. (error "No public key for %s" recipient))
  438. recipient-key)
  439. recipients))
  440. (unless recipients
  441. (error "No recipient specified")))
  442. (message-options-set 'mml-smime-epg-recipients recipients))
  443. (if mml-smime-cache-passphrase
  444. (epg-context-set-passphrase-callback
  445. context
  446. #'mml-smime-epg-passphrase-callback))
  447. (condition-case error
  448. (setq cipher
  449. (epg-encrypt-string context (buffer-string) recipients)
  450. mml-smime-epg-secret-key-id-list nil)
  451. (error
  452. (while mml-smime-epg-secret-key-id-list
  453. (password-cache-remove (car mml-smime-epg-secret-key-id-list))
  454. (setq mml-smime-epg-secret-key-id-list
  455. (cdr mml-smime-epg-secret-key-id-list)))
  456. (signal (car error) (cdr error))))
  457. (delete-region (point-min) (point-max))
  458. (goto-char (point-min))
  459. (insert "\
  460. Content-Type: application/pkcs7-mime;
  461. smime-type=enveloped-data;
  462. name=smime.p7m
  463. Content-Transfer-Encoding: base64
  464. Content-Disposition: attachment; filename=smime.p7m
  465. ")
  466. (insert (base64-encode-string cipher))
  467. (goto-char (point-max))))
  468. (defun mml-smime-epg-verify (handle ctl)
  469. (catch 'error
  470. (let ((inhibit-redisplay t)
  471. context plain signature-file part signature)
  472. (when (or (null (setq part (mm-find-raw-part-by-type
  473. ctl (or (mm-handle-multipart-ctl-parameter
  474. ctl 'protocol)
  475. "application/pkcs7-signature")
  476. t)))
  477. (null (setq signature (or (mm-find-part-by-type
  478. (cdr handle)
  479. "application/pkcs7-signature"
  480. nil t)
  481. (mm-find-part-by-type
  482. (cdr handle)
  483. "application/x-pkcs7-signature"
  484. nil t)))))
  485. (mm-set-handle-multipart-parameter
  486. mm-security-handle 'gnus-info "Corrupted")
  487. (throw 'error handle))
  488. (setq part (mm-replace-in-string part "\n" "\r\n")
  489. context (epg-make-context 'CMS))
  490. (condition-case error
  491. (setq plain (epg-verify-string context (mm-get-part signature) part))
  492. (error
  493. (mm-set-handle-multipart-parameter
  494. mm-security-handle 'gnus-info "Failed")
  495. (if (eq (car error) 'quit)
  496. (mm-set-handle-multipart-parameter
  497. mm-security-handle 'gnus-details "Quit.")
  498. (mm-set-handle-multipart-parameter
  499. mm-security-handle 'gnus-details (format "%S" error)))
  500. (throw 'error handle)))
  501. (mm-set-handle-multipart-parameter
  502. mm-security-handle 'gnus-info
  503. (epg-verify-result-to-string (epg-context-result-for context 'verify)))
  504. handle)))
  505. (defun mml-smime-epg-verify-test (handle ctl)
  506. t)
  507. (provide 'mml-smime)
  508. ;;; mml-smime.el ends here