More random values for oAuth1 verifier

The oAuth1 verifier was generated as a random number ranging from
1000 to 9999. This small range of numbers is vulnerable to
brute-force attacks as described in CWE-330. The verifier is now
a 8-character long alphanumerical string, a good compromise between
security against guessing and ease of use.

SecurityImpact
Change-Id: Ibe4a2e57a02c261d85ba6c0d61696f134c54443e
Closes-Bug: #1236675
This commit is contained in:
Matthieu Huin 2014-04-22 17:14:25 +02:00
parent 314c0325fc
commit fd02a9c3d0
4 changed files with 13 additions and 5 deletions

View File

@ -204,7 +204,8 @@ class OAuth1(object):
token_ref = self._get_request_token(session, request_token_id) token_ref = self._get_request_token(session, request_token_id)
token_dict = token_ref.to_dict() token_dict = token_ref.to_dict()
token_dict['authorizing_user_id'] = user_id token_dict['authorizing_user_id'] = user_id
token_dict['verifier'] = str(random.randint(1000, 9999)) token_dict['verifier'] = ''.join(random.sample(core.VERIFIER_CHARS,
8))
token_dict['role_ids'] = jsonutils.dumps(role_ids) token_dict['role_ids'] = jsonutils.dumps(role_ids)
new_token = RequestToken.from_dict(token_dict) new_token = RequestToken.from_dict(token_dict)

View File

@ -17,6 +17,7 @@
from __future__ import absolute_import from __future__ import absolute_import
import abc import abc
import string
import oauthlib.common import oauthlib.common
from oauthlib import oauth1 from oauthlib import oauth1
@ -39,6 +40,11 @@ AuthorizationEndpoint = oauth1.AuthorizationEndpoint
SIG_HMAC = oauth1.SIGNATURE_HMAC SIG_HMAC = oauth1.SIGNATURE_HMAC
RequestTokenEndpoint = oauth1.RequestTokenEndpoint RequestTokenEndpoint = oauth1.RequestTokenEndpoint
oRequest = oauthlib.common.Request oRequest = oauthlib.common.Request
# The characters used to generate verifiers are limited to alphanumerical
# values for ease of manual entry. Commonly confused characters are omitted.
VERIFIER_CHARS = string.ascii_letters + string.digits
CONFUSED_CHARS = 'jiIl1oO0'
VERIFIER_CHARS = ''.join(c for c in VERIFIER_CHARS if c not in CONFUSED_CHARS)
class Token(object): class Token(object):

View File

@ -58,10 +58,8 @@ class OAuthValidator(oauth1.RequestValidator):
return set(nonce) <= self.safe_characters return set(nonce) <= self.safe_characters
def check_verifier(self, verifier): def check_verifier(self, verifier):
try: return (all(i in oauth1.VERIFIER_CHARS for i in verifier) and
return 1000 <= int(verifier) <= 9999 len(verifier) == 8)
except ValueError:
return False
def get_client_secret(self, client_key, request): def get_client_secret(self, client_key, request):
client = self.oauth_api.get_consumer_with_secret(client_key) client = self.oauth_api.get_consumer_with_secret(client_key)

View File

@ -20,6 +20,7 @@ from six.moves import urllib
from keystone import config from keystone import config
from keystone.contrib import oauth1 from keystone.contrib import oauth1
from keystone.contrib.oauth1 import controllers from keystone.contrib.oauth1 import controllers
from keystone.contrib.oauth1 import core
from keystone import exception from keystone import exception
from keystone.tests import test_v3 from keystone.tests import test_v3
@ -256,6 +257,8 @@ class OAuthFlowTests(OAuth1Tests):
body = {'roles': [{'id': self.role_id}]} body = {'roles': [{'id': self.role_id}]}
resp = self.put(url, body=body, expected_status=200) resp = self.put(url, body=body, expected_status=200)
self.verifier = resp.result['token']['oauth_verifier'] self.verifier = resp.result['token']['oauth_verifier']
self.assertTrue(all(i in core.VERIFIER_CHARS for i in self.verifier))
self.assertEqual(8, len(self.verifier))
self.request_token.set_verifier(self.verifier) self.request_token.set_verifier(self.verifier)
url, headers = self._create_access_token(self.consumer, url, headers = self._create_access_token(self.consumer,