def setup(self): mkey = [ {"type": "RSA", "use": ["sig"]}, {"type": "RSA", "use": ["sig"]}, {"type": "RSA", "use": ["sig"]}, ] skey = [{"type": "RSA", "use": ["sig"]}] # Alice has multiple keys self.alice_keyjar = build_keyjar(mkey) # Bob has one single keys self.bob_keyjar = build_keyjar(skey) self.alice_keyjar["Alice"] = self.alice_keyjar[""] self.bob_keyjar["Bob"] = self.bob_keyjar[""] # To Alice's keyjar add Bob's public keys self.alice_keyjar.import_jwks(self.bob_keyjar.export_jwks(issuer="Bob"), "Bob") # To Bob's keyjar add Alice's public keys self.bob_keyjar.import_jwks(self.alice_keyjar.export_jwks(issuer="Alice"), "Alice") _jws = JWS('{"aud": "Bob", "iss": "Alice"}', alg="RS256") sig_key = self.alice_keyjar.get_signing_key("rsa", owner="Alice")[0] self.sjwt_a = _jws.sign_compact([sig_key]) _jws = JWS('{"aud": "Alice", "iss": "Bob"}', alg="RS256") sig_key = self.bob_keyjar.get_signing_key("rsa", owner="Bob")[0] self.sjwt_b = _jws.sign_compact([sig_key])
def test_remove_after(): # initial keyjar keyjar = build_keyjar(KEYDEFS) _old = [k.kid for k in keyjar.get_issuer_keys('') if k.kid] assert len(_old) == 2 # rotate_keys = create new keys + make the old as inactive keyjar = build_keyjar(KEYDEFS, keyjar=keyjar) keyjar.remove_after = 1 # None are remove since none are marked as inactive yet keyjar.remove_outdated() _interm = [k.kid for k in keyjar.get_issuer_keys('') if k.kid] assert len(_interm) == 4 # Now mark the keys to be inactivated _now = time.time() for k in keyjar.get_issuer_keys(''): if k.kid in _old: if not k.inactive_since: k.inactive_since = _now keyjar.remove_outdated(_now + 5) # The remainder are the new keys _new = [k.kid for k in keyjar.get_issuer_keys('') if k.kid] assert len(_new) == 2 # should not be any overlap between old and new assert set(_new).intersection(set(_old)) == set()
def test_jwt_unknown_key(self): _keyjar = build_keyjar(KEYDEFS) _jwt = JWT( _keyjar, iss=self.introspection_endpoint.server_get( "endpoint_context").issuer, lifetime=3600, ) _jwt.with_jti = True _payload = {"sub": "subject_id"} _token = _jwt.pack(_payload, aud="client_1") _context = self.introspection_endpoint.server_get("endpoint_context") _req = self.introspection_endpoint.parse_request({ "token": _token, "client_id": "client_1", "client_secret": _context.cdb["client_1"]["client_secret"], }) _req = self.introspection_endpoint.parse_request(_req) _resp = self.introspection_endpoint.process_request(_req) assert _resp["response_args"]["active"] is False
def test_private_key_jwt_reusage_other_endpoint(): # Own dynamic keys client_keyjar = build_keyjar(KEYDEFS) # The servers keys client_keyjar[conf["issuer"]] = KEYJAR.issuer_keys[""] _jwks = client_keyjar.export_jwks() endpoint_context.keyjar.import_jwks(_jwks, client_id) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="RS256") _jwt.with_jti = True _assertion = _jwt.pack( {"aud": [endpoint_context.endpoint["token"].full_path]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } # This should be OK PrivateKeyJWT(endpoint_context).verify(request, endpoint="token") # This should NOT be OK with pytest.raises(NotForMe): PrivateKeyJWT(endpoint_context).verify(request, endpoint="authorization") # This should NOT be OK with pytest.raises(MultipleUsage): PrivateKeyJWT(endpoint_context).verify(request, endpoint="token")
def test_private_key_jwt_auth_endpoint(self): # Own dynamic keys client_keyjar = build_keyjar(KEYDEFS) # The servers keys client_keyjar.import_jwks(KEYJAR.export_jwks(private=True), CONF["issuer"]) _jwks = client_keyjar.export_jwks() self.method.server_get("endpoint_context").keyjar.import_jwks( _jwks, client_id) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="RS256") _jwt.with_jti = True _assertion = _jwt.pack({ "aud": [self.method.server_get("endpoint", "authorization").full_path] }) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } assert self.method.is_usable(request=request) authn_info = self.method.verify( request=request, endpoint=self.method.server_get("endpoint", "authorization"), ) assert authn_info["client_id"] == client_id assert "jwt" in authn_info
def entity_statement_with_x5c(): metadata = { "application_type": "web", "claims": ["sub", "name", "email", "picture"], "id_token_signing_alg_values_supported": ["RS256", "RS512"], "redirect_uris": ["https://foodle.uninett.no/callback"], "response_types": ["code"] } iss = "https://example.com" sub = iss key_jar = build_keyjar(KEYSPEC, issuer_id=iss) authority = ["https://ntnu.no"] with open(os.path.join(BASE_PATH, "cert.pem")) as fp: pems = fp.read() _x5c_val = pems_to_x5c([pems]) _jws = create_entity_statement(iss, sub, key_jar, metadata=metadata, authority_hints=authority, x5c=_x5c_val) return _jws
def new_keys(cls, iss: str = 'Generic', **kwargs): """Creates a new key jar based on RSA keys. """ uses = ['enc', 'sig'] if kwargs.get('sign', True) else ['enc'] key_specs = [{"type": "RSA", "use": uses}] key_jar = build_keyjar(key_specs, issuer_id=iss) return cls(key_jar, iss=iss, **kwargs)
def test_import_keys_url(self): assert len(self.service_context.keyjar.get_issuer_keys('')) == 1 # One EC key for signing key_def = [{"type": "EC", "crv": "P-256", "use": ["sig"]}] keyjar = build_keyjar(key_def) with responses.RequestsMock() as rsps: _jwks_url = 'https://foobar.com/jwks.json' rsps.add("GET", _jwks_url, body=keyjar.export_jwks_as_json(), status=200, adding_headers={"Content-Type": "application/json"}) keyspec = {'url': {'https://foobar.com': _jwks_url}} self.service_context.import_keys(keyspec) self.service_context.keyjar.update() srvcntx = ServiceContext().load( self.service_context.dump( exclude_attributes=["service_context"])) # Now there should be one belonging to https://example.com assert len( srvcntx.keyjar.get_issuer_keys('https://foobar.com')) == 1
def test_create_trust_mark_self_signed(): _entity_id = "https://example.com/op" _tm = TrustMark( id="https://openid.net/certification/op", sub=_entity_id, mark="http://openid.net/wordpress-content/uploads/2016/05/oid-l-certification-mark-l-cmyk" \ "-150dpi-90mm.jpg", ref="https://openid.net/wordpress-content/uploads/2015/09/RolandHedberg-pyoidc-0.7.7" "-Basic-26-Sept-2015.zip" ) _key_jar = build_keyjar(KEYSPEC, issuer_id=_entity_id) # Create the Signed JWT representing the Trust Mark _jwt0 = JWT(key_jar=_key_jar, iss=_entity_id, lifetime=3600) _jws = _jwt0.pack(_tm) # Unpack and verify the Trust Mark _jwt1 = JWT(key_jar=_key_jar, msg_cls=TrustMark, allowed_sign_algs=["RS256"]) res_tm = _jwt1.unpack(_jws) res_tm.verify(entity_id=_entity_id) assert isinstance(res_tm, TrustMark) assert res_tm["id"] == "https://openid.net/certification/op"
def test_build_keyjar_usage(): keys = [ { "type": "RSA", "use": ["enc", "sig"] }, { "type": "EC", "crv": "P-256", "use": ["sig"] }, { "type": "oct", "use": ["enc"] }, { "type": "oct", "use": ["enc"] }, ] keyjar = build_keyjar(keys) jwks_sig = keyjar.export_jwks(usage='sig') jwks_enc = keyjar.export_jwks(usage='enc') assert len(jwks_sig.get('keys')) == 2 # A total of 2 keys with use=sig assert len(jwks_enc.get('keys')) == 3 # A total of 3 keys with use=enc
def test_unpack_aggregated_response_missing_keys(self): claims = { "address": { "street_address": "1234 Hollywood Blvd.", "locality": "Los Angeles", "region": "CA", "postal_code": "90210", "country": "US" }, "phone_number": "+1 (555) 123-4567" } _keyjar = build_keyjar(KEYSPEC) srv = JWT(_keyjar, iss=ISS, sign_alg='ES256') _jwt = srv.pack(payload=claims) resp = OpenIDSchema(sub='diana', given_name='Diana', family_name='krall', _claim_names={ 'address': 'src1', 'phone_number': 'src1' }, _claim_sources={'src1': {'JWT': _jwt}}) _resp = self.service.parse_response(resp.to_json(), state='abcde') assert _resp
def test_create_self_signed(): metadata = { "application_type": "web", "claims": ["sub", "name", "email", "picture"], "id_token_signing_alg_values_supported": ["RS256", "RS512"], "redirect_uris": ["https://foodle.uninett.no/callback"], "response_types": ["code"] } iss = "https://example.com" sub = iss key_jar = build_keyjar(KEYSPEC, issuer_id=iss) authority = ["https://ntnu.no"] _jwt = create_entity_statement(iss, sub, key_jar, metadata=metadata, authority_hints=authority) assert _jwt _verifier = factory(_jwt) keys = key_jar.get_jwt_verify_keys(_verifier.jwt) res = _verifier.verify_compact(keys=keys) assert res assert res['iss'] == iss assert res['sub'] == sub assert set(res.keys()) == { 'metadata', 'iss', 'exp', 'sub', 'iat', 'authority_hints', 'jwks' }
def setup(self): mkey = [ { "type": "RSA", "use": ["sig"] }, { "type": "RSA", "use": ["sig"] }, { "type": "RSA", "use": ["sig"] }, ] skey = [ { "type": "RSA", "use": ["sig"] }, ] # Alice has multiple keys self.alice_keyjar = build_keyjar(mkey) # Bob has one single keys self.bob_keyjar = build_keyjar(skey) self.alice_keyjar['Alice'] = self.alice_keyjar[''] self.bob_keyjar['Bob'] = self.bob_keyjar[''] # To Alice's keyjar add Bob's public keys self.alice_keyjar.import_jwks( self.bob_keyjar.export_jwks(issuer='Bob'), 'Bob') # To Bob's keyjar add Alice's public keys self.bob_keyjar.import_jwks( self.alice_keyjar.export_jwks(issuer='Alice'), 'Alice') _jws = JWS('{"aud": "Bob", "iss": "Alice"}', alg='RS256') sig_key = self.alice_keyjar.get_signing_key('rsa', owner='Alice')[0] self.sjwt_a = _jws.sign_compact([sig_key]) _jws = JWS('{"aud": "Alice", "iss": "Bob"}', alg='RS256') sig_key = self.bob_keyjar.get_signing_key('rsa', owner='Bob')[0] self.sjwt_b = _jws.sign_compact([sig_key])
def test_build_keyjar_missing(tmpdir): keys = [{ "type": "RSA", "key": os.path.join(tmpdir.dirname, "missing_file"), "use": ["enc", "sig"] }] key_jar = build_keyjar(keys) assert len(key_jar[""]) == 1
def rotate_keys(self, keyconf=None): _old = [k.kid for k in self.keyjar.get_issuer_keys('') if k.kid] if keyconf: self.keyjar = build_keyjar(keyconf, keyjar=self.keyjar)[1] elif self.keyconf: self.keyjar = build_keyjar(self.keyconf, keyjar=self.keyjar)[1] else: logger.info("QWas asked to rotate key but could not comply") return self.keyjar.remove_after = self.remove_after self.keyjar.remove_outdated() _now = time.time() for k in self.keyjar.get_issuer_keys(''): if k.kid in _old: if not k.inactive_since: k.inactive_since = _now
def test_build_EC_keyjar_missing(tmpdir): keys = [{ "type": "EC", "key": os.path.join(tmpdir.dirname, "missing_file"), "use": ["enc", "sig"], }] key_jar = build_keyjar(keys) assert key_jar is None
def test_signed_someone_else_metadata(): metadata = { "application_type": "web", "claims": ["sub", "name", "email", "picture"], "id_token_signing_alg_values_supported": ["RS256", "RS512"], "redirect_uris": ["https://foodle.uninett.no/callback"], "response_types": ["code"] } iss = "https://example.com" sub = "https://foo.example.org/rp" sub_key_jar = build_keyjar(KEYSPEC, issuer_id=sub) iss_key_jar = build_keyjar(KEYSPEC, issuer_id=iss) iss_key_jar.import_jwks_as_json( sub_key_jar.export_jwks_as_json(issuer_id=sub), issuer_id=sub) sub_key_jar.import_jwks_as_json( iss_key_jar.export_jwks_as_json(issuer_id=iss), issuer_id=iss) authority = { "https://core.example.com": ["https://federation.example.org"] } _jwt = create_entity_statement(iss, sub, iss_key_jar, metadata=metadata, authority_hints=authority) assert _jwt _verifier = factory(_jwt) keys = sub_key_jar.get_jwt_verify_keys(_verifier.jwt) res = _verifier.verify_compact(keys=keys) assert res assert res['iss'] == iss assert res['sub'] == sub assert set(res.keys()) == { 'metadata', 'iss', 'exp', 'sub', 'iat', 'authority_hints', 'jwks' }
def test_create_unpack_trust_mark_self_signed(): _entity_id = "https://example.com/op" _key_jar = build_keyjar(KEYSPEC, issuer_id=_entity_id) _tm = create_trust_mark( _entity_id, _key_jar, trust_mark_id="https://openid.net/certification/op", trust_mark=("http://openid.net/wordpress-content/uploads/2016/05/" "oid-l-certification-mark-l-cmyk-150dpi-90mm.jpg")) _tm_inst = unpack_trust_mark(_tm, _key_jar, _entity_id) assert isinstance(_tm_inst, TrustMark)
def test_create_unpack_trust_3rd_party(): _iss = "https://feide.no" _sub = "https://op.ntnu.no" _key_jar = build_keyjar(KEYSPEC, issuer_id=_iss) _tm = create_trust_mark( _iss, _key_jar, subject=_sub, trust_mark_id="https://openid.net/certification/op", trust_mark=("http://openid.net/wordpress-content/uploads/2016/05/" "oid-l-certification-mark-l-cmyk-150dpi-90mm.jpg")) _tm_inst = unpack_trust_mark(_tm, _key_jar, _sub) assert isinstance(_tm_inst, TrustMark)
def test_build_keyjar(): keys = [ {"type": "RSA", "use": ["enc", "sig"]}, {"type": "EC", "crv": "P-256", "use": ["sig"]}, ] keyjar = build_keyjar(keys) jwks = keyjar.export_jwks() for key in jwks["keys"]: assert "d" not in key # the JWKS shouldn't contain the private part # of the keys assert len(keyjar[""]) == 3 # 3 keys assert len(keyjar.get_issuer_keys("")) == 3 # A total of 3 keys assert len(keyjar.get("sig")) == 2 # 2 for signing assert len(keyjar.get("enc")) == 1 # 1 for encryption
def test_begin_2(self): ISS_ID = "https://op.example.org" OP_KEYS = build_keyjar(DEFAULT_KEY_DEFS) # The 4 steps of client_setup client = self.rph.init_client(ISS_ID) with responses.RequestsMock() as rsps: request_uri = '{}/.well-known/openid-configuration'.format(ISS_ID) _jws = ProviderConfigurationResponse( issuer=ISS_ID, authorization_endpoint='{}/authorization'.format(ISS_ID), jwks_uri='{}/jwks.json'.format(ISS_ID), response_types_supported=[ 'code', 'id_token', 'id_token token' ], subject_types_supported=['public'], id_token_signing_alg_values_supported=["RS256", "ES256"], token_endpoint='{}/token'.format(ISS_ID), registration_endpoint='{}/register'.format(ISS_ID)).to_json() rsps.add("GET", request_uri, body=_jws, status=200) rsps.add("GET", '{}/jwks.json'.format(ISS_ID), body=OP_KEYS.export_jwks_as_json(), status=200) issuer = self.rph.do_provider_info(client) # Calculating request so I can build a reasonable response self.rph.add_callbacks(client.service_context) # Publishing a JWKS instead of a JWKS_URI client.service_context.jwks_uri = '' client.service_context.jwks = client.service_context.keyjar.export_jwks( ) _req = client.service['registration'].construct_request() with responses.RequestsMock() as rsps: request_uri = client.service_context.get( 'provider_info')["registration_endpoint"] _jws = RegistrationResponse( client_id="client uno", client_secret="VerySecretAndLongEnough", **_req.to_dict()).to_json() rsps.add("POST", request_uri, body=_jws, status=200) self.rph.do_client_registration(client, ISS_ID) assert 'jwks' in client.service_context.get('registration_response')
def test_import_keys_url(self, httpserver): assert len(self.service_context.keyjar.get_issuer_keys('')) == 1 # One EC key for signing key_def = [{"type": "EC", "crv": "P-256", "use": ["sig"]}] keyjar = build_keyjar(key_def) httpserver.serve_content(keyjar.export_jwks_as_json()) keyspec = {'url': {'https://example.com': httpserver.url}} self.service_context.import_keys(keyspec) # Now there should be one belonging to https://example.com assert len( self.service_context.keyjar.get_issuer_keys( 'https://example.com')) == 1
def test_unpack_encrypted_response(self): # Add encryption key _kj = build_keyjar([{"type": "RSA", "use": ["enc"]}], owner='') # Own key jar gets the private key self.service.service_context.keyjar.import_jwks( _kj.export_jwks(private=True), issuer='client_id') # opponent gets the public key ISS_KEY.import_jwks(_kj.export_jwks(), issuer='client_id') resp = OpenIDSchema(sub='diana', given_name='Diana', family_name='krall', iss=ISS, aud='client_id') enckey = ISS_KEY.get_encrypt_key('rsa', owner='client_id') algspec = self.service.service_context.get_enc_alg_enc( self.service.service_name) enc_resp = resp.to_jwe(enckey, **algspec) _resp = self.service.parse_response(enc_resp, state='abcde', sformat='jwt') assert _resp
def test_private_key_jwt(self): # Own dynamic keys client_keyjar = build_keyjar(KEYDEFS) # The servers keys client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] _jwks = client_keyjar.export_jwks() endpoint_context.keyjar.import_jwks(_jwks, client_id) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="RS256") _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=request) assert authn_info["client_id"] == client_id assert "jwt" in authn_info
def test_private_key_jwt(): # Own dynamic keys client_keyjar = build_keyjar(KEYDEFS) # The servers keys client_keyjar[conf['issuer']] = KEYJAR.issuer_keys[''] _jwks = client_keyjar.export_jwks() endpoint_context.keyjar.import_jwks(_jwks, client_id) _jwt = JWT(client_keyjar, iss=client_id, sign_alg='RS256') _assertion = _jwt.pack({'aud': [conf['issuer']]}) request = { 'client_assertion': _assertion, 'client_assertion_type': JWT_BEARER } authn_info = PrivateKeyJWT(endpoint_context).verify(request) assert authn_info['client_id'] == client_id assert 'jwt' in authn_info
def get_jwks(private_path, keydefs, public_path): if os.path.isfile(private_path): _jwks = open(private_path, 'r').read() _kj = KeyJar() _kj.import_jwks(json.loads(_jwks), '') else: _kj = build_keyjar(keydefs)[1] jwks = _kj.export_jwks(private=True) head, tail = os.path.split(private_path) if not os.path.isdir(head): os.makedirs(head) fp = open(private_path, 'w') fp.write(json.dumps(jwks)) fp.close() jwks = _kj.export_jwks() # public part fp = open(public_path, 'w') fp.write(json.dumps(jwks)) fp.close() return _kj
def test_private_key_jwt(): # Own dynamic keys client_keyjar = build_keyjar(KEYDEFS) # The servers keys client_keyjar[conf["issuer"]] = KEYJAR.issuer_keys[""] _jwks = client_keyjar.export_jwks() endpoint_context.keyjar.import_jwks(_jwks, client_id) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="RS256") _assertion = _jwt.pack({"aud": [conf["issuer"]]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } authn_info = PrivateKeyJWT(endpoint_context).verify(request) assert authn_info["client_id"] == client_id assert "jwt" in authn_info
def test_remove_after(): # initial keyjar keyjar = build_keyjar(KEYDEFS) _old = [k.kid for k in keyjar.get_issuer_keys("") if k.kid] assert len(_old) == 2 keyjar.remove_after = 1 # rotate_keys = create new keys + make the old as inactive keyjar = rotate_keys(KEYDEFS, keyjar=keyjar) keyjar.remove_outdated(time.time() + 3600) _interm = [k.kid for k in keyjar.get_issuer_keys("") if k.kid] assert len(_interm) == 2 # The remainder are the new keys _new = [k.kid for k in keyjar.get_issuer_keys("") if k.kid] assert len(_new) == 2 # should not be any overlap between old and new assert set(_new).intersection(set(_old)) == set()
def get_signing_keys(eid, keydef, key_file): """ If the *key_file* file exists then read the keys from there, otherwise create the keys and store them a file with the name *key_file*. :param eid: The ID of the entity that the keys belongs to :param keydef: What keys to create :param key_file: A file name :return: A :py:class:`oidcmsg.key_jar.KeyJar` instance """ if os.path.isfile(key_file): kj = KeyJar() kj.import_jwks(json.loads(open(key_file, 'r').read()), eid) else: kj = build_keyjar(keydef)[1] # make it know under both names fp = open(key_file, 'w') fp.write(json.dumps(kj.export_jwks())) fp.close() kj.issuer_keys[eid] = kj.issuer_keys[''] return kj
def test_private_key_jwt_reusage_other_endpoint(self): # Own dynamic keys client_keyjar = build_keyjar(KEYDEFS) # The servers keys client_keyjar.import_jwks(KEYJAR.export_jwks(private=True), CONF["issuer"]) _jwks = client_keyjar.export_jwks() self.method.server_get("endpoint_context").keyjar.import_jwks( _jwks, client_id) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="RS256") _jwt.with_jti = True _assertion = _jwt.pack( {"aud": [self.method.server_get("endpoint", "token").full_path]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } # This should be OK assert self.method.is_usable(request=request) self.method.verify(request=request, endpoint=self.method.server_get( "endpoint", "token")) # This should NOT be OK with pytest.raises(NotForMe): self.method.verify(request, endpoint=self.method.server_get( "endpoint", "authorization")) # This should NOT be OK because this is the second time the token appears with pytest.raises(MultipleUsage): self.method.verify(request, endpoint=self.method.server_get( "endpoint", "token"))