def test_client_secret_jwt(self, services): _service_context = services['accesstoken'].service_context _service_context.token_endpoint = "https://example.com/token" _service_context.set( 'provider_info', { 'issuer': 'https://example.com/', 'token_endpoint': "https://example.com/token" }) csj = ClientSecretJWT() request = AccessTokenRequest() csj.construct(request, service=services['accesstoken'], algorithm="HS256", authn_endpoint='userinfo') assert request["client_assertion_type"] == JWT_BEARER assert "client_assertion" in request cas = request["client_assertion"] _kj = KeyJar() _kj.add_symmetric(_service_context.get('client_id'), _service_context.get('client_secret'), usage=['sig']) jso = JWT(key_jar=_kj, sign_alg='HS256').unpack(cas) assert _eq(jso.keys(), ["aud", "iss", "sub", "jti", "exp", "iat"]) _rj = JWS(alg='HS256') info = _rj.verify_compact( cas, _kj.get_signing_key(issuer_id=_service_context.get('client_id'))) assert _eq(info.keys(), ["aud", "iss", "sub", "jti", "exp", "iat"]) assert info['aud'] == [_service_context.get('provider_info')['issuer']]
def test_verify_client_jws_authn_method(self): client_keyjar = KeyJar() client_keyjar.import_jwks(KEYJAR.export_jwks(private=True), CONF["issuer"]) # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # Audience is OP issuer ID aud = "{}token".format(CONF["issuer"]) # aud == Token endpoint _assertion = _jwt.pack({"aud": [aud]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } http_info = {"headers": {}} res = verify_client( self.endpoint_context, request, http_info=http_info, endpoint=self.server.server_get("endpoint", "token"), ) assert res["method"] == "client_secret_jwt" assert res["client_id"] == "client_id"
def test_with_jti(): _kj = KeyJar() _kj.add_symmetric(ALICE, "hemligt ordsprak", usage=["sig"]) alice = JWT(key_jar=_kj, iss=ALICE, sign_alg="HS256") alice.with_jti = True payload = {"sub": "sub2"} _jwt = alice.pack(payload=payload) bob = JWT(key_jar=_kj, iss=BOB, sign_alg="HS256") info = bob.unpack(_jwt) assert "jti" in info
def test_msg_cls(): _kj = KeyJar() _kj.add_symmetric(ALICE, "hemligt ordsprak", usage=["sig"]) alice = JWT(key_jar=_kj, iss=ALICE, sign_alg="HS256") payload = {"sub": "sub2"} _jwt = alice.pack(payload=payload) bob = JWT(key_jar=_kj, iss=BOB, sign_alg="HS256") bob.msg_cls = DummyMsg info = bob.unpack(_jwt) assert isinstance(info, DummyMsg)
def test_jwt_pack_unpack_sym(): _kj = KeyJar() _kj.add_symmetric(ALICE, 'hemligt ordsprak', usage=['sig']) alice = JWT(key_jar=_kj, iss=ALICE, sign_alg="HS256") payload = {'sub': 'sub2'} _jwt = alice.pack(payload=payload) _kj = KeyJar() _kj.add_symmetric(ALICE, 'hemligt ordsprak', usage=['sig']) bob = JWT(key_jar=_kj, iss=BOB, sign_alg="HS256") info = bob.unpack(_jwt) assert info
def test_jws_authn_method_wrong_key(self): client_keyjar = KeyJar() client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] # Fake symmetric key client_keyjar.add_symmetric("", "client_secret:client_secret", ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") _assertion = _jwt.pack({"aud": [CONF["issuer"]]}) request = {"client_assertion": _assertion, "client_assertion_type": JWT_BEARER} with pytest.raises(NoSuitableSigningKeys): self.method.verify(request=request, key_type='private_key')
def test_jws_authn_method_aud_userinfo_endpoint(self): client_keyjar = KeyJar() client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # audience is the OP - not specifically the user info endpoint _assertion = _jwt.pack({"aud": [CONF["issuer"]]}) request = {"client_assertion": _assertion, "client_assertion_type": JWT_BEARER} assert self.method.verify(request=request, endpoint="userinfo", key_type='client_secret')
def test_jws_authn_method_aud_iss(self): client_keyjar = KeyJar() client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # Audience is OP issuer ID aud = CONF["issuer"] _assertion = _jwt.pack({"aud": [aud]}) request = {"client_assertion": _assertion, "client_assertion_type": JWT_BEARER} assert self.method.verify(request=request, key_type='client_secret')
def test_jws_authn_method_wrong_key(): client_keyjar = KeyJar() client_keyjar[conf["issuer"]] = KEYJAR.issuer_keys[""] # Fake symmetric key client_keyjar.add_symmetric("", "client_secret:client_secret", ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") _assertion = _jwt.pack({"aud": [conf["issuer"]]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } with pytest.raises(AuthnFailure): JWSAuthnMethod(endpoint_context).verify(request)
def test_verify_client_jws_authn_method(self): client_keyjar = KeyJar() client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # Audience is OP issuer ID aud = CONF["issuer"] + "token" _assertion = _jwt.pack({"aud": [aud]}) request = {"client_assertion": _assertion, "client_assertion_type": JWT_BEARER} res = verify_client(self.endpoint_context, request, endpoint="token") assert res["method"] == "client_secret_jwt" assert res["client_id"] == "client_id"
def test_jws_authn_method_aud_not_me(self): client_keyjar = KeyJar() client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # Other audiences not OK aud = "https://example.org" _assertion = _jwt.pack({"aud": [aud]}) request = {"client_assertion": _assertion, "client_assertion_type": JWT_BEARER} with pytest.raises(NotForMe): self.method.verify(request=request, key_type='client_secret')
def test_client_secret_jwt(self): client_keyjar = KeyJar() client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") _jwt.with_jti = True _assertion = _jwt.pack({"aud": [CONF["issuer"]]}) request = {"client_assertion": _assertion, "client_assertion_type": JWT_BEARER} assert self.method.is_usable(request=request) authn_info = self.method.verify(request) assert authn_info["client_id"] == client_id assert "jwt" in authn_info
def test_to_from_jwt(self): item = DummyMessage(req_str="Fair", opt_str="game", opt_int=9, opt_str_list=["one", "two"], req_str_list=["spike", "lee"], opt_json='{"ford": "green"}') keyjar = KeyJar() keyjar.add_symmetric('', b"A1B2C3D4E5F6G7H8") jws = item.to_jwt(key=keyjar.get_signing_key('oct'), algorithm="HS256") jitem = DummyMessage().from_jwt(jws, keyjar) assert _eq(jitem.keys(), [ 'opt_str', 'req_str', 'opt_json', 'req_str_list', 'opt_str_list', 'opt_int' ])
def test_client_secret_jwt(): client_keyjar = KeyJar() client_keyjar[conf['issuer']] = KEYJAR.issuer_keys[''] # The only own key the client has a this point client_keyjar.add_symmetric('', client_secret, ['sig']) _jwt = JWT(client_keyjar, iss=client_id, sign_alg='HS256') _assertion = _jwt.pack({'aud': [conf['issuer']]}) request = { 'client_assertion': _assertion, 'client_assertion_type': JWT_BEARER } authn_info = ClientSecretJWT(endpoint_context).verify(request) assert authn_info['client_id'] == client_id assert 'jwt' in authn_info
def test_client_secret_jwt(): client_keyjar = KeyJar() client_keyjar[conf["issuer"]] = KEYJAR.issuer_keys[""] # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") _assertion = _jwt.pack({"aud": [conf["issuer"]]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } authn_info = ClientSecretJWT(endpoint_context).verify(request) assert authn_info["client_id"] == client_id assert "jwt" in authn_info
def test_jws_authn_method_aud_token_endpoint(): client_keyjar = KeyJar() client_keyjar[conf["issuer"]] = KEYJAR.issuer_keys[""] # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # audience is OP token endpoint - that's OK aud = "{}token".format(conf["issuer"]) _assertion = _jwt.pack({"aud": [aud]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } assert JWSAuthnMethod(endpoint_context).verify(request)
def test_to_jwe_from_jwt(self): msg = DummyMessage(req_str="Fair", opt_str="game", opt_int=9, opt_str_list=["one", "two"], req_str_list=["spike", "lee"], opt_json='{"ford": "green"}') keys = [SYMKey(key="A1B2C3D4E5F6G7H8")] jwe = msg.to_jwe(keys, alg="A128KW", enc="A128CBC-HS256") keyjar = KeyJar() keyjar.add_symmetric('', 'A1B2C3D4E5F6G7H8') jitem = DummyMessage().from_jwt(jwe, keyjar) assert _eq(jitem.keys(), [ 'opt_str', 'req_str', 'opt_json', 'req_str_list', 'opt_str_list', 'opt_int' ])
def test_jws_authn_method_aud_userinfo_endpoint(self): client_keyjar = KeyJar() client_keyjar.import_jwks(KEYJAR.export_jwks(private=True), CONF["issuer"]) # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # audience is the OP - not specifically the user info endpoint _assertion = _jwt.pack({"aud": [CONF["issuer"]]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } assert self.method.verify( request=request, endpoint=self.method.server_get("endpoint", "userinfo"), key_type="client_secret", )
def test_jws_authn_method_aud_token_endpoint(self): client_keyjar = KeyJar() client_keyjar.import_jwks(KEYJAR.export_jwks(private=True), CONF["issuer"]) # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # audience is OP token endpoint - that's OK aud = "{}token".format(CONF["issuer"]) _assertion = _jwt.pack({"aud": [aud]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } assert self.method.verify( request=request, endpoint=self.method.server_get("endpoint", "token"), key_type="client_secret", )
def test_client_secret_jwt(self, entity): _service_context = entity.client_get("service_context") _service_context.token_endpoint = "https://example.com/token" _service_context.provider_info = { 'issuer': 'https://example.com/', 'token_endpoint': "https://example.com/token" } _service_context.registration_response = { 'token_endpoint_auth_signing_alg': "HS256" } csj = ClientSecretJWT() request = AccessTokenRequest() csj.construct(request, service=entity.client_get("service", 'accesstoken'), authn_endpoint='token_endpoint') assert request["client_assertion_type"] == JWT_BEARER assert "client_assertion" in request cas = request["client_assertion"] _kj = KeyJar() _kj.add_symmetric(_service_context.client_id, _service_context.client_secret, ['sig']) jso = JWT(key_jar=_kj, sign_alg='HS256').unpack(cas) assert _eq(jso.keys(), ["aud", "iss", "sub", "exp", "iat", 'jti']) _rj = JWS(alg='HS256') info = _rj.verify_compact( cas, _kj.get_signing_key(issuer_id=_service_context.client_id)) assert _eq(info.keys(), ["aud", "iss", "sub", "jti", "exp", "iat"]) assert info['aud'] == [ _service_context.provider_info['token_endpoint'] ]
def test_example(self): _symkey = KC_SYM_S.get(alg2keytype("HS256")) esreq = EndSessionRequest(id_token_hint=IDTOKEN.to_jwt( key=_symkey, algorithm="HS256", lifetime=300), redirect_url="http://example.org/jqauthz", state="state0") request = EndSessionRequest().from_urlencoded(esreq.to_urlencoded()) keyjar = KeyJar() for _key in _symkey: keyjar.add_symmetric('', _key.key) keyjar.add_symmetric(ISS, _key.key) keyjar.add_symmetric(CLIENT_ID, _key.key) request.verify(keyjar=keyjar) assert isinstance(request, EndSessionRequest) assert set(request.keys()) == { verified_claim_name('id_token_hint'), 'id_token_hint', 'redirect_url', 'state' } assert request["state"] == "state0" assert request[verified_claim_name("id_token_hint")]["aud"] == [ "client_1" ]
def test_add_symmetric(self): kj = KeyJar() kj.add_symmetric('', 'abcdefghijklmnop', ['sig']) assert list(kj.owners()) == [''] assert len(kj.get_signing_key('oct', '')) == 1
def test_add_symmetric(self): kj = KeyJar() kj.add_symmetric("", "abcdefghijklmnop", ["sig"]) assert list(kj.owners()) == [""] assert len(kj.get_signing_key("oct", "")) == 1
class ServiceContext: """ This class keeps information that a client needs to be able to talk to a server. Some of this information comes from configuration and some from dynamic provider info discovery or client registration. But information is also picked up during the conversation with a server. """ def __init__(self, keyjar=None, config=None, **kwargs): self.keyjar = keyjar or KeyJar() self.provider_info = {} self.registration_response = {} self.kid = {"sig": {}, "enc": {}} if config is None: config = {} self.config = config # Below so my IDE won't complain self.base_url = '' self.requests_dir = '' self.register_args = {} self.allow = {} self.behaviour = {} self.client_preferences = {} self.client_id = '' self._c_secret = '' self.issuer = '' self.redirect_uris = [] self.callback = None self.args = {} self.add_on = {} self.httpc_params = {} try: self.clock_skew = config['clock_skew'] except KeyError: self.clock_skew = 15 for key, val in kwargs.items(): setattr(self, key, val) for attr in [ 'client_id', 'issuer', 'base_url', 'requests_dir', 'allow', 'client_preferences', 'behaviour', 'provider_info', 'redirect_uris', 'callback' ]: try: setattr(self, attr, config[attr]) except KeyError: pass for attr in RegistrationRequest.c_param: try: self.register_args[attr] = config[attr] except KeyError: pass if 'client_secret' in config: self.set_client_secret(config['client_secret']) if self.requests_dir: # make sure the path exists. If not, then make it. if not os.path.isdir(self.requests_dir): os.makedirs(self.requests_dir) try: self.import_keys(config['keys']) except KeyError: pass if 'keydefs' in config: self.keyjar = build_keyjar(config['keydefs'], keyjar=self.keyjar) def get_client_secret(self): """Return the client secret.""" return self._c_secret def set_client_secret(self, val): """Set client secret.""" if not val: self._c_secret = "" else: self._c_secret = val # client uses it for signing # Server might also use it for signing which means the # client uses it for verifying server signatures if self.keyjar is None: self.keyjar = KeyJar() self.keyjar.add_symmetric("", str(val)) # since client secret is used as a symmetric key in some instances # some special handling is needed for the client_secret attribute client_secret = property(get_client_secret, set_client_secret) def __setitem__(self, key, value): setattr(self, key, value) def filename_from_webname(self, webname): """ A 1<->1 map is maintained between a URL pointing to a file and the name of the file in the file system. As an example if the base_url is 'https://example.com' and a jwks_uri is 'https://example.com/jwks_uri.json' then the filename of the corresponding file on the local filesystem would be 'jwks_uri'. Relative to the directory from which the RP instance is run. :param webname: The published URL :return: local filename """ if not webname.startswith(self.base_url): raise ValueError("Webname doesn't match base_url") _name = webname[len(self.base_url):] if _name.startswith('/'): return _name[1:] return _name def generate_request_uris(self, path): """ Need to generate a redirect_uri path that is unique for a OP/RP combo This is to counter the mix-up attack. :param path: Leading path :return: A list of one unique URL """ _hash = hashlib.sha256() try: _hash.update(as_bytes(self.provider_info['issuer'])) except KeyError: _hash.update(as_bytes(self.issuer)) _hash.update(as_bytes(self.base_url)) if not path.startswith('/'): return ['{}/{}/{}'.format(self.base_url, path, _hash.hexdigest())] return ['{}{}/{}'.format(self.base_url, path, _hash.hexdigest())] def import_keys(self, keyspec): """ The client needs it's own set of keys. It can either dynamically create them or load them from local storage. This method can also fetch other entities keys provided the URL points to a JWKS. :param keyspec: """ for where, spec in keyspec.items(): if where == 'file': for typ, files in spec.items(): if typ == 'rsa': for fil in files: _key = RSAKey( key=import_private_rsa_key_from_file(fil), use='sig') _bundle = KeyBundle() _bundle.append(_key) self.keyjar.add_kb('', _bundle) elif where == 'url': for iss, url in spec.items(): _bundle = KeyBundle(source=url) self.keyjar.add_kb(iss, _bundle) def get_sign_alg(self, typ): """ :param typ: ['id_token', 'userinfo', 'request_object'] :return: """ try: return self.behaviour[CLI_REG_MAP[typ]['sign']] except KeyError: try: return self.provider_info[PROVIDER_INFO_MAP[typ]['sign']] except KeyError: pass return None def get_enc_alg_enc(self, typ): """ :param typ: :return: """ res = {} for attr in ['enc', 'alg']: try: _alg = self.behaviour[CLI_REG_MAP[typ][attr]] except KeyError: try: _alg = self.provider_info[PROVIDER_INFO_MAP[typ][attr]] except KeyError: _alg = None res[attr] = _alg return res