Internet DRAFT - draft-sayre-http-hmac-digest
draft-sayre-http-hmac-digest
Network Working Group R. Sayre
Internet-Draft
Expires: October 8, 2006 A. Melnikov
Isode Ltd.
April 6, 2006
HMAC Digest Access Authentication for HTTP
draft-sayre-http-hmac-digest-01.txt
Status of this Memo
By submitting this Internet-Draft, each author represents that any
applicable patent or other IPR claims of which he or she is aware
have been or will be disclosed, and any of which he or she becomes
aware will be disclosed, in accordance with Section 6 of BCP 79.
This document may not be modified, and derivative works of it may not
be created, except to publish it as an RFC and to translate it into
languages other than English.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF), its areas, and its working groups. Note that
other groups may also distribute working documents as Internet-
Drafts.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
The list of current Internet-Drafts can be accessed at
http://www.ietf.org/ietf/1id-abstracts.txt.
The list of Internet-Draft Shadow Directories can be accessed at
http://www.ietf.org/shadow.html.
This Internet-Draft will expire on October 8, 2006.
Copyright Notice
Copyright (C) The Internet Society (2006).
Abstract
This document specifies an HTTP authentication scheme based on
cryptographic hashes.
Editor's Note
Sayre & Melnikov Expires October 8, 2006 [Page 1]
Internet-Draft HMAC Digest Authentication April 2006
To discuss this draft, please join the ietf-http-auth mailing
list [1]. Membership is open to all.
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
2. WWW-Authenticate . . . . . . . . . . . . . . . . . . . . . . . 3
3. Authorization . . . . . . . . . . . . . . . . . . . . . . . . 5
4. The Request Digest . . . . . . . . . . . . . . . . . . . . . . 6
5. Header Handling . . . . . . . . . . . . . . . . . . . . . . . 7
6. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 8
7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 8
8. Security Considerations . . . . . . . . . . . . . . . . . . . 8
9. Normative References . . . . . . . . . . . . . . . . . . . . . 8
Appendix A. Example Implementations . . . . . . . . . . . . . . . 9
A.1. Example Server . . . . . . . . . . . . . . . . . . . . . . 9
A.2. Example Client . . . . . . . . . . . . . . . . . . . . . . 11
Appendix B. Change Log . . . . . . . . . . . . . . . . . . . . . 13
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 14
Intellectual Property and Copyright Statements . . . . . . . . . . 15
Sayre & Melnikov Expires October 8, 2006 [Page 2]
Internet-Draft HMAC Digest Authentication April 2006
1. Introduction
This document specifies an HTTP authentication scheme similar to the
Digest scheme [RFC2617]. It borrows heavily from that scheme's
specifcation, but there are substantive differences. Most
importantly, the algorithm is based on a hash of the user password,
rather than the password itself. In addition, the scheme defined in
this document allows for additional message integrity checks on HTTP
request headers. It omits quality-of-protection options and
Authentication-Info headers from the server. Like Digest
authentication, the scheme specified in this document suffers from
many known weaknesses, and is only intended to improve on Basic
authentication [RFC2617].
This specification is a companion to the HTTP/1.1 specification
[RFC2616]. It uses the augmented BNF section 2.1 of that document,
and relies on both the non-terminals defined in that document and
other aspects of the HTTP/1.1 specification.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
2. WWW-Authenticate
When a server a recieves a request for an access-protected object
without an acceptable Authorization header, it responds with a "401
Unauthorized" status code, and a WWW-Authenticate header [RFC2617].
For the HMAC Digest scheme, the value of the header is as follows:
challenge = "HMACDigest" digest-challenge
digest-challenge = 1#( realm | snonce | [domain] | [reason] |
[algorithm] | [pw-algorithm] |
[salt] | [auth-param] )
realm = "realm" "=" quoted-string
snonce = "snonce" "=" quoted-string
domain = "domain" "=" <"> URI *( 1*SP URI ) <">
URI = absoluteURI | abs_path
reason = "reason" "=" ("unauthorized" | "integrity" |
token)
algorithm = "algorithm" "=" ( "HMAC-SHA-1" | "HMAC-MD5" |
token )
pw-algorithm = "pw-algorithm" "=" ( "SHA-1" | "MD5" |
token )
salt = "salt" "=" quoted-string
Sayre & Melnikov Expires October 8, 2006 [Page 3]
Internet-Draft HMAC Digest Authentication April 2006
realm: A string to be displayed to users so they know which username
and password to use. This string should contain at least the name
of the host performing the authentication and might additionally
indicate the collection of users who might have access. An
example might be "registered_users@gotham.news.com".
snonce: A server-specified data string which should be uniquely
generated each time a 401 response is made. It is recommended
that this string be base64 or hexadecimal data. Specifically,
since the string is passed in the header lines as a quoted string,
the double-quote character is not allowed.
The contents of the snonce are implementation dependent and opaque
to the client. The quality of the implementation depends on a
good choice. An snonce might, for example, be constructed as the
base64 encoding of
time-stamp hash(time-stamp ":" ETag ":" private-key)
where time-stamp is a server-generated time or other non-repeating
value, ETag is the value of the HTTP ETag header associated with
the requested entity, and private-key is data known only to the
server. With an snonce of this form, a server would recalculate
the hash portion after receiving the client authentication header
and reject the request if it did not match the snonce from that
header or if the time-stamp value is not recent enough. In this
way, the server can limit the time of the snonce's validity.[@
Eliminate replay text?] The inclusion of the ETag prevents a
replay request for an updated version of the resource. (Note:
including the IP address of the client in the snonce would appear
to offer the server the ability to limit the reuse of the snonce
to the same client that originally got it. However, that would
break proxy farms, where requests from a single user often go
through different proxies in the farm. Also, IP address spoofing
is not that hard.)
domain: A quoted, space-separated list of URI references [RFC3986]
that define the protection space. If a URI is an abs_path, it is
relative to the canonical root URL [RFC2617] of the server being
accessed. An absoluteURI in this list may refer to a different
server than the one being accessed. The client can use this list
to determine the set of URIs for which the same authentication
information may be sent: any URI that has a URI in this list as a
prefix (after both have been made absolute) may be assumed to be
in the same protection space. If this directive is omitted or its
value is empty, the client should assume that the protection space
consists of all URIs on the responding server.
Sayre & Melnikov Expires October 8, 2006 [Page 4]
Internet-Draft HMAC Digest Authentication April 2006
reason: The value of this directive indicates the reason for the
rejection of the previous client request. "unauthorized" indicates
that the request did not contain a valid digest. "stale" indicates
that the previous request from the client was rejected because the
snonce value was stale. The client may wish to simply retry the
request with a new encrypted response, without reprompting the
user for a new username and password. "integrity" indicates that
the request contained unverified content that the server requires
be included in the calculation of the digest. If the directive is
not present, or a value other than "integrity" or "stale", the
client should behave as though its value were "unauthorized".
algorithm: This directive indicates the HMAC construction to be used
[RFC2104]. If not present, it is assumed to be "HMAC-SHA-1".
pw-algorithm: This directive indicates the algorithm to be used when
preparing an HMAC key. If not present, it is assumed to be
"SHA-1".
salt: If present, this directive indicates a value that is appended
to the password before the initial hash function is applied.
3. Authorization
The Authorization request header contains client credentials
generated according to the directives recieved in a WWW-Authenticate
response header.
credentials = "HMACDigest" digest-response
digest-response = 1#( username | realm | cnonce | snonce |
digest-uri | created | response | [headers] |
[auth-param] )
username = "username" "=" quoted-string
cnonce = "cnonce" "=" quoted-string
digest-uri = "uri" "=" request-uri
response = "response" "=" request-digest
request-digest = <"> *LHEX <">
LHEX = "0" | "1" | "2" | "3" |
"4" | "5" | "6" | "7" |
"8" | "9" | "a" | "b" |
"c" | "d" | "e" | "f"
headers = "headers" "=" header-list
header-list = <"> field-name *( 1*SP field-name ) <">
Sayre & Melnikov Expires October 8, 2006 [Page 5]
Internet-Draft HMAC Digest Authentication April 2006
username: The user's name in the specified realm.
cnonce: The cnonce value is an opaque quoted string value provided by
the client and used by both client and server to avoid chosen
plaintext attacks, to provide mutual authentication, and to
provide some message integrity protection.
digest-uri: The URI from Request-URI of the Request-Line; duplicated
here because proxies are allowed to change the Request-Line in
transit.
response: The response value is a string containing hexadecimal data.
The two HMAC constructions listed by this specification will
produce strings of 32 or 40 characters in length.
created: The created value is an RFC3339 timestamp [RFC3339].
headers: The headers value is a space-separated list of HTTP headers
used to calculate the request-digest.
4. The Request Digest
This section describes the process a client uses to calculate the
request digest, and how the server can verify it.
1. The client applies the algorithm specified by the pw-algorithm
directive to the user password. If present, the value of the
salt directive is appended to the password prior to calculation.
2. The client applies the algorithm specified by the pw-algorithm
directive to the concatenation of the username, a colon, the
lowercased hexadecimal digest of the result of step 1, a colon,
and the value of the realm directive. The lowercased hexadecimal
digest of the result serves as the HMAC key.
3. The client generates a cnonce, a data string which should be
uniquely generated each time a request is made. It is
recommended that this string be base64 or hexadecimal data.
Specifically, since the string is passed in the header lines as a
quoted string, the double-quote character is not allowed. A
combination of a timestamp and a random number is sufficient for
many purposes.
4. The client forms a list of request headers it wishes to include
in the digest calculation. The most useful headers to include
are entity headers such as Content-Type, Content-Length, and
Content-MD5 (see Section 5).
Sayre & Melnikov Expires October 8, 2006 [Page 6]
Internet-Draft HMAC Digest Authentication April 2006
5. The client generates a timestamp using the current time.
6. The client concatenates the request method, a colon, the request
URI, a colon, the cnonce, a colon, the snonce, a colon, and the
value of each applicable header in the header list (see
Section 5). This value is the message data.
7. The client applies the HMAC construction specified by the
algorithm directive to the key and the message data. The
lowercased hexadecimal digest of this calculation is the value of
the response directive.
8. The client then uses the relevant values to compose an
Authorization header, and sends the request.
When the server receives a request containing an Authorization header
using the HMAC Digest scheme, it can validate it value using the
procedure listed below.
1. The server should already have the hash of the user's password
available, using the algorithm it instructed the client to use in
the WWW-Authenticate header.
2. The server uses the realm and username directives supplied in the
Authorization header to check for a candidate key.
3. The server concatenates the request method, a colon, the value of
the uri directive, a colon, the value of the nonce directive, a
colon, the value of the created directive, a colon, and the value
of each header in the headers directive (see Section 5). This
value is the message data.
4. The server uses the key to calculate the HMAC for the message
data. If the hexadecimal digest of this calculation matches the
value provided in the response directive, the request is valid.
5. Header Handling
When selecting headers for inclusion in the Digest calculation,
clients SHOULD NOT include hop-by-hop headers. HTTP 1.1 [RFC2616]
defines eight hop-by-hop headers: Connection, Keep-Alive, Proxy-
Authenticate, Proxy-Authorization, TE, Trailers, Transfer-Encoding,
and Upgrade. HTTP 1.1 also requires that extension hop-by-hop
headers are listed in the Connection header.
When creating or verifying a digest, leading whitespace in the header
values MUST be stripped and header unfolding MUST NOT be done. If a
Sayre & Melnikov Expires October 8, 2006 [Page 7]
Internet-Draft HMAC Digest Authentication April 2006
header in the header list appears multiple times, those values are be
combined in order. For example, if a client specifies headers="A B",
and the request contains A,B,A headers (in that order), both sides
MUST calculate the digest using header values in the order A,A,B.
6. Acknowledgements
This document is largely based on the Digest section of "HTTP
Authentication: Basic and Digest Access Authentication" [RFC2617],
and includes some sections of that document verbatim.
The technique of including header values in the digest calculation
was originally proposed by James Undery for the SIP protocol.
7. IANA Considerations
This memo includes no request to IANA.
8. Security Considerations
[TBD... depends on how much needs to be repeated from RFC2617.]
9. Normative References
[RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-
Hashing for Message Authentication", RFC 2104,
February 1997.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
[RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S.,
Leach, P., Luotonen, A., and L. Stewart, "HTTP
Authentication: Basic and Digest Access Authentication",
RFC 2617, June 1999.
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet:
Timestamps", RFC 3339, July 2002.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
Resource Identifier (URI): Generic Syntax", STD 66,
Sayre & Melnikov Expires October 8, 2006 [Page 8]
Internet-Draft HMAC Digest Authentication April 2006
RFC 3986, January 2005.
[1] <http://lists.osafoundation.org/cgi-bin/mailman/listinfo/
ietf-http-auth>
[2] <http://www.python.org>
Appendix A. Example Implementations
This section provides example implementations in the Python [2]
programming language, version 2.4.
A.1. Example Server
The example server program responds to all request URIs with the same
response, and knows of only one user. If the server program is saved
in the file "hmac-digest-server.py", it can be started by typing
"python hmac-digest-server.py".
import BaseHTTPServer, cgi, urllib2
import time
import hmac, sha, md5, base64
PORT = 8888
user = "user"
password = "password"
salt = 'xyzzy'
realm = "HMACDigest Sample"
algo = "HMAC-SHA-1"
pw_algo = "MD5"
key_str = "%s:%s:%s" % (user,
md5.new(password+salt).hexdigest(),
realm)
key = md5.new(key_str).hexdigest()
secret_key = "moo"
digest_header = 'HMACDigest realm="%s", '
digest_header += 'snonce="%s", '
digest_header += 'reason="%s", '
digest_header += 'domain="/ http://www.example.com/", '
digest_header += 'algorithm="%s", '
digest_header += 'pw-algorithm="%s", '
digest_header += 'salt="%s"'
class HMACDigestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
Sayre & Melnikov Expires October 8, 2006 [Page 9]
Internet-Draft HMAC Digest Authentication April 2006
auth = self.headers.getheader('authorization')
result = self.check(auth)
if result is False:
self.send_401()
elif result == 'stale':
self.send_401(reason='stale')
else:
self.send_response(200)
self.send_header('Content-type','text/plain')
self.end_headers()
self.wfile.write("\nAuthentication Successful!\n")
def check(self, auth):
if auth is None:
return False
token, fields = auth.split(' ', 1)
if token != 'HMACDigest':
return False
cred = urllib2.parse_http_list(fields)
cred = urllib2.parse_keqv_list(cred)
if cred['username'] != user or cred['realm'] != realm:
return False
snonce = cred['snonce']
nonce_time,s_hash = base64.b64decode(snonce).split()
test_hash = md5.new(nonce_time +
":fake_etag:" + secret_key).hexdigest()
if s_hash != test_hash:
return False
now = time.mktime(time.gmtime())
# allow 10 minute old nonces... this is arbitrary
if now - float(nonce_time) > 600:
return "stale"
names = cred.get('headers','').split()
vals = ''.join([self.headers.getheader(h) for h in names])
msg = "%s:%s:%s:%s:%s" % (self.command, self.path,
cred['cnonce'], snonce,
vals)
the_hmac = hmac.new(key, msg, sha).hexdigest()
if cred['response'] == the_hmac:
return True
else:
return False
def send_401(self, reason="unauthorized"):
auth_header = digest_header % (realm, self.snonce(),
reason, algo, pw_algo, salt)
self.send_response(401)
self.send_header('WWW-Authenticate', auth_header)
Sayre & Melnikov Expires October 8, 2006 [Page 10]
Internet-Draft HMAC Digest Authentication April 2006
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write('\nUnauthorized\n')
def snonce(self):
now = str(time.mktime(time.gmtime()))
print now
h = md5.new(now+":fake_etag:"+secret_key).hexdigest()
return base64.b64encode(now + " " + h)
httpd = BaseHTTPServer.HTTPServer(("", PORT), HMACDigestHandler)
print "Serving at port", PORT
httpd.serve_forever()
A.2. Example Client
The example client program makes one request to the example server
without an authorization header, examines the WWW-Authenticate header
returned in the 401 response, and then creates an Authorization
header to make a second (successful) request.
import httplib, urllib2, md5, sha, hmac, time, random, os
PORT = 8888
username = "user"
password = "password"
params = {}
headers = {"accept": "text/X-Oh-Several-Things+xml, */*",
"user-agent": "libwww-perl/5.803",
"x-hopbyhop": "some proxy information",
"x-fooproxy": "some more proxy info",
"x-freedom-is-what-you-think-it-is":
"But there ain't no train to Stockholm",
"connection": "close, x-hopbyhop, x-fooproxy"}
# Make an initial request
conn = httplib.HTTPConnection("localhost",PORT)
conn.request("GET","",params,headers)
response = conn.getresponse()
data = response.read()
# Print the rejection letter
print "First request:",data
# Get the challenge
wa = response.getheader('WWW-Authenticate')
Sayre & Melnikov Expires October 8, 2006 [Page 11]
Internet-Draft HMAC Digest Authentication April 2006
token, kv = wa.split(' ', 1)
challenge = urllib2.parse_keqv_list(urllib2.parse_http_list(kv))
realm = challenge['realm']
snonce = challenge['snonce']
algorithm = challenge.get('algorithm', 'HMAC-SHA-1')
pw_algorithm = challenge.get('pw-algorithm', 'SHA-1')
salt = challenge.get('salt','')
# Choose our HMAC construct
if algorithm == 'HMAC-SHA-1':
hashmod = sha
elif algoritm == 'HMAC-MD5':
hashmod = md5
# Choose our password algorithm
if pw_algorithm == 'SHA-1':
pwhashmod = sha
elif pw_algorithm == 'MD5':
pwhashmod = md5
# Make the key
key = "%s:%s:%s" % (username,
pwhashmod.new(password+salt).hexdigest(),
realm)
key = pwhashmod.new(key).hexdigest()
# Put together the headers
keys = set(headers.keys())
hop_by_hops = set(['connection', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers',
'transfer-encoding', 'upgrade'])
if 'connection' in keys:
ext_hop_heads = urllib2.parse_http_list(headers['connection'])
ext_hop_heads.remove('close')
hop_by_hops = hop_by_hops.union(ext_hop_heads)
keys = keys.difference(hop_by_hops)
keylist = ''.join(["%s " % k for k in keys])
header_vals = ''.join([headers[k] for k in keys])
# Make a cnonce
created = time.strftime('%Y-%m-%dT%H:%M%SZ',time.gmtime())
cnonce = sha.new(str(random.getrandbits(512))+created).hexdigest()
# Calculate the HMAC
msg = "%s:%s:%s:%s:%s" % ("GET", "/", cnonce, snonce, header_vals)
response = hmac.new(key, msg, hashmod).hexdigest()
# Compose the Authorization header
Sayre & Melnikov Expires October 8, 2006 [Page 12]
Internet-Draft HMAC Digest Authentication April 2006
auth = 'username="%s", realm="%s", cnonce="%s", uri="%s", ' \
'snonce="%s", response="%s", headers="%s"'
auth = auth % (username, realm, cnonce, "/",
snonce, response, keylist)
headers['Authorization'] = "HMACDigest " + auth
# wait a few seconds
time.sleep(2)
conn.request("GET","/",params,headers)
response = conn.getresponse()
data = response.read()
# Print the successful response
print "Second request:",data
conn.close()
Appendix B. Change Log
01: Add server nonce, remove client timestamp.
Specify header handling more extensively.
Add RFC2119 terms for header handling.
Demo client excludes hop-by-hop headers.
Sayre & Melnikov Expires October 8, 2006 [Page 13]
Internet-Draft HMAC Digest Authentication April 2006
Authors' Addresses
Robert Sayre
Email: rfsayre@boswijck.com
Alexey Melnikov
Isode Ltd.
Email: Alexey.Melnikov@isode.com
Sayre & Melnikov Expires October 8, 2006 [Page 14]
Internet-Draft HMAC Digest Authentication April 2006
Intellectual Property Statement
The IETF takes no position regarding the validity or scope of any
Intellectual Property Rights or other rights that might be claimed to
pertain to the implementation or use of the technology described in
this document or the extent to which any license under such rights
might or might not be available; nor does it represent that it has
made any independent effort to identify any such rights. Information
on the procedures with respect to rights in RFC documents can be
found in BCP 78 and BCP 79.
Copies of IPR disclosures made to the IETF Secretariat and any
assurances of licenses to be made available, or the result of an
attempt made to obtain a general license or permission for the use of
such proprietary rights by implementers or users of this
specification can be obtained from the IETF on-line IPR repository at
http://www.ietf.org/ipr.
The IETF invites any interested party to bring to its attention any
copyrights, patents or patent applications, or other proprietary
rights that may cover technology that may be required to implement
this standard. Please address the information to the IETF at
ietf-ipr@ietf.org.
Disclaimer of Validity
This document and the information contained herein are provided on an
"AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Copyright Statement
Copyright (C) The Internet Society (2006). This document is subject
to the rights, licenses and restrictions contained in BCP 78, and
except as set forth therein, the authors retain all their rights.
Acknowledgment
Funding for the RFC Editor function is currently provided by the
Internet Society.
Sayre & Melnikov Expires October 8, 2006 [Page 15]