diff options
| author | Magnus Henoch | 2015-02-13 19:54:57 +1100 |
|---|---|---|
| committer | Lars Magne Ingebrigtsen | 2015-02-13 19:57:56 +1100 |
| commit | e7d21b4ab11e73c709420eeeb32ffe2421fafe98 (patch) | |
| tree | 67ce2998e3b6c8540e0c468012cdd4d4ce34f4e2 | |
| parent | f61c87f12a36bb2063c25b6742380b5916618ab5 (diff) | |
| download | emacs-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/ChangeLog | 9 | ||||
| -rw-r--r-- | lisp/net/sasl-scram-rfc.el | 160 | ||||
| -rw-r--r-- | lisp/net/sasl.el | 6 | ||||
| -rw-r--r-- | test/ChangeLog | 4 | ||||
| -rw-r--r-- | test/automated/sasl-scram-rfc-tests.el | 50 |
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 @@ | |||
| 1 | 2015-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 | |||
| 1 | 2015-02-13 Lars Ingebrigtsen <larsi@gnus.org> | 10 | 2015-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 @@ | |||
| 1 | 2015-02-13 Magnus Henoch <magnus.henoch@gmail.com> | ||
| 2 | |||
| 3 | * automated/sasl-scram-rfc-tests.el: New file. | ||
| 4 | |||
| 1 | 2015-02-11 Nicolas Petton <nicolas@petton.fr> | 5 | 2015-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 | ||