aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Magne Ingebrigtsen2014-11-23 14:56:43 +0100
committerLars Magne Ingebrigtsen2014-11-23 14:56:43 +0100
commit4c298b2a73bda5ad99c1a7c2428b0db91e950820 (patch)
tree9cbec90cbac94adbe863a5bab50429dbb513ae4e
parenta85950469e6fc045de6157f9ad739e28f30ecd8d (diff)
downloademacs-4c298b2a73bda5ad99c1a7c2428b0db91e950820.tar.gz
emacs-4c298b2a73bda5ad99c1a7c2428b0db91e950820.zip
Implement a Network Security Manager
* processes.texi (Network): Mention the new :warn-unless-encrypted parameter to `open-network-stream'. (Network): Mention the Network Security Manager. * net/nsm.el: New file that implements a Network Security Manager. * net/network-stream.el (open-network-stream): Add a new :warn-unless-encrypted parameter. (network-stream-open-plain): Allow warning unless encrypted. (network-stream-open-starttls): Call the Network Security Manager. (network-stream-open-tls): Ditto.
-rw-r--r--doc/lispref/ChangeLog6
-rw-r--r--doc/lispref/processes.texi27
-rw-r--r--lisp/ChangeLog10
-rw-r--r--lisp/net/network-stream.el17
-rw-r--r--lisp/net/nsm.el409
5 files changed, 468 insertions, 1 deletions
diff --git a/doc/lispref/ChangeLog b/doc/lispref/ChangeLog
index 0c8792af81f..5cc85aa60dc 100644
--- a/doc/lispref/ChangeLog
+++ b/doc/lispref/ChangeLog
@@ -1,3 +1,9 @@
12014-11-23 Lars Magne Ingebrigtsen <larsi@gnus.org>
2
3 * processes.texi (Network): Mention the new :warn-unless-encrypted
4 parameter to `open-network-stream'.
5 (Network): Mention the Network Security Manager.
6
12014-11-21 Ulf Jasper <ulf.jasper@web.de> 72014-11-21 Ulf Jasper <ulf.jasper@web.de>
2 8
3 * text.texi (Parsing HTML/XML): Document new optional parameter 9 * text.texi (Parsing HTML/XML): Document new optional parameter
diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index db80f0537e0..48429e6fd93 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -2041,6 +2041,12 @@ Regular expression matching a successful @acronym{STARTTLS} negotiation.
2041If non-@code{nil}, do opportunistic @acronym{STARTTLS} upgrades even if Emacs 2041If non-@code{nil}, do opportunistic @acronym{STARTTLS} upgrades even if Emacs
2042doesn't have built-in @acronym{TLS} support. 2042doesn't have built-in @acronym{TLS} support.
2043 2043
2044@item :warn-unless-encrypted @var{boolean}
2045If non-@code{nil}, and @code{:return-value} is also non-@code{nil},
2046Emacs will warn if the connection isn't encrypted. This is useful for
2047protocols like @acronym{IMAP} and the like, where most users would
2048expect the network traffic to be encrypted.
2049
2044@item :client-certificate @var{list-or-t} 2050@item :client-certificate @var{list-or-t}
2045Either a list of the form @code{(@var{key-file} @var{cert-file})}, 2051Either a list of the form @code{(@var{key-file} @var{cert-file})},
2046naming the certificate key file and certificate file itself, or 2052naming the certificate key file and certificate file itself, or
@@ -2066,6 +2072,27 @@ The connection type: @samp{plain} or @samp{tls}.
2066 2072
2067@end defun 2073@end defun
2068 2074
2075@cindex Network Security Manager
2076After establishing the connection, the connection is then passed on to
2077the Network Security Manager (@acronym{NSM}). If the connection is a
2078@acronym{TLS} or @acronym{STARTTLS} connection, the @acronym{NSM} will
2079check whether the certificate used to establish the identity of the
2080server we're connecting to can be verified. If this can't be done,
2081the @acronym{NSM} will query the user whether to proceed with the
2082connection.
2083
2084The user is given the choice of registering a permanent security
2085exception, a temporary one, or whether to refuse the connection
2086entirely.
2087
2088If the connection is unencrypted, but it was encrypted in previous
2089sessions, the user will also be notified about this.
2090
2091@vindex nsm-security-level
2092The @code{nsm-security-level} variable determines the security level.
2093If this is @code{low}, no security checks are performed.
2094
2095
2069@node Network Servers 2096@node Network Servers
2070@section Network Servers 2097@section Network Servers
2071@cindex network servers 2098@cindex network servers
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 43b3f9abc8c..e503a6e3194 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,13 @@
12014-11-23 Lars Magne Ingebrigtsen <larsi@gnus.org>
2
3 * net/nsm.el: New file that implements a Network Security Manager.
4
5 * net/network-stream.el (open-network-stream): Add a new
6 :warn-unless-encrypted parameter.
7 (network-stream-open-plain): Allow warning unless encrypted.
8 (network-stream-open-starttls): Call the Network Security Manager.
9 (network-stream-open-tls): Ditto.
10
12014-11-23 Leo Liu <sdl.web@gmail.com> 112014-11-23 Leo Liu <sdl.web@gmail.com>
2 12
3 * calendar/cal-china.el (calendar-chinese-from-absolute-for-diary) 13 * calendar/cal-china.el (calendar-chinese-from-absolute-for-diary)
diff --git a/lisp/net/network-stream.el b/lisp/net/network-stream.el
index 28e9d0ccf32..a1e9729bac3 100644
--- a/lisp/net/network-stream.el
+++ b/lisp/net/network-stream.el
@@ -45,6 +45,7 @@
45(require 'tls) 45(require 'tls)
46(require 'starttls) 46(require 'starttls)
47(require 'auth-source) 47(require 'auth-source)
48(require 'nsm)
48 49
49(autoload 'gnutls-negotiate "gnutls") 50(autoload 'gnutls-negotiate "gnutls")
50(autoload 'open-gnutls-stream "gnutls") 51(autoload 'open-gnutls-stream "gnutls")
@@ -128,11 +129,14 @@ values:
128:use-starttls-if-possible is a boolean that says to do opportunistic 129:use-starttls-if-possible is a boolean that says to do opportunistic
129STARTTLS upgrades even if Emacs doesn't have built-in TLS functionality. 130STARTTLS upgrades even if Emacs doesn't have built-in TLS functionality.
130 131
132:warn-unless-encrypted is a boolean which, if :return-list is
133non-nil, is used warn the user if the connection isn't encrypted.
134
131:nogreeting is a boolean that can be used to inhibit waiting for 135:nogreeting is a boolean that can be used to inhibit waiting for
132a greeting from the server. 136a greeting from the server.
133 137
134:nowait is a boolean that says the connection should be made 138:nowait is a boolean that says the connection should be made
135 asynchronously, if possible." 139asynchronously, if possible."
136 (unless (featurep 'make-network-process) 140 (unless (featurep 'make-network-process)
137 (error "Emacs was compiled without networking support")) 141 (error "Emacs was compiled without networking support"))
138 (let ((type (plist-get parameters :type)) 142 (let ((type (plist-get parameters :type))
@@ -196,6 +200,8 @@ a greeting from the server.
196 (stream (make-network-process :name name :buffer buffer 200 (stream (make-network-process :name name :buffer buffer
197 :host host :service service 201 :host host :service service
198 :nowait (plist-get parameters :nowait)))) 202 :nowait (plist-get parameters :nowait))))
203 (when (plist-get parameters :warn-unless-encrypted)
204 (setq stream (nsm-verify-connection stream host service nil t)))
199 (list stream 205 (list stream
200 (network-stream-get-response stream start 206 (network-stream-get-response stream start
201 (plist-get parameters :end-of-command)) 207 (plist-get parameters :end-of-command))
@@ -319,6 +325,12 @@ a greeting from the server.
319 "' program was found")))) 325 "' program was found"))))
320 (delete-process stream) 326 (delete-process stream)
321 (setq stream nil)) 327 (setq stream nil))
328 ;; Check certificate validity etc.
329 (when builtin-starttls
330 (setq stream (nsm-verify-connection
331 stream host service
332 (eq resulting-type 'tls)
333 (plist-get parameters :warn-unless-encrypted))))
322 ;; Return value: 334 ;; Return value:
323 (list stream greeting capabilities resulting-type error))) 335 (list stream greeting capabilities resulting-type error)))
324 336
@@ -352,6 +364,9 @@ a greeting from the server.
352 'open-tls-stream) 364 'open-tls-stream)
353 name buffer host service)) 365 name buffer host service))
354 (eoc (plist-get parameters :end-of-command))) 366 (eoc (plist-get parameters :end-of-command)))
367 ;; Check certificate validity etc.
368 (when (and use-builtin-gnutls stream)
369 (setq stream (nsm-verify-connection stream host service)))
355 (if (null stream) 370 (if (null stream)
356 (list nil nil nil 'plain) 371 (list nil nil nil 'plain)
357 ;; If we're using tls.el, we have to delete the output from 372 ;; If we're using tls.el, we have to delete the output from
diff --git a/lisp/net/nsm.el b/lisp/net/nsm.el
new file mode 100644
index 00000000000..f51201a1270
--- /dev/null
+++ b/lisp/net/nsm.el
@@ -0,0 +1,409 @@
1;;; nsm.el --- Network Security Manager
2
3;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6;; Keywords: encryption, security, network
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;;; Code:
26
27(require 'cl-lib)
28
29(defvar nsm-permanent-host-settings nil)
30(defvar nsm-temporary-host-settings nil)
31
32(defgroup nsm nil
33 "Network Security Manager"
34 :version "25.1"
35 :group 'comm)
36
37(defcustom nsm-security-level 'medium
38 "How secure the network should be."
39 :version "25.1"
40 :group 'nsm
41 :type '(choice (const :tag "Low" low)
42 (const :tag "Medium" medium)
43 (const :tag "High" high)
44 (const :tag "Paranoid" paranoid)))
45
46(defcustom nsm-settings-file (expand-file-name "network-security.data"
47 user-emacs-directory)
48 "The file the security manager settings will be stored in."
49 :version "25.1"
50 :group 'nsm
51 :type 'file)
52
53(defcustom nsm-save-host-names nil
54 "If non-nil, always save host names in the structures in `nsm-settings-file'.
55By default, only hosts that have exceptions have their names
56stored in plain text."
57 :version "25.1"
58 :group 'nsm
59 :type 'boolean)
60
61(defvar nsm-noninteractive nil
62 "If non-nil, the connection is opened in a non-interactive context.
63This means that no queries should be performed.")
64
65(defun nsm-verify-connection (process host port &optional
66 save-fingerprint warn-unencrypted)
67 "Verify the security status of PROCESS that's connected to HOST:PORT.
68If PROCESS is a gnutls connection, the certificate validity will
69be examined. If it's a non-TLS connection, it may be compared
70against previous connections. If the function determines that
71there is something odd about the connection, the user will be
72queried about what to do about it.
73
74The process it returned if everything is OK, and otherwise, the
75process will be deleted and nil is returned.
76
77If SAVE-FINGERPRINT, always save the fingerprint of the
78server (if the connection is a TLS connection). This is useful
79to keep track of the TLS status of STARTTLS servers.
80
81If WARN-UNENCRYPTED, query the user if the connection is
82unencrypted."
83 (if (eq nsm-security-level 'low)
84 process
85 (let* ((status (gnutls-peer-status process))
86 (id (nsm-id host port))
87 (settings (nsm-host-settings id)))
88 (cond
89 ((not (process-live-p process))
90 nil)
91 ((not status)
92 ;; This is a non-TLS connection.
93 (nsm-check-plain-connection process host port settings
94 warn-unencrypted))
95 (t
96 (let ((process
97 (nsm-check-tls-connection process host port status settings)))
98 (when (and process save-fingerprint
99 (null (nsm-host-settings id)))
100 (nsm-save-host host port status 'fingerprint 'always))
101 process))))))
102
103(defun nsm-check-tls-connection (process host port status settings)
104 (let ((warnings (plist-get status :warnings)))
105 (cond
106
107 ;; The certificate validated, but perhaps we want to do
108 ;; certificate pinning.
109 ((null warnings)
110 (cond
111 ((< (nsm-level nsm-security-level) (nsm-level 'high))
112 process)
113 ;; The certificate is fine, but if we're paranoid, we might
114 ;; want to check whether it's changed anyway.
115 ((and (>= (nsm-level nsm-security-level) (nsm-level 'high))
116 (not (nsm-fingerprint-ok-p host port status settings)))
117 (delete-process process)
118 nil)
119 ;; We haven't seen this before, and we're paranoid.
120 ((and (eq nsm-security-level 'paranoid)
121 (null settings)
122 (not (nsm-new-fingerprint-ok-p host port status)))
123 (delete-process process)
124 nil)
125 ((>= (nsm-level nsm-security-level) (nsm-level 'high))
126 ;; Save the host fingerprint so that we can check it the
127 ;; next time we connect.
128 (nsm-save-host host port status 'fingerprint 'always)
129 process)
130 (t
131 process)))
132
133 ;; The certificate did not validate.
134 ((not (equal nsm-security-level 'low))
135 ;; We always want to pin the certificate of invalid connections
136 ;; to track man-in-the-middle or the like.
137 (if (not (nsm-fingerprint-ok-p host port status settings))
138 (progn
139 (delete-process process)
140 nil)
141 ;; We have a warning, so query the user.
142 (if (and (not (nsm-warnings-ok-p status settings))
143 (not (nsm-query
144 host port status 'conditions
145 "The TLS connection to %s:%s is insecure\nfor the following reason%s:\n\n%s"
146 host port
147 (if (> (length warnings) 1)
148 "s" "")
149 (mapconcat 'cadr warnings "\n"))))
150 (progn
151 (delete-process process)
152 nil)
153 process))))))
154
155(defun nsm-fingerprint (status)
156 (plist-get (plist-get status :certificate) :public-key-id))
157
158(defun nsm-fingerprint-ok-p (host port status settings)
159 (let ((did-query nil))
160 (if (and settings
161 (not (eq (plist-get settings :fingerprint) :none))
162 (not (equal (nsm-fingerprint status)
163 (plist-get settings :fingerprint)))
164 (not
165 (setq did-query
166 (nsm-query
167 host port status 'fingerprint
168 "The fingerprint for the connection to %s:%s has changed from\n%s to\n%s"
169 host port
170 (plist-get settings :fingerprint)
171 (nsm-fingerprint status)))))
172 ;; Not OK.
173 nil
174 (when did-query
175 ;; Remove any exceptions that have been set on the previous
176 ;; certificate.
177 (plist-put settings :conditions nil))
178 t)))
179
180(defun nsm-new-fingerprint-ok-p (host port status)
181 (nsm-query
182 host port nil 'fingerprint
183 "The fingerprint for the connection to %s:%s is new:\n%s"
184 host port
185 (nsm-fingerprint status)))
186
187(defun nsm-check-plain-connection (process host port settings warn-unencrypted)
188 ;; If this connection used to be TLS, but is now plain, then it's
189 ;; possible that we're being Man-In-The-Middled by a proxy that's
190 ;; stripping out STARTTLS announcements.
191 (cond
192 ((and (plist-get settings :fingerprint)
193 (not (eq (plist-get settings :fingerprint) :none))
194 (not
195 (nsm-query
196 host port nil 'conditions
197 "The connection to %s:%s used to be an encrypted\nconnection, but is now unencrypted. This might mean that there's a\nman-in-the-middle tapping this connection."
198 host port)))
199 (delete-process process)
200 nil)
201 ((and warn-unencrypted
202 (not (memq :unencrypted (plist-get settings :conditions)))
203 (not (nsm-query
204 host port nil 'conditions
205 "The connection to %s:%s is unencrypted."
206 host port)))
207 (delete-process process)
208 nil)
209 (t
210 process)))
211
212(defun nsm-query (host port status what message &rest args)
213 ;; If there is no user to answer queries, then say `no' to everything.
214 (if (or noninteractive
215 nsm-noninteractive)
216 nil
217 (let ((response
218 (condition-case nil
219 (nsm-query-user message args (nsm-format-certificate status))
220 ;; Make sure we manage to close the process if the user hits
221 ;; `C-g'.
222 (quit 'no)
223 (error 'no))))
224 (if (eq response 'no)
225 nil
226 (nsm-save-host host port status what response)
227 t))))
228
229(defun nsm-query-user (message args cert)
230 (let ((buffer (get-buffer-create "*Network Security Manager*")))
231 (with-help-window buffer
232 (with-current-buffer buffer
233 (erase-buffer)
234 (when (> (length cert) 0)
235 (insert cert "\n"))
236 (insert (apply 'format message args))))
237 (let ((responses '((?n . no)
238 (?s . session)
239 (?a . always)))
240 (prefix "")
241 response)
242 (while (not response)
243 (setq response
244 (cdr
245 (assq (downcase
246 (read-char
247 (concat prefix
248 "Continue connecting? (No, Session only, Always)")))
249 responses)))
250 (unless response
251 (ding)
252 (setq prefix "Invalid choice. ")))
253 (kill-buffer buffer)
254 ;; If called from a callback, `read-char' will insert things
255 ;; into the pending input. Clear that.
256 (clear-this-command-keys)
257 response)))
258
259(defun nsm-save-host (host port status what permanency)
260 (let* ((id (nsm-id host port))
261 (saved
262 (list :id id
263 :fingerprint (or (nsm-fingerprint status)
264 ;; Plain connection.
265 :none))))
266 (when (or (eq what 'conditions)
267 nsm-save-host-names)
268 (nconc saved (list :host (format "%s:%s" host port))))
269 ;; We either want to save/update the fingerprint or the conditions
270 ;; of the certificate/unencrypted connection.
271 (when (eq what 'conditions)
272 (nconc saved (list :host (format "%s:%s" host port)))
273 (cond
274 ((not status)
275 (nconc saved `(:conditions (:unencrypted))))
276 ((plist-get status :warnings)
277 (nconc saved
278 `(:conditions ,(mapcar 'car (plist-get status :warnings)))))))
279 (if (eq permanency 'always)
280 (progn
281 (nsm-remove-temporary-setting id)
282 (nsm-remove-permanent-setting id)
283 (push saved nsm-permanent-host-settings)
284 (nsm-write-settings))
285 (nsm-remove-temporary-setting id)
286 (push saved nsm-temporary-host-settings))))
287
288(defun nsm-write-settings ()
289 (with-temp-file nsm-settings-file
290 (insert "(\n")
291 (dolist (setting nsm-permanent-host-settings)
292 (insert " ")
293 (prin1 setting (current-buffer))
294 (insert "\n"))
295 (insert ")\n")))
296
297(defun nsm-read-settings ()
298 (setq nsm-permanent-host-settings
299 (with-temp-buffer
300 (insert-file-contents nsm-settings-file)
301 (goto-char (point-min))
302 (ignore-errors (read (current-buffer))))))
303
304(defun nsm-id (host port)
305 (concat "sha1:" (sha1 (format "%s:%s" host port))))
306
307(defun nsm-host-settings (id)
308 (when (and (not nsm-permanent-host-settings)
309 (file-exists-p nsm-settings-file))
310 (nsm-read-settings))
311 (let ((result nil))
312 (dolist (elem (append nsm-temporary-host-settings
313 nsm-permanent-host-settings))
314 (when (and (not result)
315 (equal (plist-get elem :id) id))
316 (setq result elem)))
317 result))
318
319(defun nsm-warnings-ok-p (status settings)
320 (let ((not-ok nil)
321 (conditions (plist-get settings :conditions)))
322 (dolist (warning (plist-get status :warnings))
323 (when (memq (car warning) conditions)
324 (setq not-ok t)))
325 not-ok))
326
327(defun nsm-remove-permanent-setting (id)
328 (setq nsm-permanent-host-settings
329 (cl-delete-if
330 (lambda (elem)
331 (equal (plist-get elem :id) id))
332 nsm-permanent-host-settings)))
333
334(defun nsm-remove-temporary-setting (id)
335 (setq nsm-temporary-host-settings
336 (cl-delete-if
337 (lambda (elem)
338 (equal (plist-get elem :id) id))
339 nsm-temporary-host-settings)))
340
341(defun nsm-format-certificate (status)
342 (let ((cert (plist-get status :certificate)))
343 (when cert
344 (with-temp-buffer
345 (insert
346 "Certificate information\n"
347 "Issued by:"
348 (nsm-certificate-part (plist-get cert :issuer) "CN" t) "\n"
349 "Issued to:"
350 (or (nsm-certificate-part (plist-get cert :subject) "O")
351 (nsm-certificate-part (plist-get cert :subject) "OU" t))
352 "\n"
353 "Hostname:"
354 (nsm-certificate-part (plist-get cert :subject) "CN" t) "\n"
355 "Public key:" (plist-get cert :public-key-algorithm)
356 ", signature: " (plist-get cert :signature-algorithm) "\n"
357 "Security level:"
358 (propertize (plist-get cert :certificate-security-level)
359 'face 'bold)
360 "\n"
361 "Valid:From " (plist-get cert :valid-from)
362 " to " (plist-get cert :valid-to) "\n\n")
363 (goto-char (point-min))
364 (while (re-search-forward "^[^:]+:" nil t)
365 (insert (make-string (- 20 (current-column)) ? )))
366 (buffer-string)))))
367
368(defun nsm-certificate-part (string part &optional full)
369 (let ((part (cadr (assoc part (nsm-parse-subject string)))))
370 (cond
371 (part part)
372 (full string)
373 (t nil))))
374
375(defun nsm-parse-subject (string)
376 (with-temp-buffer
377 (insert string)
378 (goto-char (point-min))
379 (let ((start (point))
380 (result nil))
381 (while (not (eobp))
382 (push (replace-regexp-in-string
383 "[\\]\\(.\\)" "\\1"
384 (buffer-substring start
385 (if (re-search-forward "[^\\]," nil 'move)
386 (1- (point))
387 (point))))
388 result)
389 (setq start (point)))
390 (mapcar
391 (lambda (elem)
392 (let ((pos (cl-position ?= elem)))
393 (if pos
394 (list (substring elem 0 pos)
395 (substring elem (1+ pos)))
396 elem)))
397 (nreverse result)))))
398
399(defun nsm-level (symbol)
400 "Return a numerical level for SYMBOL for easier comparison."
401 (cond
402 ((eq symbol 'low) 0)
403 ((eq symbol 'medium) 1)
404 ((eq symbol 'high) 2)
405 (t 3)))
406
407(provide 'nsm)
408
409;;; nsm.el ends here