def __setitem__(self, key, value): """ :param key: issuer ID :type: String :param value: Cryptographic keys that should be connected to to an issuer ID. :type value: KeyJar or a JWKS (JSON document) """ if not isinstance(value, KeyJar): kj = KeyJar() kj.import_jwks(value, issuer=key) value = kj else: _val = value.copy() _iss = list(_val.owners()) if _iss == ['']: _val.issuer_keys[key] = _val.issuer_keys[''] del _val.issuer_keys[''] elif len(_iss) == 1: if _iss[0] != key: _val.issuer_keys[key] = _val.issuer_keys[_iss[0]] del _val.issuer_keys[_iss[0]] else: raise ValueError('KeyJar contains to many issuers') value = _val self.bundle[key] = value
def test_dump_issuer_keys(self): kb = keybundle_from_local_file("file://%s/jwk.json" % BASE_PATH, "jwks", ["sig"]) assert len(kb) == 1 kj = KeyJar() kj.issuer_keys[""] = [kb] _jwks_dict = kj.export_jwks() _info = _jwks_dict['keys'][0] assert _info == { 'use': 'sig', 'e': 'AQAB', 'kty': 'RSA', 'alg': 'RS256', 'n': 'pKybs0WaHU_y4cHxWbm8Wzj66HtcyFn7Fh3n' '-99qTXu5yNa30MRYIYfSDwe9JVc1JUoGw41yq2StdGBJ40HxichjE' '-Yopfu3B58Q' 'lgJvToUbWD4gmTDGgMGxQxtv1En2yedaynQ73sDpIK-12JJDY55pvf' '-PCiSQ9OjxZLiVGKlClDus44_uv2370b9IN2JiEOF-a7JB' 'qaTEYLPpXaoKWDSnJNonr79tL0T7iuJmO1l705oO3Y0TQ' '-INLY6jnKG_RpsvyvGNnwP9pMvcP1phKsWZ10ofuuhJGRp8IxQL9Rfz' 'T87OvF0RBSO1U73h09YP-corWDsnKIi6TbzRpN5YDw', 'kid': 'abc' }
def verify(self, **kwargs): """ Verifies that an instance of this class adhers to the given restrictions. """ super(MetadataStatement, self).verify(**kwargs) if "signing_keys" in self: if 'signing_keys_uri' in self: raise VerificationError( 'You can only have one of "signing_keys" and ' '"signing_keys_uri" in a metadata statement') else: # signing_keys MUST be a JWKS kj = KeyJar() try: kj.import_jwks(self['signing_keys'], '') except Exception: raise VerificationError('"signing_keys" not a proper JWKS') if "metadata_statements" in self and "metadata_statement_uris" in self: s = set(self['metadata_statements'].keys()) t = set(self['metadata_statement_uris'].keys()) if s.intersection(t): raise VerificationError( 'You should not have the same key in "metadata_statements" ' 'and in "metadata_statement_uris"') return True
def test_jwt_pack_unpack_sym(): kj = KeyJar() kj.add_symmetric(owner='', key='client_secret', usage=['sig']) kj['https://fedop.example.org'] = kj[''] srv = JWT(kj, iss=issuer, sign_alg="HS256") payload = {'sub': 'sub2'} _jwt = srv.pack(payload=payload) info = srv.unpack(_jwt) assert info
def test_provider(self): provider_info = { "jwks_uri": "https://connect-op.herokuapp.com/jwks.json", } ks = KeyJar() ks.load_keys(provider_info, "https://connect-op.heroku.com") assert ks["https://connect-op.heroku.com"][0].keys()
def test_self_signed_jwks(): kj = KeyJar() kj.issuer_keys['abc'] = KEYJAR.issuer_keys[''] ssj = self_sign_jwks(kj, 'abc', kid='', lifetime=3600) assert ssj res = verify_self_signed_jwks(ssj) _kj = jwks_to_keyjar(res['jwks'], res['iss']) assert list(_kj.owners()) == ['abc'] assert len(_kj.get_signing_key('RSA',owner='abc')) == 2 assert len(_kj.get_signing_key('EC',owner='abc')) == 1
def read_jwks_file(jwks_file): """ Reads a file containing a JWKS and populates a oic.utils.keyio.KeyJar from it. :param jwks_file: file name of the JWKS file :return: A oic.utils.keyio.KeyJar instance """ _jwks = open(jwks_file, 'r').read() _kj = KeyJar() _kj.import_jwks(json.loads(_jwks), '') return _kj
def test_request_signed_by_signing_keys(): kj = KeyJar() kj.issuer_keys['abc'] = KEYJAR.issuer_keys[''] msreq = MetadataStatement(signing_keys=json.dumps(JWKS)) smsreq = request_signed_by_signing_keys(kj, msreq, 'abc', 3600) assert smsreq res = verify_request_signed_by_signing_keys(smsreq) assert set(res.keys()) == {'ms', 'iss'} assert res['iss'] == 'abc'
def as_keyjar(self): """ Convert a key bundle into a KeyJar instance. :return: An :py:class:`oic.utils.keyio.KeyJar` instance """ kj = KeyJar() for iss, k in self.bundle.items(): try: kj.issuer_keys[iss] = k.issuer_keys[iss] except KeyError: kj.issuer_keys[iss] = k.issuer_keys[''] return kj
def keyjar_from_metadata_statements(iss, msl): """ Builds a keyJar instance based on the information in the 'signing_keys' claims in a list of metadata statements. :param iss: Owner of the signing keys :param msl: List of :py:class:`MetadataStatement` instances. :return: A oic.utils.keyio.KeyJar instance """ keyjar = KeyJar() for ms in msl: keyjar.import_jwks(ms['signing_keys'], iss) return keyjar
def test_create_verify(): sign_keyjar = build_keyjar(KEYDEFS)[1] jb = make_jwks_bundle('https://example.com', ['fo0', 'fo1', 'fo2', 'fo3'], sign_keyjar, KEYDEFS) _jws = jb.create_signed_bundle() _jwks = sign_keyjar.export_jwks() kj = KeyJar() kj.import_jwks(_jwks, 'https://example.com') bundle = verify_signed_bundle(_jws, kj) assert bundle
def __init__(self, keys=None, source="", cache_time=300, verify_ssl=True, fileformat="jwk", keytype="RSA", keyusage=None, verify_keys=None): super(KeyBundle, self).__init__(keys=keys, source=source, cache_time=cache_time, verify_ssl=verify_ssl, fileformat=fileformat, keytype=keytype, keyusage=keyusage) if verify_keys is not None: if isinstance(verify_keys, KeyJar): self.verify_keys = verify_keys else: self.verify_keys = KeyJar() self.verify_keys.import_jwks(verify_keys, '')
def test_keyjar_eq(): kj1 = KeyJar() kj1.import_jwks(JWKS_SPO, '') kj2 = KeyJar() kj2.import_jwks(JWKS_SPO, '') assert kj1 == kj2
def oauth_post_parse_response(self, resp, cli_info, **kwargs): """ Deal with Provider Config Response :param resp: The provider info response :param cli_info: Information about the client/server session """ issuer = cli_info.issuer if "issuer" in resp: _pcr_issuer = resp["issuer"] if resp["issuer"].endswith("/"): if issuer.endswith("/"): _issuer = issuer else: _issuer = issuer + "/" else: if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer try: cli_info.allow['issuer_mismatch'] except KeyError: if _issuer != _pcr_issuer: raise OicCliError( "provider info issuer mismatch '%s' != '%s'" % (_issuer, _pcr_issuer)) else: # No prior knowledge _pcr_issuer = issuer cli_info.issuer = _pcr_issuer cli_info.provider_info = resp for key, val in resp.items(): if key.endswith("_endpoint"): for _srv in cli_info.service.values(): if _srv.endpoint_name == key: _srv.endpoint = val try: kj = cli_info.keyjar except KeyError: kj = KeyJar() kj.load_keys(resp, _pcr_issuer) cli_info.keyjar = kj
def jwks_to_keyjar(jwks, iss=''): """ Convert a JWKS to a KeyJar instance. :param jwks: String representation of a JWKS :return: A :py:class:`oic.utils.keyio.KeyJar` instance """ if not isinstance(jwks, dict): try: jwks = json.loads(jwks) except json.JSONDecodeError: raise ValueError('No proper JSON') kj = KeyJar() kj.import_jwks(jwks, issuer=iss) return kj
def __init__(self, ca_certs=None, client_authn_method=None, keyjar=None, verify_ssl=True, config=None, client_cert=None, httplib=None, services=None, service_factory=None): """ :param ca_certs: Certificates used to verify HTTPS certificates :param client_authn_method: Methods that this client can use to authenticate itself. It's a dictionary with method names as keys and method classes as values. :param verify_ssl: Whether the SSL certificate should be verified. :return: Client instance """ self.http = httplib or HTTPLib(ca_certs=ca_certs, verify_ssl=verify_ssl, client_cert=client_cert, keyjar=keyjar) if not keyjar: keyjar = KeyJar() self.events = None self.client_info = ClientInfo(keyjar, config=config) if self.client_info.client_id: self.client_id = self.client_info.client_id _cam = client_authn_method or CLIENT_AUTHN_METHOD self.service_factory = service_factory or service.factory _srvs = services or DEFAULT_SERVICES self.service = build_services(_srvs, self.service_factory, self.http, keyjar, _cam) self.client_info.service = self.service self.verify_ssl = verify_ssl
class KeyBundle(key_bundle.KeyBundle): def __init__(self, keys=None, source="", cache_time=300, verify_ssl=True, fileformat="jwk", keytype="RSA", keyusage=None, verify_keys=None): super(KeyBundle, self).__init__(keys=keys, source=source, cache_time=cache_time, verify_ssl=verify_ssl, fileformat=fileformat, keytype=keytype, keyusage=keyusage) if verify_keys is not None: if isinstance(verify_keys, KeyJar): self.verify_keys = verify_keys else: self.verify_keys = KeyJar() self.verify_keys.import_jwks(verify_keys, '') def _parse_remote_response(self, response): """ Parse simple JWKS or signed JWKS from the HTTP response. :param response: HTTP response from the 'jwks_uri' or 'signed_jwks_uri' endpoint :return: response parsed as JSON """ # Check if the content type is the right one. try: if response.headers["Content-Type"] == 'application/json': logger.debug( "Loaded JWKS: %s from %s" % (response.text, self.source)) try: return json.loads(response.text) except ValueError: return None elif response.headers["Content-Type"] == 'application/jose': logger.debug( "Signed JWKS: %s from %s" % (response.text, self.source)) _jws = factory(response.text) _resp = _jws.verify_compact( response.text, keys=self.verify_keys.get_signing_key()) return _resp else: logger.error('Wrong content type: {}'.format( response.headers['Content-Type'])) return None except KeyError: pass
def public_jwks_bundle(jwks_bundle): jb_copy = JWKSBundle('') for fo, kj in jwks_bundle.bundle.items(): kj_copy = KeyJar() for owner in kj.owners(): public_keys_keyjar(kj, owner, kj_copy, owner) jb_copy.bundle[fo] = kj_copy return jb_copy
def test_copy(): kj = KeyJar() kj['A'] = [KeyBundle(JWK0['keys'])] kj['B'] = [KeyBundle(JWK1['keys'])] kj['C'] = [KeyBundle(JWK2['keys'])] kjc = kj.copy() assert set(kjc.owners()) == {'A', 'B', 'C'} assert len(kjc.get('sig', 'oct', 'A')) == 0 assert len(kjc.get('sig', 'rsa', 'A')) == 1 assert len(kjc.get('sig', 'oct', 'B')) == 1 assert len(kjc.get('sig', 'rsa', 'B')) == 1 assert len(kjc.get('sig', 'oct', 'C')) == 0 assert len(kjc.get('sig', 'rsa', 'C')) == 4
def test_get_signing_key_use_undefined(): kj = KeyJar() kj.import_jwks(JWK1, '') keys = kj.get_signing_key(kid='rsa1') assert len(keys) == 1 keys = kj.get_signing_key(key_type='rsa') assert len(keys) == 1 keys = kj.get_signing_key(key_type='rsa', kid='rsa1') assert len(keys) == 1
def setup(self): mkey = [ { "type": "RSA", "use": ["sig"] }, { "type": "RSA", "use": ["sig"] }, { "type": "RSA", "use": ["sig"] }, ] skey = [ { "type": "RSA", "use": ["sig"] }, ] kj1 = build_keyjar(mkey)[1] kj2 = build_keyjar(skey)[1] self.keyjar = KeyJar() self.keyjar['A'] = kj1[''] self.keyjar['B'] = kj2[''] _jws = JWS('{"aud": "A"}', alg='RS256') sig_key = self.keyjar.get_signing_key('rsa', owner='A')[0] self.sjwt_a = _jws.sign_compact([sig_key]) _jws = JWS('{"aud": "B"}', alg='RS256') sig_key = self.keyjar.get_signing_key('rsa', owner='B')[0] self.sjwt_b = _jws.sign_compact([sig_key])
def test_items(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, "der", ["ver", "sig"])) assert len(ks.items()) == 2
def test_issuer_missing_slash(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, "der", ["ver", "sig"])) assert ks.get('sig', 'RSA', 'http://www.example.org')
def test_get_enc_not_mine(self): ks = KeyJar() ks[""] = KeyBundle([{ "kty": "oct", "key": "a1b2c3d4", "use": "sig" }, { "kty": "oct", "key": "a1b2c3d4", "use": "enc" }]) 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, "der", ["ver", "sig"])) assert ks.get('enc', 'oct', 'http://www.example.org/')
def __init__(self, ca_certs=None, verify_ssl=True, keyjar=None, client_cert=None): """ A base class for OAuth2 clients and servers :param ca_certs: the path to a CA_BUNDLE file or directory with certificates of trusted CAs :param verify_ssl: If True then the server SSL certificate is not verfied :param keyjar: A place to keep keys for signing/encrypting messages :param client_cert: local cert to use as client side certificate, as a single file (containing the private key and the certificate) or as a tuple of both file's path """ self.keyjar = keyjar or KeyJar(verify_ssl=verify_ssl) self.request_args = {"allow_redirects": False} self.cookiejar = FileCookieJar() self.ca_certs = ca_certs if ca_certs: if verify_ssl is False: raise ValueError( 'conflict: ca_certs defined, but verify_ssl is False') # Instruct requests to verify certificate against the CA cert # bundle located at the path given by `ca_certs`. self.request_args["verify"] = ca_certs elif verify_ssl: # Instruct requests to verify server certificates against the # default CA bundle provided by 'certifi'. See # http://docs.python-requests.org/en/master/user/advanced/#ca # -certificates self.request_args["verify"] = True else: # Instruct requests to not perform server cert verification. self.request_args["verify"] = False self.events = None self.req_callback = None if client_cert: self.request_args['cert'] = client_cert
def own_sign_keys(sigkey_name, issuer, sig_def_keys): try: jwks = json.loads(open(sigkey_name, 'r').read()) sign_kj = KeyJar() sign_kj.import_jwks(jwks, issuer) except FileNotFoundError: jwks, sign_kj, _ = build_keyjar(sig_def_keys) sign_kj.issuer_keys[issuer] = sign_kj.issuer_keys[''] fp = open(sigkey_name, 'w') fp.write(json.dumps(sign_kj.export_jwks(private=True, issuer=issuer))) fp.close() return sign_kj
def test_key_export(): kj = KeyJar() url = key_export("http://example.com/keys/", "outbound", "secret", keyjar=kj, sig={ "alg": "rsa", "format": ["x509", "jwks"] }) 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 loads(self, jstr): """ Upload a bundle from an unsigned JSON document :param jstr: A bundle as a dictionary or a JSON document """ if isinstance(jstr, dict): _info = jstr else: _info = json.loads(jstr) for iss, jwks in _info.items(): kj = KeyJar() if isinstance(jwks, dict): kj.import_jwks(jwks, issuer=iss) else: kj.import_jwks_as_json(jwks, issuer=iss) self.bundle[iss] = kj return self
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:`oic.utils.keyio.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
class TestVerifyJWTKeys(object): @pytest.fixture(autouse=True) def setup(self): mkey = [ { "type": "RSA", "use": ["sig"] }, { "type": "RSA", "use": ["sig"] }, { "type": "RSA", "use": ["sig"] }, ] skey = [ { "type": "RSA", "use": ["sig"] }, ] kj1 = build_keyjar(mkey)[1] kj2 = build_keyjar(skey)[1] self.keyjar = KeyJar() self.keyjar['A'] = kj1[''] self.keyjar['B'] = kj2[''] _jws = JWS('{"aud": "A"}', alg='RS256') sig_key = self.keyjar.get_signing_key('rsa', owner='A')[0] self.sjwt_a = _jws.sign_compact([sig_key]) _jws = JWS('{"aud": "B"}', alg='RS256') sig_key = self.keyjar.get_signing_key('rsa', owner='B')[0] self.sjwt_b = _jws.sign_compact([sig_key]) def test_no_kid_multiple_keys(self): """ This is extremely strict """ _jwt = factory(self.sjwt_a) # remove kid reference _jwt.jwt.headers['kid'] = '' keys = self.keyjar.get_jwt_verify_keys(_jwt.jwt) assert len(keys) == 0 def test_no_kid_single_key(self): _jwt = factory(self.sjwt_b) _jwt.jwt.headers['kid'] = '' keys = self.keyjar.get_jwt_verify_keys(_jwt.jwt) assert len(keys) == 1 def test_no_kid_multiple_keys_no_kid_issuer(self): a_kids = [ k.kid for k in self.keyjar.get_verify_key(owner='A', key_type='RSA') ] no_kid_issuer = {'A': a_kids} _jwt = factory(self.sjwt_a) _jwt.jwt.headers['kid'] = '' keys = self.keyjar.get_jwt_verify_keys(_jwt.jwt, no_kid_issuer=no_kid_issuer) assert len(keys) == 3 assert set([k.kid for k in keys]) == set(a_kids) def test_no_kid_multiple_keys_no_kid_issuer_lim(self): a_kids = [ k.kid for k in self.keyjar.get_verify_key(owner='A', key_type='RSA') ] no_kid_issuer = {'A': []} _jwt = factory(self.sjwt_a) _jwt.jwt.headers['kid'] = '' keys = self.keyjar.get_jwt_verify_keys(_jwt.jwt, no_kid_issuer=no_kid_issuer) assert len(keys) == 3 assert set([k.kid for k in keys]) == set(a_kids) def test_matching_kid(self): _jwt = factory(self.sjwt_b) keys = self.keyjar.get_jwt_verify_keys(_jwt.jwt) assert len(keys) == 1 def test_no_matching_kid(self): _jwt = factory(self.sjwt_b) _jwt.jwt.headers['kid'] = 'abcdef' keys = self.keyjar.get_jwt_verify_keys(_jwt.jwt) assert keys == [] def test_aud(self): self.keyjar.import_jwks(JWK1, issuer='D') _jws = JWS('{"iss": "D", "aud": "A"}', alg='HS256') sig_key = self.keyjar.get_signing_key('oct', owner='D')[0] _sjwt = _jws.sign_compact([sig_key]) no_kid_issuer = {'D': []} _jwt = factory(_sjwt) keys = self.keyjar.get_jwt_verify_keys(_jwt.jwt, no_kid_issuer=no_kid_issuer) assert len(keys) == 1