def test_issued_software_statement_contains_kid(self): federation = Federation(self.signing_key) jws = federation.create_software_statement({}) _jws = JWS() _jws.verify_compact(jws, keys=[self.signing_key]) assert _jws.jwt.headers["kid"] == self.signing_key.kid
def check_jwks(self, entity): # all keys in JWKS has scoped kid assert all(key.kid.startswith(entity.name) for key in entity.jwks[""][0].keys()) _jws = JWS() assert _jws.verify_compact(entity.signed_jwks, keys=[entity.intermediate_key]) assert _jws.jwt.headers["kid"] == entity.intermediate_key.kid
def _get_jwt_signature(self, params): try: jws = JWS(params, alg=self.jwt_algorithm) return jws.sign_compact(keys=self.issuer_private_keys) except NoSuitableSigningKeys: raise NoIssuerKey( "An issuer key wasn't loaded. Please run set_issuer() first.")
def test_a_1_3b(): _jwt = ("eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJl" "eHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0c" "nVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk") keys = [SYMKey(key=jwkest.intarr2bin(HMAC_KEY))] _jws2 = JWS() _jws2.verify_compact(_jwt, keys)
def test_refresh_token(self): """ A request to the Token Endpoint can also use a Refresh Token by using the grant_type value refresh_token, as described in Section 6 of OAuth 2.0 [RFC6749]. """ SIGKEYS = self._get_keys() # Retrieve refresh token code = self._create_code() post_data = self._auth_code_post_data(code=code.code) real_now = timezone.now with patch('oidc_provider.lib.utils.token.timezone.now') as now: now.return_value = real_now() response = self._post_request(post_data) response_dic1 = json.loads(response.content.decode('utf-8')) id_token1 = JWS().verify_compact(response_dic1['id_token'].encode('utf-8'), SIGKEYS) # Use refresh token to obtain new token post_data = self._refresh_token_post_data(response_dic1['refresh_token']) with patch('oidc_provider.lib.utils.token.timezone.now') as now: now.return_value = real_now() + timedelta(minutes=10) response = self._post_request(post_data) response_dic2 = json.loads(response.content.decode('utf-8')) id_token2 = JWS().verify_compact(response_dic2['id_token'].encode('utf-8'), SIGKEYS) self.assertNotEqual(response_dic1['id_token'], response_dic2['id_token']) self.assertNotEqual(response_dic1['access_token'], response_dic2['access_token']) self.assertNotEqual(response_dic1['refresh_token'], response_dic2['refresh_token']) # http://openid.net/specs/openid-connect-core-1_0.html#rfc.section.12.2 self.assertEqual(id_token1['iss'], id_token2['iss']) self.assertEqual(id_token1['sub'], id_token2['sub']) self.assertNotEqual(id_token1['iat'], id_token2['iat']) self.assertEqual(id_token1['aud'], id_token2['aud']) self.assertEqual(id_token1['auth_time'], id_token2['auth_time']) self.assertEqual(id_token1.get('azp'), id_token2.get('azp')) # Refresh token can't be reused post_data = self._refresh_token_post_data(response_dic1['refresh_token']) response = self._post_request(post_data) self.assertIn('invalid_grant', response.content.decode('utf-8')) # Old access token is invalidated self.assertEqual(self._get_userinfo(response_dic1['access_token']).status_code, 401) self.assertEqual(self._get_userinfo(response_dic2['access_token']).status_code, 200) # Empty refresh token is invalid post_data = self._refresh_token_post_data('') response = self._post_request(post_data) self.assertIn('invalid_grant', response.content.decode('utf-8')) # No refresh token is invalid post_data = self._refresh_token_post_data('') del post_data['refresh_token'] response = self._post_request(post_data) self.assertIn('invalid_grant', response.content.decode('utf-8'))
def test_hmac_256(): payload = b'Please take a moment to register today' keys = [SYMKey(key=jwkest.intarr2bin(HMAC_KEY))] _jws = JWS(payload, alg="HS256") _jwt = _jws.sign_compact(keys) info = JWS().verify_compact(_jwt, keys) assert info == payload.decode("utf-8")
def encode_id_token(payload, client): """ Represent the ID Token as a JSON Web Token (JWT). Return a hash. """ keys = get_client_alg_keys(client) _jws = JWS(payload, alg=client.jwt_alg) return _jws.sign_compact(keys)
def test_jws_1(): msg = {"iss": "joe", "exp": 1300819380, "http://example.com/is_root": True} key = SYMKey(key=jwkest.intarr2bin(HMAC_KEY)) _jws = JWS(msg, cty="JWT", alg="HS256", jwk=key.serialize()) res = _jws.sign_compact() _jws2 = JWS(alg="HS256") _jws2.verify_compact(res, keys=[key]) assert _jws2.msg == msg
def test_hmac_from_keyrep(): payload = "Please take a moment to register today" symkeys = [k for k in SIGKEYS if k.kty == "oct"] _jws = JWS(payload, alg="HS512") _jwt = _jws.sign_compact(symkeys) _rj = JWS() info = _rj.verify_compact(_jwt, symkeys) assert info == payload
def test_jws_1(): msg = {"iss": "joe", "exp": 1300819380, "http://example.com/is_root": True} jwk = SYMKey(key=jwkest.intarr2bin(HMAC_KEY)) _jws = JWS(msg, cty="JWT", alg="HS256", jwk=json.dumps(jwk.to_dict())) res = _jws.sign_compact() _jws2 = JWS(alg="HS256") _jws2.verify_compact(res) assert _jws2.msg == msg
def test_hmac_512(): payload = "Please take a moment to register today" keys = [SYM_key(key="My hollow echo")] _jws = JWS(payload, alg="HS256") _jwt = _jws.sign_compact(keys) _rj = JWS() info = _rj.verify_compact(_jwt, keys) assert info == payload
def test_dj_usage(): key_string = open(full_path("./size2048.key"), 'r').read() key = RSA.importKey(key_string) payload = "Please take a moment to register today" keys = [RSAKey(key=key, kid=md5(key_string.encode('utf-8')).hexdigest())] _jws = JWS(payload, alg='RS256') sjwt = _jws.sign_compact(keys) _jwt = factory(sjwt) assert _jwt.jwt.headers['alg'] == 'RS256'
def test_sign_registration_request(self): rp_root_key = rsa_key() rp = RP(None, rp_root_key, [], None, None) reg_req = FederationRegistrationRequest(**{"foo": "bar"}) signed = rp._sign_registration_request(reg_req) _jws = JWS() assert _jws.is_jws(signed) assert _jws.jwt.headers["kid"] == rp.intermediate_key.kid assert SignedHttpRequest(rp.intermediate_key).verify(signed, body=reg_req.to_json())
def encode_id_token(payload): """ Represent the ID Token as a JSON Web Token (JWT). Return a hash. """ key_string = get_rsa_key().encode('utf-8') keys = [ RSAKey(key=importKey(key_string), kid=md5(key_string).hexdigest()) ] _jws = JWS(payload, alg='RS256') return _jws.sign_compact(keys)
def test_create_software_statement(self): registration_data = { "foo": "bar" } federation = Federation(self.signing_key) jws = federation.create_software_statement(registration_data) software_statement = JWS().verify_compact(jws, keys=[self.signing_key]) assert all(item in software_statement.items() for item in registration_data.items())
def test_signer_ps384(): payload = "Please take a moment to register today" keys = [RSAKey(key=import_rsa_key_from_file(KEY))] #keys[0]._keytype = "private" _jws = JWS(payload, alg="PS384") _jwt = _jws.sign_compact(keys) _rj = JWS() info = _rj.verify_compact(_jwt, keys) assert info == payload
def test_signer_es384(): payload = "Please take a moment to register today" _key = ECKey().load_key(P384) keys = [_key] _jws = JWS(payload, alg="ES384") _jwt = _jws.sign_compact(keys) _rj = JWS() info = _rj.verify_compact(_jwt, keys) assert info == payload
def _to_jws(self, data: dict) -> str: """ Converts data to a jws :param data: Data to be converted to jws :return: a signed jwt """ algorithm = "RS256" _jws = JWS(json.dumps(data), alg=algorithm) return _jws.sign_compact([self.sign_key])
def test_rs512(): payload = "Please take a moment to register today" keys = [RSA_key(key=rsa_load(KEY))] keys[0]._keytype = "private" _jws = JWS(payload, alg="RS512") _jwt = _jws.sign_compact(keys) _rj = JWS() info = _rj.verify_compact(_jwt, keys) assert info == payload
def to_jwt(self, key=None, algorithm="", lev=0): """ Create a signed JWT representation of the class instance :param key: The signing key :param algorithm: The signature algorithm to use :return: A signed JWT """ _jws = JWS(self.to_json(lev), alg=algorithm) return _jws.sign_compact(key)
def test_signer_es256_verbose(): payload = "Please take a moment to register today" _key = ECKey().load_key(P256) keys = [_key] _jws = JWS(payload, alg="ES256") _jwt = _jws.sign_compact(keys) _rj = JWS() info = _rj.verify_compact_verbose(_jwt, keys) assert info['msg'] == payload assert info['key'] == _key
def test_1(): claimset = {"iss": "joe", "exp": 1300819380, "http://example.com/is_root": True} _jws = JWS(claimset, cty="JWT") _jwt = _jws.sign_compact() _jr = JWS() _msg = _jr.verify_compact(_jwt, allow_none=True) print(_jr) assert _jr.jwt.headers["alg"] == "none" assert _msg == claimset
def encode_id_token(payload): """ Represent the ID Token as a JSON Web Token (JWT). Return a hash. """ keys = [ RSAKey(key=importKey(get_rsa_key())) ] _jws = JWS(payload, alg='RS256') _jwt = _jws.sign_compact(keys) return _jwt.decode('utf-8')
def test_signer_ps512(): payload = "Please take a moment to register today" # Key has to be big enough > 512+512+2 keys = [RSAKey(key=import_rsa_key_from_file(full_path("./size2048.key")))] #keys[0]._keytype = "private" _jws = JWS(payload, alg="PS521") _jwt = _jws.sign_compact(keys) _rj = JWS() info = _rj.verify_compact(_jwt, keys) assert info == payload
def test_signer_es512(): payload = "Please take a moment to register today" _key = ECKey().load_key(P521) keys = [_key] #keys[0]._keytype = "private" _jws = JWS(payload, alg="ES512") _jwt = _jws.sign_compact(keys) _rj = JWS() info = _rj.verify_compact(_jwt, keys) assert info == payload
def _verify(self, jws, keys): # type: (str, Sequence[Key]) -> Dict[str, Union[str, Lists[str]]] """ Verify signature of JWS. :param jws: JWS to verify signature of :param keys: possible keys to verify the signature with :return: payload of the JWS """ unpacked = JWS() unpacked.verify_compact(jws, keys=keys) return unpacked
def test_create_software_statement_with_policy(self): registration_data = { "foo": "bar" } policy_attributes = {"abc": "xyz"} federation = Federation(self.signing_key, policy=policy_attributes) jws = federation.create_software_statement(registration_data) software_statement = JWS().verify_compact(jws, keys=[self.signing_key]) assert all(item in software_statement.items() for item in (set(registration_data.items()) | set(policy_attributes.items())))
def test_full_flow(self, satosa_config_dict, oidc_frontend_config, saml_backend_config, idp_conf): user_id = "testuser1" # proxy config satosa_config_dict["FRONTEND_MODULES"] = [oidc_frontend_config] satosa_config_dict["BACKEND_MODULES"] = [saml_backend_config] satosa_config_dict["INTERNAL_ATTRIBUTES"]["attributes"] = {attr_name: {"openid": [attr_name], "saml": [attr_name]} for attr_name in USERS[user_id]} _, backend_metadata = create_entity_descriptors(SATOSAConfig(satosa_config_dict)) # application test_client = Client(make_app(SATOSAConfig(satosa_config_dict)), BaseResponse) # get frontend OP config info provider_config = json.loads(test_client.get("/.well-known/openid-configuration").data.decode("utf-8")) # create auth req claims_request = ClaimsRequest(id_token=Claims(**{k: None for k in USERS[user_id]})) req_args = {"scope": "openid", "response_type": "id_token", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "nonce": "nonce", "claims": claims_request.to_json()} auth_req = urlparse(provider_config["authorization_endpoint"]).path + "?" + urlencode(req_args) # make auth req to proxy proxied_auth_req = test_client.get(auth_req) assert proxied_auth_req.status == "303 See Other" # config test IdP backend_metadata_str = str(backend_metadata[saml_backend_config["name"]][0]) idp_conf["metadata"]["inline"].append(backend_metadata_str) fakeidp = FakeIdP(USERS, config=IdPConfig().load(idp_conf, metadata_construction=False)) # create auth resp req_params = dict(parse_qsl(urlparse(proxied_auth_req.data.decode("utf-8")).query)) url, authn_resp = fakeidp.handle_auth_req( req_params["SAMLRequest"], req_params["RelayState"], BINDING_HTTP_REDIRECT, user_id, response_binding=BINDING_HTTP_REDIRECT) # make auth resp to proxy authn_resp_req = urlparse(url).path + "?" + urlencode(authn_resp) authn_resp = test_client.get("/" + authn_resp_req) assert authn_resp.status == "303 See Other" # verify auth resp from proxy resp_dict = dict(parse_qsl(urlparse(authn_resp.data.decode("utf-8")).fragment)) signing_key = RSAKey(key=rsa_load(oidc_frontend_config["config"]["signing_key_path"]), use="sig", alg="RS256") id_token_claims = JWS().verify_compact(resp_dict["id_token"], keys=[signing_key]) assert all((k, v[0]) in id_token_claims.items() for k, v in USERS[user_id].items())
def test_signing(): kb = keybundle_from_local_file("file://jwk.json", "jwk", ["ver", "sig"]) assert len(kb) == 1 kj = KeyJar() kj.issuer_keys[""] = [kb] keys = kj.get_signing_key() payload = "Please take a moment to register today" _jws = JWS(payload, alg="RS512") try: _jwt = _jws.sign_compact(keys) assert False except (NoSuitableSigningKeys, WrongTypeOfKey): assert True
def _to_jws(self, data): """ Converts data to a jws :type data: Any :rtype: str :param data: Data to be converted to jws :return: a jws """ algorithm = "RS256" _jws = JWS(json.dumps(data), alg=algorithm) return _jws.sign_compact([self.sign_key])
def validate_and_return_id_token(jws, nonce=None, validate_nonce=True): """ Validates the id_token according to the OpenID Connect specification. """ shared_key = oidc_rp_settings.CLIENT_SECRET \ if oidc_rp_settings.PROVIDER_SIGNATURE_ALG == 'HS256' \ else oidc_rp_settings.PROVIDER_SIGNATURE_KEY # RS256 try: # Decodes the JSON Web Token and raise an error if the signature is invalid. id_token = JWS().verify_compact(force_bytes(jws), _get_jwks_keys(shared_key)) except JWKESTException: return # Validates the claims embedded in the id_token. _validate_claims(id_token, nonce=nonce, validate_nonce=validate_nonce) return id_token
def entities(self, entity_id=None, **kwargs): """ Get the metadata for specific entity id (client_id in the case OpenIDConnect) or all known entities. """ content_type = cherrypy.tools.accept.callable( media=MIME_TYPES_SUPPORTED) # get the clients preferred mime type try: self.validator.validate(cherrypy.request) except MalformedRequestError as e: logger.info("Malformed request, reason: '{}'".format(str(e))) if e.http_status_code == 405: cherrypy.response.headers['Allow'] = 'GET' raise cherrypy.HTTPError(e.http_status_code, e.message) # Handle b64-encoded entity id's if entity_id is not None and entity_id.startswith("{b64}"): entity_id = base64.urlsafe_b64decode(entity_id[5:]) # No entity id specified if entity_id is None: entity = self.metadata_store.all() else: try: entity = self.metadata_store[entity_id] except KeyError: _msg = "Unknown entity id '{}'".format(entity_id) logger.info(_msg) raise cherrypy.HTTPError(404, _msg) cherrypy.response.headers["Content-Type"] = MIME_TYPE_JSON cherrypy.response.headers["Cache-Control"] = "max-age={}".format( self.update_frequency) cherrypy.response.headers["Last-Modified"] = entity.last_modified data = json.dumps(entity.metadata) if content_type == MIME_TYPE_JWT: algorithm = kwargs.get(MDQHandler.SIGNING_ALG_QUERY_PARAM, None) or "none" data = JWS(data, alg=algorithm).sign_compact(keys=self.signing_keys) cherrypy.response.headers["Content-Type"] = MIME_TYPE_JWT return data
def _consent_registration(self, consent_args): """ Register a request at the consent service :type consent_args: dict :rtype: str :param consent_args: All necessary parameters for the consent request :return: Ticket received from the consent service """ jws = JWS(json.dumps(consent_args), alg=self.signing_key.alg).sign_compact([self.signing_key]) request = "{}/creq/{}".format(self.api_url, jws) res = requests.get(request) if res.status_code != 200: raise UnexpectedResponseError("Consent service error: %s %s", res.status_code, res.text) return res.text
def test_idtoken_sign_validation(self): """ We MUST validate the signature of the ID Token according to JWS using the algorithm specified in the alg Header Parameter of the JOSE Header. """ SIGKEYS = self._get_keys() RSAKEYS = [k for k in SIGKEYS if k.kty == 'RSA'] code = self._create_code() post_data = self._auth_code_post_data(code=code.code) response = self._post_request(post_data) response_dic = json.loads(response.content.decode('utf-8')) id_token = JWS().verify_compact( response_dic['id_token'].encode('utf-8'), RSAKEYS)
def test_1(): claimset = {"iss": "joe", "exp": 1300819380, "http://example.com/is_root": True} _jws = JWS(claimset, cty="JWT") _jwt = _jws.sign_compact() _jr = JWS() _msg = _jr.verify_compact(_jwt, allow_none=True) print(_jr) assert _jr.jwt.headers["alg"] == 'none' assert _msg == claimset
def test_no_alg_and_alg_none_same(): payload = "Please take a moment to register today" _jws = JWS(payload, alg="none") # Create a JWS (signed JWT) _jwt0 = _jws.sign_compact([]) # The class instance that sets up the signing operation _jws = JWS(payload) # Create a JWS (signed JWT) _jwt1 = _jws.sign_compact([]) assert _jwt0 == _jwt1
def setUpClass(cls): cls.g_config = {} with open("../src/config/config.json") as j: cls.g_config = json.load(j) wkh = WellKnownHandler(cls.g_config["auth_server_url"], secure=False) cls.__TOKEN_ENDPOINT = wkh.get(TYPE_OIDC, KEY_OIDC_TOKEN_ENDPOINT) #Generate ID Token _rsakey = RSA.generate(2048) _private_key = _rsakey.exportKey() _public_key = _rsakey.publickey().exportKey() file_out = open("private.pem", "wb") file_out.write(_private_key) file_out.close() file_out = open("public.pem", "wb") file_out.write(_public_key) file_out.close() #Admin JWT _rsajwk = RSAKey(kid='RSA1', key=import_rsa_key(_private_key)) _payload = { "iss": cls.g_config["client_id"], "sub": cls.g_config["client_id"], "aud": cls.__TOKEN_ENDPOINT, "jti": datetime.datetime.today().strftime('%Y%m%d%s'), "exp": int(time.time())+3600, "isOperator": False } _jws = JWS(_payload, alg="RS256") cls.jwt_admin = _jws.sign_compact(keys=[_rsajwk]) #ROTest user JWT _payload = { "iss": cls.g_config["client_id"], "sub": "54d10251-6cb5-4aee-8e1f-f492f1105c94", "aud": cls.__TOKEN_ENDPOINT, "jti": datetime.datetime.today().strftime('%Y%m%d%s'), "exp": int(time.time())+3600, "isOperator": False } _jws = JWS(_payload, alg="RS256") cls.jwt_rotest = _jws.sign_compact(keys=[_rsajwk]) cls.scopes = 'public_access' cls.resourceName = "TestROChangePEP" cls.PEP_HOST = "http://localhost:5566"
def validate_and_return_id_token(self, jws): """ Validates the id_token according to the spec. http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation. """ try: # Decode the JWT and raise an error if the sig is invalid. # Make sure to specify the expected algorithm to prevent attacks # that used an RSA public key as an HMAC secret key. id_token = JWS().verify_compact( jws.encode('utf-8'), keys=self.get_jwks_keys(), sigalg=self.setting('TOKEN_SIGNING_ALGORITHM')) except JWKESTException as e: raise AuthTokenError( self, ('User info error: Signature verification failed. ' 'Reason: {0}'.format(e))) self.validate_id_token_claims(id_token) return id_token
def test_existing_account_linking_with_known_known_uuid(self, account_linking_config, internal_response, context): uuid = "uuid" data = { "idp": internal_response.auth_info.issuer, "id": internal_response.user_id, "redirect_endpoint": self.account_linking.base_url + "/account_linking/handle_account_linking" } key = RSAKey(key=rsa_load(account_linking_config["sign_key"]), use="sig", alg="RS256") jws = JWS(json.dumps(data), alg=key.alg).sign_compact([key]) responses.add( responses.GET, "%s/get_id?jwt=%s" % (account_linking_config["api_url"], jws), status=200, body=uuid, content_type="text/html", match_querystring=True ) self.account_linking.process(context, internal_response) assert internal_response.user_id == uuid
def bundle2keyjar(): url = 'https://localhost:8080/bundle' r = requests.get(url, verify=False) assert r.status_code == 200 _bundle = r.text url = 'https://localhost:8080/bundle/sigkey' r = requests.get(url, verify=False) assert r.status_code == 200 _sigkey_jwks = json.loads(as_unicode(r.text)) kj = KeyJar() kj.import_jwks(_sigkey_jwks, '') _ver_bundle = JWS().verify_compact(_bundle, kj.get_verify_key()) jwks_dir = _ver_bundle['bundle'] for iss, jwks in jwks_dir.items(): kj.import_jwks(jwks, iss) return kj
def verify_id(token): global jwks_uri header, claims, signature = token.split('.') header = b64d(header) claims = b64d(claims) if not signature: raise ValueError('Invalid Token') if header['alg'] not in ['HS256', 'RS256']: raise ValueError('Unsupported signing method') if header['alg'] == 'RS256': signing_keys = load_jwks_from_url(jwks_uri) else: signing_keys = [SYMKey(key=str(CLIENT_SECRET))] id_token = JWS().verify_compact(token, signing_keys) id_token['header_info'] = header return id_token
def validate_and_return_id_token(jws, nonce=None, validate_nonce=True): """ Validates the id_token according to the OpenID Connect specification. """ log_prompt = "Validate ID Token: {}" logger.debug(log_prompt.format('Get shared key')) shared_key = settings.AUTH_OPENID_CLIENT_ID \ if settings.AUTH_OPENID_PROVIDER_SIGNATURE_ALG == 'HS256' \ else settings.AUTH_OPENID_PROVIDER_SIGNATURE_KEY # RS256 try: # Decodes the JSON Web Token and raise an error if the signature is invalid. logger.debug(log_prompt.format('Verify compact jwk')) id_token = JWS().verify_compact(force_bytes(jws), _get_jwks_keys(shared_key)) except JWKESTException as e: logger.debug(log_prompt.format('Verify compact jwkest exception: {}'.format(str(e)))) return # Validates the claims embedded in the id_token. logger.debug(log_prompt.format('Validate claims')) _validate_claims(id_token, nonce=nonce, validate_nonce=validate_nonce) return id_token
def test_1(): claimset = { "iss": "joe", "exp": 1300819380, "http://example.com/is_root": True } _jws = JWS(claimset, cty="JWT") _jwt = _jws.sign_compact() _jr = JWS() _jr.verify_compact(_jwt) print _jr assert _jr.alg == u'none' assert _jr.msg == { "iss": "joe", "exp": 1300819380, "http://example.com/is_root": True }
def _handle_webauthn_response(self, context): saved_state = context.state[self.name] internal_response = InternalData.from_dict(saved_state) message = { "user_id": internal_response["attributes"][self.user_id][0], "nonce": internal_response['nonce'], "time": str(int(time.time())) } message_json = json.dumps(message) jws = JWS(message_json, alg=self.signing_key.alg).sign_compact([self.signing_key]) request = self.api_url + "/" + jws response = requests.get(request) response_dict = json.loads(response.text) if response_dict["result"] != "okay" or not hmac.compare_digest( response_dict["nonce"], internal_response["nonce"]): raise Exception("Authentication was unsuccessful.") if "authn_context_class_ref" in context.state: internal_response["auth_info"]["auth_class_ref"] = context.state[ "authn_context_class_ref"] return super().process(context, internal_response)
def test_full_flow(self, account_linking_config, internal_response, context): ticket = "ticket" with responses.RequestsMock() as rsps: rsps.add(responses.GET, "%s/get_id" % account_linking_config["api_url"], status=404, body=ticket, content_type="text/html") result = self.account_linking.process(context, internal_response) assert isinstance(result, Redirect) assert result.message.startswith( account_linking_config["redirect_url"]) data = { "idp": internal_response.auth_info.issuer, "id": internal_response.subject_id, "redirect_endpoint": self.account_linking.base_url + "/account_linking/handle_account_linking" } key = RSAKey(key=rsa_load(account_linking_config["sign_key"]), use="sig", alg="RS256") jws = JWS(json.dumps(data), alg=key.alg).sign_compact([key]) uuid = "uuid" with responses.RequestsMock() as rsps: # account is linked, 200 OK rsps.add(responses.GET, "%s/get_id?jwt=%s" % (account_linking_config["api_url"], jws), status=200, body=uuid, content_type="text/html", match_querystring=True) internal_response = self.account_linking._handle_al_response( context) assert internal_response.subject_id == uuid
def create_jwt(self, user): """ Creates a signed (JWS) ID token. Returns: str: JWS """ key = SYMKey(key=self.site.siteconfiguration.oauth_settings['SOCIAL_AUTH_EDX_OAUTH2_SECRET']) now = datetime.datetime.utcnow() expiration_datetime = now + datetime.timedelta(seconds=3600) issue_datetime = now payload = { 'iss': self.site.siteconfiguration.lms_url_root, 'administrator': False, 'iat': timegm(issue_datetime.utctimetuple()), 'sub': str(uuid.uuid4()), 'preferred_username': user.username, 'aud': self.site.siteconfiguration.oauth_settings['SOCIAL_AUTH_EDX_OAUTH2_KEY'], 'exp': timegm(expiration_datetime.utctimetuple()), } access_token = JWS(payload, jwk=key, alg='HS512').sign_compact() return access_token
def create_id_token(self, user): """ Creates a signed (JWS) ID token. Returns: str: JWS """ key = SYMKey(key=self.site.siteconfiguration. oauth_settings['SOCIAL_AUTH_EDX_OIDC_SECRET']) now = datetime.datetime.utcnow() expiration_datetime = now + datetime.timedelta(seconds=3600) issue_datetime = now payload = { 'iss': self.site.siteconfiguration.oauth2_provider_url, 'administrator': False, 'iat': timegm(issue_datetime.utctimetuple()), 'given_name': user.first_name, 'sub': str(uuid.uuid4()), 'preferred_username': user.username, 'aud': self.site.siteconfiguration. oauth_settings['SOCIAL_AUTH_EDX_OIDC_KEY'], 'email': user.email, 'exp': timegm(expiration_datetime.utctimetuple()), 'name': user.get_full_name(), 'family_name': user.last_name, } access_token = JWS(payload, jwk=key, alg='HS512').sign_compact() return access_token
def test_scope_is_ignored_for_auth_code(self): """ Scope is ignored for token respones to auth code grant type. """ SIGKEYS = self._get_keys() for code_scope in [['openid'], ['openid', 'email']]: code = self._create_code(code_scope) post_data = self._auth_code_post_data( code=code.code, scope=['openid', 'profile']) response = self._post_request(post_data) response_dic = json.loads(response.content.decode('utf-8')) self.assertEqual(response.status_code, 200) id_token = JWS().verify_compact(response_dic['id_token'].encode('utf-8'), SIGKEYS) if 'email' in code_scope: self.assertIn('email', id_token) else: self.assertNotIn('email', id_token)
def _decode_jwt(verify_expiration): """ Helper method to decode a JWT with the ability to verify the expiration of said token """ keys = KEYS() if should_be_asymmetric_key: keys.load_jwks(settings.JWT_AUTH['JWT_PUBLIC_SIGNING_JWK_SET']) else: keys.add({'key': secret_key, 'kty': 'oct'}) _ = JWS().verify_compact(access_token.encode('utf-8'), keys) return jwt.decode( access_token, secret_key, algorithms=[settings.JWT_AUTH['JWT_ALGORITHM']], audience=audience, issuer=issuer, verify_expiration=verify_expiration, options={'verify_signature': False}, )
def test_rs256_rm_signature(): payload = "Please take a moment to register today" keys = [RSAKey(key=import_rsa_key_from_file(KEY))] # keys[0]._keytype = "private" _jws = JWS(payload, alg="RS256") _jwt = _jws.sign_compact(keys) p = _jwt.split('.') _jwt = '.'.join(p[:-1]) _rj = JWS() try: _ = _rj.verify_compact(_jwt, keys) except jwkest.WrongNumberOfParts: pass else: assert False
def create_jws_access_token(self, expires_in=3600, issuer=None, key=None, alg='RS512'): """ Creates a signed (JWS) access token. Arguments: expires_in (int): Number of seconds after which the token expires. issuer (str): Issuer of the token. key (jwkest.jwk.Key): Key used to sign the token. alg (str): Signing algorithm. Returns: str: JWS """ key = key or self.key now = datetime.datetime.utcnow() expiration_datetime = now + datetime.timedelta(seconds=expires_in) issue_datetime = now payload = { 'iss': issuer or self.url_root, 'administrator': False, 'iat': timegm(issue_datetime.utctimetuple()), 'given_name': 'Joe', 'sub': 'e3bfe0e4e7c6693efba9c3a93ee7f31b', 'preferred_username': self.expected_username, 'aud': 'InkocujLikyucsEdwiWatdebrEackmevLakDuifKooshkakWow', 'scopes': ['read', 'write', 'profile', 'email', 'user_id'], 'email': '*****@*****.**', 'exp': timegm(expiration_datetime.utctimetuple()), 'name': 'Joe Smith', 'family_name': 'Smith', 'user_id': '1', } access_token = JWS(payload, jwk=key, alg=alg).sign_compact() return access_token
def _get_uuid(self, context, issuer, id): """ Ask the account linking service for a uuid. If the given issuer/id pair is not linked, then the function will return a ticket. This ticket should be used for linking the issuer/id pair to the user account :type context: satosa.context.Context :type issuer: str :type id: str :rtype: (int, str) :param context: The current context :param issuer: the issuer used for authentication :param id: the given id :return: response status code and message (200, uuid) or (404, ticket) """ data = { "idp": issuer, "id": id, "redirect_endpoint": "%s/account_linking%s" % (self.base_url, self.endpoint) } jws = JWS(json.dumps(data), alg=self.signing_key.alg).sign_compact([self.signing_key]) try: request = "{}/get_id?jwt={}".format(self.api_url, jws) response = requests.get(request) except Exception as con_exc: msg = "Could not connect to account linking service" satosa_logging(logger, logging.CRITICAL, msg, context.state, exc_info=True) raise SATOSAAuthenticationError(context.state, msg) from con_exc if response.status_code not in [200, 404]: msg = "Got status code '%s' from account linking service" % (response.status_code) satosa_logging(logger, logging.CRITICAL, msg, context.state) raise SATOSAAuthenticationError(context.state, msg) return response.status_code, response.text
def test_authorization_code(self): """ We MUST validate the signature of the ID Token according to JWS using the algorithm specified in the alg Header Parameter of the JOSE Header. """ SIGKEYS = self._get_keys() code = self._create_code() post_data = self._auth_code_post_data(code=code.code) response = self._post_request(post_data) response_dic = json.loads(response.content.decode('utf-8')) id_token = JWS().verify_compact(response_dic['id_token'].encode('utf-8'), SIGKEYS) token = Token.objects.get(user=self.user) self.assertEqual(response_dic['access_token'], token.access_token) self.assertEqual(response_dic['refresh_token'], token.refresh_token) self.assertEqual(response_dic['token_type'], 'bearer') self.assertEqual(response_dic['expires_in'], 720) self.assertEqual(id_token['sub'], str(self.user.id)) self.assertEqual(id_token['aud'], self.client.client_id)
def jwtValidate(self, token): """ jwt方式解析token :param token: 需要解析的token. :type field: str :returns: 解析成功返回None;解析失败返回错误信息. :rtype: object .. versionadded:: 1.0 """ parts = token.split('.') if len(parts) != 3: raise BadSignature('Invalid JWT. Only JWS supported.') header = json.loads(base64_urldecode(parts[0])) payload = json.loads(base64_urldecode(parts[1])) # 校验 issuer if self.expectedIssuer != payload['iss']: return "Invalid issuer %s, expected %s" % (payload['iss'], self.expectedIssuer) # 校验 client_id if payload["aud"]: if (isinstance(payload["aud"], str) and payload["aud"] != self.clientId) or self.clientId not in payload['aud']: return "Invalid audience %s, expected %s" % (payload['aud'], self.clientId) # 校验过期时间 if int(time.time()) >= int(payload['exp']): return "Token has expired" # 校验生效时间 if int(time.time()) <= int(payload['iat']): return "Token issued in the past" jws = JWS(alg=header['alg']) try: jws.verify_compact(token, self.jwks) return except Exception as e: # 第一次解析异常时,更新jwks信息重新解析 try: self.jwks = self.load_keys() jws.verify_compact(token, self.jwks) return except Exception as e: return 'Invalid token!'
def test_idtoken_sign_validation(self): """ We MUST validate the signature of the ID Token according to JWS using the algorithm specified in the alg Header Parameter of the JOSE Header. """ # Get public key from discovery. request = self.factory.get(reverse('oidc_provider:jwks')) response = JwksView.as_view()(request) jwks_dic = json.loads(response.content.decode('utf-8')) SIGKEYS = KEYS() SIGKEYS.load_dict(jwks_dic) RSAKEYS = [k for k in SIGKEYS if k.kty == 'RSA'] code = self._create_code() post_data = self._post_data(code=code.code) response = self._post_request(post_data) response_dic = json.loads(response.content.decode('utf-8')) id_token = JWS().verify_compact( response_dic['id_token'].encode('utf-8'), RSAKEYS)
def prepare_access_token_body(self, client_key=None, tamper_message=False, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Prepares a provider access token response. Arguments: client_id -- (str) OAuth ID for the client that requested authentication. expiration_time -- (datetime) Date and time after which the response should be considered invalid. """ body = {'access_token': 'foobar', 'token_type': 'bearer'} client_key = client_key or self.client_key now = datetime.datetime.utcnow() expiration_datetime = expiration_datetime or \ (now + datetime.timedelta(seconds=30)) issue_datetime = issue_datetime or now nonce = nonce or 'a-nonce' issuer = issuer or self.issuer id_token = self.get_id_token( client_key, timegm(expiration_datetime.utctimetuple()), timegm(issue_datetime.utctimetuple()), nonce, issuer) body['id_token'] = JWS(id_token, jwk=self.key, alg='RS256').sign_compact() if tamper_message: header, msg, sig = body['id_token'].split('.') id_token['sub'] = '1235' msg = b64encode_item(id_token).decode('utf-8') body['id_token'] = '.'.join([header, msg, sig]) return json.dumps(body)
def _verify_response(response): if not config.no_jwt and response.status_code < 300: now = datetime.utcnow().timestamp() jwt_token = response.headers.get('X-JWT') if jwt_token is None: raise Exception("No response header X-JWT") jwt = JWS().verify_compact(jwt_token, sig_keys) if config.verbose: print("Response JWT Claims:") print(json.dumps(jwt, indent=2)) print() if jwt['jti'] != jti: raise Exception( "Unexpected response jti. Expected {} but received {}".format( jti, jwt['jti'])) if jwt['iss'] != config.audience: raise Exception( "Unexpected response issuer. Expected {} but received {}". format(audience, jwt['iss'])) if jwt['aud'] != config.issuer: raise Exception( "Unexpected response audience. Expected {} but received {}". format(issuer, jwt['aud'])) if jwt['nbf'] < now - config.leeway: raise Exception("Response nbf is out of bounds.") if jwt['exp'] > now + config.leeway: raise Exception("Response is expired.") if jwt['response']['status_code'] != response.status_code: raise Exception( "Unexpected response stats_code. Expected {} but received {}". format(response.status_code, jwt['response']['status_code'])) hashes = dict(S256='sha256', S384='sha384', S512='sha512') hasher = hashlib.new(hashes[jwt['response']['body_hash_alg']]) hasher.update(response.content) body_hash = hasher.hexdigest() if jwt['response']['body_hash'] != body_hash: raise Exception("Unexpected response body_hash")
def process(self, context, internal_response): """ Manage account linking and recovery :type context: satosa.context.Context :type internal_response: satosa.internal.InternalData :rtype: satosa.response.Response :param context: :param internal_response: :return: response : """ status_code, message = self._get_uuid(context, internal_response.auth_info.issuer, internal_response.subject_id) data = { "issuer": internal_response.auth_info.issuer, "redirect_endpoint": "%s/account_linking%s" % (self.base_url, self.endpoint) } # Store the issuer subject_id/sub because we'll need it in handle_al_response internal_response.attributes['issuer_user_id'] = internal_response.subject_id if status_code == 200: satosa_logging(logger, logging.INFO, "issuer/id pair is linked in AL service", context.state) internal_response.subject_id = message data['user_id'] = message if self.id_to_attr: internal_response.attributes[self.id_to_attr] = [message] else: satosa_logging(logger, logging.INFO, "issuer/id pair is not linked in AL service. Got a ticket", context.state) data['ticket'] = message jws = JWS(json.dumps(data), alg=self.signing_key.alg).sign_compact([self.signing_key]) context.state[self.name] = internal_response.to_dict() return Redirect("%s/%s" % (self.redirect_url, jws))
def test_signer_protected_headers(): payload = "Please take a moment to register today" _key = ECKey().load_key(P256) keys = [_key] _jws = JWS(payload, alg="ES256") protected = dict(header1=u"header1 is protected", header2="header2 is protected too", a=1) _jwt = _jws.sign_compact(keys, protected=protected) exp_protected = protected.copy() exp_protected['alg'] = 'ES256' enc_header, enc_payload, sig = _jwt.split('.') assert json.loads(b64d(enc_header.encode("utf-8")).decode("utf-8")) == exp_protected assert b64d(enc_payload.encode("utf-8")).decode("utf-8") == payload _rj = JWS() info = _rj.verify_compact(_jwt, keys) assert info == payload
def get_request_with_key(message): ''' This url path is called after authentication took place to check the result of it ''' message = JWS().verify_compact(message, keys=[public_key]) satosa_request = Request(message) request = database.get_request(satosa_request.nonce) response = "" if not request or request.userId != satosa_request.userId or request.success == 0 or int( request.time) + 300 < int(satosa_request.time): response = cfg['responses']['failure'] elif request.success == 1: database.make_invalid(request) response = cfg['responses']['success'] elif request.success == 2: response = cfg['responses']['invalid-request'] else: response = "error" response_dict = { "result": response, "current_time": str(int(time.time())), "nonce": satosa_request.nonce } return json.dumps(response_dict)