diff options
| author | Lars Magne Ingebrigtsen | 2014-11-23 14:56:43 +0100 |
|---|---|---|
| committer | Lars Magne Ingebrigtsen | 2014-11-23 14:56:43 +0100 |
| commit | 4c298b2a73bda5ad99c1a7c2428b0db91e950820 (patch) | |
| tree | 9cbec90cbac94adbe863a5bab50429dbb513ae4e | |
| parent | a85950469e6fc045de6157f9ad739e28f30ecd8d (diff) | |
| download | emacs-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/ChangeLog | 6 | ||||
| -rw-r--r-- | doc/lispref/processes.texi | 27 | ||||
| -rw-r--r-- | lisp/ChangeLog | 10 | ||||
| -rw-r--r-- | lisp/net/network-stream.el | 17 | ||||
| -rw-r--r-- | lisp/net/nsm.el | 409 |
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 @@ | |||
| 1 | 2014-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 | |||
| 1 | 2014-11-21 Ulf Jasper <ulf.jasper@web.de> | 7 | 2014-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. | |||
| 2041 | If non-@code{nil}, do opportunistic @acronym{STARTTLS} upgrades even if Emacs | 2041 | If non-@code{nil}, do opportunistic @acronym{STARTTLS} upgrades even if Emacs |
| 2042 | doesn't have built-in @acronym{TLS} support. | 2042 | doesn't have built-in @acronym{TLS} support. |
| 2043 | 2043 | ||
| 2044 | @item :warn-unless-encrypted @var{boolean} | ||
| 2045 | If non-@code{nil}, and @code{:return-value} is also non-@code{nil}, | ||
| 2046 | Emacs will warn if the connection isn't encrypted. This is useful for | ||
| 2047 | protocols like @acronym{IMAP} and the like, where most users would | ||
| 2048 | expect the network traffic to be encrypted. | ||
| 2049 | |||
| 2044 | @item :client-certificate @var{list-or-t} | 2050 | @item :client-certificate @var{list-or-t} |
| 2045 | Either a list of the form @code{(@var{key-file} @var{cert-file})}, | 2051 | Either a list of the form @code{(@var{key-file} @var{cert-file})}, |
| 2046 | naming the certificate key file and certificate file itself, or | 2052 | naming 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 | ||
| 2076 | After establishing the connection, the connection is then passed on to | ||
| 2077 | the Network Security Manager (@acronym{NSM}). If the connection is a | ||
| 2078 | @acronym{TLS} or @acronym{STARTTLS} connection, the @acronym{NSM} will | ||
| 2079 | check whether the certificate used to establish the identity of the | ||
| 2080 | server we're connecting to can be verified. If this can't be done, | ||
| 2081 | the @acronym{NSM} will query the user whether to proceed with the | ||
| 2082 | connection. | ||
| 2083 | |||
| 2084 | The user is given the choice of registering a permanent security | ||
| 2085 | exception, a temporary one, or whether to refuse the connection | ||
| 2086 | entirely. | ||
| 2087 | |||
| 2088 | If the connection is unencrypted, but it was encrypted in previous | ||
| 2089 | sessions, the user will also be notified about this. | ||
| 2090 | |||
| 2091 | @vindex nsm-security-level | ||
| 2092 | The @code{nsm-security-level} variable determines the security level. | ||
| 2093 | If 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 @@ | |||
| 1 | 2014-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 | |||
| 1 | 2014-11-23 Leo Liu <sdl.web@gmail.com> | 11 | 2014-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 |
| 129 | STARTTLS upgrades even if Emacs doesn't have built-in TLS functionality. | 130 | STARTTLS 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 | ||
| 133 | non-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 |
| 132 | a greeting from the server. | 136 | a 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." | 139 | asynchronously, 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'. | ||
| 55 | By default, only hosts that have exceptions have their names | ||
| 56 | stored 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. | ||
| 63 | This 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. | ||
| 68 | If PROCESS is a gnutls connection, the certificate validity will | ||
| 69 | be examined. If it's a non-TLS connection, it may be compared | ||
| 70 | against previous connections. If the function determines that | ||
| 71 | there is something odd about the connection, the user will be | ||
| 72 | queried about what to do about it. | ||
| 73 | |||
| 74 | The process it returned if everything is OK, and otherwise, the | ||
| 75 | process will be deleted and nil is returned. | ||
| 76 | |||
| 77 | If SAVE-FINGERPRINT, always save the fingerprint of the | ||
| 78 | server (if the connection is a TLS connection). This is useful | ||
| 79 | to keep track of the TLS status of STARTTLS servers. | ||
| 80 | |||
| 81 | If WARN-UNENCRYPTED, query the user if the connection is | ||
| 82 | unencrypted." | ||
| 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 | ||