epa-file.el 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. ;;; epa-file.el --- the EasyPG Assistant, transparent file encryption -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2006-2012 Free Software Foundation, Inc.
  3. ;; Author: Daiki Ueno <ueno@unixuser.org>
  4. ;; Keywords: PGP, GnuPG
  5. ;; Package: epa
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Code:
  18. (require 'epa)
  19. (require 'epa-hook)
  20. (defcustom epa-file-cache-passphrase-for-symmetric-encryption nil
  21. "If non-nil, cache passphrase for symmetric encryption.
  22. For security reasons, this option is turned off by default and
  23. not recommended to use. Instead, consider using public-key
  24. encryption with gpg-agent which does the same job in a safer
  25. way."
  26. :type 'boolean
  27. :group 'epa-file)
  28. (defcustom epa-file-select-keys nil
  29. "Control whether or not to pop up the key selection dialog.
  30. If t, always asks user to select recipients.
  31. If nil, query user only when `epa-file-encrypt-to' is not set.
  32. If neither t nor nil, doesn't ask user. In this case, symmetric
  33. encryption is used."
  34. :type '(choice (const :tag "Ask always" t)
  35. (const :tag "Ask when recipients are not set" nil)
  36. (const :tag "Don't ask" silent))
  37. :group 'epa-file)
  38. (defvar epa-file-passphrase-alist nil)
  39. (eval-and-compile
  40. (if (fboundp 'encode-coding-string)
  41. (defalias 'epa-file--encode-coding-string 'encode-coding-string)
  42. (defalias 'epa-file--encode-coding-string 'identity)))
  43. (eval-and-compile
  44. (if (fboundp 'decode-coding-string)
  45. (defalias 'epa-file--decode-coding-string 'decode-coding-string)
  46. (defalias 'epa-file--decode-coding-string 'identity)))
  47. (defun epa-file-passphrase-callback-function (context key-id file)
  48. (if (and epa-file-cache-passphrase-for-symmetric-encryption
  49. (eq key-id 'SYM))
  50. (progn
  51. (setq file (file-truename file))
  52. (let ((entry (assoc file epa-file-passphrase-alist))
  53. passphrase)
  54. (or (copy-sequence (cdr entry))
  55. (progn
  56. (unless entry
  57. (setq entry (list file)
  58. epa-file-passphrase-alist
  59. (cons entry
  60. epa-file-passphrase-alist)))
  61. (setq passphrase (epa-passphrase-callback-function context
  62. key-id
  63. file))
  64. (setcdr entry (copy-sequence passphrase))
  65. passphrase))))
  66. (epa-passphrase-callback-function context key-id file)))
  67. ;;;###autoload
  68. (defun epa-file-handler (operation &rest args)
  69. (save-match-data
  70. (let ((op (get operation 'epa-file)))
  71. (if op
  72. (apply op args)
  73. (epa-file-run-real-handler operation args)))))
  74. (defun epa-file-run-real-handler (operation args)
  75. (let ((inhibit-file-name-handlers
  76. (cons 'epa-file-handler
  77. (and (eq inhibit-file-name-operation operation)
  78. inhibit-file-name-handlers)))
  79. (inhibit-file-name-operation operation))
  80. (apply operation args)))
  81. (defun epa-file-decode-and-insert (string file visit beg end replace)
  82. (if (fboundp 'decode-coding-inserted-region)
  83. (save-restriction
  84. (narrow-to-region (point) (point))
  85. (insert (if enable-multibyte-characters
  86. (string-to-multibyte string)
  87. string))
  88. (decode-coding-inserted-region
  89. (point-min) (point-max)
  90. (substring file 0 (string-match epa-file-name-regexp file))
  91. visit beg end replace))
  92. (insert (epa-file--decode-coding-string string (or coding-system-for-read
  93. 'undecided)))))
  94. (defvar epa-file-error nil)
  95. (defun epa-file--find-file-not-found-function ()
  96. (let ((error epa-file-error))
  97. (save-window-excursion
  98. (kill-buffer))
  99. (signal 'file-error
  100. (cons "Opening input file" (cdr error)))))
  101. (defvar last-coding-system-used)
  102. (defun epa-file-insert-file-contents (file &optional visit beg end replace)
  103. (barf-if-buffer-read-only)
  104. (if (and visit (or beg end))
  105. (error "Attempt to visit less than an entire file"))
  106. (setq file (expand-file-name file))
  107. (let* ((local-copy
  108. (condition-case nil
  109. (epa-file-run-real-handler #'file-local-copy (list file))
  110. (error)))
  111. (local-file (or local-copy file))
  112. (context (epg-make-context))
  113. string length entry)
  114. (if visit
  115. (setq buffer-file-name file))
  116. (epg-context-set-passphrase-callback
  117. context
  118. (cons #'epa-file-passphrase-callback-function
  119. local-file))
  120. (epg-context-set-progress-callback
  121. context
  122. (cons #'epa-progress-callback-function
  123. (format "Decrypting %s" file)))
  124. (unwind-protect
  125. (progn
  126. (if replace
  127. (goto-char (point-min)))
  128. (condition-case error
  129. (setq string (epg-decrypt-file context local-file nil))
  130. (error
  131. (if (setq entry (assoc file epa-file-passphrase-alist))
  132. (setcdr entry nil))
  133. ;; Hack to prevent find-file from opening empty buffer
  134. ;; when decryption failed (bug#6568). See the place
  135. ;; where `find-file-not-found-functions' are called in
  136. ;; `find-file-noselect-1'.
  137. (when (file-exists-p local-file)
  138. (make-local-variable 'epa-file-error)
  139. (setq epa-file-error error)
  140. (add-hook 'find-file-not-found-functions
  141. 'epa-file--find-file-not-found-function
  142. nil t))
  143. (signal 'file-error
  144. (cons "Opening input file" (cdr error)))))
  145. (make-local-variable 'epa-file-encrypt-to)
  146. (setq epa-file-encrypt-to
  147. (mapcar #'car (epg-context-result-for context 'encrypted-to)))
  148. (if (or beg end)
  149. (setq string (substring string (or beg 0) end)))
  150. (save-excursion
  151. ;; If visiting, bind off buffer-file-name so that
  152. ;; file-locking will not ask whether we should
  153. ;; really edit the buffer.
  154. (let ((buffer-file-name
  155. (if visit nil buffer-file-name)))
  156. (save-restriction
  157. (narrow-to-region (point) (point))
  158. (epa-file-decode-and-insert string file visit beg end replace)
  159. (setq length (- (point-max) (point-min))))
  160. (if replace
  161. (delete-region (point) (point-max))))
  162. (if visit
  163. (set-visited-file-modtime))))
  164. (if (and local-copy
  165. (file-exists-p local-copy))
  166. (delete-file local-copy)))
  167. (list file length)))
  168. (put 'insert-file-contents 'epa-file 'epa-file-insert-file-contents)
  169. (defun epa-file-write-region (start end file &optional append visit lockname
  170. mustbenew)
  171. (if append
  172. (error "Can't append to the file"))
  173. (setq file (expand-file-name file))
  174. (let* ((coding-system (or coding-system-for-write
  175. (if (fboundp 'select-safe-coding-system)
  176. ;; This is needed since Emacs 22 has
  177. ;; no-conversion setting for *.gpg in
  178. ;; `auto-coding-alist'.
  179. (let ((buffer-file-name
  180. (file-name-sans-extension file)))
  181. (select-safe-coding-system
  182. (point-min) (point-max)))
  183. buffer-file-coding-system)))
  184. (context (epg-make-context))
  185. (coding-system-for-write 'binary)
  186. string entry
  187. (recipients
  188. (cond
  189. ((listp epa-file-encrypt-to) epa-file-encrypt-to)
  190. ((stringp epa-file-encrypt-to) (list epa-file-encrypt-to)))))
  191. (epg-context-set-passphrase-callback
  192. context
  193. (cons #'epa-file-passphrase-callback-function
  194. file))
  195. (epg-context-set-progress-callback
  196. context
  197. (cons #'epa-progress-callback-function
  198. (format "Encrypting %s" file)))
  199. (epg-context-set-armor context epa-armor)
  200. (condition-case error
  201. (setq string
  202. (epg-encrypt-string
  203. context
  204. (if (stringp start)
  205. (epa-file--encode-coding-string start coding-system)
  206. (unless start
  207. (setq start (point-min)
  208. end (point-max)))
  209. (epa-file--encode-coding-string (buffer-substring start end)
  210. coding-system))
  211. (if (or (eq epa-file-select-keys t)
  212. (and (null epa-file-select-keys)
  213. (not (local-variable-p 'epa-file-encrypt-to
  214. (current-buffer)))))
  215. (epa-select-keys
  216. context
  217. "Select recipients for encryption.
  218. If no one is selected, symmetric encryption will be performed. "
  219. recipients)
  220. (if epa-file-encrypt-to
  221. (epg-list-keys context recipients)))))
  222. (error
  223. (if (setq entry (assoc file epa-file-passphrase-alist))
  224. (setcdr entry nil))
  225. (signal 'file-error (cons "Opening output file" (cdr error)))))
  226. (epa-file-run-real-handler
  227. #'write-region
  228. (list string nil file append visit lockname mustbenew))
  229. (if (boundp 'last-coding-system-used)
  230. (setq last-coding-system-used coding-system))
  231. (if (eq visit t)
  232. (progn
  233. (setq buffer-file-name file)
  234. (set-visited-file-modtime))
  235. (if (stringp visit)
  236. (progn
  237. (set-visited-file-modtime)
  238. (setq buffer-file-name visit))))
  239. (if (or (eq visit t)
  240. (eq visit nil)
  241. (stringp visit))
  242. (message "Wrote %s" buffer-file-name))))
  243. (put 'write-region 'epa-file 'epa-file-write-region)
  244. (defun epa-file-select-keys ()
  245. "Select recipients for encryption."
  246. (interactive)
  247. (make-local-variable 'epa-file-encrypt-to)
  248. (setq epa-file-encrypt-to
  249. (mapcar
  250. (lambda (key)
  251. (epg-sub-key-id (car (epg-key-sub-key-list key))))
  252. (epa-select-keys
  253. (epg-make-context)
  254. "Select recipients for encryption.
  255. If no one is selected, symmetric encryption will be performed. "))))
  256. ;;;###autoload
  257. (defun epa-file-enable ()
  258. (interactive)
  259. (if (memq epa-file-handler file-name-handler-alist)
  260. (message "`epa-file' already enabled")
  261. (setq file-name-handler-alist
  262. (cons epa-file-handler file-name-handler-alist))
  263. (add-hook 'find-file-hook 'epa-file-find-file-hook)
  264. (setq auto-mode-alist (cons epa-file-auto-mode-alist-entry auto-mode-alist))
  265. (message "`epa-file' enabled")))
  266. ;;;###autoload
  267. (defun epa-file-disable ()
  268. (interactive)
  269. (if (memq epa-file-handler file-name-handler-alist)
  270. (progn
  271. (setq file-name-handler-alist
  272. (delq epa-file-handler file-name-handler-alist))
  273. (remove-hook 'find-file-hook 'epa-file-find-file-hook)
  274. (setq auto-mode-alist (delq epa-file-auto-mode-alist-entry
  275. auto-mode-alist))
  276. (message "`epa-file' disabled"))
  277. (message "`epa-file' already disabled")))
  278. (provide 'epa-file)
  279. ;;; epa-file.el ends here