def genkeys(ctx, path): from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa from authlib.jose import jwk if path is None: context = ctx.obj['context'] config = context.get('config') path = config.config_path / 'keys' path.mkdir(exist_ok=True) else: path = pathlib.Path(path) private_file = path / 'private.json' public_file = path / 'public.json' if private_file.exists(): raise click.Abort(f"{private_file} file already exists.") if public_file.exists(): raise click.Abort(f"{public_file} file already exists.") key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) with private_file.open('w') as f: json.dump(jwk.dumps(key), f, indent=4, ensure_ascii=False) click.echo(f"Private key saved to {private_file}.") with public_file.open('w') as f: json.dump(jwk.dumps(key.public_key()), f, indent=4, ensure_ascii=False) click.echo(f"Public key saved to {public_file}.")
def test_runtime_error_fetch_jwks_uri(self): key = jwk.dumps('secret', 'oct', kid='f') token = get_bearer_token() id_token = generate_id_token( token, {'sub': '123'}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, nonce='n', ) app = Flask(__name__) app.secret_key = '!' oauth = OAuth(app) client = oauth.register( 'dev', client_id='dev', client_secret='dev', fetch_token=get_bearer_token, jwks={'keys': [jwk.dumps('secret', 'oct', kid='b')]}, issuer='https://i.b', id_token_signing_alg_values_supported=['HS256'], ) with app.test_request_context(): session['_dev_authlib_nonce_'] = 'n' token['id_token'] = id_token self.assertRaises(RuntimeError, client.parse_id_token, token)
def test_dumps_okp_public_key(self): key = read_file_path('ed25519-ssh.pub') self.assertRaises(ValueError, jwk.dumps, key) obj = jwk.dumps(key, 'OKP') self.assertEqual(obj['kty'], 'OKP') self.assertEqual(obj['crv'], 'Ed25519') key = read_file_path('ed25519-pub.pem') obj = jwk.dumps(key, 'OKP') self.assertEqual(obj['kty'], 'OKP') self.assertEqual(obj['crv'], 'Ed25519')
def test_parse_id_token_nonce_supported(self): key = jwk.dumps('secret', 'oct', kid='f') token = get_bearer_token() id_token = generate_id_token( token, {'sub': '123', 'nonce_supported': False}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, ) app = Flask(__name__) app.secret_key = '!' oauth = OAuth(app) client = oauth.register( 'dev', client_id='dev', client_secret='dev', fetch_token=get_bearer_token, jwks={'keys': [key]}, issuer='https://i.b', id_token_signing_alg_values_supported=['HS256', 'RS256'], ) with app.test_request_context(): session['_dev_authlib_nonce_'] = 'n' token['id_token'] = id_token user = client.parse_id_token(token) self.assertEqual(user.sub, '123')
def test_mac_computation(self): # https://tools.ietf.org/html/rfc7520#section-3.5 obj = { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" } key = jwk.loads(obj) new_obj = jwk.dumps(key) self.assertEqual(obj['k'], new_obj['k']) self.assertNotIn('use', new_obj) new_obj = jwk.dumps(key, use='sig') self.assertEqual(new_obj['use'], 'sig')
async def test_runtime_error_fetch_jwks_uri(): key = jwk.dumps('secret', 'oct', kid='f') token = get_bearer_token() id_token = generate_id_token( token, {'sub': '123'}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, nonce='n', ) oauth = OAuth() client = oauth.register( 'dev', client_id='dev', client_secret='dev', fetch_token=get_bearer_token, issuer='https://i.b', id_token_signing_alg_values_supported=['HS256'], ) req_scope = {'type': 'http', 'session': {'_dev_authlib_nonce_': 'n'}} req = Request(req_scope) token['id_token'] = id_token with pytest.raises(RuntimeError): await client.parse_id_token(req, token)
def test_rsa_public_key(self): # https://tools.ietf.org/html/rfc7520#section-3.3 obj = read_file_path('jwk_public.json') key = jwk.loads(obj) new_obj = jwk.dumps(key) self.assertBase64IntEqual(new_obj['n'], obj['n']) self.assertBase64IntEqual(new_obj['e'], obj['e'])
def make_jwk(key: str, ident: str): '''convert tempfile to jwk''' with open(key, 'rb') as fin: json_key = jwk.dumps(fin.read(), kty='RSA') json_key['alg'] = 'RS256' json_key['kid'] = ident return json_key
def encode_jwk(*, key_file_name: str, kid: str, alg: str) -> Tuple[ValidateCode, Union[Exception, dict]]: """ Encode JWK from a PEM file :param key_file_name Key File Name :param kid kid to be added to the JWK :param alg algorithm to be added :return ValidateCode or None, exception or None, JWK or None: """ pem_data = None try: with open(key_file_name) as public_key_fh: pem_data = public_key_fh.read() public_key_fh.close() except Exception as e: return ValidateCode.UNABLE_TO_LOAD_KEYS, e try: result = jwk.dumps(pem_data, kty='RSA') result["kid"] = kid result["alg"] = alg result["use"] = "sig" return ValidateCode.VALID, result except Exception as e: return ValidateCode.INVALID, e
def create_account(username: str, password: str): secret_key = key_utils.generate_secret_key(username) master_unlock_salt = key_utils.generate_salt() authentication_salt = key_utils.generate_salt() # Generate key to decrypt master_unlock_key = key_utils.derive_key_from_master_password_and_secret_key( username=username, master_password=password, secret_key=secret_key, salt=master_unlock_salt, ) # Generate x for SRP srp_x = key_utils.derive_key_from_master_password_and_secret_key( username=username, master_password=password, secret_key=secret_key, salt=authentication_salt, ) # Create account's keypair key_id = uuid.uuid1() key_pair = key_utils.generate_asymmetric_key_pair() # TODO: design flexible way to specify kty, if switching between EC and RSA, for example print( jwk.dumps(key_pair.private_key, kty="RSA", kid="priv"), jwk.dumps(key_pair.public_key, kty="RSA"), ) from passman.srp_example import create_v verifier = create_v(srp_x) # Attempt to create an account url = "http://localhost:443/create_account" data = { "username": username, "display_name": "Person", "auth_salt_hex": authentication_salt.hex(), "muk_salt_hex": master_unlock_salt.hex(), "auth_verifier_hex": verifier.hex(), } x = requests.post(url, json=data) print(x.text)
def test_dumps_okp_private_key(self): key = read_file_path('ed25519-pkcs8.pem') jwk = JsonWebKey(RFC8037_ALGORITHMS) self.assertRaises(ValueError, jwk.dumps, key) obj = jwk.dumps(key, 'OKP') self.assertEqual(obj['kty'], 'OKP') self.assertEqual(obj['crv'], 'Ed25519') self.assertIn('d', obj)
def test_ec_private_key(self): # https://tools.ietf.org/html/rfc7520#section-3.2 obj = read_file_path('ec_private.json') key = jwk.loads(obj) new_obj = jwk.dumps(key, 'EC') self.assertEqual(new_obj['crv'], obj['crv']) self.assertBase64IntEqual(new_obj['x'], obj['x']) self.assertBase64IntEqual(new_obj['y'], obj['y']) self.assertBase64IntEqual(new_obj['d'], obj['d'])
def generate_token() -> Tuple: """Generate RSA Key pair to be used to sign token and the JWT Token itself.""" private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) public_key = private_key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) # we set no `exp` and other claims as they are optional in a real scenario these should bde set # See available claims here: https://www.iana.org/assignments/jwt/jwt.xhtml # the important claim is the "authorities" public_jwk = jwk.dumps(public_key, kty="RSA") private_jwk = jwk.dumps(pem, kty="RSA") return (public_jwk, private_jwk)
def test_loads_okp_public_key(self): obj = { "x": "AD9E0JYnpV-OxZbd8aN1t4z71Vtf6JcJC7TYHT0HDbg", "crv": "Ed25519", "kty": "OKP" } key = jwk.loads(obj) new_obj = jwk.dumps(key) self.assertEqual(obj['x'], new_obj['x'])
def test_ec_public_key(self): # https://tools.ietf.org/html/rfc7520#section-3.1 obj = read_file_path('ec_public.json') key = jwk.loads(obj) new_obj = jwk.dumps(key) self.assertEqual(new_obj['crv'], obj['crv']) self.assertBase64IntEqual(new_obj['x'], obj['x']) self.assertBase64IntEqual(new_obj['y'], obj['y']) self.assertEqual(key.as_json()[0], '{')
def _get_public_key(): global public_key_json if public_key_json is None: public_key = read_file(current_app.app_config.oidc.public_rsa_signing_key_path) jwks = jwk.dumps(public_key, kty='RSA') jwks["alg"] = "RS256" jwks["kid"] = "sbs" jwks["use"] = "sig" public_key_json = {"keys": [jwks]} return public_key_json
def test_loads_okp_private_key(self): obj = { 'x': '11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo', 'd': 'nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A', 'crv': 'Ed25519', 'kty': 'OKP' } key = jwk.loads(obj) new_obj = jwk.dumps(key) self.assertEqual(obj['d'], new_obj['d'])
def get_public_key(private_key): return jwk.dumps( jwk.loads(private_key).public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ).decode(), kty=JWT_KEY_ALGORITHM, use="sig", kid=JWT_KEY_ID, alg=JWT_ALGORITHM, )
def generate_jwks() -> Tuple[dict, dict]: """Generate JWK set.""" # Generate keys print("Generating keys.") private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) public_key = private_key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) # Public data to public_key.json public_data = {"keys": [jwk.dumps(public_key, kty="RSA")]} public_data["keys"][0].update({"kid": secrets.token_hex(4)}) public_data["keys"][0].update({"alg": "RS256"}) # Private data to private_key.json return public_data, jwk.dumps(pem, kty="RSA")
def test_rsa_private_key(self): # https://tools.ietf.org/html/rfc7520#section-3.4 obj = RSA_PRIVATE_KEY key = jwk.loads(obj) new_obj = jwk.dumps(key, 'RSA') self.assertBase64IntEqual(new_obj['n'], obj['n']) self.assertBase64IntEqual(new_obj['e'], obj['e']) self.assertBase64IntEqual(new_obj['d'], obj['d']) self.assertBase64IntEqual(new_obj['p'], obj['p']) self.assertBase64IntEqual(new_obj['q'], obj['q']) self.assertBase64IntEqual(new_obj['dp'], obj['dp']) self.assertBase64IntEqual(new_obj['dq'], obj['dq']) self.assertBase64IntEqual(new_obj['qi'], obj['qi'])
def test_error_upon_invalid_jwt(self, load_config_mock): load_config_mock.return_value = load_test_env_vars( OIDC_DISCOVERY_URI= 'http://test.token.com/.well-known/oidc-configuration', SELF_DOMAIN='test.com', ) event = { 'queryStringParameters': { 'state': 'abc123', 'code': 'def987', }, 'headers': { 'Cookie': f'{main.USER_STATE_COOKIE_KEY}=abc123', }, } discovery_doc = { 'authorization_endpoint': 'http://test.token.com/token', 'jwks_uri': 'http://test.token.com/jwks', 'token_endpoint': 'http://test.token.com/token', } key_data = jwk.dumps(TEST_RSA_KEY, kty='RSA') test_jwt = jwt.encode( { 'alg': 'RS256' }, { 'username': '******' }, key_data, ).decode('utf-8') with requests_mock.Mocker() as mock: mock.get(os.environ['OIDC_DISCOVERY_URI'], json=discovery_doc) mock.get(discovery_doc['jwks_uri'], json={ 'keys': [ key_data, ], }) mock.post(discovery_doc['token_endpoint'], json={ 'id_token': 'testtoken', }) response = main.callback(event, None) assert response['statusCode'] == 400 assert 'Set-Cookie' not in response['headers']
def test_openid_authorize(self): request = testing.DummyRequest(path="/login") key = jwk.dumps('secret', 'oct', kid='f') oauth = OAuth() client = oauth.register( 'dev', client_id='dev', jwks={'keys': [key]}, api_base_url='https://i.b/api', access_token_url='https://i.b/token', authorize_url='https://i.b/authorize', client_kwargs={'scope': 'openid profile'}, ) resp = client.authorize_redirect(request, 'https://b.com/bar') assert resp.status_code == 302 url = resp.headers.get('location') assert 'nonce=' in url query_data = dict(url_decode(urlparse.urlparse(url).query)) token = get_bearer_token() token['id_token'] = generate_id_token( token, {'sub': '123'}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, nonce=query_data['nonce'], ) state = query_data['state'] metadata = { "issuer": "https://i.b", "id_token_signing_alg_values_supported": ["HS256", "RS256"], 'jwks': {'keys': [{'k': 'c2VjcmV0', 'kid': 'f', 'kty': 'oct'}]} } with ( mock.patch('requests.sessions.Session.send') as send, mock.patch.object(client, "load_server_metadata") as load_server_metadata ): send.return_value = mock_send_value(token) load_server_metadata.return_value = metadata request2 = testing.DummyRequest( path='/authorize', params={"state": state, "code": 'foo'}, ) request2.session = request.session token = client.authorize_access_token(request2) assert token['access_token'] == 'a' assert 'userinfo' in token assert token['userinfo']['sub'] == '123'
def jwks(): with open(current_app.config["JWT"]["PUBLIC_KEY"]) as fd: pubkey = fd.read() obj = jwk.dumps(pubkey, current_app.config["JWT"].get("KTY", DEFAULT_JWT_KTY)) return jsonify({ "keys": [{ "kid": None, "use": "sig", "alg": current_app.config["JWT"].get("ALG", DEFAULT_JWT_ALG), **obj, }] })
def test_openid_authorize(self): app = Flask(__name__) app.secret_key = '!' oauth = OAuth(app) key = jwk.dumps('secret', 'oct', kid='f') client = oauth.register( 'dev', client_id='dev', api_base_url='https://i.b/api', access_token_url='https://i.b/token', authorize_url='https://i.b/authorize', client_kwargs={'scope': 'openid profile'}, jwks={'keys': [key]}, ) with app.test_request_context(): resp = client.authorize_redirect('https://b.com/bar') self.assertEqual(resp.status_code, 302) url = resp.headers['Location'] query_data = dict(url_decode(urlparse.urlparse(url).query)) state = query_data['state'] self.assertIsNotNone(state) session_data = session[f'_state_dev_{state}'] nonce = session_data['data']['nonce'] self.assertIsNotNone(nonce) self.assertEqual(nonce, query_data['nonce']) token = get_bearer_token() token['id_token'] = generate_id_token( token, {'sub': '123'}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, nonce=query_data['nonce'], ) path = '/?code=a&state={}'.format(state) with app.test_request_context(path=path): session[f'_state_dev_{state}'] = session_data with mock.patch('requests.sessions.Session.send') as send: send.return_value = mock_send_value(token) token = client.authorize_access_token() self.assertEqual(token['access_token'], 'a') self.assertIn('userinfo', token)
def encrypt(self, data, type='AES', Key=None, dump=True): """ Encrypt data using RSA , AES param: type= type of key used in encryption: 'AES' or 'RSA' param: key= use your own key for RSA encryption param: dump= dumps the data if neccessary """ if dump == True: payload = str(json.dumps(data)).encode() else: payload = str(data).encode() if type == 'RSA': key = RSA.generate(2048) key = key.exportKey('PEM') key = jwk.dumps(key, kty='RSA') if Key: key = Key protected = {'alg': 'RSA-OAEP', 'enc': 'A256GCM'} jwe = JsonWebEncryption() token = jwe.serialize_compact(protected, payload, key) token = token.decode() else: key = secrets.token_urlsafe(32) token = SJCL().encrypt(payload, key, "ccm", 1000, 32) token['salt'] = str(token['salt']).replace("b'", "").replace("'", "") token['ct'] = str(token['ct']).replace("b'", "").replace("'", "") token['iv'] = str(token['iv']).replace("b'", "").replace("'", "") dic = {} dic['iv'] = token['iv'] dic['v'] = token['v'] dic['iter'] = token['iter'] dic['ks'] = token['ks'] dic['ts'] = token['ts'] dic['mode'] = token['mode'] dic['adata'] = token['adata'] dic['cipher'] = token['cipher'] dic['salt'] = token['salt'] dic['ct'] = token['ct'] token = dic return {'token': token, 'key': key}
def test_openid_authorize(self): request = self.factory.get('/login') request.session = self.factory.session key = jwk.dumps('secret', 'oct', kid='f') oauth = OAuth() client = oauth.register( 'dev', client_id='dev', jwks={'keys': [key]}, api_base_url='https://i.b/api', access_token_url='https://i.b/token', authorize_url='https://i.b/authorize', client_kwargs={'scope': 'openid profile'}, ) resp = client.authorize_redirect(request, 'https://b.com/bar') self.assertEqual(resp.status_code, 302) url = resp.get('Location') self.assertIn('nonce=', url) query_data = dict(url_decode(urlparse.urlparse(url).query)) token = get_bearer_token() token['id_token'] = generate_id_token( token, {'sub': '123'}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, nonce=query_data['nonce'], ) state = query_data['state'] with mock.patch('requests.sessions.Session.send') as send: send.return_value = mock_send_value(token) request2 = self.factory.get( '/authorize?state={}&code=foo'.format(state)) request2.session = request.session token = client.authorize_access_token(request2) self.assertEqual(token['access_token'], 'a') self.assertIn('userinfo', token) self.assertEqual(token['userinfo']['sub'], '123')
def test_rsa_private_key2(self): obj = { "kty": "RSA", "kid": "*****@*****.**", "use": "sig", "n": RSA_PRIVATE_KEY['n'], 'd': RSA_PRIVATE_KEY['d'], "e": "AQAB" } key = jwk.loads(obj) new_obj = jwk.dumps(key, 'RSA') self.assertBase64IntEqual(new_obj['n'], obj['n']) self.assertBase64IntEqual(new_obj['e'], obj['e']) self.assertBase64IntEqual(new_obj['d'], obj['d']) self.assertBase64IntEqual(new_obj['p'], RSA_PRIVATE_KEY['p']) self.assertBase64IntEqual(new_obj['q'], RSA_PRIVATE_KEY['q']) self.assertBase64IntEqual(new_obj['dp'], RSA_PRIVATE_KEY['dp']) self.assertBase64IntEqual(new_obj['dq'], RSA_PRIVATE_KEY['dq']) self.assertBase64IntEqual(new_obj['qi'], RSA_PRIVATE_KEY['qi'])
def test_parse_id_token(self): key = jwk.dumps('secret', 'oct', kid='f') token = get_bearer_token() id_token = generate_id_token( token, {'sub': '123'}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, nonce='n', ) app = Flask(__name__) app.secret_key = '!' oauth = OAuth(app) client = oauth.register( 'dev', client_id='dev', client_secret='dev', fetch_token=get_bearer_token, jwks={'keys': [key]}, issuer='https://i.b', id_token_signing_alg_values_supported=['HS256', 'RS256'], ) with app.test_request_context(): self.assertIsNone(client.parse_id_token(token, nonce='n')) token['id_token'] = id_token user = client.parse_id_token(token, nonce='n') self.assertEqual(user.sub, '123') claims_options = {'iss': {'value': 'https://i.b'}} user = client.parse_id_token(token, nonce='n', claims_options=claims_options) self.assertEqual(user.sub, '123') claims_options = {'iss': {'value': 'https://i.c'}} self.assertRaises(InvalidClaimError, client.parse_id_token, token, 'n', claims_options)
async def test_parse_id_token(): key = jwk.dumps('secret', 'oct', kid='f') token = get_bearer_token() id_token = generate_id_token( token, {'sub': '123'}, key, alg='HS256', iss='https://i.b', aud='dev', exp=3600, nonce='n', ) oauth = OAuth() client = oauth.register( 'dev', client_id='dev', client_secret='dev', fetch_token=get_bearer_token, jwks={'keys': [key]}, issuer='https://i.b', id_token_signing_alg_values_supported=['HS256', 'RS256'], ) req_scope = {'type': 'http', 'session': {'_dev_authlib_nonce_': 'n'}} req = Request(req_scope) user = await client.parse_id_token(req, token) assert user is None token['id_token'] = id_token user = await client.parse_id_token(req, token) assert user.sub == '123' claims_options = {'iss': {'value': 'https://i.b'}} user = await client.parse_id_token(req, token, claims_options) assert user.sub == '123' with pytest.raises(InvalidClaimError): claims_options = {'iss': {'value': 'https://i.c'}} await client.parse_id_token(req, token, claims_options)
def test_force_fetch_jwks_uri(self): secret_keys = read_file_path('jwks_private.json') token = get_bearer_token() id_token = generate_id_token( token, {'sub': '123'}, secret_keys, alg='RS256', iss='https://i.b', aud='dev', exp=3600, nonce='n', ) app = Flask(__name__) app.secret_key = '!' oauth = OAuth(app) client = oauth.register( 'dev', client_id='dev', client_secret='dev', fetch_token=get_bearer_token, jwks={'keys': [jwk.dumps('secret', 'oct', kid='f')]}, jwks_uri='https://i.b/jwks', issuer='https://i.b', ) def fake_send(sess, req, **kwargs): resp = mock.MagicMock() resp.json = lambda: read_file_path('jwks_public.json') resp.status_code = 200 return resp with app.test_request_context(): session['_dev_authlib_nonce_'] = 'n' self.assertIsNone(client.parse_id_token(token)) with mock.patch('requests.sessions.Session.send', fake_send): token['id_token'] = id_token user = client.parse_id_token(token) self.assertEqual(user.sub, '123')