From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Magnus Henoch Newsgroups: gmane.emacs.bugs Subject: bug#17636: Implement SCRAM-SHA-1 SASL mechanism Date: Thu, 29 May 2014 22:32:35 +0100 Message-ID: NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1401399273 28358 80.91.229.3 (29 May 2014 21:34:33 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Thu, 29 May 2014 21:34:33 +0000 (UTC) To: 17636@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Thu May 29 23:34:25 2014 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1Wq7xx-0005OZ-6w for geb-bug-gnu-emacs@m.gmane.org; Thu, 29 May 2014 23:34:25 +0200 Original-Received: from localhost ([::1]:50415 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wq7xw-0004mi-Kr for geb-bug-gnu-emacs@m.gmane.org; Thu, 29 May 2014 17:34:24 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:42561) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wq7xj-0004ce-Pp for bug-gnu-emacs@gnu.org; Thu, 29 May 2014 17:34:21 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Wq7xa-0002ly-MD for bug-gnu-emacs@gnu.org; Thu, 29 May 2014 17:34:11 -0400 Original-Received: from debbugs.gnu.org ([140.186.70.43]:37644) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wq7xa-0002lu-IK for bug-gnu-emacs@gnu.org; Thu, 29 May 2014 17:34:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.80) (envelope-from ) id 1Wq7xa-0002wo-9N for bug-gnu-emacs@gnu.org; Thu, 29 May 2014 17:34:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Magnus Henoch Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 29 May 2014 21:34:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 17636 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.140139919711238 (code B ref -1); Thu, 29 May 2014 21:34:02 +0000 Original-Received: (at submit) by debbugs.gnu.org; 29 May 2014 21:33:17 +0000 Original-Received: from localhost ([127.0.0.1]:36521 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1Wq7wq-0002vB-UO for submit@debbugs.gnu.org; Thu, 29 May 2014 17:33:17 -0400 Original-Received: from eggs.gnu.org ([208.118.235.92]:39405) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1Wq7wn-0002ur-Rd for submit@debbugs.gnu.org; Thu, 29 May 2014 17:33:14 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Wq7wZ-0002N2-Cc for submit@debbugs.gnu.org; Thu, 29 May 2014 17:33:08 -0400 Original-Received: from lists.gnu.org ([2001:4830:134:3::11]:39799) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wq7wZ-0002Mu-9x for submit@debbugs.gnu.org; Thu, 29 May 2014 17:32:59 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:40444) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wq7wQ-0003N4-84 for bug-gnu-emacs@gnu.org; Thu, 29 May 2014 17:32:59 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Wq7wH-0002Ai-0q for bug-gnu-emacs@gnu.org; Thu, 29 May 2014 17:32:50 -0400 Original-Received: from mail-wi0-x236.google.com ([2a00:1450:400c:c05::236]:39573) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wq7wG-00029X-Ly for bug-gnu-emacs@gnu.org; Thu, 29 May 2014 17:32:40 -0400 Original-Received: by mail-wi0-f182.google.com with SMTP id r20so64011wiv.9 for ; Thu, 29 May 2014 14:32:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:message-id:user-agent:mime-version :content-type; bh=0oiDq9el0A7uI0MczGTFBnMQQPwS7QSnqCUUeD+Br3o=; b=o68emTdYI++tgdgC0BYMoCMUAr7+9Wg2SMOiXvDX94eVQOtou4SFj39SYL0ljMhXi0 w0Ob7ZCVZVNU5ErnWKuB8Xyt2pqk/hLicX6I+m9BiDSP1oQtdiMS5ZPNC9PkUvcVeD0G ++ZmRYox4mpY3PRmlGeVrfhh5aqjf4XHLqKH6OVlvkS+GOd/ETWKAY4KEO+f8jIYcLDu PxUmtxm2XtRYThrPSmdGvpJtkuk49TmMwlOrDOlYl7OkiFkcMwF6kM5Ka0I4q/T9hToK v/GEU8qFERCOg3vZ8k7B5MdogO5WJOAQZXcyYiv0QBnbvINYwGhNMl74CXwcTke9tB5I 2/AQ== X-Received: by 10.194.110.71 with SMTP id hy7mr14781099wjb.23.1401399159521; Thu, 29 May 2014 14:32:39 -0700 (PDT) Original-Received: from poki-sona-sin.local (2e40e3ee.skybroadband.com. [46.64.227.238]) by mx.google.com with ESMTPSA id br5sm4700195wjc.9.2014.05.29.14.32.36 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 29 May 2014 14:32:37 -0700 (PDT) User-Agent: Gnus/5.13001 (Ma Gnus v0.10) Emacs/24.4.50 (darwin) X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.15 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 140.186.70.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.bugs:89725 Archived-At: --=-=-= Content-Type: text/plain Severity: wishlist Tags: patch The attached changes implement the SCRAM-SHA-1 SASL mechanism (as specified in RFC 5802) for Emacs' SASL library. Automated tests are included. I chose to put the implementation in a file called sasl-scram-rfc.el. The Emacs SASL library was imported from FLIM a long time ago, apart from a single file called sasl-scram.el, for which copyright assignment wasn't completed. This file implemented the now obsolete SCRAM-MD5 SASL mechanism, based on a draft of what eventually became RFC 5802. I chose to use a different file name, to prevent the new code from being shadowed in case someone has FLIM installed, but I'm open to be persuaded to use another file name. I removed SCRAM-MD5 from the list of SASL mechanisms in sasl.el, and added SCRAM-SHA-1 first, so that it gets picked preferentially by sasl-find-mechanism. SCRAM-SHA-1 requires an implementation of HMAC-SHA1, so I added a module for that, too. Ideally, this module should implement the mechanism SCRAM-SHA-1-PLUS, too. This mechanism includes channel binding, which guarantees that the entity you're authenticating to is the same entity that you completed a TLS handshake with. Implementing this would require some cooperation from gnutls.el, as well as some thought about new API functions/options for sasl.el. For lisp/ChangeLog: * 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. * net/sasl-scram-rfc.el: New file. * net/hmac-sha1.el: New file. For test/ChangeLog: * automated/sasl-scram-rfc-tests.el: New file. * automated/hmac-sha1-tests.el: New file. --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=hmac-sha1.el Content-Transfer-Encoding: quoted-printable Content-Description: lisp/net/hmac-sha1.el ;;; hmac-sha1.el --- Compute HMAC-SHA1. -*- lexical-binding: t= ; -*- ;; Copyright (C) 2014 Magnus Henoch ;; Author: Magnus Henoch ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; See test/automated/hmac-sha1-tests.el for tests. ;;; Code: (eval-when-compile (require 'hmac-def)) ;; We need a function that takes one argument and returns a binary ;; SHA-1 hash. (defsubst hmac-sha1--sha1-binary (data) (sha1 data nil nil t)) (define-hmac-function hmac-sha1 hmac-sha1--sha1-binary 64 20) (provide 'hmac-sha1) ;;; hmac-sha1.el ends here --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=hmac-sha1-tests.el Content-Transfer-Encoding: quoted-printable Content-Description: test/automated/hmac-sha-1-tests.el ;;; hmac-sha1-tests.el --- tests for HMAC-SHA1 -*- lexical-binding: t= ; -*- ;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Magnus Henoch ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; Test cases from RFC 2202, "Test Cases for HMAC-MD5 and HMAC-SHA-1". ;;; Code: (require 'hex-util) (require 'hmac-sha1) (ert-deftest hmac-sha1-tests () "HMAC-SHA1 test cases from RFC 2202" ;; Test case 1 (should (equal (hmac-sha1 "Hi There" (decode-hex-string "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")) (decode-hex-string "b617318655057264e28bc0b6fb378c8ef146be00"))) ;; Test case 2 (should (equal (hmac-sha1 "what do ya want for nothing?" "Jefe") (decode-hex-string "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"))) ;; Test case 3 (should (equal (hmac-sha1 (string-as-unibyte (make-string 50 (make-char 'eight-bit #xd= d))) (string-as-unibyte (decode-hex-string "aaaaaaaaaaaaaaaaaaaaaaaaaaaa= aaaaaaaaaaaa"))) (decode-hex-string "125d7342b9ac11cd91a39af48aa17b4f63f175d3"))) ;; Test case 4 (should (equal (hmac-sha1 (string-as-unibyte (make-string 50 (make-char 'eight-bit #xc= d))) (decode-hex-string "0102030405060708090a0b0c0d0e0f10111213141516171= 819")) (decode-hex-string "4c9007f4026250c6bc8414f9bf50c86c2d7235da"))) ;; Test case 5 (should (equal (hmac-sha1 "Test With Truncation" (string-as-unibyte (decode-hex-string "0c0c0c0c0c0c0c0c0c0c0c0c0c0c= 0c0c0c0c0c0c"))) (decode-hex-string "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"))) ;; Test case 6 (should (equal (hmac-sha1 "Test Using Larger Than Block-Size Key - Hash Key First" (string-as-unibyte (make-string 80 (make-char 'eight-bit #xaa)))) (decode-hex-string "aa4ae5e15272d00e95705637ce8a3b55ed402112"))) ;; Test case 7 (should (equal (hmac-sha1 "Test Using Larger Than Block-Size Key and Larger Than One B= lock-Size Data" (string-as-unibyte (make-string 80 (make-char 'eight-bit #xaa)))) (decode-hex-string "e8e99d0f45237d786d6bbaa7965c7808bbff1a91")))) ;;; hmac-sha1-tests.el ends here --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=sasl-scram-rfc.el Content-Transfer-Encoding: quoted-printable Content-Description: lisp/net/sasl-scram-rfc.el ;;; sasl-scram-rfc.el --- SCRAM-SHA-1 module for the SASL client framework = -*- lexical-binding: t; -*- ;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Magnus Henoch ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; This program is implemented from RFC 5802. It implements the ;; SCRAM-SHA-1 SASL mechanism. ;; ;; RFC 5802 foresees "hash agility", i.e. new mechanisms based on the ;; same protocol but using a different hash function. Likewise, this ;; module attempts to separate generic and specific functions, which ;; should make it easy to implement any future SCRAM-* SASL mechanism. ;; It should be as simple as copying the SCRAM-SHA-1 section below and ;; replacing all SHA-1 references. ;; ;; This module does not yet implement the variants with channel ;; binding, i.e. SCRAM-*-PLUS. That would require cooperation from ;; the TLS library. ;;; Code: (require 'cl-lib) (require 'sasl) ;;; SCRAM-SHA-1 (require 'hmac-sha1) (defconst sasl-scram-sha-1-steps '(sasl-scram-client-first-message sasl-scram-sha-1-client-final-message sasl-scram-sha-1-authenticate-server)) (defun sasl-scram-sha-1-client-final-message (client step) (sasl-scram--client-final-message ;; return hash as binary: (lambda (x) (sha1 x nil nil t)) 'hmac-sha1 client step)) (defun sasl-scram-sha-1-authenticate-server (client step) (sasl-scram--authenticate-server 'hmac-sha1 client step)) (put 'sasl-scram-sha-1 'sasl-mechanism (sasl-make-mechanism "SCRAM-SHA-1" sasl-scram-sha-1-steps)) (provide 'sasl-scram-sha-1) ;;; Generic for SCRAM-* (defun sasl-scram-client-first-message (client _step) (let ((c-nonce (sasl-unique-id))) (sasl-client-set-property client 'c-nonce c-nonce)) (concat ;; n =3D client doesn't support channel binding "n," ;; TODO: where would we get authorization id from? "," (sasl-scram--client-first-message-bare client))) (defun sasl-scram--client-first-message-bare (client) (let ((c-nonce (sasl-client-property client 'c-nonce))) (concat ;; TODO: saslprep username or disallow non-ASCII characters "n=3D" (sasl-client-name client) "," "r=3D" c-nonce))) (defun sasl-scram--client-final-message (hash-fun hmac-fun client step) (unless (string-match "^r=3D\\([^,]+\\),s=3D\\([^,]+\\),i=3D\\([0-9]+\\)\\(?:$\\|,\\)" (sasl-step-data step)) (sasl-error "Unexpected server response")) (let* ((step-data (sasl-step-data step)) (nonce (match-string 1 step-data)) (salt-base64 (match-string 2 step-data)) (iteration-count (string-to-number (match-string 3 step-data))) (c-nonce (sasl-client-property client 'c-nonce)) ;; no channel binding, no authorization id (cbind-input "n,,")) (unless (string-prefix-p c-nonce nonce) (sasl-error "Invalid nonce from server")) (let* ((client-final-message-without-proof (concat "c=3D" (base64-encode-string cbind-input) "," "r=3D" nonce)) (password ;; TODO: either apply saslprep or disallow non-ASCII characters (sasl-read-passphrase (format "%s passphrase for %s: " (sasl-mechanism-name (sasl-client-mechanism client)) (sasl-client-name client)))) (salt (base64-decode-string salt-base64)) (salted-password ;; Hi(str, salt, i): (let ((digest (concat salt (string 0 0 0 1))) (xored nil)) (dotimes (_i iteration-count xored) (setq digest (funcall hmac-fun digest password)) (setq xored (if (null xored) digest (cl-map 'string 'logxor xored digest)))))) (client-key (funcall hmac-fun "Client Key" salted-password)) (stored-key (funcall hash-fun client-key)) (auth-message (concat (sasl-scram--client-first-message-bare client) "," step-data "," client-final-message-without-proof)) (client-signature (funcall hmac-fun (encode-coding-string auth-message = 'utf-8) stored-key)) (client-proof (cl-map 'string 'logxor client-key client-signature)) (client-final-message (concat client-final-message-without-proof "," "p=3D" (base64-encode-string client-proof)))) (sasl-client-set-property client 'auth-message auth-message) (sasl-client-set-property client 'salted-password salted-password) client-final-message))) (defun sasl-scram--authenticate-server (hmac-fun client step) (cond ((string-match "^e=3D\\([^,]+\\)" (sasl-step-data step)) (sasl-error (format "Server error: %s" (match-string 1 (sasl-step-data = step))))) ((string-match "^v=3D\\([^,]+\\)" (sasl-step-data step)) (let* ((verifier (base64-decode-string (match-string 1 (sasl-step-data = step)))) (auth-message (sasl-client-property client 'auth-message)) (salted-password (sasl-client-property client 'salted-password)) (server-key (funcall hmac-fun "Server Key" salted-password)) (expected-server-signature (funcall hmac-fun (encode-coding-string auth-message 'utf-8) server-ke= y))) (unless (string=3D expected-server-signature verifier) (sasl-error "Server not authenticated")))) (t (sasl-error "Invalid response from server")))) (provide 'sasl-scram-rfc) ;;; sasl-scram-rfc.el ends here --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=sasl-scram-rfc-tests.el Content-Transfer-Encoding: quoted-printable Content-Description: test/automated/sasl-scram-rfc-tests.el ;;; sasl-scram-rfc-tests.el --- tests for SCRAM-SHA-1 -*- lexical-bin= ding: t; -*- ;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Magnus Henoch ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; Test cases from RFC 5802. ;;; Code: (require 'sasl) (require 'sasl-scram-rfc) (ert-deftest sasl-scram-sha-1-test () ;; The following strings are taken from section 5 of RFC 5802. (let ((client (sasl-make-client (sasl-find-mechanism '("SCRAM-SHA-1")) "user" "imap" "localhost")) (data "r=3Dfyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=3DQSXCR+Q6sek8bf92= ,i=3D4096") (c-nonce "fyko+d2lbbFgONRv9qkxdawL") (sasl-read-passphrase (lambda (_prompt) (copy-sequence "pencil")))) (sasl-client-set-property client 'c-nonce c-nonce) (should (equal (sasl-scram-sha-1-client-final-message client (vector nil data)) "c=3Dbiws,r=3Dfyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=3Dv0X8v3Bz= 2T0CJGbJQyF0X+HI4Ts=3D")) ;; This should not throw an error: (sasl-scram-sha-1-authenticate-server client (vector nil "v=3DrmF9pqV8S= 7suAoZWja4dJRkFsKQ=3D ")))) ;;; sasl-scram-rfc-tests.el ends here --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=sasl.patch Content-Description: changes to lisp/net/sasl.el diff --git a/lisp/net/sasl.el b/lisp/net/sasl.el index 03a8f72..adb13b9 100644 --- a/lisp/net/sasl.el +++ b/lisp/net/sasl.el @@ -35,8 +35,8 @@ ;;; Code: (defvar sasl-mechanisms - '("CRAM-MD5" "DIGEST-MD5" "PLAIN" "LOGIN" "ANONYMOUS" - "NTLM" "SCRAM-MD5")) + '("SCRAM-SHA-1" "CRAM-MD5" "DIGEST-MD5" "PLAIN" "LOGIN" "ANONYMOUS" + "NTLM")) (defvar sasl-mechanism-alist '(("CRAM-MD5" sasl-cram) @@ -45,7 +45,7 @@ ("LOGIN" sasl-login) ("ANONYMOUS" sasl-anonymous) ("NTLM" sasl-ntlm) - ("SCRAM-MD5" sasl-scram))) + ("SCRAM-SHA-1" sasl-scram-sha-1))) (defvar sasl-unique-id-function #'sasl-unique-id-function) --=-=-=--