Commit ef875721 authored by Laurent Peuch's avatar Laurent Peuch
Browse files

fix(security): drop md5 in favor of sha512 because it's 2020 god damnit

parent 190415376fd5
......@@ -40,7 +40,7 @@ request. This header is computed as a HMAC hash with:
By default, the `method` is the `Cubicweb` string, and the signed
headers are 'Content-MD5', 'Content-Type' and 'Date'.
headers are 'Content-SHA512', 'Content-Type' and 'Date'.
The `doc/` script is an simple python script showing how to
forge such a HTTP GET request using `urllib2`.
......@@ -57,15 +57,15 @@ from email.utils import formatdate
def sign(req, token):
headers_to_sign = ('Content-MD5', 'Content-Type', 'Date')
headers_to_sign = ('Content-SHA512', 'Content-Type', 'Date')
to_sign = (req.method + req.url +
''.join(req.headers.get(field, '')
for field in headers_to_sign))
return, to_sign).hexdigest()
def md5(data):
h = hashlib.md5(data)
def sha512(data):
h = hashlib.sha512(data)
return h.hexdigest()
......@@ -76,7 +76,7 @@ class SignedRequestAuth(requests.auth.AuthBase):
def __call__(self, r):
if r.method in ('PUT', 'POST'):
r.headers['Content-MD5'] = md5(r.body or '')
r.headers['Content-SHA512'] = sha512(r.body or '')
r.headers['Authorization'] = 'Cubicweb %s:%s' % (
self.token_id, sign(r, self.secret))
return r
......@@ -30,11 +30,11 @@ logger = logging.getLogger(__name__)
def body_hash_tween_factory(handler, registry):
"""pyramid tween that add a body_hash attribute to the request with
the md5 sum of the body. This tween must be insterted before any
the sha512 sum of the body. This tween must be insterted before any
body rewrite logic, otherwise we cannot check request's signature"""
def body_hash(request):
"compute and attach the md5 sum of the body of a request"
"compute and attach the sha512 sum of the body of a request"
# code to be executed for each request before
# the actual application code goes here
request.body_hash = hash_content(request.body_file_seekable)
......@@ -50,7 +50,7 @@ class SignedRequestAuthPolicy(object):
Authentication header.
headers_to_sign = ('Content-MD5', 'Content-Type', 'Date')
headers_to_sign = ('Content-SHA512', 'Content-Type', 'Date')
def unauthenticated_userid(self, request):
return None
......@@ -47,7 +47,7 @@ except ImportError:
log = logging.getLogger(__name__)
HEADERS_TO_SIGN = ('Content-MD5', 'Content-Type', 'Date')
HEADERS_TO_SIGN = ('Content-SHA512', 'Content-Type', 'Date')
ALTERNATE_HEADERS = {'Date': ['X-Cubicweb-Date', 'Date']}
......@@ -74,20 +74,20 @@ def get_replaceable_header_value(request, header_name, default=None):
def hash_content(content):
"""compute the md5 sum of a file-like object's content
"""compute the sha512 sum of a file-like object's content
Does NOT put the file's pos at the original position
md5 = hashlib.md5()
sha512 = hashlib.sha512()
while True:
data =
if not data:
return md5.hexdigest()
return sha512.hexdigest()
def get_credentials_from_headers(request, content_md5):
def get_credentials_from_headers(request, content_sha512):
"""Parse the request headers to retrieve the authentication credentials
Returns the parsed credentials as a string '<tokenid>:<signature>' where
......@@ -110,10 +110,10 @@ def get_credentials_from_headers(request, content_md5):
log.debug('SIGNED REQUEST: method is not Cubicweb')
if request.http_method() != 'GET':
if content_md5 != get_replaceable_header_value(request, 'Content-MD5'):
log.error('SIGNED REQUEST: wrong md5, %s != %s' % (
get_replaceable_header_value(request, 'Content-MD5')))
if content_sha512 != get_replaceable_header_value(request, 'Content-SHA512'):
log.error('SIGNED REQUEST: wrong sha512, %s != %s' % (
get_replaceable_header_value(request, 'Content-SHA512')))
raise AuthenticationError()
date_header = get_replaceable_header_value(request, 'Date')
if date_header is None:
......@@ -183,7 +183,7 @@ def authenticate_user(session, tokenid, signed_content, signature):
assert len(rset) == 1
user_eid, secret_key = rset[0]
expected_signature ='utf-8'),
signed_content, digestmod="md5").hexdigest()
signed_content, digestmod="sha512").hexdigest()
if compare_digest(expected_signature, signature):
return user_eid
......@@ -55,9 +55,9 @@ class HttpRESTAuthRetriever(WebAuthInfoRetriever):
# XXX cw 3.15 compat
content = req._twreq.content
md5 = hash_content(content)
sha512 = hash_content(content)
credentials = get_credentials_from_headers(req, md5)
credentials = get_credentials_from_headers(req, sha512)
if credentials is None:
raise NoAuthInfo()
return credentials.split(':', 1)
......@@ -66,7 +66,7 @@ class SignedRequestBaseTC(object):
{'id': id})
assert rset
string_to_sign, digestmod="md5").hexdigest()
string_to_sign, digestmod="sha512").hexdigest()
def _test_header_format(self, method, login, http_method='GET',
signature=None, headers=None,
......@@ -76,7 +76,9 @@ class SignedRequestBaseTC(object):
def get_valid_authdata(self, headers=None):
if headers is None:
headers = {}
headers.setdefault('Content-MD5', 'd41d8cd98f00b204e9800998ecf8427e')
headers.setdefault('Content-Type', 'application/xhtml+xml')
headers.setdefault('Date', formatdate(usegmt=True))
return headers
......@@ -133,7 +135,8 @@ class SignedRequestBaseTC(object):
self._build_string_to_sign = orig
def test_post_http_request_signature(self):
headers = {'Content-MD5': '43115f65c182069f76b56df967e5c3fd',
headers = {'Content-SHA512': 'f1bb758ab06b3b4e6c5b0545d827cbc4958c2b0d0b242bdcae2562a517220'
'Content-Type': 'application/x-www-form-urlencoded',
'Date': formatdate(usegmt=True)}
result, req = self._test_header_format(method='Cubicweb',
......@@ -145,7 +148,8 @@ class SignedRequestBaseTC(object):
def test_post_http_request_signature_with_multipart(self):
date = formatdate(usegmt=True)
headers = {
'Content-MD5': 'f47787068b27ee123d28392f2d21cf70',
'Content-SHA512': '28a505eb101c411f40a36b19d24c008b7f8d4945ec30e17b7fd6d60fd0b27fbf80bb'
'Content-Type': 'multipart/form-data; '
'Date': date}
......@@ -172,7 +176,8 @@ class SignedRequestBaseTC(object):
def test_post_http_request_signature_with_data(self):
date = formatdate(usegmt=True)
headers = {'Content-MD5': '9893532233caff98cd083a116b013c0b',
headers = {'Content-SHA512': '65c256c639bd6dd483be341831c19a3996954901bb2a07f79593f3e3af569'
'Date': date}
# string_to_sign = ('POST'
# 'key1=value19893532233caff98cd083a116b013c0b%s'%date)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment