From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Jarno Malmari Newsgroups: gmane.emacs.devel Subject: [PATCH 3/3] Initial implementation for HTTP Digest qop for url Date: Sun, 30 Aug 2015 19:17:07 +0300 Message-ID: <1440951427-12486-3-git-send-email-jarno@malmari.fi> References: <1440951427-12486-1-git-send-email-jarno@malmari.fi> NNTP-Posting-Host: plane.gmane.org X-Trace: ger.gmane.org 1440951480 21213 80.91.229.3 (30 Aug 2015 16:18:00 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Sun, 30 Aug 2015 16:18:00 +0000 (UTC) Cc: larsi@gnus.org To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sun Aug 30 18:17:48 2015 Return-path: Envelope-to: ged-emacs-devel@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 1ZW5Ih-0004cc-Nj for ged-emacs-devel@m.gmane.org; Sun, 30 Aug 2015 18:17:47 +0200 Original-Received: from localhost ([::1]:59311 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZW5Ih-0004Br-NR for ged-emacs-devel@m.gmane.org; Sun, 30 Aug 2015 12:17:47 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:57353) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZW5IJ-0004BZ-4p for emacs-devel@gnu.org; Sun, 30 Aug 2015 12:17:24 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ZW5IF-0001sq-Ih for emacs-devel@gnu.org; Sun, 30 Aug 2015 12:17:22 -0400 Original-Received: from out4-smtp.messagingengine.com ([66.111.4.28]:33296) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZW5IF-0001sR-Et for emacs-devel@gnu.org; Sun, 30 Aug 2015 12:17:19 -0400 Original-Received: from compute2.internal (compute2.nyi.internal [10.202.2.42]) by mailout.nyi.internal (Postfix) with ESMTP id 2FCC9208E7 for ; Sun, 30 Aug 2015 12:17:18 -0400 (EDT) Original-Received: from frontend1 ([10.202.2.160]) by compute2.internal (MEProxy); Sun, 30 Aug 2015 12:17:18 -0400 DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=malmari.fi; h=cc :date:from:in-reply-to:message-id:references:subject:to :x-sasl-enc:x-sasl-enc; s=mesmtp; bh=jgZIOt6DNbt6RWI5UDITg67Z8qg =; b=ZMFDgNJ2Qm6KGRYpp/wrz4cFLiKPvLV0xxgGORbQxYcneNafRjKQ2Z7At8J bhSk1OvnU1R4j+UzutaU984B8I9CLV6S1hTTCQPD/+Y/O4nfq1wIt8dFZ/rbEHac spnZQP8vt3B09OJpGoolVrSrvStp1674zBSJd7BRrUJkC2nY= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d= messagingengine.com; h=cc:date:from:in-reply-to:message-id :references:subject:to:x-sasl-enc:x-sasl-enc; s=smtpout; bh=jgZI Ot6DNbt6RWI5UDITg67Z8qg=; b=PPKyjrGRzIu8/q7ILk3neZqcse44e5acSxlw 2wJpYoeFKwwagx9mC3n0tpqLXuOBfJjmqAI7i4KX3Y4kl1QAOM9+yXXNmXVjiOMw EqdA+WTAlGFjj4IP/fLjma/XzMAOx5J468uzUVDPPI8NM4TgIHebsiEtB/mxW74D NXttzgM= X-Sasl-enc: MQpXyG6FDrU5mQPO9XByoSKypyyW1OFvonotmDA3Z3VQ 1440951437 Original-Received: from vabi.intranet (a91-155-178-71.elisa-laajakaista.fi [91.155.178.71]) by mail.messagingengine.com (Postfix) with ESMTPA id 7E99CC00014; Sun, 30 Aug 2015 12:17:17 -0400 (EDT) X-Mailer: git-send-email 2.5.0.330.g130be8e In-Reply-To: <1440951427-12486-1-git-send-email-jarno@malmari.fi> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 66.111.4.28 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:189323 Archived-At: Some servers have dropped backward compatibility with HTTP Digest Authentication without "qop". The Quality of protection scheme is partially implemented: * only one supported qop, qop=auth * only one supported algorithm, algorithm=md5 * nonce count remains always as 1, no replays --- lisp/url/url-auth.el | 59 +++++++++++++++++++++++++++++++++++---- test/automated/url-auth-tests.el | 60 +++++++++++++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/lisp/url/url-auth.el b/lisp/url/url-auth.el index 23ee313..d365e1a 100644 --- a/lisp/url/url-auth.el +++ b/lisp/url/url-auth.el @@ -128,8 +128,8 @@ instead of the filename inheritance method." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Digest authorization code ;;; ------------------------ -;;; This implements the DIGEST authorization type. See the internet draft -;;; ftp://ds.internic.net/internet-drafts/draft-ietf-http-digest-aa-01.txt +;;; This implements the DIGEST authorization type. See RFC 2617 +;;; https://www.ietf.org/rfc/rfc2617.txt ;;; for the complete documentation on this type. ;;; ;;; This is very secure @@ -164,6 +164,15 @@ METHOD and DIGEST-URI are strings used to create the hash from." Inputs for this are the hash strings HA1, HA2, and NONCE." (url-digest-auth-kd (url-digest-auth-colonjoin nonce ha2) ha1)) +(defsubst url-digest-auth-make-request-digest-qop (qop ha1 ha2 nonce nc cnonce) + "Construct the request-digest with qop as described in RFC 2617. +QOP describes the \"quality of protection\" and algorithm to use. +HA1, HA2, and NONCE, NC, and CNONCE are string values, described +in RFC 2617. It's worth noting that HA2 already depends on value +of QOP." + (url-digest-auth-kd (url-digest-auth-colonjoin + nonce nc cnonce qop ha2) ha1)) + (defsubst url-digest-auth-directory-id (url realm) "Make an identifier to use for server keys. The identifier is made either from URL's path or REALM." @@ -174,6 +183,21 @@ The identifier is made either from URL's path or REALM." The identifier is made from URL's host and port." (format "%s:%d" (url-host url) (url-port url))) +(defun url-digest-auth-make-cnonce () + "Compute a new unique client nonce value." + (base64-encode-string + (apply 'format "%016x%04x%04x%05x%05x" (random) (current-time)) t)) + +(defun url-digest-auth-nonce-count (nonce) + "The number requests sent to server with the given NONCE. +This count includes the request we're preparing here. + +Currently, this is not implemented and will always return 1. + +Value returned is in string format with leading zeroes, such as +\"00000001\"." + (format "%08x" 1)) + (defun url-digest-auth-name-value-string (pairs) "Concatenate name-value pairs in association list PAIRS. @@ -278,12 +302,20 @@ Some fields are filled with the given object URL, string REALM, and contents of alist ATTRS. ATTRS is expected to contain at least the server's \"nonce\" -value. It also might contain the optional \"opaque\" value." +value. It also might contain the optional \"opaque\" value. +Newer implementations conforming to RFC 2617 should also contain +qop (Quality Of Protection) and related attributes. + +Restrictions on Quality of Protection scheme: The qop value +\"auth-int\" or algorithm any other than \"MD5\" are not +implemented." + (when key (let ((user (nth 1 key)) (ha1 (nth 2 key)) (ha2 (nth 3 key)) (digest-uri (url-filename url)) + (qop (cdr-safe (assoc "qop" attrs))) (nonce (cdr-safe (assoc "nonce" attrs))) (opaque (cdr-safe (assoc "opaque" attrs)))) @@ -293,9 +325,24 @@ value. It also might contain the optional \"opaque\" value." (append (list (cons 'username user) (cons 'realm realm) (cons 'nonce nonce) - (cons 'uri digest-uri) - (cons 'response (url-digest-auth-make-request-digest - ha1 ha2 nonce))) + (cons 'uri digest-uri)) + + (cond + ((null qop) + (list (cons 'response (url-digest-auth-make-request-digest + ha1 ha2 nonce)))) + ((string= qop "auth") + (let ((nc (url-digest-auth-nonce-count nonce)) + (cnonce (url-digest-auth-make-cnonce))) + (list (cons 'qop qop) + (cons 'nc nc) + (cons 'cnonce cnonce) + (cons 'response + (url-digest-auth-make-request-digest-qop + qop ha1 ha2 nonce nc cnonce))))) + (t (message "Quality of protection \"%s\" is not implemented." qop) + nil)) + (if opaque (list (cons 'opaque opaque))))))))) diff --git a/test/automated/url-auth-tests.el b/test/automated/url-auth-tests.el index 462d5f2..a956ad9 100644 --- a/test/automated/url-auth-tests.el +++ b/test/automated/url-auth-tests.el @@ -34,6 +34,18 @@ server's WWW-Authenticate header field.") ;; Set explicitly for easier modification for re-runs. (setq url-auth-test-challenges (list + (list :qop "auth" + :nonce "uBr3+qkQBybTr/dKWkmpUqVO7SaEwWYzyTKO7g==$" + :uri "/random/path" + :method "GET" + :realm "Some test realm" + :cnonce "YWU4NDcxYWMxMDAxMjlkMjAwMDE4MjI5MDAwMGY4NGQ=" + :nc "00000001" + :username "jytky" + :password "xi5Ac2HEfKt1lKKO05DCSqsK0u7hqqtsT" + :expected-ha1 "af521db3a83abd91262fead04fa31892" + :expected-ha2 "e490a6a147c79404b365d1f6059ddda5" + :expected-response "ecb6396e93b9e09e31f19264cfd8f854") (list :nonce "a1be8a3065e00c5bf190ad499299aea5" :opaque "d7c2a27230fc8c74bb6e06be8c9cd189" :realm "The Test Realm" @@ -90,14 +102,23 @@ server's WWW-Authenticate header field.") (plist-get row :expected-ha2))))) (ert-deftest url-auth-test-digest-request-digest () - "Check digest response value when not supporting `qop'." + "Check digest response value." (dolist (row url-auth-test-challenges) - (should (string= (url-digest-auth-make-request-digest - ;; HA1 and HA2 already tested - (plist-get row :expected-ha1) - (plist-get row :expected-ha2) - (plist-get row :nonce)) - (plist-get row :expected-response))))) + (should (string= (plist-get row :expected-response) + (if (plist-member row :qop) + (url-digest-auth-make-request-digest-qop + (plist-get row :qop) + ;; HA1 and HA2 already tested + (plist-get row :expected-ha1) + (plist-get row :expected-ha2) + (plist-get row :nonce) + (plist-get row :nc) + (plist-get row :cnonce)) + (url-digest-auth-make-request-digest + ;; HA1 and HA2 already tested + (plist-get row :expected-ha1) + (plist-get row :expected-ha2) + (plist-get row :nonce))))))) (ert-deftest url-auth-test-digest-create-key () "Check user credentials in their hashed form." @@ -203,9 +224,15 @@ Essential is how realms and paths are matched." (should-not auth))))) (ert-deftest url-auth-test-digest-auth () - "Check common authorization string contents." + "Check common authorization string contents. +Challenges with qop are not checked for response since a unique +cnonce is used for generating them which is not mocked by the +test and cannot be passed by arguments to `url-digest-auth'." (dolist (challenge url-auth-test-challenges) - (let* ((attrs (list (cons "nonce" (plist-get challenge :nonce)))) + (let* ((attrs (append + (list (cons "nonce" (plist-get challenge :nonce))) + (if (plist-get challenge :qop) + (list (cons "qop" (plist-get challenge :qop)))))) (url (concat "http://example.org" (plist-get challenge :uri))) url-digest-auth-storage auth) @@ -225,16 +252,23 @@ Essential is how realms and paths are matched." (plist-get challenge :realm) attrs)) (should auth) (should (string-prefix-p "Digest " auth)) - (should (string-match ".*response=\"\\(.*?\\)\".*" auth)) - (should (string= (match-string 1 auth) - (plist-get challenge :expected-response))) (should (string-match ".*username=\"\\(.*?\\)\".*" auth)) (should (string= (match-string 1 auth) (plist-get challenge :username))) (should (string-match ".*realm=\"\\(.*?\\)\".*" auth)) (should (string= (match-string 1 auth) (plist-get challenge :realm))) - ))) + + (if (plist-member challenge :qop) + (progn + ;; We don't know these, just check that they exists. + (should (string-match-p ".*response=\".*?\".*" auth)) + (should (string-match-p ".*nc=\".*?\".*" auth)) + (should (string-match-p ".*cnonce=\".*?\".*" auth))) + (should (string-match ".*response=\"\\(.*?\\)\".*" auth)) + (should (string= (match-string 1 auth) + (plist-get challenge :expected-response)))) + ))) (ert-deftest url-auth-test-digest-auth-opaque () "Check that `opaque' value is added to result when presented by -- 2.5.0.330.g130be8e