def test_verify_token_encrypted(): idt = IdToken( sub="553df2bcf909104751cfd8b2", aud=["5542958437706128204e0000", "554295ce3770612820620000"], auth_time=1441364872, azp="554295ce3770612820620000", ) kj = KeyJar() kb = KeyBundle() kb.do_local_der( os.path.join(os.path.dirname(__file__), "data", "keys", "cert.key"), "some", ["enc", "sig"], ) kj.add_kb("", kb) kj.add_kb("https://sso.qa.7pass.ctf.prosiebensat1.com", kb) packer = JWT( kj, lifetime=3600, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", encrypt=True, ) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(id_token=_jws) vidt = verify_id_token( msg, keyjar=kj, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000", ) assert vidt assert vidt.jwe_header == {"enc": "A128CBC-HS256", "alg": "RSA1_5", "cty": "JWT"}
def test_keyjar_pairkeys(): ks = KeyJar() ks[""] = KeyBundle([{ "kty": "oct", "key": "a1b2c3d4", "use": "sig" }, { "kty": "oct", "key": "a1b2c3d4", "use": "ver" }]) ks["http://www.example.org"] = KeyBundle([{ "kty": "oct", "key": "e5f6g7h8", "use": "sig" }, { "kty": "oct", "key": "e5f6g7h8", "use": "ver" }]) ks["http://www.example.org"].append( keybundle_from_local_file(RSAKEY, "rsa", ["ver", "sig"])) collection = ks.verify_keys("http://www.example.org") assert len(collection) == 3 assert len([k for k in collection if k.kty == "oct"]) == 2 assert len([k for k in collection if k.kty == "RSA"]) == 1
def test_dump_private_jwks(): keys = [ { "type": "RSA", "use": ["enc", "sig"] }, { "type": "EC", "crv": "P-256", "use": ["sig"] }, ] jwks, keyjar, kidd = build_keyjar(keys) kbl = keyjar.issuer_keys[''] dump_jwks(kbl, 'foo.jwks', private=True) kb_public = KeyBundle(source='file://./foo.jwks') # All RSA keys for k in kb_public.keys(): if k.kty == 'RSA': assert k.d assert k.p assert k.q else: # MUST be 'EC' assert k.d
def test_parse_jwt_request(self): ar = AuthorizationRequest( response_type=["code"], client_id="foobar", redirect_uri="http://foobar.example.com/oaclient", state="cold", ) self.srv.keyjar["foobar"] = KeyBundle( [ {"kty": "oct", "key": "A1B2C3D4".encode("utf-8"), "use": "ver"}, {"kty": "oct", "key": "A1B2C3D4".encode("utf-8"), "use": "sig"}, ] ) self.srv.keyjar[""] = KeyBundle( [ {"kty": "oct", "key": "A1B2C3D4".encode("utf-8"), "use": "ver"}, {"kty": "oct", "key": "A1B2C3D4".encode("utf-8"), "use": "sig"}, ] ) keys = self.srv.keyjar.get_signing_key(owner="foobar") _jwt = ar.to_jwt(key=keys, algorithm="HS256") req = self.srv.parse_jwt_request(txt=_jwt) assert isinstance(req, AuthorizationRequest) assert req["response_type"] == ["code"] assert req["client_id"] == "foobar" assert req["redirect_uri"] == "http://foobar.example.com/oaclient" assert req["state"] == "cold"
def test_dump_public_jwks(): keys = [ { "type": "RSA", "use": ["enc", "sig"] }, { "type": "EC", "crv": "P-256", "use": ["sig"] }, ] jwks, keyjar, kidd = build_keyjar(keys) kbl = keyjar.issuer_keys[""] dump_jwks(kbl, "foo.jwks") kb_public = KeyBundle(source="file://./foo.jwks") # All RSA keys for k in kb_public.keys(): if k.kty == "RSA": assert not k.d assert not k.p assert not k.q else: # MUST be 'EC' assert not k.d
def export(self, client, cconf, role): # has to be there self.trace.info("EXPORT") if client.keyjar is None: client.keyjar = KeyJar() kbl = [] for typ, info in cconf["keys"].items(): kb = KeyBundle(source="file://%s" % info["key"], fileformat="der", keytype=typ) for k in kb.keys(): k.serialize() client.keyjar.add_kb("", kb) kbl.append(kb) try: new_name = "static/%s_jwks.json" % role dump_jwks(kbl, new_name) client.jwks_uri = "%s%s" % (cconf["_base_url"], new_name) except KeyError: pass if not self.args.external_server and not self.keysrv_running: self._pop = start_key_server(cconf["_base_url"]) self.environ["keyprovider"] = self._pop self.trace.info("Started key provider") time.sleep(1) self.keysrv_running = True
def export(self): # has to be there self.trace.info("EXPORT") if self.client.keyjar is None: self.client.keyjar = KeyJar() kbl = [] kid_template = "a%d" kid = 0 for typ, info in self.cconf["keys"].items(): kb = KeyBundle(source="file://%s" % info["key"], fileformat="der", keytype=typ) for k in kb.keys(): k.serialize() k.kid = kid_template % kid kid += 1 self.client.kid[k.use][k.kty] = k.kid self.client.keyjar.add_kb("", kb) kbl.append(kb) try: new_name = "static/jwks.json" dump_jwks(kbl, new_name) self.client.jwks_uri = "%s%s" % (self.cconf["_base_url"], new_name) except KeyError: pass if self.args.internal_server: self._pop = start_key_server(self.cconf["_base_url"], self.args.script_path or None) self.environ["keyprovider"] = self._pop self.trace.info("Started key provider") time.sleep(1)
def test_key_export(): kj = KeyJar() url = key_export( "http://example.com/keys/", "outbound", "secret", keyjar=kj, sig={ "alg": "rsa", "format": ["x509", "jwk"] }, ) assert url == "http://example.com/keys/outbound/jwks" # Now a jwks should reside in './keys/outbound/jwks' kb = KeyBundle(source="file://./keys/outbound/jwks") # One key assert len(kb) == 1 # more specifically one RSA key assert len(kb.get("RSA")) == 1 k = kb.get("RSA")[0] # For signing assert k.use == "sig"
def test_keyjar_group_keys(self): ks = KeyJar() ks[""] = KeyBundle([{ "kty": "oct", "key": "a1b2c3d4", "use": "sig" }, { "kty": "oct", "key": "a1b2c3d4", "use": "ver" }]) ks["http://www.example.org"] = KeyBundle([{ "kty": "oct", "key": "e5f6g7h8", "use": "sig" }, { "kty": "oct", "key": "e5f6g7h8", "use": "ver" }]) ks["http://www.example.org"].append( keybundle_from_local_file(RSAKEY, "rsa", ["ver", "sig"])) verified_keys = ks.verify_keys("http://www.example.org") assert len(verified_keys) == 6 assert len([k for k in verified_keys if k.kty == "oct"]) == 4 assert len([k for k in verified_keys if k.kty == "RSA"]) == 2
def __call__(self): keyjar = self.conv.entity.keyjar self.conv.entity.original_keyjar = keyjar.copy() # invalidate the old key old_kid = self.op_args["old_kid"] old_key = keyjar.get_key_by_kid(old_kid) old_key.inactive_since = time.time() # setup new key key_spec = self.op_args["new_key"] typ = key_spec["type"].upper() if typ == "RSA": kb = KeyBundle(keytype=typ, keyusage=key_spec["use"]) kb.append(RSAKey(use=key_spec["use"]).load_key( RSA.generate(key_spec["bits"]))) elif typ == "EC": kb = ec_init(key_spec) else: raise Exception('Wrong key type') # add new key to keyjar with list(kb.keys())[0].kid = self.op_args["new_kid"] keyjar.add_kb("", kb) # make jwks and update file keys = [] for kb in keyjar[""]: keys.extend( [k.to_dict() for k in list(kb.keys()) if not k.inactive_since]) jwks = dict(keys=keys) with open(self.op_args["jwks_path"], "w") as f: f.write(json.dumps(jwks))
def __call__(self): # find the name of the file to which the JWKS should be written try: _uri = self.conv.entity.registration_response["jwks_uri"] except KeyError: raise RequirementsNotMet("No dynamic key handling") r = urlparse(_uri) # find the old key for this key usage and mark that as inactive for kb in self.conv.entity.keyjar.issuer_keys[""]: for key in list(kb.keys()): if key.use in self.new_key["use"]: key.inactive = True kid = 0 # only one key _nk = self.new_key _typ = _nk["type"].upper() if _typ == "RSA": kb = KeyBundle(source="file://%s" % _nk["key"], fileformat="der", keytype=_typ, keyusage=_nk["use"]) else: kb = {} for k in list(kb.keys()): k.serialize() k.kid = self.kid_template % kid kid += 1 self.conv.entity.kid[k.use][k.kty] = k.kid self.conv.entity.keyjar.add_kb("", kb) dump_jwks(self.conv.entity.keyjar[""], r.path[1:])
def test_verify_token_encrypted_no_key(): idt = IdToken( sub="553df2bcf909104751cfd8b2", aud=["5542958437706128204e0000", "554295ce3770612820620000"], auth_time=1441364872, azp="554295ce3770612820620000", ) kj = KeyJar() kb = KeyBundle() kb.do_local_der( os.path.join(os.path.dirname(__file__), "data", "keys", "cert.key"), "some", ["enc", "sig"], ) kj.add_kb("", kb) kj.add_kb("https://sso.qa.7pass.ctf.prosiebensat1.com", kb) packer = JWT( kj, lifetime=3600, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", encrypt=True, ) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(id_token=_jws) # Do not pass they keyjar with keys with pytest.raises(VerificationError): verify_id_token( msg, keyjar=KeyJar(), iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000", )
def _func(self, conv): response = get_protocol_response(conv, ASConfigurationResponse) if not response: response = get_protocol_response(conv, ServerMetadata) response = response[-1] # Should only be one but ... res = {} try: _jwks_uri = response['jwks_uri'] except KeyError: try: kb = KeyBundle(response['jwks']) except KeyBundle: self._message = "Neither jwks_uri or jwks defined" self._status = ERROR except UnknownKeyType as err: self._message = '{}'.format(err) self._status = ERROR else: kb = KeyBundle(source=_jwks_uri, verify_ssl=False) try: kb.update() except UpdateFailed as err: self._message = '{}'.format(err) self._status = ERROR return res
def init_keyjar(): # Keys that are kept by the AS kb = KeyBundle() kb.do_keys(JWKS["keys"]) keyjar = KeyJar() keyjar.add_kb('', kb) return keyjar
def __call__(self, conv, **kwargs): # find the name of the file to which the JWKS should be written try: _uri = conv.client.registration_response["jwks_uri"] except KeyError: raise RequirementsNotMet("No dynamic key handling") r = urlparse(_uri) # find the old key for this key usage and mark that as inactive for kb in conv.client.keyjar.issuer_keys[""]: for key in kb.keys(): if key.use in self.new_key["use"]: key.inactive = True kid = 0 # only one key _nk = self.new_key _typ = _nk["type"].upper() if _typ == "RSA": kb = KeyBundle(source="file://%s" % _nk["key"], fileformat="der", keytype=_typ, keyusage=_nk["use"]) else: kb = {} for k in kb.keys(): k.serialize() k.kid = self.kid_template % kid kid += 1 conv.client.kid[k.use][k.kty] = k.kid conv.client.keyjar.add_kb("", kb) dump_jwks(conv.client.keyjar[""], r.path[1:])
def construct_jwks(_client, key_conf): """ Construct the jwks """ if _client.keyjar is None: _client.keyjar = KeyJar() kbl = [] kid_template = "a%d" kid = 0 for typ, info in key_conf.items(): kb = KeyBundle(source="file://%s" % info["key"], fileformat="der", keytype=typ) for k in kb.keys(): k.serialize() k.kid = kid_template % kid kid += 1 _client.kid[k.use][k.kty] = k.kid _client.keyjar.add_kb("", kb) kbl.append(kb) jwks = {"keys": []} for kb in kbl: # ignore simple keys jwks["keys"].extend([k.to_dict() for k in kb.keys() if k.kty != 'oct']) return jwks
def rotate_jwks(self): # type: () -> None """Replace the current JWKS with a fresh one.""" self.jwks = KeyJar() kb = KeyBundle(keyusage=["enc", "sig"]) kb.append(RSAKey(key=RSA.generate(1024), kid=self._create_kid())) self.jwks.add_kb("", kb)
def test_remove_key(self): ks = KeyJar() ks[""] = KeyBundle([{"kty": "oct", "key": "a1b2c3d4", "use": "sig"}, {"kty": "oct", "key": "a1b2c3d4", "use": "ver"}]) ks["http://www.example.org"] = [ KeyBundle([ {"kty": "oct", "key": "e5f6g7h8", "use": "sig"}, {"kty": "oct", "key": "e5f6g7h8", "use": "ver"}]), keybundle_from_local_file(RSAKEY, "rsa", ["enc", "dec"]) ] ks["http://www.example.com"] = keybundle_from_local_file(RSA0, "rsa", ["enc", "dec"]) coll = ks["http://www.example.org"] # coll is list of KeyBundles assert len(coll) == 2 keys = ks.get_encrypt_key(key_type="RSA", owner="http://www.example.org") assert len(keys) == 1 _key = keys[0] ks.remove_key("http://www.example.org", "RSA", _key) coll = ks["http://www.example.org"] assert len(coll) == 1 # Only one remaining key keys = ks.get_encrypt_key(key_type="rsa", owner="http://www.example.org") assert len(keys) == 0 keys = ks.verify_keys("http://www.example.com") assert len(keys) == 1 assert len([k for k in keys if k.kty == "oct"]) == 1 keys = ks.decrypt_keys("http://www.example.org") assert keys == []
def _create_symmetric_key(issuer, key): provider_keys = KeyJar() key = SYMKey(use='sig', k=key) kb = KeyBundle(keytype='oct') kb.append(key) provider_keys[issuer] = [kb] return provider_keys
def store_key(self, key): kb = KeyBundle() kb.do_keys([key]) # Store key with thumbprint as key key_thumbprint = b64e(kb.keys()[0].thumbprint("SHA-256")).decode("utf8") self.thumbprint2key[key_thumbprint] = key return key_thumbprint
def __call__(self): kb = KeyBundle(source=self.conv.entity.provider_info["jwks_uri"]) kb.verify_ssl = False kb.update() try: self.conv.keybundle.append(kb) except AttributeError: self.conv.keybundle = [kb]
def store_key(self, key): kb = KeyBundle() kb.do_keys([key]) # Store key with thumbprint as key key_thumbprint = b64e(kb.keys()[0].thumbprint('SHA-256')).decode( 'utf8') self.thumbprint2key[key_thumbprint] = key return key_thumbprint
def __call__(self, conv, **kwargs): pi = conv.client.provider_info kb = KeyBundle(source=pi["jwks_uri"]) kb.verify_ssl = False kb.update() try: conv.keybundle.append(kb) except AttributeError: conv.keybundle = [kb]
def test_chain_1(): kc = KeyBundle([{"kty": "oct", "key": "supersecret", "use": "sig"}]) assert len(kc.get("oct")) == 1 assert len(kc.get("rsa")) == 0 assert kc.remote is False assert kc.source is None kc.update() # Nothing should happen assert len(kc.get("oct")) == 1 assert len(kc.get("rsa")) == 0 assert kc.remote is False assert kc.source is None
def create_provider(self): kb = KeyBundle(JWKS["keys"]) kj = KeyJar() kj.issuer_keys[''] = [kb] _sdb = SessionDB("https://example.com/", db={}, code_factory=DefaultToken('supersecret', 'verybadpassword', typ='A', lifetime=600), token_factory=JWTToken('T', keyjar=kj, lt_pattern={ 'code': 3600, 'token': 900 }, iss='https://example.com/as', sign_alg='RS256'), refresh_token_factory=JWTToken( 'R', keyjar=kj, lt_pattern={'': 24 * 3600}, iss='https://example.com/as')) # name, sdb, cdb, authn_broker, authz, client_authn, self.provider = Provider("as", _sdb, CDB, AUTHN_BROKER, AUTHZ, verify_client, baseurl='https://example.com/as')
def test_enc_hmac(): payload = { 'nonce': 'CYeHPyA6Kmr_jy5HDHXykznu2BpDLm8ngbIJvhBoupI,', 'sub': 'diana', 'iss': 'https://xenosmilus2.umdc.umu.se:8091/', 'acr': '2', 'exp': 1401176001, 'iat': 1401096801, 'aud': ['ApB7TBoKV1tV'] } _jwe = JWE(json.dumps(payload), alg="A128KW", enc="A128CBC-HS256") kb = KeyBundle(JWK1["keys"]) kj = KeyJar() kj.issuer_keys["abcdefgh"] = [kb] keys = kj.get_encrypt_key(owner="abcdefgh") _enctxt = _jwe.encrypt(keys, context="public") assert _enctxt # and now for decryption msg, state = _jwe.decrypt(_enctxt, keys) assert json.loads(msg) == payload
def test_pkce_token(): kb = KeyBundle(JWKS["keys"]) kj = KeyJar() kj.issuer_keys[""] = [kb] constructor = JWTToken( "A", keyjar=kj, lt_pattern={"": 900}, iss="https://example.com/as", sign_alg="RS256", encrypt=True, ) sid = rndstr(32) session_info = { "sub": "subject_id", "client_id": "https://example.com/rp", "response_type": ["code"], "authzreq": "{}", } _cli = Client(config={"code_challenge": {"method": "S512", "length": 96}}) args, cv = _cli.add_code_challenge() access_grant = constructor( sid, sinfo=session_info, kid="sign1", code_challenge=args["code_challenge"], code_challenge_method=args["code_challenge_method"], ) _info = constructor.get_info(access_grant) assert _info["code_challenge_method"] == args["code_challenge_method"] assert _info["code_challenge"] == args["code_challenge"]
def create_sdb(self): kb = KeyBundle(JWKS["keys"]) kj = KeyJar() kj.issuer_keys[""] = [kb] self.sdb = SessionDB( "https://example.com/", db=DictSessionBackend(), code_factory=DefaultToken("supersecret", "verybadpassword", typ="A", lifetime=600), token_factory=JWTToken( "T", keyjar=kj, lt_pattern={ "code": 3600, "token": 900 }, iss="https://example.com/as", sign_alg="RS256", ), refresh_token_factory=JWTToken( "R", keyjar=kj, lt_pattern={"": 24 * 3600}, iss="https://example.com/as", token_storage={}, ), )
def test_pkce_token(): kb = KeyBundle(JWKS["keys"]) kj = KeyJar() kj.issuer_keys[''] = [kb] constructor = JWTToken('A', keyjar=kj, lt_pattern={'': 900}, iss='https://example.com/as', sign_alg='RS256', encrypt=True) sid = rndstr(32) session_info = { 'sub': 'subject_id', 'client_id': 'https://example.com/rp', 'response_type': ['code'], 'authzreq': '{}' } _cli = Client(config={'code_challenge': {'method': 'S512', 'length': 96}}) args, cv = _cli.add_code_challenge() access_grant = constructor( sid, sinfo=session_info, kid='sign1', code_challenge=args['code_challenge'], code_challenge_method=args['code_challenge_method']) _info = constructor.get_info(access_grant) assert _info['code_challenge_method'] == args['code_challenge_method'] assert _info['code_challenge'] == args['code_challenge']
def load_keys(self, request, client_id, client_secret): try: self.keyjar.load_keys(request, client_id) try: n_keys = len(self.keyjar[client_id]) msg = "Found {} keys for client_id={}" logger.debug(msg.format(n_keys, client_id)) except KeyError: pass except Exception as err: msg = "Failed to load client keys: {}" logger.error(msg.format(sanitize(request.to_dict()))) logger.error("%s", err) err = ClientRegistrationError( error="invalid_configuration_parameter", error_description="%s" % err) return Response(err.to_json(), content="application/json", status_code="400 Bad Request") # Add the client_secret as a symmetric key to the keyjar _kc = KeyBundle([{ "kty": "oct", "key": client_secret, "use": "ver" }, { "kty": "oct", "key": client_secret, "use": "sig" }]) try: self.keyjar[client_id].append(_kc) except KeyError: self.keyjar[client_id] = [_kc]
def load_keys(self, request, client_id, client_secret): try: self.keyjar.load_keys(request, client_id) try: logger.debug("keys for %s: [%s]" % (client_id, ",".join( ["%s" % x for x in self.keyjar[client_id]]))) except KeyError: pass except Exception as err: logger.error("Failed to load client keys: %s" % request.to_dict()) logger.error("%s", err) err = ClientRegistrationError( error="invalid_configuration_parameter", error_description="%s" % err) return Response(err.to_json(), content="application/json", status="400 Bad Request") # Add the client_secret as a symmetric key to the keyjar _kc = KeyBundle([{ "kty": "oct", "key": client_secret, "use": "ver" }, { "kty": "oct", "key": client_secret, "use": "sig" }]) try: self.keyjar[client_id].append(_kc) except KeyError: self.keyjar[client_id] = [_kc]
def test_construct(self, client): _key = rsa_load(os.path.join(BASE_PATH, "data/keys/rsa.key")) kc_rsa = KeyBundle([{ "key": _key, "kty": "RSA", "use": "ver" }, { "key": _key, "kty": "RSA", "use": "sig" }]) client.keyjar[""] = kc_rsa client.token_endpoint = "https://example.com/token" client.provider_info = { 'issuer': 'https://example.com/', 'token_endpoint': "https://example.com/token" } cis = AccessTokenRequest() pkj = PrivateKeyJWT(client) http_args = pkj.construct(cis, algorithm="RS256", authn_endpoint='token') assert http_args == {} cas = cis["client_assertion"] _jwt = JWT().unpack(cas) jso = _jwt.payload() assert _eq(jso.keys(), ["aud", "iss", "sub", "jti", "exp", "iat"]) assert _jwt.headers == {'alg': 'RS256'} assert jso['aud'] == [client.provider_info['token_endpoint']]
def test_issuer_mismatch(self): ISSUER = "https://login.microsoftonline.com/b4ea3de6-839e-4ad1-ae78-c78e5c0cdc06/v2.0/" kb = KeyBundle(JWK2["keys"]) kj = KeyJar() kj.issuer_keys[ISSUER] = [kb] kj.issuer_keys[""] = [] authz_resp = AuthorizationResponse().from_urlencoded( "id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1u" "Q19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiIwMTZlZDBlNC1mYzUyLTRlYjgtOWVhYy1lODg1MmM4MjEwNTUiLCJpc3Mi" "OiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYjRlYTNkZTYtODM5ZS00YWQxLWFlNzgtYzc4ZTVjMGNkYzA2L3YyLjAvI" "iwiaWF0IjoxNDM5OTIzNDY5LCJuYmYiOjE0Mzk5MjM0NjksImV4cCI6MTQzOTkyNzM2OSwidmVyIjoiMi4wIiwidGlkIjoiYjRlYTNkZT" "YtODM5ZS00YWQxLWFlNzgtYzc4ZTVjMGNkYzA2Iiwib2lkIjoiNDJjMzliNWUtYmQwNS00YTlhLTlhNWUtMTY5ZDc2N2ZlZjJmIiwicHJ" "lZmVycmVkX3VzZXJuYW1lIjoiaW50ZXJvcEBrYXV0aS5vbm1pY3Jvc29mdC5jb20iLCJzdWIiOiJFWGlaVldjakpsREN1LXZzOUxNb1V3" "ZGRNNEJZZ2ZISzBJNk40dWpXZkRFIiwibmFtZSI6ImludGVyb3AiLCJub25jZSI6IlpkSHRxQWwzR3c4QiJ9.tH4FKM4H9YCHX2XF4V64" "SsLaKh31c0oLpEVlFxFHw8jxL5HujUthZJDUMwngXZ2mPU_1G152ybKiRCV9DKaBh1rFSlZxTDBp0SV_YTwOkGYOt-sOzFUJyvVCjGmRh" "vFkOF1kiT3IYjDoRh72U8pMchj1duWSytLczdOc4LJmg24ya5jwqApuyQu7gVqoDH1kEqBAuhBj3a7ZDwxIt-bTKZklsht0RutZjv4Ckg" "8qJpzWnY7rIjSKFKfEpAAfk_LqWvTktvDMKTHXLxEPVZymoskE1LthtC8AYoNmtVPxgxf87yGCqYZBsuAnVChdnsItXP7tPeqUjC8Lm3J" "jabV-5g&id_token_expires_in=3599&state=6o3FmQ0QZl1zifsE&session_state=d2c97e8a-497c-4ce1-bb10-5058501164eb" ) try: authz_resp.verify(keyjar=kj, skew=100000000) except MissingSigningKey: authz_resp.verify(keyjar=kj, sender=ISSUER, skew=100000000)
def test_get_inactive_ver(self): ks = KeyJar() ks['http://example.com'] = KeyBundle( [{"kty": "oct", "key": "a1b2c3d4", "use": "sig"}, {"kty": "oct", "key": "a1b2c3d4", "use": "ver"}]) ks['http://example.com'][0]._keys[1].inactive_since = 1 key = ks.get_verify_key(owner='http://example.com') assert len(key) == 2
def test_verify_token_encrypted_no_key(): idt = IdToken(sub='553df2bcf909104751cfd8b2', aud=['5542958437706128204e0000', '554295ce3770612820620000'], auth_time=1441364872, azp='554295ce3770612820620000') kj = KeyJar() kb = KeyBundle() kb.do_local_der(os.path.join(os.path.dirname(__file__), 'data', 'keys', 'cert.key'), 'some', ['enc', 'sig']) kj.add_kb('', kb) kj.add_kb('https://sso.qa.7pass.ctf.prosiebensat1.com', kb) packer = JWT(kj, lifetime=3600, iss='https://sso.qa.7pass.ctf.prosiebensat1.com', encrypt=True) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(id_token=_jws) # Do not pass they keyjar with keys with pytest.raises(VerificationError): verify_id_token(msg, keyjar=KeyJar(), iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000")
def test_verify_token_encrypted(): idt = IdToken(sub='553df2bcf909104751cfd8b2', aud=['5542958437706128204e0000', '554295ce3770612820620000'], auth_time=1441364872, azp='554295ce3770612820620000') kj = KeyJar() kb = KeyBundle() kb.do_local_der(os.path.join(os.path.dirname(__file__), 'data', 'keys', 'cert.key'), 'some', ['enc', 'sig']) kj.add_kb('', kb) kj.add_kb('https://sso.qa.7pass.ctf.prosiebensat1.com', kb) packer = JWT(kj, lifetime=3600, iss='https://sso.qa.7pass.ctf.prosiebensat1.com', encrypt=True) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(id_token=_jws) vidt = verify_id_token(msg, keyjar=kj, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000") assert vidt assert vidt.jwe_header == {'enc': 'A128CBC-HS256', 'alg': 'RSA1_5', 'cty': 'JWT'}
def test_get_inactive_sig_for_ver(self): """get_verify_key can return inactive `sig` key.""" ks = KeyJar() ks['http://example.com'] = KeyBundle( [{"kty": "oct", "key": "a1b2c3d4", "use": "sig"}]) ks['http://example.com'][0]._keys[0].inactive_since = 1 key = ks.get_verify_key(owner='http://example.com') assert len(key) == 1
def create_token(self): kb = KeyBundle(JWKS["keys"]) kj = KeyJar() kj.issuer_keys[''] = [kb] self.access_token = JWTToken('T', keyjar=kj, iss='https://example.com/as', sign_alg='RS256')
def create_token(self): kb = KeyBundle(JWKS["keys"]) kj = KeyJar() kj.issuer_keys[""] = [kb] self.access_token = JWTToken("T", keyjar=kj, iss="https://example.com/as", sign_alg="RS256")
def test_key_export(): kj = KeyJar() url = key_export("http://example.com/keys/", "outbound", "secret", keyjar=kj, sig={"alg": "rsa", "format": ["x509", "jwk"]}) assert url == "http://example.com/keys/outbound/jwks" # Now a jwks should reside in './keys/outbound/jwks' kb = KeyBundle(source='file://./keys/outbound/jwks') # One key assert len(kb) == 1 # more specifically one RSA key assert len(kb.get('RSA')) == 1 k = kb.get('RSA')[0] # For signing assert k.use == 'sig'
def test_get_inactive_sig(self): """get_signing_key cannot return inactive `sig` key.""" ks = KeyJar() ks["http://example.com"] = KeyBundle( [{"kty": "oct", "key": "a1b2c3d4", "use": "sig"}] ) ks["http://example.com"][0]._keys[0].inactive_since = 1 key = ks.get_signing_key(owner="http://example.com") assert len(key) == 0
def test_dump_private_jwks(): keys = [ {"type": "RSA", "use": ["enc", "sig"]}, {"type": "EC", "crv": "P-256", "use": ["sig"]}, ] jwks, keyjar, kidd = build_keyjar(keys) kbl = keyjar.issuer_keys[''] dump_jwks(kbl, 'foo.jwks', private=True) kb_public = KeyBundle(source='file://./foo.jwks') # All RSA keys for k in kb_public.keys(): if k.kty == 'RSA': assert k.d assert k.p assert k.q else: # MUST be 'EC' assert k.d
def __init__(self, base_url, op, sp): self.base_url = base_url self.op = op self.sp = sp # Setup key for encrypting/decrypting the state (passed in the SAML RelayState). source = "file://symkey.json" self.key_bundle = KeyBundle(source=source, fileformat="jwk") for key in self.key_bundle.keys(): key.deserialize()
def add_software_statement(oper, arg): argkeys = list(arg.keys()) kwargs = {} tre = oper.conf.TRUSTED_REGISTRATION_ENTITY iss = tre['iss'] kb = KeyBundle() kb.imp_jwks = json.load(open(tre['jwks'])) kb.do_keys(kb.imp_jwks['keys']) oper.conv.entity.keyjar.add_kb(iss, kb) if arg['redirect_uris'] is None: kwargs['redirect_uris'] = oper.conv.entity.redirect_uris else: kwargs['redirect_uris'] = arg['redirect_uris'] argkeys.remove('redirect_uris') if 'jwks_uri' in argkeys: if arg['jwks_uri'] is None: kwargs['jwks_uri'] = oper.conv.entity.jwks_uri else: kwargs['jwks_uri'] = arg['jwks_uri'] argkeys.remove('jwks_uri') elif 'jwks' in argkeys: if arg['jwks'] is None: kwargs['jwks'] = { "keys": oper.conv.entity.keyjar.dump_issuer_keys("")} else: kwargs['jwks'] = arg['jwks'] argkeys.remove('jwks') for a in argkeys: kwargs[a] = arg[a] oper.req_args['software_statement'] = make_software_statement( oper.conv.entity.keyjar, iss=iss, owner=iss, **kwargs)
def test_chain_3(): kc = KeyBundle(source="file://../oc3/certs/server.crt", type="rsa", src_type="x509", usage=["sig", "enc"]) assert kc.usage == ["sig", "enc"] assert kc.remote == False assert kc.source == "../oc3/certs/server.crt" assert len(kc.get("hmac")) == 0 assert len(kc.get("rsa")) == 1 key = kc.get("rsa")[0] assert isinstance(key, M2Crypto.RSA.RSA) kc.update() assert kc.usage == ["sig", "enc"] assert kc.remote == False assert kc.source == "../oc3/certs/server.crt" assert len(kc.get("hmac")) == 0 assert len(kc.get("rsa")) == 1 key = kc.get("rsa")[0] assert isinstance(key, M2Crypto.RSA.RSA)
def test_chain_2(): kc = KeyBundle(source="file://../oc3/certs/mycert.key", type="rsa", usage=["ver", "sig"]) assert kc.usage == ["ver", "sig"] assert kc.remote == False assert kc.source == "../oc3/certs/mycert.key" assert len(kc.get("hmac")) == 0 assert len(kc.get("rsa")) == 1 key = kc.get("rsa")[0] assert isinstance(key, M2Crypto.RSA.RSA) kc.update() assert kc.usage == ["ver", "sig"] assert kc.remote == False assert kc.source == "../oc3/certs/mycert.key" assert len(kc.get("hmac")) == 0 assert len(kc.get("rsa")) == 1 key = kc.get("rsa")[0] assert isinstance(key, M2Crypto.RSA.RSA)
def test_chain_1(): kc = KeyBundle({"hmac": "supersecret"}, usage="sig") assert len(kc.get("hmac")) == 1 assert len(kc.get("rsa")) == 0 assert kc.usage == ["sig"] assert kc.remote == False assert kc.source is None kc.update() # Nothing should happen assert len(kc.get("hmac")) == 1 assert len(kc.get("rsa")) == 0 assert kc.usage == ["sig"] assert kc.remote == False assert kc.source is None
def test_parse_remote_response(caplog): """ Tests parsing Content-Type header for _parse_remote_response """ class FakeResponse(): def __init__(self, header): self.headers = {"Content-Type": header} self.text = "{}" with caplog.at_level(logging.WARNING, logger='oic.utils.keyio'): kb_public = KeyBundle(source='file://./foo.jwks') res = FakeResponse('application/json;encoding=utf-8') kb_public._parse_remote_response(res) assert caplog.record_tuples != [ ('oic.utils.keyio', logging.WARNING, 'Wrong Content_type') ] caplog.clear() res = FakeResponse('application/json') kb_public._parse_remote_response(res) assert caplog.record_tuples != [ ('oic.utils.keyio', logging.WARNING, 'Wrong Content_type') ] caplog.clear() res = FakeResponse('Application/json') kb_public._parse_remote_response(res) assert caplog.record_tuples != [ ('oic.utils.keyio', logging.WARNING, 'Wrong Content_type') ] caplog.clear() res = FakeResponse('text/plain') kb_public._parse_remote_response(res) assert caplog.record_tuples == [ ('oic.utils.keyio', logging.WARNING, 'Wrong Content_type') ]
def test_reload(): """ Emulates what happens if you fetch keys from a remote site and you get back the same JWKS as the last time. """ _jwks = JWK0 kb = KeyBundle() kb.imp_jwks = _jwks kb.do_keys(kb.imp_jwks['keys']) assert len(kb) == 1 kb.do_keys(kb.imp_jwks['keys']) assert len(kb) == 1
def keybundle_from_local_file(filename, typ, usage, kid): if typ.upper() == "RSA": kb = KeyBundle() k = RSAKey(kid=kid) k.load(filename) k.use = usage[0] kb.append(k) for use in usage[1:]: _k = RSAKey(kid=kid + "1") _k.use = use _k.load_key(k.key) kb.append(_k) elif typ.lower() == "jwk": kb = KeyBundle(source=filename, fileformat="jwk", keyusage=usage) else: raise UnknownKeyType("Unsupported key type") return kb
if config.baseurl.endswith("/"): config.baseurl = config.baseurl[:-1] OAS.baseurl = "%s:%d" % (config.baseurl, args.port) if not OAS.baseurl.endswith("/"): OAS.baseurl += "/" # load extra keys try: extern = config.TRUSTED_REGISTRATION_ENTITIES except AttributeError: pass else: for ent in extern: iss = ent['iss'] kb = KeyBundle() kb.imp_jwks = json.load(open(ent['jwks'])) kb.do_keys(kb.imp_jwks['keys']) OAS.keyjar.add_kb(iss, kb) LOGGER.debug("URLS: '%s" % (URLS,)) # Initiate the web server SRV = wsgiserver.CherryPyWSGIServer(('0.0.0.0', args.port), application) https = "" if config.SERVICE_URL.startswith("https"): https = " using HTTPS" # SRV.ssl_adapter = ssl_pyopenssl.pyOpenSSLAdapter( # config.SERVER_CERT, config.SERVER_KEY, config.CERT_CHAIN) SRV.ssl_adapter = BuiltinSSLAdapter(config.SERVER_CERT, config.SERVER_KEY,
from pytest import raises from fakeoicsrv import MyFakeOICServer def _eq(l1, l2): s1 = set(l1) s2 = set(l2) return s1 == s2 CLIENT_SECRET = "abcdefghijklmnop" CLIENT_ID = "client_1" KC_SYM_S = KeyBundle({"kty": "oct", "key": "abcdefghijklmnop", "use": "sig"}) _key = rsa_load("../oc3/certs/mycert.key") KC_RSA = KeyBundle({"key": _key, "kty": "RSA", "use": "sig"}) KEYJ = KeyJar() KEYJ[""] = [KC_RSA, KC_SYM_S] KEYJ["client_1"] = [KC_SYM_S] IDTOKEN = IdToken(iss="http://oic.example.org/", sub="user_id", aud=CLIENT_ID, exp=utc_time_sans_frac() + 86400, nonce="N0nce", iat=time.time()) # ----------------- CLIENT --------------------
from oic.utils.keyio import KeyBundle, key_eq __author__ = 'rolandh' jwk_url = ["https://connect.openid4.us/connect4us.jwk", # edmund "https://connect-op.heroku.com/jwk.json"] # nov x509_url = ["https://connect-op.heroku.com/cert.pem"] kc0 = KeyBundle(source=jwk_url[1], src_type="jwk", type="rsa", usage=["sig", "enc"]) kc1 = KeyBundle(source=x509_url[0], src_type="x509", type="rsa", usage=["sig", "enc"]) kc0.update() print kc0 kc1.update() print kc1 print key_eq(kc0.get("rsa")[0], kc1.get("rsa")[0])
class InAcademiaMediator(object): """The main CherryPy application, with all exposed endpoints. This app mediates between a OpenIDConnect provider front-end, which uses SAML as the back-end for authenticating users. """ def __init__(self, base_url, op, sp): self.base_url = base_url self.op = op self.sp = sp # Setup key for encrypting/decrypting the state (passed in the SAML RelayState). source = "file://symkey.json" self.key_bundle = KeyBundle(source=source, fileformat="jwk") for key in self.key_bundle.keys(): key.deserialize() @cherrypy.expose def index(self): raise cherrypy.HTTPRedirect("http://www.inacademia.org") @cherrypy.expose def status(self): return @cherrypy.expose def authorization(self, *args, **kwargs): """Where the OP Authentication Request arrives. """ transaction_session = self.op.verify_authn_request(cherrypy.request.query_string) state = self._encode_state(transaction_session) log_transaction_start(logger, cherrypy.request, state, transaction_session["client_id"], transaction_session["scope"], transaction_session["redirect_uri"]) return self.sp.redirect_to_auth(state, transaction_session["scope"]) @cherrypy.expose def disco(self, state=None, entityID=None, **kwargs): """Where the SAML Discovery Service response arrives. """ if state is None: raise cherrypy.HTTPError(404, _('Page not found.')) transaction_session = self._decode_state(state) if "error" in kwargs: abort_with_client_error(state, transaction_session, cherrypy.request, logger, "Discovery service error: '{}'.".format(kwargs["error"])) elif entityID is None or entityID == "": abort_with_client_error(state, transaction_session, cherrypy.request, logger, "No entity id returned from discovery server.") return self.sp.disco(entityID, state, transaction_session) @cherrypy.expose def error(self, lang=None, error=None): """Where the i18n of the error page is handled. """ if error is None: raise cherrypy.HTTPError(404, _("Page not found.")) self._set_language(lang) error = json.loads(urllib.unquote_plus(error)) raise EndUserErrorResponse(**error) def webfinger(self, rel=None, resource=None): """Where the WebFinger request arrives. This function is mapped explicitly using PathDiscpatcher. """ try: assert rel == OIC_ISSUER assert resource is not None except AssertionError as e: raise cherrypy.HTTPError(400, "Missing or incorrect parameter in webfinger request.") cherrypy.response.headers["Content-Type"] = "application/jrd+json" return WebFinger().response(resource, self.op.OP.baseurl) def openid_configuration(self): """Where the OP configuration request arrives. This function is mapped explicitly using PathDispatcher. """ return response_to_cherrypy(self.op.OP.providerinfo_endpoint()) def consent_allow(self, state=None, released_claims=None): """Where the approved consent arrives. This function is mapped explicitly using PathDispatcher. """ if state is None or released_claims is None: raise cherrypy.HTTPError(404, _("Page not found.")) state = json.loads(urllib.unquote_plus(state)) released_claims = json.loads(urllib.unquote_plus(released_claims)) transaction_session = self._decode_state(state["state"]) log_internal(logger, "consented claims: {}".format(json.dumps(released_claims)), cherrypy.request, state["state"], transaction_session["client_id"]) return self.op.id_token(released_claims, state["idp_entity_id"], state["state"], transaction_session) def consent_deny(self, state=None, released_claims=None): """Where the denied consent arrives. This function is mapped explicitly using PathDispatcher. """ if state is None: raise cherrypy.HTTPError(404, _("Page not found.")) state = json.loads(urllib.unquote_plus(state)) transaction_session = self._decode_state(state["state"]) negative_transaction_response(state["state"], transaction_session, cherrypy.request, logger, "User did not give consent.", state["idp_entity_id"]) def consent_index(self, lang=None, state=None, released_claims=None): """Where the i18n of the consent page arrives. This function is mapped explicitly using PathDispatcher. """ if state is None or released_claims is None: raise cherrypy.HTTPError(404, _("Page not found.")) self._set_language(lang) state = json.loads(urllib.unquote_plus(state)) rp_client_id = self._decode_state(state["state"])["client_id"] released_claims = json.loads(urllib.unquote_plus(released_claims)) client_name = self._get_client_name(rp_client_id) return ConsentPage.render(client_name, state["idp_entity_id"], released_claims, state["state"]) def acs_post(self, SAMLResponse=None, RelayState=None, **kwargs): """Where the SAML Authentication Response arrives. This function is mapped explicitly using PathDiscpatcher. """ return self._acs(SAMLResponse, RelayState, BINDING_HTTP_POST) def acs_redirect(self, SAMLResponse=None, RelayState=None): """Where the SAML Authentication Response arrives. """ return self._acs(SAMLResponse, RelayState, BINDING_HTTP_REDIRECT) def _acs(self, SAMLResponse, RelayState, binding): """Handle the SAMLResponse from the IdP and produce the consent page. :return: HTML of the OP consent page. """ transaction_session = self._decode_state(RelayState) user_id, affiliation, identity, auth_time, idp_entity_id = self.sp.acs(SAMLResponse, binding, RelayState, transaction_session) # if we have passed all checks, ask the user for consent before finalizing released_claims = self.op.get_claims_to_release(user_id, affiliation, identity, auth_time, idp_entity_id, self.sp.metadata, transaction_session) log_internal(logger, "claims to consent: {}".format(json.dumps(released_claims)), cherrypy.request, RelayState, transaction_session["client_id"]) client_name = self._get_client_name(transaction_session["client_id"]) return ConsentPage.render(client_name, idp_entity_id, released_claims, RelayState) def _set_language(self, lang): """Set the language. """ if lang is None: lang = "en" # Modify the Accept-Language header and use the CherryPy i18n tool for translation cherrypy.request.headers["Accept-Language"] = lang i18n_args = { "default": cherrypy.config["tools.I18nTool.default"], "mo_dir": cherrypy.config["tools.I18nTool.mo_dir"], "domain": cherrypy.config["tools.I18nTool.domain"] } cherrypy.tools.I18nTool.callable(**i18n_args) def _decode_state(self, state): """Decode the transaction data. If the state can not be decoded, the transaction will fail with error page for the user. We can't notify the client since the transaction state now is unknown. """ try: return deconstruct_state(state, self.key_bundle.keys()) except DecryptionFailed as e: abort_with_enduser_error(state, "-", cherrypy.request, logger, _( "We could not complete your validation because an error occurred while handling " "your request. Please return to the service which initiated the validation " "request and try again."), "Transaction state missing or broken in incoming response.") def _encode_state(self, payload): """Encode the transaction data. """ _kids = self.key_bundle.kids() _kids.sort() return construct_state(payload, self.key_bundle.get_key_with_kid(_kids[-1])) def _get_client_name(self, client_id): """Get the display name for the client. :return: the clients display name, or client_id if no display name is known. """ try: client_info = self.op.OP.cdb[client_id] return client_info.get("display_name", client_id) except KeyError as e: return client_id