aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMagnus Henoch2015-02-13 19:54:57 +1100
committerLars Magne Ingebrigtsen2015-02-13 19:57:56 +1100
commite7d21b4ab11e73c709420eeeb32ffe2421fafe98 (patch)
tree67ce2998e3b6c8540e0c468012cdd4d4ce34f4e2
parentf61c87f12a36bb2063c25b6742380b5916618ab5 (diff)
downloademacs-e7d21b4ab11e73c709420eeeb32ffe2421fafe98.tar.gz
emacs-e7d21b4ab11e73c709420eeeb32ffe2421fafe98.zip
Implement SCRAM-SHA-1 SASL mechanism
Fixes: debbugs:17636 * lisp/net/sasl-scram-rfc.el: New file. * lisp/net/sasl.el (sasl-mechanisms): Remove SCRAM-MD5. Add SCRAM-SHA-1 first. (sasl-mechanism-alist): Remove SCRAM-MD5 entry. Add SCRAM-SHA-1 entry. * test/automated/sasl-scram-rfc-tests.el: New file.
-rw-r--r--lisp/ChangeLog9
-rw-r--r--lisp/net/sasl-scram-rfc.el160
-rw-r--r--lisp/net/sasl.el6
-rw-r--r--test/ChangeLog4
-rw-r--r--test/automated/sasl-scram-rfc-tests.el50
5 files changed, 226 insertions, 3 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 8393009c061..02a7c3a7e9c 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,12 @@
12015-02-13 Magnus Henoch <magnus.henoch@gmail.com>
2
3 * net/sasl-scram-rfc.el: New file.
4
5 * net/sasl.el (sasl-mechanisms): Remove SCRAM-MD5. Add
6 SCRAM-SHA-1 first.
7 (sasl-mechanism-alist): Remove SCRAM-MD5 entry. Add SCRAM-SHA-1
8 entry (bug#17636).
9
12015-02-13 Lars Ingebrigtsen <larsi@gnus.org> 102015-02-13 Lars Ingebrigtsen <larsi@gnus.org>
2 11
3 * net/shr.el (shr-tag-li): Speed up rendering pages with lots of 12 * net/shr.el (shr-tag-li): Speed up rendering pages with lots of
diff --git a/lisp/net/sasl-scram-rfc.el b/lisp/net/sasl-scram-rfc.el
new file mode 100644
index 00000000000..3d86da43f35
--- /dev/null
+++ b/lisp/net/sasl-scram-rfc.el
@@ -0,0 +1,160 @@
1;;; sasl-scram-rfc.el --- SCRAM-SHA-1 module for the SASL client framework -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5;; Author: Magnus Henoch <magnus.henoch@gmail.com>
6
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software: you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21
22;;; Commentary:
23
24;; This program is implemented from RFC 5802. It implements the
25;; SCRAM-SHA-1 SASL mechanism.
26;;
27;; RFC 5802 foresees "hash agility", i.e. new mechanisms based on the
28;; same protocol but using a different hash function. Likewise, this
29;; module attempts to separate generic and specific functions, which
30;; should make it easy to implement any future SCRAM-* SASL mechanism.
31;; It should be as simple as copying the SCRAM-SHA-1 section below and
32;; replacing all SHA-1 references.
33;;
34;; This module does not yet implement the variants with channel
35;; binding, i.e. SCRAM-*-PLUS. That would require cooperation from
36;; the TLS library.
37
38;;; Code:
39
40(require 'cl-lib)
41(require 'sasl)
42
43;;; SCRAM-SHA-1
44
45(require 'hex-util)
46(require 'rfc2104)
47
48(defconst sasl-scram-sha-1-steps
49 '(sasl-scram-client-first-message
50 sasl-scram-sha-1-client-final-message
51 sasl-scram-sha-1-authenticate-server))
52
53(defun sasl-scram-sha-1-client-final-message (client step)
54 (sasl-scram--client-final-message
55 ;; HMAC-SHA1 uses block length 64 and hash length 20; see RFC 2104.
56 'sha1 64 20 client step))
57
58(defun sasl-scram-sha-1-authenticate-server (client step)
59 (sasl-scram--authenticate-server
60 'sha1 64 20 client step))
61
62(put 'sasl-scram-sha-1 'sasl-mechanism
63 (sasl-make-mechanism "SCRAM-SHA-1" sasl-scram-sha-1-steps))
64
65(provide 'sasl-scram-sha-1)
66
67;;; Generic for SCRAM-*
68
69(defun sasl-scram-client-first-message (client _step)
70 (let ((c-nonce (sasl-unique-id)))
71 (sasl-client-set-property client 'c-nonce c-nonce))
72 (concat
73 ;; n = client doesn't support channel binding
74 "n,"
75 ;; TODO: where would we get authorization id from?
76 ","
77 (sasl-scram--client-first-message-bare client)))
78
79(defun sasl-scram--client-first-message-bare (client)
80 (let ((c-nonce (sasl-client-property client 'c-nonce)))
81 (concat
82 ;; TODO: saslprep username or disallow non-ASCII characters
83 "n=" (sasl-client-name client) ","
84 "r=" c-nonce)))
85
86(defun sasl-scram--client-final-message (hash-fun block-length hash-length client step)
87 (unless (string-match
88 "^r=\\([^,]+\\),s=\\([^,]+\\),i=\\([0-9]+\\)\\(?:$\\|,\\)"
89 (sasl-step-data step))
90 (sasl-error "Unexpected server response"))
91 (let* ((hmac-fun (lambda (text key)
92 (decode-hex-string
93 (rfc2104-hash hash-fun block-length hash-length key text))))
94 (step-data (sasl-step-data step))
95 (nonce (match-string 1 step-data))
96 (salt-base64 (match-string 2 step-data))
97 (iteration-count (string-to-number (match-string 3 step-data)))
98
99 (c-nonce (sasl-client-property client 'c-nonce))
100 ;; no channel binding, no authorization id
101 (cbind-input "n,,"))
102 (unless (string-prefix-p c-nonce nonce)
103 (sasl-error "Invalid nonce from server"))
104 (let* ((client-final-message-without-proof
105 (concat "c=" (base64-encode-string cbind-input) ","
106 "r=" nonce))
107 (password
108 ;; TODO: either apply saslprep or disallow non-ASCII characters
109 (sasl-read-passphrase
110 (format "%s passphrase for %s: "
111 (sasl-mechanism-name (sasl-client-mechanism client))
112 (sasl-client-name client))))
113 (salt (base64-decode-string salt-base64))
114 (salted-password
115 ;; Hi(str, salt, i):
116 (let ((digest (concat salt (string 0 0 0 1)))
117 (xored nil))
118 (dotimes (_i iteration-count xored)
119 (setq digest (funcall hmac-fun digest password))
120 (setq xored (if (null xored)
121 digest
122 (cl-map 'string 'logxor xored digest))))))
123 (client-key
124 (funcall hmac-fun "Client Key" salted-password))
125 (stored-key (decode-hex-string (funcall hash-fun client-key)))
126 (auth-message
127 (concat
128 (sasl-scram--client-first-message-bare client) ","
129 step-data ","
130 client-final-message-without-proof))
131 (client-signature (funcall hmac-fun (encode-coding-string auth-message 'utf-8) stored-key))
132 (client-proof (cl-map 'string 'logxor client-key client-signature))
133 (client-final-message
134 (concat client-final-message-without-proof ","
135 "p=" (base64-encode-string client-proof))))
136 (sasl-client-set-property client 'auth-message auth-message)
137 (sasl-client-set-property client 'salted-password salted-password)
138 client-final-message)))
139
140(defun sasl-scram--authenticate-server (hash-fun block-length hash-length client step)
141 (cond
142 ((string-match "^e=\\([^,]+\\)" (sasl-step-data step))
143 (sasl-error (format "Server error: %s" (match-string 1 (sasl-step-data step)))))
144 ((string-match "^v=\\([^,]+\\)" (sasl-step-data step))
145 (let* ((hmac-fun (lambda (text key)
146 (decode-hex-string
147 (rfc2104-hash hash-fun block-length hash-length key text))))
148 (verifier (base64-decode-string (match-string 1 (sasl-step-data step))))
149 (auth-message (sasl-client-property client 'auth-message))
150 (salted-password (sasl-client-property client 'salted-password))
151 (server-key (funcall hmac-fun "Server Key" salted-password))
152 (expected-server-signature
153 (funcall hmac-fun (encode-coding-string auth-message 'utf-8) server-key)))
154 (unless (string= expected-server-signature verifier)
155 (sasl-error "Server not authenticated"))))
156 (t
157 (sasl-error "Invalid response from server"))))
158
159(provide 'sasl-scram-rfc)
160;;; sasl-scram-rfc.el ends here
diff --git a/lisp/net/sasl.el b/lisp/net/sasl.el
index 648e6227497..e59ed5d43aa 100644
--- a/lisp/net/sasl.el
+++ b/lisp/net/sasl.el
@@ -35,8 +35,8 @@
35;;; Code: 35;;; Code:
36 36
37(defvar sasl-mechanisms 37(defvar sasl-mechanisms
38 '("CRAM-MD5" "DIGEST-MD5" "PLAIN" "LOGIN" "ANONYMOUS" 38 '("SCRAM-SHA-1" "CRAM-MD5" "DIGEST-MD5" "PLAIN" "LOGIN" "ANONYMOUS"
39 "NTLM" "SCRAM-MD5")) 39 "NTLM"))
40 40
41(defvar sasl-mechanism-alist 41(defvar sasl-mechanism-alist
42 '(("CRAM-MD5" sasl-cram) 42 '(("CRAM-MD5" sasl-cram)
@@ -45,7 +45,7 @@
45 ("LOGIN" sasl-login) 45 ("LOGIN" sasl-login)
46 ("ANONYMOUS" sasl-anonymous) 46 ("ANONYMOUS" sasl-anonymous)
47 ("NTLM" sasl-ntlm) 47 ("NTLM" sasl-ntlm)
48 ("SCRAM-MD5" sasl-scram))) 48 ("SCRAM-SHA-1" sasl-scram-sha-1)))
49 49
50(defvar sasl-unique-id-function #'sasl-unique-id-function) 50(defvar sasl-unique-id-function #'sasl-unique-id-function)
51 51
diff --git a/test/ChangeLog b/test/ChangeLog
index 979214c45da..29b7c7d59ea 100644
--- a/test/ChangeLog
+++ b/test/ChangeLog
@@ -1,3 +1,7 @@
12015-02-13 Magnus Henoch <magnus.henoch@gmail.com>
2
3 * automated/sasl-scram-rfc-tests.el: New file.
4
12015-02-11 Nicolas Petton <nicolas@petton.fr> 52015-02-11 Nicolas Petton <nicolas@petton.fr>
2 6
3 * automated/seq-tests.el (test-seq-reverse, test-seq-group-by): 7 * automated/seq-tests.el (test-seq-reverse, test-seq-group-by):
diff --git a/test/automated/sasl-scram-rfc-tests.el b/test/automated/sasl-scram-rfc-tests.el
new file mode 100644
index 00000000000..c747e5f65c3
--- /dev/null
+++ b/test/automated/sasl-scram-rfc-tests.el
@@ -0,0 +1,50 @@
1;;; sasl-scram-rfc-tests.el --- tests for SCRAM-SHA-1 -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5;; Author: Magnus Henoch <magnus.henoch@gmail.com>
6
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
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; Test cases from RFC 5802.
23
24;;; Code:
25
26(require 'sasl)
27(require 'sasl-scram-rfc)
28
29(ert-deftest sasl-scram-sha-1-test ()
30 ;; The following strings are taken from section 5 of RFC 5802.
31 (let ((client
32 (sasl-make-client (sasl-find-mechanism '("SCRAM-SHA-1"))
33 "user"
34 "imap"
35 "localhost"))
36 (data "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096")
37 (c-nonce "fyko+d2lbbFgONRv9qkxdawL")
38 (sasl-read-passphrase
39 (lambda (_prompt) (copy-sequence "pencil"))))
40 (sasl-client-set-property client 'c-nonce c-nonce)
41 (should
42 (equal
43 (sasl-scram-sha-1-client-final-message client (vector nil data))
44 "c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="))
45
46 ;; This should not throw an error:
47 (sasl-scram-sha-1-authenticate-server client (vector nil "v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
48"))))
49
50;;; sasl-scram-rfc-tests.el ends here