diff options
author | Viktor Haag <ViktorHaag@users.noreply.github.com> | 2017-11-14 07:44:44 -0800 |
---|---|---|
committer | Omer Katz <omer.drow@gmail.com> | 2017-11-14 17:44:44 +0200 |
commit | cfb82feb03fcd60b3b66ac09bf1b478cd5f11b7d (patch) | |
tree | c7c9a66b98d34d4e398cd92e8291d65b4b844d85 | |
parent | fa0b63cfaced831d8b916c5a125128f582acf044 (diff) | |
download | oauthlib-cfb82feb03fcd60b3b66ac09bf1b478cd5f11b7d.tar.gz |
Add support for HMAC-SHA256 (builds on PR#388) (#498)
* Add support for HMAC-SHA256
* Add explicit declaration of HMAC-SHA1 and point HMAC at it
To avoid confusion, HMAC constant name should explicitly state which SHA variant is used, but for backwards compatibility, SIGNATURE_HMAC is still needed
* add support for HMAC-SHA256 including tests and comments
* constructor tests verify client built with correct signer method
-rw-r--r-- | oauthlib/oauth1/__init__.py | 2 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/__init__.py | 11 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/signature.py | 57 | ||||
-rw-r--r-- | tests/oauth1/rfc5849/test_client.py | 40 |
4 files changed, 103 insertions, 7 deletions
diff --git a/oauthlib/oauth1/__init__.py b/oauthlib/oauth1/__init__.py index f9dff74..dc908d4 100644 --- a/oauthlib/oauth1/__init__.py +++ b/oauthlib/oauth1/__init__.py @@ -9,7 +9,7 @@ and Server classes. from __future__ import absolute_import, unicode_literals from .rfc5849 import Client -from .rfc5849 import SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT +from .rfc5849 import SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_PLAINTEXT from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY from .rfc5849 import SIGNATURE_TYPE_BODY from .rfc5849.request_validator import RequestValidator diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py index 06902e2..f9113ab 100644 --- a/oauthlib/oauth1/rfc5849/__init__.py +++ b/oauthlib/oauth1/rfc5849/__init__.py @@ -27,10 +27,12 @@ from oauthlib.common import Request, urlencode, generate_nonce from oauthlib.common import generate_timestamp, to_unicode from . import parameters, signature -SIGNATURE_HMAC = "HMAC-SHA1" +SIGNATURE_HMAC_SHA1 = "HMAC-SHA1" +SIGNATURE_HMAC_SHA256 = "HMAC-SHA256" +SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1 SIGNATURE_RSA = "RSA-SHA1" SIGNATURE_PLAINTEXT = "PLAINTEXT" -SIGNATURE_METHODS = (SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT) +SIGNATURE_METHODS = (SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_PLAINTEXT) SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER' SIGNATURE_TYPE_QUERY = 'QUERY' @@ -43,7 +45,8 @@ class Client(object): """A client used to sign OAuth 1.0 RFC 5849 requests.""" SIGNATURE_METHODS = { - SIGNATURE_HMAC: signature.sign_hmac_sha1_with_client, + SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client, + SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client, SIGNATURE_RSA: signature.sign_rsa_sha1_with_client, SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client } @@ -57,7 +60,7 @@ class Client(object): resource_owner_key=None, resource_owner_secret=None, callback_uri=None, - signature_method=SIGNATURE_HMAC, + signature_method=SIGNATURE_HMAC_SHA1, signature_type=SIGNATURE_TYPE_AUTH_HEADER, rsa_key=None, verifier=None, realm=None, encoding='utf-8', decoding=None, diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index 10d057f..30001ef 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -469,6 +469,63 @@ def sign_hmac_sha1(base_string, client_secret, resource_owner_secret): # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8 return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8') + +def sign_hmac_sha256_with_client(base_string, client): + return sign_hmac_sha256(base_string, + client.client_secret, + client.resource_owner_secret + ) + + +def sign_hmac_sha256(base_string, client_secret, resource_owner_secret): + """**HMAC-SHA256** + + The "HMAC-SHA256" signature method uses the HMAC-SHA256 signature + algorithm as defined in `RFC4634`_:: + + digest = HMAC-SHA256 (key, text) + + Per `section 3.4.2`_ of the spec. + + .. _`RFC4634`: http://tools.ietf.org/html/rfc4634 + .. _`section 3.4.2`: http://tools.ietf.org/html/rfc5849#section-3.4.2 + """ + + # The HMAC-SHA256 function variables are used in following way: + + # text is set to the value of the signature base string from + # `Section 3.4.1.1`_. + # + # .. _`Section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 + text = base_string + + # key is set to the concatenated values of: + # 1. The client shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key = utils.escape(client_secret or '') + + # 2. An "&" character (ASCII code 38), which MUST be included + # even when either secret is empty. + key += '&' + + # 3. The token shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key += utils.escape(resource_owner_secret or '') + + # FIXME: HMAC does not support unicode! + key_utf8 = key.encode('utf-8') + text_utf8 = text.encode('utf-8') + signature = hmac.new(key_utf8, text_utf8, hashlib.sha256) + + # digest is used to set the value of the "oauth_signature" protocol + # parameter, after the result octet string is base64-encoded + # per `RFC2045, Section 6.8`. + # + # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8 + return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8') + _jwtrs1 = None #jwt has some nice pycrypto/cryptography abstractions diff --git a/tests/oauth1/rfc5849/test_client.py b/tests/oauth1/rfc5849/test_client.py index dcb4c3d..777efc2 100644 --- a/tests/oauth1/rfc5849/test_client.py +++ b/tests/oauth1/rfc5849/test_client.py @@ -2,7 +2,8 @@ from __future__ import absolute_import, unicode_literals from oauthlib.common import Request -from oauthlib.oauth1 import (SIGNATURE_PLAINTEXT, SIGNATURE_RSA, +from oauthlib.oauth1 import (SIGNATURE_PLAINTEXT, SIGNATURE_HMAC_SHA1, + SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY) from oauthlib.oauth1.rfc5849 import Client, bytes_type @@ -62,13 +63,48 @@ class ClientConstructorTests(TestCase): self.assertIsInstance(k, bytes_type) self.assertIsInstance(v, bytes_type) + def test_hmac_sha1(self): + client = Client('client_key') + # instance is using the correct signer method + self.assertEqual(Client.SIGNATURE_METHODS[SIGNATURE_HMAC_SHA1], + client.SIGNATURE_METHODS[client.signature_method]) + + def test_hmac_sha256(self): + client = Client('client_key', signature_method=SIGNATURE_HMAC_SHA256) + # instance is using the correct signer method + self.assertEqual(Client.SIGNATURE_METHODS[SIGNATURE_HMAC_SHA256], + client.SIGNATURE_METHODS[client.signature_method]) + def test_rsa(self): client = Client('client_key', signature_method=SIGNATURE_RSA) - self.assertIsNone(client.rsa_key) # don't need an RSA key to instantiate + # instance is using the correct signer method + self.assertEqual(Client.SIGNATURE_METHODS[SIGNATURE_RSA], + client.SIGNATURE_METHODS[client.signature_method]) + # don't need an RSA key to instantiate + self.assertIsNone(client.rsa_key) class SignatureMethodTest(TestCase): + def test_hmac_sha1_method(self): + client = Client('client_key', timestamp='1234567890', nonce='abc') + u, h, b = client.sign('http://example.com') + correct = ('OAuth oauth_nonce="abc", oauth_timestamp="1234567890", ' + 'oauth_version="1.0", oauth_signature_method="HMAC-SHA1", ' + 'oauth_consumer_key="client_key", ' + 'oauth_signature="hH5BWYVqo7QI4EmPBUUe9owRUUQ%3D"') + self.assertEqual(h['Authorization'], correct) + + def test_hmac_sha256_method(self): + client = Client('client_key', signature_method=SIGNATURE_HMAC_SHA256, + timestamp='1234567890', nonce='abc') + u, h, b = client.sign('http://example.com') + correct = ('OAuth oauth_nonce="abc", oauth_timestamp="1234567890", ' + 'oauth_version="1.0", oauth_signature_method="HMAC-SHA256", ' + 'oauth_consumer_key="client_key", ' + 'oauth_signature="JzgJWBxX664OiMW3WE4MEjtYwOjI%2FpaUWHqtdHe68Es%3D"') + self.assertEqual(h['Authorization'], correct) + def test_rsa_method(self): private_key = ( "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDk1/bxy" |