123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896 |
- # -*- coding: utf-8 -*-
- from oauthlib.oauth1.rfc5849.signature import (
- base_string_uri, collect_parameters, normalize_parameters,
- sign_hmac_sha1_with_client, sign_hmac_sha256_with_client,
- sign_hmac_sha512_with_client, sign_plaintext_with_client,
- sign_rsa_sha1_with_client, sign_rsa_sha256_with_client,
- sign_rsa_sha512_with_client, signature_base_string, verify_hmac_sha1,
- verify_hmac_sha256, verify_hmac_sha512, verify_plaintext, verify_rsa_sha1,
- verify_rsa_sha256, verify_rsa_sha512,
- )
- from tests.unittest import TestCase
- # ################################################################
- class MockRequest:
- """
- Mock of a request used by the verify_* functions.
- """
- def __init__(self,
- method: str,
- uri_str: str,
- params: list,
- signature: str):
- """
- The params is a list of (name, value) tuples. It is not a dictionary,
- because there can be multiple parameters with the same name.
- """
- self.uri = uri_str
- self.http_method = method
- self.params = params
- self.signature = signature
- # ################################################################
- class MockClient:
- """
- Mock of client credentials used by the sign_*_with_client functions.
- For HMAC, set the client_secret and resource_owner_secret.
- For RSA, set the rsa_key to either a PEM formatted PKCS #1 public key or
- PEM formatted PKCS #1 private key.
- """
- def __init__(self,
- client_secret: str = None,
- resource_owner_secret: str = None,
- rsa_key: str = None):
- self.client_secret = client_secret
- self.resource_owner_secret = resource_owner_secret
- self.rsa_key = rsa_key # used for private or public key: a poor design!
- # ################################################################
- class SignatureTests(TestCase):
- """
- Unit tests for the oauthlib/oauth1/rfc5849/signature.py module.
- The tests in this class are organised into sections, to test the
- functions relating to:
- - Signature base string calculation
- - HMAC-based signature methods
- - RSA-based signature methods
- - PLAINTEXT signature method
- Each section is separated by a comment beginning with "====".
- Those comments have been formatted to remain visible when the code is
- collapsed using PyCharm's code folding feature. That is, those section
- heading comments do not have any other comment lines around it, so they
- don't get collapsed when the contents of the class is collapsed. While
- there is a "Sequential comments" option in the code folding configuration,
- by default they are folded.
- They all use some/all of the example test vector, defined in the first
- section below.
- """
- # ==== Example test vector =======================================
- eg_signature_base_string =\
- 'POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q' \
- '%26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_' \
- 'key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m' \
- 'ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk' \
- '9d7dh3k39sjv7'
- # The _signature base string_ above is copied from the end of
- # RFC 5849 section 3.4.1.1.
- #
- # It corresponds to the three values below.
- #
- # The _normalized parameters_ below is copied from the end of
- # RFC 5849 section 3.4.1.3.2.
- eg_http_method = 'POST'
- eg_base_string_uri = 'http://example.com/request'
- eg_normalized_parameters =\
- 'a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj' \
- 'dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1' \
- '&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7'
- # The above _normalized parameters_ corresponds to the parameters below.
- #
- # The parameters below is copied from the table at the end of
- # RFC 5849 section 3.4.1.3.1.
- eg_params = [
- ('b5', '=%3D'),
- ('a3', 'a'),
- ('c@', ''),
- ('a2', 'r b'),
- ('oauth_consumer_key', '9djdj82h48djs9d2'),
- ('oauth_token', 'kkk9d7dh3k39sjv7'),
- ('oauth_signature_method', 'HMAC-SHA1'),
- ('oauth_timestamp', '137131201'),
- ('oauth_nonce', '7d8f3e4a'),
- ('c2', ''),
- ('a3', '2 q'),
- ]
- # The above parameters correspond to parameters from the three values below.
- #
- # These come from RFC 5849 section 3.4.1.3.1.
- eg_uri_query = 'b5=%3D%253D&a3=a&c%40=&a2=r%20b'
- eg_body = 'c2&a3=2+q'
- eg_authorization_header =\
- 'OAuth realm="Example", oauth_consumer_key="9djdj82h48djs9d2",' \
- ' oauth_token="kkk9d7dh3k39sjv7", oauth_signature_method="HMAC-SHA1",' \
- ' oauth_timestamp="137131201", oauth_nonce="7d8f3e4a",' \
- ' oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D"'
- # ==== Signature base string calculating function tests ==========
- def test_signature_base_string(self):
- """
- Test the ``signature_base_string`` function.
- """
- # Example from RFC 5849
- self.assertEqual(
- self.eg_signature_base_string,
- signature_base_string(
- self.eg_http_method,
- self.eg_base_string_uri,
- self.eg_normalized_parameters))
- # Test method is always uppercase in the signature base string
- for test_method in ['POST', 'Post', 'pOST', 'poST', 'posT', 'post']:
- self.assertEqual(
- self.eg_signature_base_string,
- signature_base_string(
- test_method,
- self.eg_base_string_uri,
- self.eg_normalized_parameters))
- def test_base_string_uri(self):
- """
- Test the ``base_string_uri`` function.
- """
- # ----------------
- # Examples from the OAuth 1.0a specification: RFC 5849.
- # First example from RFC 5849 section 3.4.1.2.
- #
- # GET /r%20v/X?id=123 HTTP/1.1
- # Host: EXAMPLE.COM:80
- #
- # Note: there is a space between "r" and "v"
- self.assertEqual(
- 'http://example.com/r%20v/X',
- base_string_uri('http://EXAMPLE.COM:80/r v/X?id=123'))
- # Second example from RFC 5849 section 3.4.1.2.
- #
- # GET /?q=1 HTTP/1.1
- # Host: www.example.net:8080
- self.assertEqual(
- 'https://www.example.net:8080/',
- base_string_uri('https://www.example.net:8080/?q=1'))
- # ----------------
- # Scheme: will always be in lowercase
- for uri in [
- 'foobar://www.example.com',
- 'FOOBAR://www.example.com',
- 'Foobar://www.example.com',
- 'FooBar://www.example.com',
- 'fOObAR://www.example.com',
- ]:
- self.assertEqual('foobar://www.example.com/', base_string_uri(uri))
- # ----------------
- # Host: will always be in lowercase
- for uri in [
- 'http://www.example.com',
- 'http://WWW.EXAMPLE.COM',
- 'http://www.EXAMPLE.com',
- 'http://wWW.eXAMPLE.cOM',
- ]:
- self.assertEqual('http://www.example.com/', base_string_uri(uri))
- # base_string_uri has an optional host parameter that can be used to
- # override the URI's netloc (or used as the host if there is no netloc)
- # The "netloc" refers to the "hostname[:port]" part of the URI.
- self.assertEqual(
- 'http://actual.example.com/',
- base_string_uri('http://IGNORE.example.com', 'ACTUAL.example.com'))
- self.assertEqual(
- 'http://override.example.com/path',
- base_string_uri('http:///path', 'OVERRIDE.example.com'))
- # ----------------
- # Host: valid host allows for IPv4 and IPv6
- self.assertEqual(
- 'https://192.168.0.1/',
- base_string_uri('https://192.168.0.1')
- )
- self.assertEqual(
- 'https://192.168.0.1:13000/',
- base_string_uri('https://192.168.0.1:13000')
- )
- self.assertEqual(
- 'https://[123:db8:fd00:1000::5]:13000/',
- base_string_uri('https://[123:db8:fd00:1000::5]:13000')
- )
- self.assertEqual(
- 'https://[123:db8:fd00:1000::5]/',
- base_string_uri('https://[123:db8:fd00:1000::5]')
- )
- # ----------------
- # Port: default ports always excluded; non-default ports always included
- self.assertEqual(
- "http://www.example.com/",
- base_string_uri("http://www.example.com:80/")) # default port
- self.assertEqual(
- "https://www.example.com/",
- base_string_uri("https://www.example.com:443/")) # default port
- self.assertEqual(
- "https://www.example.com:999/",
- base_string_uri("https://www.example.com:999/")) # non-default port
- self.assertEqual(
- "http://www.example.com:443/",
- base_string_uri("HTTP://www.example.com:443/")) # non-default port
- self.assertEqual(
- "https://www.example.com:80/",
- base_string_uri("HTTPS://www.example.com:80/")) # non-default port
- self.assertEqual(
- "http://www.example.com/",
- base_string_uri("http://www.example.com:/")) # colon but no number
- # ----------------
- # Paths
- self.assertEqual(
- 'http://www.example.com/',
- base_string_uri('http://www.example.com')) # no slash
- self.assertEqual(
- 'http://www.example.com/',
- base_string_uri('http://www.example.com/')) # with slash
- self.assertEqual(
- 'http://www.example.com:8080/',
- base_string_uri('http://www.example.com:8080')) # no slash
- self.assertEqual(
- 'http://www.example.com:8080/',
- base_string_uri('http://www.example.com:8080/')) # with slash
- self.assertEqual(
- 'http://www.example.com/foo/bar',
- base_string_uri('http://www.example.com/foo/bar')) # no slash
- self.assertEqual(
- 'http://www.example.com/foo/bar/',
- base_string_uri('http://www.example.com/foo/bar/')) # with slash
- # ----------------
- # Query parameters & fragment IDs do not appear in the base string URI
- self.assertEqual(
- 'https://www.example.com/path',
- base_string_uri('https://www.example.com/path?foo=bar'))
- self.assertEqual(
- 'https://www.example.com/path',
- base_string_uri('https://www.example.com/path#fragment'))
- # ----------------
- # Percent encoding
- #
- # RFC 5849 does not specify what characters are percent encoded, but in
- # one of its examples it shows spaces being percent encoded.
- # So it is assumed that spaces must be encoded, but we don't know what
- # other characters are encoded or not.
- self.assertEqual(
- 'https://www.example.com/hello%20world',
- base_string_uri('https://www.example.com/hello world'))
- self.assertEqual(
- 'https://www.hello%20world.com/',
- base_string_uri('https://www.hello world.com/'))
- # ----------------
- # Errors detected
- # base_string_uri expects a string
- self.assertRaises(ValueError, base_string_uri, None)
- self.assertRaises(ValueError, base_string_uri, 42)
- self.assertRaises(ValueError, base_string_uri, b'http://example.com')
- # Missing scheme is an error
- self.assertRaises(ValueError, base_string_uri, '')
- self.assertRaises(ValueError, base_string_uri, ' ') # single space
- self.assertRaises(ValueError, base_string_uri, 'http')
- self.assertRaises(ValueError, base_string_uri, 'example.com')
- # Missing host is an error
- self.assertRaises(ValueError, base_string_uri, 'http:')
- self.assertRaises(ValueError, base_string_uri, 'http://')
- self.assertRaises(ValueError, base_string_uri, 'http://:8080')
- # Port is not a valid TCP/IP port number
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:0')
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:-1')
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:65536')
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:3.14')
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:BAD')
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:NaN')
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com: ')
- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:42:42')
- def test_collect_parameters(self):
- """
- Test the ``collect_parameters`` function.
- """
- # ----------------
- # Examples from the OAuth 1.0a specification: RFC 5849.
- params = collect_parameters(
- self.eg_uri_query,
- self.eg_body,
- {'Authorization': self.eg_authorization_header})
- # Check params contains the same pairs as control_params, ignoring order
- self.assertEqual(sorted(self.eg_params), sorted(params))
- # ----------------
- # Examples with no parameters
- self.assertEqual([], collect_parameters('', '', {}))
- self.assertEqual([], collect_parameters(None, None, None))
- self.assertEqual([], collect_parameters())
- self.assertEqual([], collect_parameters(headers={'foo': 'bar'}))
- # ----------------
- # Test effect of exclude_oauth_signature"
- no_sig = collect_parameters(
- headers={'authorization': self.eg_authorization_header})
- with_sig = collect_parameters(
- headers={'authorization': self.eg_authorization_header},
- exclude_oauth_signature=False)
- self.assertEqual(sorted(no_sig + [('oauth_signature',
- 'djosJKDKJSD8743243/jdk33klY=')]),
- sorted(with_sig))
- # ----------------
- # Test effect of "with_realm" as well as header name case insensitivity
- no_realm = collect_parameters(
- headers={'authorization': self.eg_authorization_header},
- with_realm=False)
- with_realm = collect_parameters(
- headers={'AUTHORIZATION': self.eg_authorization_header},
- with_realm=True)
- self.assertEqual(sorted(no_realm + [('realm', 'Example')]),
- sorted(with_realm))
- def test_normalize_parameters(self):
- """
- Test the ``normalize_parameters`` function.
- """
- # headers = {'Authorization': self.authorization_header}
- # parameters = collect_parameters(
- # uri_query=self.uri_query, body=self.body, headers=headers)
- # normalized = normalize_parameters(parameters)
- #
- # # Unicode everywhere and always
- # self.assertIsInstance(normalized, str)
- #
- # # Lets see if things are in order
- # # check to see that querystring keys come in alphanumeric order:
- # querystring_keys = ['a2', 'a3', 'b5', 'oauth_consumer_key',
- # 'oauth_nonce', 'oauth_signature_method',
- # 'oauth_timestamp', 'oauth_token']
- # index = -1 # start at -1 because the 'a2' key starts at index 0
- # for key in querystring_keys:
- # self.assertGreater(normalized.index(key), index)
- # index = normalized.index(key)
- # ----------------
- # Example from the OAuth 1.0a specification: RFC 5849.
- # Params from end of section 3.4.1.3.1. and the expected
- # normalized parameters from the end of section 3.4.1.3.2.
- self.assertEqual(self.eg_normalized_parameters,
- normalize_parameters(self.eg_params))
- # ==== HMAC-based signature method tests =========================
- hmac_client = MockClient(
- client_secret='ECrDNoq1VYzzzzzzzzzyAK7TwZNtPnkqatqZZZZ',
- resource_owner_secret='just-a-string asdasd')
- # The following expected signatures were calculated by putting the value of
- # the eg_signature_base_string in a file ("base-str.txt") and running:
- #
- # echo -n `cat base-str.txt` | openssl dgst -hmac KEY -sha1 -binary| base64
- #
- # Where the KEY is the concatenation of the client_secret, an ampersand and
- # the resource_owner_secret. But those values need to be encoded properly,
- # so the spaces in the resource_owner_secret must be represented as '%20'.
- #
- # Note: the "echo -n" is needed to remove the last newline character, which
- # most text editors will add.
- expected_signature_hmac_sha1 = \
- 'wsdNmjGB7lvis0UJuPAmjvX/PXw='
- expected_signature_hmac_sha256 = \
- 'wdfdHUKXHbOnOGZP8WFAWMSAmWzN3EVBWWgXGlC/Eo4='
- expected_signature_hmac_sha512 = \
- 'u/vlyZFDxOWOZ9UUXwRBJHvq8/T4jCA74ocRmn2ECnjUBTAeJiZIRU8hDTjS88Tz' \
- '1fGONffMpdZxUkUTW3k1kg=='
- def test_sign_hmac_sha1_with_client(self):
- """
- Test sign and verify with HMAC-SHA1.
- """
- self.assertEqual(
- self.expected_signature_hmac_sha1,
- sign_hmac_sha1_with_client(self.eg_signature_base_string,
- self.hmac_client))
- self.assertTrue(verify_hmac_sha1(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- self.expected_signature_hmac_sha1),
- self.hmac_client.client_secret,
- self.hmac_client.resource_owner_secret))
- def test_sign_hmac_sha256_with_client(self):
- """
- Test sign and verify with HMAC-SHA256.
- """
- self.assertEqual(
- self.expected_signature_hmac_sha256,
- sign_hmac_sha256_with_client(self.eg_signature_base_string,
- self.hmac_client))
- self.assertTrue(verify_hmac_sha256(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- self.expected_signature_hmac_sha256),
- self.hmac_client.client_secret,
- self.hmac_client.resource_owner_secret))
- def test_sign_hmac_sha512_with_client(self):
- """
- Test sign and verify with HMAC-SHA512.
- """
- self.assertEqual(
- self.expected_signature_hmac_sha512,
- sign_hmac_sha512_with_client(self.eg_signature_base_string,
- self.hmac_client))
- self.assertTrue(verify_hmac_sha512(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- self.expected_signature_hmac_sha512),
- self.hmac_client.client_secret,
- self.hmac_client.resource_owner_secret))
- def test_hmac_false_positives(self):
- """
- Test verify_hmac-* functions will correctly detect invalid signatures.
- """
- _ros = self.hmac_client.resource_owner_secret
- for functions in [
- (sign_hmac_sha1_with_client, verify_hmac_sha1),
- (sign_hmac_sha256_with_client, verify_hmac_sha256),
- (sign_hmac_sha512_with_client, verify_hmac_sha512),
- ]:
- signing_function = functions[0]
- verify_function = functions[1]
- good_signature = \
- signing_function(
- self.eg_signature_base_string,
- self.hmac_client)
- bad_signature_on_different_value = \
- signing_function(
- 'not the signature base string',
- self.hmac_client)
- bad_signature_produced_by_different_client_secret = \
- signing_function(
- self.eg_signature_base_string,
- MockClient(client_secret='wrong-secret',
- resource_owner_secret=_ros))
- bad_signature_produced_by_different_resource_owner_secret = \
- signing_function(
- self.eg_signature_base_string,
- MockClient(client_secret=self.hmac_client.client_secret,
- resource_owner_secret='wrong-secret'))
- bad_signature_produced_with_no_resource_owner_secret = \
- signing_function(
- self.eg_signature_base_string,
- MockClient(client_secret=self.hmac_client.client_secret))
- bad_signature_produced_with_no_client_secret = \
- signing_function(
- self.eg_signature_base_string,
- MockClient(resource_owner_secret=_ros))
- self.assertTrue(verify_function(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- good_signature),
- self.hmac_client.client_secret,
- self.hmac_client.resource_owner_secret))
- for bad_signature in [
- '',
- 'ZG9uJ3QgdHJ1c3QgbWUK', # random base64 encoded value
- 'altérer', # value with a non-ASCII character in it
- bad_signature_on_different_value,
- bad_signature_produced_by_different_client_secret,
- bad_signature_produced_by_different_resource_owner_secret,
- bad_signature_produced_with_no_resource_owner_secret,
- bad_signature_produced_with_no_client_secret,
- ]:
- self.assertFalse(verify_function(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- bad_signature),
- self.hmac_client.client_secret,
- self.hmac_client.resource_owner_secret))
- # ==== RSA-based signature methods tests =========================
- rsa_private_client = MockClient(rsa_key='''
- -----BEGIN RSA PRIVATE KEY-----
- MIICXgIBAAKBgQDk1/bxyS8Q8jiheHeYYp/4rEKJopeQRRKKpZI4s5i+UPwVpupG
- AlwXWfzXwSMaKPAoKJNdu7tqKRniqst5uoHXw98gj0x7zamu0Ck1LtQ4c7pFMVah
- 5IYGhBi2E9ycNS329W27nJPWNCbESTu7snVlG8V8mfvGGg3xNjTMO7IdrwIDAQAB
- AoGBAOQ2KuH8S5+OrsL4K+wfjoCi6MfxCUyqVU9GxocdM1m30WyWRFMEz2nKJ8fR
- p3vTD4w8yplTOhcoXdQZl0kRoaDzrcYkm2VvJtQRrX7dKFT8dR8D/Tr7dNQLOXfC
- DY6xveQczE7qt7Vk7lp4FqmxBsaaEuokt78pOOjywZoInjZhAkEA9wz3zoZNT0/i
- rf6qv2qTIeieUB035N3dyw6f1BGSWYaXSuerDCD/J1qZbAPKKhyHZbVawFt3UMhe
- 542UftBaxQJBAO0iJy1I8GQjGnS7B3yvyH3CcLYGy296+XO/2xKp/d/ty1OIeovx
- C60pLNwuFNF3z9d2GVQAdoQ89hUkOtjZLeMCQQD0JO6oPHUeUjYT+T7ImAv7UKVT
- Suy30sKjLzqoGw1kR+wv7C5PeDRvscs4wa4CW9s6mjSrMDkDrmCLuJDtmf55AkEA
- kmaMg2PNrjUR51F0zOEFycaaqXbGcFwe1/xx9zLmHzMDXd4bsnwt9kk+fe0hQzVS
- JzatanQit3+feev1PN3QewJAWv4RZeavEUhKv+kLe95Yd0su7lTLVduVgh4v5yLT
- Ga6FHdjGPcfajt+nrpB1n8UQBEH9ZxniokR/IPvdMlxqXA==
- -----END RSA PRIVATE KEY-----
- ''')
- rsa_public_client = MockClient(rsa_key='''
- -----BEGIN RSA PUBLIC KEY-----
- MIGJAoGBAOTX9vHJLxDyOKF4d5hin/isQomil5BFEoqlkjizmL5Q/BWm6kYCXBdZ
- /NfBIxoo8Cgok127u2opGeKqy3m6gdfD3yCPTHvNqa7QKTUu1DhzukUxVqHkhgaE
- GLYT3Jw1Lfb1bbuck9Y0JsRJO7uydWUbxXyZ+8YaDfE2NMw7sh2vAgMBAAE=
- -----END RSA PUBLIC KEY-----
- ''')
- # The above private key was generated using:
- # $ openssl genrsa -out example.pvt 1024
- # $ chmod 600 example.pvt
- # Public key was extract from it using:
- # $ ssh-keygen -e -m pem -f example.pvt
- # PEM encoding requires the key to be concatenated with linebreaks.
- # The following expected signatures were calculated by putting the private
- # key in a file (test.pvt) and the value of sig_base_str_rsa in another file
- # ("base-str.txt") and running:
- #
- # echo -n `cat base-str.txt` | openssl dgst -sha1 -sign test.pvt| base64
- #
- # Note: the "echo -n" is needed to remove the last newline character, which
- # most text editors will add.
- expected_signature_rsa_sha1 = \
- 'mFY2KOEnlYWsTvUA+5kxuBIcvBYXu+ljw9ttVJQxKduMueGSVPCB1tK1PlqVLK738' \
- 'HK0t19ecBJfb6rMxUwrriw+MlBO+jpojkZIWccw1J4cAb4qu4M81DbpUAq4j/1w/Q' \
- 'yTR4TWCODlEfN7Zfgy8+pf+TjiXfIwRC1jEWbuL1E='
- expected_signature_rsa_sha256 = \
- 'jqKl6m0WS69tiVJV8ZQ6aQEfJqISoZkiPBXRv6Al2+iFSaDpfeXjYm+Hbx6m1azR' \
- 'drZ/35PM3cvuid3LwW/siAkzb0xQcGnTyAPH8YcGWzmnKGY7LsB7fkqThchNxvRK' \
- '/N7s9M1WMnfZZ+1dQbbwtTs1TG1+iexUcV7r3M7Heec='
- expected_signature_rsa_sha512 = \
- 'jL1CnjlsNd25qoZVHZ2oJft47IRYTjpF5CvCUjL3LY0NTnbEeVhE4amWXUFBe9GL' \
- 'DWdUh/79ZWNOrCirBFIP26cHLApjYdt4ZG7EVK0/GubS2v8wT1QPRsog8zyiMZkm' \
- 'g4JXdWCGXG8YRvRJTg+QKhXuXwS6TcMNakrgzgFIVhA='
- def test_sign_rsa_sha1_with_client(self):
- """
- Test sign and verify with RSA-SHA1.
- """
- self.assertEqual(
- self.expected_signature_rsa_sha1,
- sign_rsa_sha1_with_client(self.eg_signature_base_string,
- self.rsa_private_client))
- self.assertTrue(verify_rsa_sha1(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- self.expected_signature_rsa_sha1),
- self.rsa_public_client.rsa_key))
- def test_sign_rsa_sha256_with_client(self):
- """
- Test sign and verify with RSA-SHA256.
- """
- self.assertEqual(
- self.expected_signature_rsa_sha256,
- sign_rsa_sha256_with_client(self.eg_signature_base_string,
- self.rsa_private_client))
- self.assertTrue(verify_rsa_sha256(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- self.expected_signature_rsa_sha256),
- self.rsa_public_client.rsa_key))
- def test_sign_rsa_sha512_with_client(self):
- """
- Test sign and verify with RSA-SHA512.
- """
- self.assertEqual(
- self.expected_signature_rsa_sha512,
- sign_rsa_sha512_with_client(self.eg_signature_base_string,
- self.rsa_private_client))
- self.assertTrue(verify_rsa_sha512(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- self.expected_signature_rsa_sha512),
- self.rsa_public_client.rsa_key))
- def test_rsa_false_positives(self):
- """
- Test verify_rsa-* functions will correctly detect invalid signatures.
- """
- another_client = MockClient(rsa_key='''
- -----BEGIN RSA PRIVATE KEY-----
- MIICXQIBAAKBgQDZcD/1OZNJJ6Y3QZM16Z+O7fkD9kTIQuT2BfpAOUvDfxzYhVC9
- TNmSDHCQhr+ClutyolBk5jTE1/FXFUuHoPsTrkI7KQFXPP834D4gnSY9jrAiUJHe
- DVF6wXNuS7H4Ueh16YPjUxgLLRh/nn/JSEj98gsw+7DP01OWMfWS99S7eQIDAQAB
- AoGBALsQZRXVyK7BG7CiC8HwEcNnXDpaXmZjlpNKJTenk1THQMvONd4GBZAuf5D3
- PD9fE4R1u/ByVKecmBaxTV+L0TRQfD8K/nbQe0SKRQIkLI2ymLJKC/eyw5iTKT0E
- +BS6wYpVd+mfcqgvpHOYpUmz9X8k/eOa7uslFmvt+sDb5ZcBAkEA+++SRqqUxFEG
- s/ZWAKw9p5YgkeVUOYVUwyAeZ97heySrjVzg1nZ6v6kv7iOPi9KOEpaIGPW7x1K/
- uQuSt4YEqQJBANzyNqZTTPpv7b/R8ABFy0YMwPVNt3b1GOU1Xxl6iuhH2WcHuueo
- UB13JHoZCMZ7hsEqieEz6uteUjdRzRPKclECQFNhVK4iop3emzNQYeJTHwyp+RmQ
- JrHq2MTDioyiDUouNsDQbnFMQQ/RtNVB265Q/0hTnbN1ELLFRkK9+87VghECQQC9
- hacLFPk6+TffCp3sHfI3rEj4Iin1iFhKhHWGzW7JwJfjoOXaQK44GDLZ6Q918g+t
- MmgDHR2tt8KeYTSgfU+BAkBcaVF91EQ7VXhvyABNYjeYP7lU7orOgdWMa/zbLXSU
- 4vLsK1WOmwPY9zsXpPkilqszqcru4gzlG462cSbEdAW9
- -----END RSA PRIVATE KEY-----
- ''')
- for functions in [
- (sign_rsa_sha1_with_client, verify_rsa_sha1),
- (sign_rsa_sha256_with_client, verify_rsa_sha256),
- (sign_rsa_sha512_with_client, verify_rsa_sha512),
- ]:
- signing_function = functions[0]
- verify_function = functions[1]
- good_signature = \
- signing_function(self.eg_signature_base_string,
- self.rsa_private_client)
- bad_signature_on_different_value = \
- signing_function('wrong value signed', self.rsa_private_client)
- bad_signature_produced_by_different_private_key = \
- signing_function(self.eg_signature_base_string, another_client)
- self.assertTrue(verify_function(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- good_signature),
- self.rsa_public_client.rsa_key))
- for bad_signature in [
- '',
- 'ZG9uJ3QgdHJ1c3QgbWUK', # random base64 encoded value
- 'altérer', # value with a non-ASCII character in it
- bad_signature_on_different_value,
- bad_signature_produced_by_different_private_key,
- ]:
- self.assertFalse(verify_function(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- bad_signature),
- self.rsa_public_client.rsa_key))
- def test_rsa_bad_keys(self):
- """
- Testing RSA sign and verify with bad key values produces errors.
- This test is useful for coverage tests, since it runs the code branches
- that deal with error situations.
- """
- # Signing needs a private key
- for bad_value in [None, '', 'foobar']:
- self.assertRaises(ValueError,
- sign_rsa_sha1_with_client,
- self.eg_signature_base_string,
- MockClient(rsa_key=bad_value))
- self.assertRaises(AttributeError,
- sign_rsa_sha1_with_client,
- self.eg_signature_base_string,
- self.rsa_public_client) # public key doesn't sign
- # Verify needs a public key
- for bad_value in [None, '', 'foobar', self.rsa_private_client.rsa_key]:
- self.assertRaises(TypeError,
- verify_rsa_sha1,
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- self.expected_signature_rsa_sha1),
- MockClient(rsa_key=bad_value))
- # For completeness, this text could repeat the above for RSA-SHA256 and
- # RSA-SHA512 signing and verification functions.
- def test_rsa_jwt_algorithm_cache(self):
- # Tests cache of RSAAlgorithm objects is implemented correctly.
- # This is difficult to test, since the cache is internal.
- #
- # Running this test with coverage will show the cache-hit branch of code
- # being executed by two signing operations with the same hash algorithm.
- self.test_sign_rsa_sha1_with_client() # creates cache entry
- self.test_sign_rsa_sha1_with_client() # reuses cache entry
- # Some possible bugs will be detected if multiple signing operations
- # with different hash algorithms produce the wrong results (e.g. if the
- # cache incorrectly returned the previously used algorithm, instead
- # of the one that is needed).
- self.test_sign_rsa_sha256_with_client()
- self.test_sign_rsa_sha256_with_client()
- self.test_sign_rsa_sha1_with_client()
- self.test_sign_rsa_sha256_with_client()
- self.test_sign_rsa_sha512_with_client()
- # ==== PLAINTEXT signature method tests ==========================
- plaintext_client = hmac_client # for convenience, use the same HMAC secrets
- expected_signature_plaintext = (
- 'ECrDNoq1VYzzzzzzzzzyAK7TwZNtPnkqatqZZZZ'
- '&'
- 'just-a-string%20%20%20%20asdasd')
- def test_sign_plaintext_with_client(self):
- # With PLAINTEXT, the "signature" is always the same: regardless of the
- # contents of the request. It is the concatenation of the encoded
- # client_secret, an ampersand, and the encoded resource_owner_secret.
- #
- # That is why the spaces in the resource owner secret are "%20".
- self.assertEqual(self.expected_signature_plaintext,
- sign_plaintext_with_client(None, # request is ignored
- self.plaintext_client))
- self.assertTrue(verify_plaintext(
- MockRequest('PUT',
- 'http://example.com/some-other-path',
- [('description', 'request is ignored in PLAINTEXT')],
- self.expected_signature_plaintext),
- self.plaintext_client.client_secret,
- self.plaintext_client.resource_owner_secret))
- def test_plaintext_false_positives(self):
- """
- Test verify_plaintext function will correctly detect invalid signatures.
- """
- _ros = self.plaintext_client.resource_owner_secret
- good_signature = \
- sign_plaintext_with_client(
- self.eg_signature_base_string,
- self.plaintext_client)
- bad_signature_produced_by_different_client_secret = \
- sign_plaintext_with_client(
- self.eg_signature_base_string,
- MockClient(client_secret='wrong-secret',
- resource_owner_secret=_ros))
- bad_signature_produced_by_different_resource_owner_secret = \
- sign_plaintext_with_client(
- self.eg_signature_base_string,
- MockClient(client_secret=self.plaintext_client.client_secret,
- resource_owner_secret='wrong-secret'))
- bad_signature_produced_with_no_resource_owner_secret = \
- sign_plaintext_with_client(
- self.eg_signature_base_string,
- MockClient(client_secret=self.plaintext_client.client_secret))
- bad_signature_produced_with_no_client_secret = \
- sign_plaintext_with_client(
- self.eg_signature_base_string,
- MockClient(resource_owner_secret=_ros))
- self.assertTrue(verify_plaintext(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- good_signature),
- self.plaintext_client.client_secret,
- self.plaintext_client.resource_owner_secret))
- for bad_signature in [
- '',
- 'ZG9uJ3QgdHJ1c3QgbWUK', # random base64 encoded value
- 'altérer', # value with a non-ASCII character in it
- bad_signature_produced_by_different_client_secret,
- bad_signature_produced_by_different_resource_owner_secret,
- bad_signature_produced_with_no_resource_owner_secret,
- bad_signature_produced_with_no_client_secret,
- ]:
- self.assertFalse(verify_plaintext(
- MockRequest('POST',
- 'http://example.com/request',
- self.eg_params,
- bad_signature),
- self.plaintext_client.client_secret,
- self.plaintext_client.resource_owner_secret))
|