def verify(self, request, **kwargs): _jwt = JWT(self.endpoint_context.keyjar) try: ca_jwt = _jwt.unpack(request["client_assertion"]) except (Invalid, MissingKey, BadSignature) as err: logger.info("%s" % sanitize(err)) raise AuthnFailure("Could not verify client_assertion.") try: logger.debug("authntoken: %s" % sanitize(ca_jwt.to_dict())) except AttributeError: logger.debug("authntoken: %s" % sanitize(ca_jwt)) request[verified_claim_name("client_assertion")] = ca_jwt try: client_id = kwargs["client_id"] except KeyError: client_id = ca_jwt["iss"] # I should be among the audience # could be either my issuer id or the token endpoint if self.endpoint_context.issuer in ca_jwt["aud"]: pass elif self.endpoint_context.endpoint["token"].full_path in ca_jwt[ "aud"]: pass else: raise NotForMe("Not for me!") return {"client_id": client_id, "jwt": ca_jwt}
def test_sign_encrypt_id_token(self): client_info = RegistrationResponse( id_token_signed_response_alg="RS512", client_id="client_1") session_info = { "authn_req": AREQN, "sub": "sub", "authn_event": { "authn_info": "loa2", "authn_time": time.time() }, } self.endpoint_context.jwx_def["signing_alg"] = {"id_token": "RS384"} self.endpoint_context.cdb["client_1"] = client_info.to_dict() _token = self.endpoint_context.idtoken.sign_encrypt(session_info, "client_1", sign=True) assert _token _jws = jws.factory(_token) assert _jws.jwt.headers["alg"] == "RS512" client_keyjar = KeyJar() _jwks = self.endpoint_context.keyjar.export_jwks() client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) _jwt = JWT(key_jar=client_keyjar, iss="client_1") res = _jwt.unpack(_token) assert isinstance(res, dict) assert res["aud"] == ["client_1"]
def test_client_claims_with_default(self): session_info = { "authn_req": AREQN, "sub": "sub", "authn_event": { "authn_info": "loa2", "authn_time": time.time(), "uid": "diana" }, } self.endpoint_context.cdb["client_1"]['id_token_claims'] = { "address": None } self.endpoint_context.idtoken.kwargs['default_claims'] = { "nickname": { "essential": True } } self.endpoint_context.idtoken.enable_claims_per_client = True req = {"client_id": "client_1"} _token = self.endpoint_context.idtoken.make(req, session_info) assert _token client_keyjar = KeyJar() _jwks = self.endpoint_context.keyjar.export_jwks() client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) _jwt = JWT(key_jar=client_keyjar, iss="client_1") res = _jwt.unpack(_token) assert "address" in res assert "nickname" in res
def test_sign_encrypt_id_token(): client_info = RegistrationResponse(id_token_signed_response_alg='RS512', client_id='client_1') session_info = { 'authn_req': AREQN, 'sub': 'sub', 'authn_event': { "authn_info": 'loa2', "authn_time": time.time() } } ENDPOINT_CONTEXT.jwx_def["signing_alg"] = {'id_token': 'RS384'} ENDPOINT_CONTEXT.cdb['client_1'] = client_info.to_dict() _token = sign_encrypt_id_token(ENDPOINT_CONTEXT, session_info, 'client_1', sign=True) assert _token _jws = jws.factory(_token) assert _jws.jwt.headers['alg'] == 'RS512' client_keyjar = KeyJar() _jwks = KEYJAR.export_jwks() client_keyjar.import_jwks(_jwks, ENDPOINT_CONTEXT.issuer) _jwt = JWT(key_jar=client_keyjar, iss='client_1') res = _jwt.unpack(_token) assert isinstance(res, dict) assert res['aud'] == ['client_1']
class EncryptedJWTFactory(EncryptedTokenFactory): key_jar: KeyJar def __init__(self, key_jar: KeyJar = None, encrypt: bool = True, sign: bool = True, **kwargs): if encrypt is not True: raise RuntimeError('JWT encryption is mandatory.') self.key_jar = key_jar if key_jar is not None else KeyJar() self._jwt = JWT(key_jar=self.key_jar, encrypt=encrypt, sign=sign, **kwargs) @property def issuer_id(self): return self._jwt.iss @classmethod 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 generate(self, payload: dict, subject: str = 'token', recv: typing.Optional[str] = None) -> str: if recv is None: recv = self._jwt.iss token = self._jwt.pack( recv=recv, payload={ "sub": subject, "data": payload }, encrypt=True, ) return token def decrypt(self, token: str) -> dict: info = self._jwt.unpack(token) if self._jwt.lifetime: now = utc_time_sans_frac() if 'iat' not in info or 'exp' not in info: raise MissingExpirationHeader() if now < info['iat']: raise NotYet() if now > info['exp']: raise Expired() return info.get('data')
class Challenger(object): def __init__(self, kb: KeyBundle, issuer: str, lifetime: int) -> None: self.logger = logging.getLogger(__name__).getChild( self.__class__.__name__) self.issuer = issuer self.kj = KeyJar() self.kj.add_kb(self.issuer, kb) sign_alg = None enc_alg = None enc_enc = None kid = None for key in self.kj.get(key_use="enc", issuer_id=issuer): kid = key.kid if key.kty == "oct": sign_alg = "HS256" enc_alg = None enc_enc = None break elif key.kty == "EC": sign_alg = "ES256" enc_alg = "ECDH-ES" enc_enc = "A128GCM" break elif key.kty == "RSA": sign_alg = "RS256" enc_alg = "RSA1_5" enc_enc = "A128GCM" break if kid is None: raise Exception("No challenge key found") sign = sign_alg is not None encrypt = enc_alg is not None self.logger.info("Using challenge key kid=%s sign=%s encrypt=%s", kid, sign, encrypt) self.jwt = JWT( key_jar=self.kj, iss=self.issuer, lifetime=lifetime, sign=sign, sign_alg=sign_alg, encrypt=encrypt, enc_alg=enc_alg, enc_enc=enc_enc, ) def issue_bytes(self, payload: dict) -> bytes: """Issue token challenge""" return str(self.jwt.pack(payload=payload, recv=self.issuer)).encode() def verify_bytes(self, challenge: bytes) -> dict: """Verify challenge and return payload""" token = challenge.decode() payload = self.jwt.unpack(token) now = time.time() if "nbf" in payload and now < int(payload.get("nbf")): raise BadSignature("Token not yet valid (t < nbf)") if "exp" in payload and now >= int(payload.get("exp")): raise BadSignature("Token expired (t >= exp)") return dict(payload)
def _do_jwt(self, info): args = {'allowed_sign_algs': self.service_context.get_sign_alg(self.service_name)} enc_algs = self.service_context.get_enc_alg_enc(self.service_name) args['allowed_enc_algs'] = enc_algs['alg'] args['allowed_enc_encs'] = enc_algs['enc'] _jwt = JWT(key_jar=self.service_context.keyjar, **args) _jwt.iss = self.service_context.get('client_id') return _jwt.unpack(info)
def verify_jwt( jwt: JWT, token: str, required_claims: ClaimsRequired = {}, max_lifetime: Optional[int] = None, max_timeskew: int = 0, enforce_lifetime: bool = True, ) -> dict: """Authenticate API request using JWK""" logger = _logger.getChild("verify_jwt") try: payload = jwt.unpack(token) except BadSyntax: raise BadRequest("Invalid token syntax") except BadSignature: raise Unauthorized("Invalid token signature") except IssuerNotFound as exc: raise Unauthorized("Issuer not found: " + str(exc)) except NoSuitableSigningKeys as exc: logger.warning("Unknown token issuer", extra={ "jwt": token, "error": str(exc) }) raise Unauthorized("Unknown token issuer") logger.debug("Verified token payload: %s", payload, extra={"jwt_payload": payload}) now = int(time.time()) if max_lifetime is not None and max_lifetime < 0: raise ValueError("Negative max_lifetime") if max_timeskew is not None and max_timeskew < 0: raise ValueError("Negative max_timeskew") if "nbf" in payload: nbf = int(payload["nbf"]) - max_timeskew if now < nbf: raise Unauthorized("Token not yet valid (t < nbf)") if "exp" in payload: exp = int(payload["exp"]) delta = exp - now if max_lifetime is not None and delta > max_lifetime: if enforce_lifetime: logger.warning( "Token expire exceeded, truncated (max %d, got %d)", max_lifetime, delta, ) exp = now + max_lifetime else: logger.warning( "Token expire exceeded, not enforced (max %d, got %d)", max_lifetime, delta, ) if now >= exp: raise Unauthorized("Token expired (t >= exp)") ensure_claims(payload, required_claims) return dict(payload)
def test_jwt_pack_encrypt(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE) payload = {"sub": "sub", "aud": BOB} _jwt = alice.pack(payload=payload, encrypt=True, recv=BOB) bob = JWT(key_jar=BOB_KEY_JAR, iss=BOB) info = bob.unpack(_jwt) assert set(info.keys()) == {"iat", "iss", "sub", "aud"}
def test_jwt_pack_and_unpack_with_lifetime(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, lifetime=600) payload = {'sub': 'sub'} _jwt = alice.pack(payload=payload) bob = JWT(key_jar=BOB_KEY_JAR, iss=BOB) info = bob.unpack(_jwt) assert set(info.keys()) == {'iat', 'iss', 'sub', 'exp'}
def test_jwt_pack_unpack_sym(): _sym_key = SYMKey(key='hemligt ord', use='sig') alice = JWT(own_keys=[_sym_key], iss=ALICE, sign_alg="HS256") payload = {'sub': 'sub2'} _jwt = alice.pack(payload=payload) bob = JWT(own_keys=None, iss=BOB, rec_keys={ALICE: [_sym_key]}) info = bob.unpack(_jwt) assert info
def test_jwt_pack_encrypt(): alice = JWT(own_keys=ALICE_KEYS, iss=ALICE, rec_keys={BOB: BOB_PUB_KEYS}) payload = {'sub': 'sub', 'aud': BOB} _jwt = alice.pack(payload=payload, encrypt=True, recv=BOB) bob = JWT(own_keys=BOB_KEYS, iss=BOB, rec_keys={ALICE: ALICE_PUB_KEYS}) info = bob.unpack(_jwt) assert set(info.keys()) == {'iat', 'iss', 'sub', 'kid', 'aud'}
def test_jwt_pack_and_unpack_with_lifetime(): alice = JWT(own_keys=ALICE_KEYS, iss=ALICE, lifetime=600) payload = {'sub': 'sub'} _jwt = alice.pack(payload=payload) bob = JWT(own_keys=BOB_KEYS, iss=BOB, rec_keys={ALICE: ALICE_PUB_KEYS}) info = bob.unpack(_jwt) assert set(info.keys()) == {'iat', 'iss', 'sub', 'kid', 'exp', 'aud'}
def test_jwt_pack_encrypt(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE) payload = {'sub': 'sub', 'aud': BOB} _jwt = alice.pack(payload=payload, encrypt=True, recv=BOB) bob = JWT(key_jar=BOB_KEY_JAR, iss=BOB) info = bob.unpack(_jwt) assert set(info.keys()) == {'iat', 'iss', 'sub', 'aud'}
def test_jwt_pack_and_unpack(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg='RS256') payload = {'sub': 'sub'} _jwt = alice.pack(payload=payload) bob = JWT(key_jar=BOB_KEY_JAR, iss=BOB, allowed_sign_algs=["RS256"]) info = bob.unpack(_jwt) assert set(info.keys()) == {'iat', 'iss', 'sub'}
def test_jwt_pack_and_unpack_with_alg(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg="RS384") payload = {"sub": "sub"} _jwt = alice.pack(payload=payload) bob = JWT(BOB_KEY_JAR, sign_alg="RS384") info = bob.unpack(_jwt) assert set(info.keys()) == {"iat", "iss", "sub"}
def test_jwt_pack_and_unpack_with_alg(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg='RS384') payload = {'sub': 'sub'} _jwt = alice.pack(payload=payload) bob = JWT(BOB_KEY_JAR, sign_alg='RS384') info = bob.unpack(_jwt) assert set(info.keys()) == {'iat', 'iss', 'sub'}
def test_jwt_pack_and_unpack_with_lifetime(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, lifetime=600) payload = {"sub": "sub"} _jwt = alice.pack(payload=payload) bob = JWT(key_jar=BOB_KEY_JAR, iss=BOB) info = bob.unpack(_jwt) assert set(info.keys()) == {"iat", "iss", "sub", "exp"}
def test_jwt_pack_and_unpack_unknown_issuer(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg="RS256") payload = {"sub": "sub"} _jwt = alice.pack(payload=payload) kj = KeyJar() bob = JWT(key_jar=kj, iss=BOB, allowed_sign_algs=["RS256"]) with pytest.raises(IssuerNotFound): info = bob.unpack(_jwt)
def test_jwt_pack_and_unpack(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg="RS256") payload = {"sub": "sub"} _jwt = alice.pack(payload=payload) bob = JWT(key_jar=BOB_KEY_JAR, iss=BOB, allowed_sign_algs=["RS256"]) info = bob.unpack(_jwt) assert set(info.keys()) == {"iat", "iss", "sub"}
def test_jwt_pack_and_unpack_unknown_key(): alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg="RS256") payload = {"sub": "sub"} _jwt = alice.pack(payload=payload) kj = KeyJar() kj.add_kb(ALICE, KeyBundle()) bob = JWT(key_jar=kj, iss=BOB, allowed_sign_algs=["RS256"]) with pytest.raises(NoSuitableSigningKeys): info = bob.unpack(_jwt)
def verify_signed_bundle(signed_bundle, ver_keys): """ Verify the signature of a signed JWT. :param signed_bundle: A signed JWT where the body is a JWKS bundle :param ver_keys: Keys that can be used to verify signatures of the signed_bundle. :type ver_keys: A :py:class:`oidcmsg.key_jar.KeyJar` instance :return: The bundle or None """ _jwt = JWT(ver_keys) return _jwt.unpack(signed_bundle)
def test_parse(self): session_id = setup_session( self.endpoint.endpoint_context, AUTH_REQ, uid="diana" ) _dic = self.endpoint.endpoint_context.sdb.upgrade_to_token(key=session_id) _verifier = JWT(self.endpoint.endpoint_context.keyjar) _info = _verifier.unpack(_dic["access_token"]) assert _info["ttype"] == "T" assert _info["phone_number"] == "+46907865000" assert set(_info["aud"]) == {"client_1", "https://example.org/appl"}
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_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 verify(self, request, key_type, **kwargs): _context = self.server_get("endpoint_context") _jwt = JWT(_context.keyjar, msg_cls=JsonWebToken) try: ca_jwt = _jwt.unpack(request["client_assertion"]) except (Invalid, MissingKey, BadSignature) as err: logger.info("%s" % sanitize(err)) raise AuthnFailure("Could not verify client_assertion.") _sign_alg = ca_jwt.jws_header.get("alg") if _sign_alg and _sign_alg.startswith("HS"): if key_type == "private_key": raise AttributeError("Wrong key type") keys = _context.keyjar.get("sig", "oct", ca_jwt["iss"], ca_jwt.jws_header.get("kid")) _secret = _context.cdb[ca_jwt["iss"]].get("client_secret") if _secret and keys[0].key != as_bytes(_secret): raise AttributeError( "Oct key used for signing not client_secret") else: if key_type == "client_secret": raise AttributeError("Wrong key type") authtoken = sanitize(ca_jwt.to_dict()) logger.debug("authntoken: {}".format(authtoken)) _endpoint = kwargs.get("endpoint") if _endpoint is None or not _endpoint: if _context.issuer in ca_jwt["aud"]: pass else: raise NotForMe("Not for me!") else: if set(ca_jwt["aud"]).intersection( _endpoint.allowed_target_uris()): pass else: raise NotForMe("Not for me!") # If there is a jti use it to make sure one-time usage is true _jti = ca_jwt.get("jti") if _jti: _key = "{}:{}".format(ca_jwt["iss"], _jti) if _key in _context.jti_db: raise MultipleUsage("Have seen this token once before") else: _context.jti_db[_key] = utc_time_sans_frac() request[verified_claim_name("client_assertion")] = ca_jwt client_id = kwargs.get("client_id") or ca_jwt["iss"] return {"client_id": client_id, "jwt": ca_jwt}
def test_no_default_claims(self): session_info = { "authn_req": AREQN, "sub": "sub", "authn_event": { "authn_info": "loa2", "authn_time": time.time(), "uid": "diana" }, } req = {"client_id": "client_1"} _token = self.endpoint_context.idtoken.make(req, session_info) assert _token client_keyjar = KeyJar() _jwks = self.endpoint_context.keyjar.export_jwks() client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) _jwt = JWT(key_jar=client_keyjar, iss="client_1") res = _jwt.unpack(_token) assert "nickname" not in res
def test_parse(self): session_id = self._create_session(AUTH_REQ) # apply consent grant = self.endpoint_context.authz(session_id=session_id, request=AUTH_REQ) # grant = self.session_manager[session_id] code = self._mint_token("authorization_code", grant, session_id) access_token = self._mint_token("access_token", grant, session_id, code, resources=[AUTH_REQ["client_id"]]) _verifier = JWT(self.endpoint_context.keyjar) _info = _verifier.unpack(access_token.value) assert _info["token_class"] == "access_token" # assert _info["eduperson_scoped_affiliation"] == ["*****@*****.**"] assert set(_info["aud"]) == {"client_1"}
def test_enable_claims_per_client(self, enable_claims_per_client): # Set up configuration self.endpoint_context.cdb["client_1"]["access_token_claims"] = { "address": None } self.endpoint_context.session_manager.token_handler.handler[ "access_token"].kwargs[ "enable_claims_per_client"] = enable_claims_per_client session_id = self._create_session(AUTH_REQ) # apply consent grant = self.endpoint_context.authz(session_id=session_id, request=AUTH_REQ) # code = self._mint_token("authorization_code", grant, session_id) access_token = self._mint_token("access_token", grant, session_id, code) _jwt = JWT(key_jar=KEYJAR, iss="client_1") res = _jwt.unpack(access_token.value) assert enable_claims_per_client is ("address" in res)