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 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_verify_client_jws_authn_method(self): client_keyjar = KeyJar() client_keyjar.import_jwks(KEYJAR.export_jwks(private=True), CONF["issuer"]) # The only own key the client has a this point client_keyjar.add_symmetric("", client_secret, ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") # Audience is OP issuer ID aud = "{}token".format(CONF["issuer"]) # aud == Token endpoint _assertion = _jwt.pack({"aud": [aud]}) request = { "client_assertion": _assertion, "client_assertion_type": JWT_BEARER } http_info = {"headers": {}} res = verify_client( self.endpoint_context, request, http_info=http_info, endpoint=self.server.server_get("endpoint", "token"), ) assert res["method"] == "client_secret_jwt" assert res["client_id"] == "client_id"
def make_openid_request(arq, keys, issuer, request_object_signing_alg, recv, with_jti=False, lifetime=0): """ Construct the JWT to be passed by value (the request parameter) or by reference (request_uri). The request will be signed :param arq: The Authorization request :param keys: Keys to use for signing/encrypting. A KeyJar instance :param issuer: Who is signing this JSON Web Token :param request_object_signing_alg: Which signing algorithm to use :param recv: The intended receiver of the request :param with_jti: Whether a JTI should be included in the JWT. :param lifetime: How long the JWT is expect to be live. :return: JWT encoded OpenID request """ _jwt = JWT(key_jar=keys, iss=issuer, sign_alg=request_object_signing_alg) if with_jti: _jwt.with_jti = True if lifetime: _jwt.lifetime = lifetime return _jwt.pack(arq.to_dict(), owner=issuer, recv=recv)
def do_back_channel_logout(self, cinfo, sid): """ :param cinfo: Client information :param sid: The session ID :return: Tuple with logout URI and signed logout token """ _context = self.server_get("endpoint_context") try: back_channel_logout_uri = cinfo["backchannel_logout_uri"] except KeyError: return None # Create the logout token # always include sub and sid so I don't check for # backchannel_logout_session_required enc_msg = self._encrypt_sid(sid) payload = {"sid": enc_msg, "events": {BACK_CHANNEL_LOGOUT_EVENT: {}}} try: alg = cinfo["id_token_signed_response_alg"] except KeyError: alg = _context.provider_info["id_token_signing_alg_values_supported"][0] _jws = JWT(_context.keyjar, iss=_context.issuer, lifetime=86400, sign_alg=alg) _jws.with_jti = True _logout_token = _jws.pack(payload=payload, recv=cinfo["client_id"]) return back_channel_logout_uri, _logout_token
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 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_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_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_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_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_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_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 do_response(self, response_args=None, request=None, client_id="", **kwargs): if "error" in kwargs and kwargs["error"]: return Endpoint.do_response(self, response_args, request, **kwargs) _context = self.endpoint_context if not client_id: raise MissingValue("client_id") # Should I return a JSON or a JWT ? _cinfo = _context.cdb[client_id] # default is not to sign or encrypt try: sign_alg = _cinfo["userinfo_signed_response_alg"] sign = True except KeyError: sign_alg = "" sign = False try: enc_enc = _cinfo["userinfo_encrypted_response_enc"] enc_alg = _cinfo["userinfo_encrypted_response_alg"] encrypt = True except KeyError: encrypt = False enc_alg = enc_enc = "" if encrypt or sign: _jwt = JWT( self.endpoint_context.keyjar, iss=self.endpoint_context.issuer, sign=sign, sign_alg=sign_alg, encrypt=encrypt, enc_enc=enc_enc, enc_alg=enc_alg, ) resp = _jwt.pack(response_args, recv=client_id) content_type = "application/jwt" else: if isinstance(response_args, dict): resp = json.dumps(response_args) else: resp = response_args.to_json() content_type = "application/json" http_headers = [("Content-type", content_type)] http_headers.extend(OAUTH2_NOCACHE_HEADERS) return {"response": resp, "http_headers": http_headers}
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 sign_encrypt( self, session_info, client_id, code=None, access_token=None, user_info=None, sign=True, encrypt=False, lifetime=None, extra_claims=None, ): """ Signed and or encrypt a IDToken :param session_info: Session information :param client_id: Client ID :param code: Access grant :param access_token: Access Token :param user_info: User information :param sign: If the JWT should be signed :param encrypt: If the JWT should be encrypted :param extra_claims: Extra claims to be added to the ID Token :return: IDToken as a signed and/or encrypted JWT """ _cntx = self.endpoint_context client_info = _cntx.cdb[client_id] alg_dict = get_sign_and_encrypt_algorithms(_cntx, client_info, "id_token", sign=sign, encrypt=encrypt) _authn_event = session_info["authn_event"] _idt_info = self.payload( session_info, acr=_authn_event["authn_info"], alg=alg_dict["sign_alg"], code=code, access_token=access_token, user_info=user_info, auth_time=_authn_event["authn_time"], lifetime=lifetime, extra_claims=extra_claims, ) _jwt = JWT(_cntx.keyjar, iss=_cntx.issuer, lifetime=_idt_info["lifetime"], **alg_dict) return _jwt.pack(_idt_info["payload"], recv=client_id)
def create_entity_statement(iss, sub, key_jar, metadata=None, metadata_policy=None, authority_hints=None, lifetime=86400, aud='', include_jwks=True, constraints=None, **kwargs): """ :param iss: The issuer of the signed JSON Web Token :param sub: The subject which the metadata describes :param key_jar: A KeyJar instance :param metadata: The entity's metadata organised as a dictionary with the entity type as key :param metadata_policy: Metadata policy :param authority_hints: A dictionary with immediate superiors in the trust chains as keys and lists of identifier of trust roots as values. :param lifetime: The life time of the signed JWT. :param aud: Possible audience for the JWT :param include_jwks: Add JWKS :param constraints: A dictionary with constraints. :return: A signed JSON Web Token """ msg = {'sub': sub} if metadata: msg['metadata'] = metadata if metadata_policy: msg['metadata_policy'] = metadata_policy if authority_hints: msg['authority_hints'] = authority_hints if aud: msg['aud'] = aud if constraints: msg['constraints'] = constraints if kwargs: msg.update(kwargs) if include_jwks: # The public signing keys of the subject msg['jwks'] = key_jar.export_jwks(issuer_id=sub) packer = JWT(key_jar=key_jar, iss=iss, lifetime=lifetime) return packer.pack(payload=msg)
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 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_jws_authn_method_wrong_key(self): client_keyjar = KeyJar() client_keyjar[CONF["issuer"]] = KEYJAR.issuer_keys[""] # Fake symmetric key client_keyjar.add_symmetric("", "client_secret:client_secret", ["sig"]) _jwt = JWT(client_keyjar, iss=client_id, sign_alg="HS256") _assertion = _jwt.pack({"aud": [CONF["issuer"]]}) request = {"client_assertion": _assertion, "client_assertion_type": JWT_BEARER} with pytest.raises(NoSuitableSigningKeys): self.method.verify(request=request, key_type='private_key')
def sign_encrypt( self, session_id, client_id, code=None, access_token=None, sign=True, encrypt=False, lifetime=None, extra_claims=None, ) -> str: """ Signed and or encrypt a IDToken :param lifetime: How long the ID Token should be valid :param session_id: Session information :param client_id: Client ID :param code: Access grant :param access_token: Access Token :param sign: If the JWT should be signed :param encrypt: If the JWT should be encrypted :param extra_claims: Extra claims to be added to the ID Token :return: IDToken as a signed and/or encrypted JWT """ _context = self.server_get("endpoint_context") client_info = _context.cdb[client_id] alg_dict = get_sign_and_encrypt_algorithms(_context, client_info, "id_token", sign=sign, encrypt=encrypt) _payload = self.payload( session_id=session_id, alg=alg_dict["sign_alg"], code=code, access_token=access_token, extra_claims=extra_claims, ) if lifetime is None: lifetime = self.lifetime _jwt = JWT(_context.keyjar, iss=_context.issuer, lifetime=lifetime, **alg_dict) return _jwt.pack(_payload, recv=client_id)
def pack(self, req, receiver='', iss='', lifetime=0, sign=True, sign_alg='', encrypt=False, enc_enc="A128CBC-HS256", enc_alg="RSA1_5", aud=None): """ :param req: Original metadata statement as a :py:class:`MetadataStatement` instance :param receiver: The immediate receiver of the JWS :param iss: :param lifetime: :param sign: :param sign_alg: :param encrypt: :param enc_alg: :param enc_enc: :param aud: The audience, a list of receivers. :return: A dictionary with a signed JWT as value with the key 'sms' """ if not iss: iss = self.iss if not lifetime: lifetime = self.lifetime keyjar = self.keyjar # Own copy _metadata = copy.deepcopy(req) if self.add_ons: _metadata.update(self.add_ons) args = {} if sign: if sign_alg: args['sign_alg'] = sign_alg else: args['sign_alg'] = self.alg if encrypt: args['enc_enc'] = enc_enc args['enc_alg'] = enc_alg _jwt = JWT(keyjar, iss=iss, msg_cls=_metadata.__class__, lifetime=lifetime, **args) # _jwt.sign_alg = self.alg if iss in keyjar.issuer_keys: owner = iss else: owner = '' return _jwt.pack(payload=_metadata.to_dict(), owner=owner, recv=receiver, aud=aud)