def getPrivateKey(self, kid=None): """Get private key :param str kid: key ID :return: S_OK(obj)/S_ERROR() """ result = self.getActiveKeys(kid) if not result["OK"]: return result jwks = result["Value"] if kid: strkey = jwks[0]["key"] return S_OK(JsonWebKey.import_key(json.loads(jwks[0]["key"]))) newer = {} for jwk in jwks: if int(jwk["expires_at"]) > int( newer.get("expires_at", time.time() + (24 * 3600))): newer = jwk if not newer.get("key"): result = self.generateRSAKeys() if not result["OK"]: return result newer = result["Value"] return S_OK(JsonWebKey.import_key(json.loads(newer["key"])))
def jwks(): from authlib.jose import JsonWebKey from authlib.jose import JWK_ALGORITHMS jwk = JsonWebKey(algorithms=JWK_ALGORITHMS) key = jwk.dumps(config['PUBLIC_KEY'], kty='RSA') key['kid'] = '1' return {"keys": [key]}
def __init__(self): rsa = RSAKey.generate_key(key_size=4096, is_private=True) self.jwk_public_key = JsonWebKey.import_key(rsa.as_pem(is_private=False)) self.jwk_private_key = JsonWebKey.import_key(rsa.as_pem(is_private=True)) self._key_alg = 'RSA-OAEP-256' self._content_alg = 'A256GCM' self._compression_alg = 'DEF'
def load_key(header, payload): jwk_set = JsonWebKey.import_key_set(self.fetch_jwk_set()) try: return jwk_set.find_by_kid(header.get('kid')) except ValueError: # re-try with new jwk set jwk_set = JsonWebKey.import_key_set( self.fetch_jwk_set(force=True)) return jwk_set.find_by_kid(header.get('kid'))
def jwks_schema() -> dict: from authlib.jose import JsonWebKey from authlib.jose import JWK_ALGORITHMS jwk = JsonWebKey(algorithms=JWK_ALGORITHMS) key = jwk.dumps(current_app.config['PUBLIC_KEY'], kty='RSA') key['kid'] = '1' return {'keys': [key]}
def test_import_keys(self): rsa_pub_pem = read_file_path('rsa_public.pem') self.assertRaises(ValueError, JsonWebKey.import_key, rsa_pub_pem, {'kty': 'EC'}) key = JsonWebKey.import_key(raw=rsa_pub_pem, options={'kty': 'RSA'}) self.assertIn('e', dict(key)) self.assertIn('n', dict(key)) key = JsonWebKey.import_key(raw=rsa_pub_pem) self.assertIn('e', dict(key)) self.assertIn('n', dict(key))
def get_jwk(): LOGGER.debug('Loading jwk from public key...') key_data = None with open(app_context().config['jwk_public_key_path'], 'rb') as _key_file: key_data = _key_file.read() _jwk = JsonWebKey(JWK_ALGORITHMS) _key_dict = _jwk.dumps(key_data, kty='RSA', use='sig', alg='RS256', kid="demo_key") return {'keys': [_key_dict]}
def test_03_keyid(self): print("\n-----", sys._getframe().f_code.co_name, "-----") pem1 = "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDSt1IOhS5ZmY6nkX/Wh7pT+Y45TmYxrwoc1pG72v387oAoGCCqGSM49\nAwEHoUQDQgAEdEsjD2i2LytHOjNxxc9PbFeqQ89aMLOfmdBbEoSOhZBukJ52EqQM\nhOdgHqyqD4hEyYxgDu3uIbKat+lEZEhb3Q==\n-----END EC PRIVATE KEY-----" keypair1 = bbclib.KeyPair() keypair1.mk_keyobj_from_private_key_pem(pem1) keyid1 = keypair1.get_key_id() pubkey = keypair1.get_public_key_in_pem() jwk = JsonWebKey(algorithms=JWK_ALGORITHMS) obj = jwk.dumps(pubkey, kty='EC') json_obj = json.dumps(obj, separators=(',', ':'), sort_keys=True) keyid2 = hashlib.sha256(json_obj.encode()).digest() assert keyid1 == keyid2
def test_generate_keys(self): key = JsonWebKey.generate_key(kty='oct', crv_or_size=256, is_private=True) self.assertEqual(key['kty'], 'oct') key = JsonWebKey.generate_key(kty='EC', crv_or_size='P-256') self.assertEqual(key['kty'], 'EC') key = JsonWebKey.generate_key(kty='RSA', crv_or_size=2048) self.assertEqual(key['kty'], 'RSA') key = JsonWebKey.generate_key(kty='OKP', crv_or_size='Ed25519') self.assertEqual(key['kty'], 'OKP')
def test_import_key_set(self): jwks_public = read_file_path('jwks_public.json') key_set1 = JsonWebKey.import_key_set(jwks_public) key1 = key_set1.find_by_kid('abc') self.assertEqual(key1['e'], 'AQAB') key_set2 = JsonWebKey.import_key_set(jwks_public['keys']) key2 = key_set2.find_by_kid('abc') self.assertEqual(key2['e'], 'AQAB') key_set3 = JsonWebKey.import_key_set(json_dumps(jwks_public)) key3 = key_set3.find_by_kid('abc') self.assertEqual(key3['e'], 'AQAB') self.assertRaises(ValueError, JsonWebKey.import_key_set, 'invalid')
def generate_key_pair(filename, kid=None): """ 'kid' will default to the jwk thumbprint if not set explicitly. Reference: https://tools.ietf.org/html/rfc7638 """ options = {} if kid: options["kid"] = kid jwk = JsonWebKey.generate_key("RSA", 2048, is_private=True, options=options) print(("Writing public key to %s.jwk" % filename)) with open("%s.jwk" % filename, mode="w") as f: f.truncate(0) f.write(jwk.as_json()) print(("Writing key ID to %s.kid" % filename)) with open("%s.kid" % filename, mode="w") as f: f.truncate(0) f.write(jwk.as_dict()["kid"]) print(("Writing private key to %s.pem" % filename)) with open("%s.pem" % filename, mode="wb") as f: f.truncate(0) f.write(jwk.get_private_key().private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ))
def _create_token( self, type_token: str, exp_time: Optional[int], *, custom_claims, ) -> str: # Data section claims = { "iat": timezone.now(), "nbf": timezone.now(), "jti": str(uuid.uuid4()), "type": type_token, **custom_claims, } if exp_time: claims["exp"] = exp_time if self.issuer: claims["iss"] = self.issuer if self.audience: claims["aud"] = self.audience key = JsonWebKey.import_key(self.private_key) headers = {"alg": "RS256", "kid": key.thumbprint()} return jwt.encode(header=headers, payload=claims, key=key)
def encrypted_script_response( self, opts: vpb.ExecuteScriptRequest.EncryptionOptions ) -> vpb.ExecuteScriptResponse: ''' Returns a script repsonse with encrypted row batch fields, if they exist, and if the options are set. ''' es_resp = self.execute_script_response if not es_resp.HasField("data"): return es_resp if opts is None: return es_resp # Now we encrypt the batch. rb = es_resp.data.batch.SerializeToString() key = JsonWebKey.import_key(json.loads(opts.jwk_key)) encrypted_batch = JsonWebEncryption().serialize_compact( { 'alg': opts.key_alg, 'enc': opts.content_alg, 'zip': opts.compression_alg, }, rb, key) # Make sure we only send the encrypted_batch through. es_resp.data.ClearField('batch') es_resp.data.encrypted_batch = encrypted_batch return es_resp
def _load_keys_from_url(url, verify=True): """ Expects something on this form: {"keys": [ { "kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1" }, { "kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFb....." "e":"AQAB", "kid":"2011-04-29" } ] } """ keys = [] r = request("GET", url, allow_redirects=True, verify=verify) if r.status_code == 200: keys_dict = json.loads(r.text) for key_spec in keys_dict["keys"]: key = JsonWebKey.import_key(key_spec) keys.append(key) else: raise Exception("Error loading JWK set - HTTP GET error: %s" % r.status_code) return keys
def userinfo(): req = flask.request log_request('GET-USERINFO', req) access_token = req.headers.get('Authorization', None) # TODO: if not access_token # TODO: Validate access-token log.info("GET-USERINFO: Access token: '{}'".format(access_token)) access_token_parts = access_token.split() if access_token_parts[0].lower() != 'bearer' or len(access_token_parts) != 2: return flask.render_template('error.html', text='Invalid authorization') access_token = access_token_parts[1] # FIXME with open('jwt-key.pub', 'rb') as f: key_data = f.read() pub_key = JsonWebKey.import_key(key_data, {'kty': 'RSA'}) access_token_json = jwt.decode(access_token, pub_key) scope = access_token_json['scope'] # TODO: Validate audience in access token covers /userinfo log.info("GET-USERINFO: Access token audience: '{}'".format(access_token_json['aud'])) log.info("GET-USERINFO: Scope '{}'".format(scope)) claims = dict() # See https://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims for what claims to include in access token if 'profile' in scope: claims['name'] = 'Name of user is {}'.format(access_token_json['sub'].capitalize()) return claims
def generate_service_key(service, expiration_date, kid=None, name="", metadata=None, rotation_duration=None): """ 'kid' will default to the jwk thumbprint if not set explicitly. Reference: https://tools.ietf.org/html/rfc7638 """ options = {} if kid: options["kid"] = kid jwk = JsonWebKey.generate_key("RSA", 2048, is_private=True, options=options) kid = jwk.as_dict()["kid"] key = create_service_key( name, kid, service, jwk.as_dict(), metadata or {}, expiration_date, rotation_duration=rotation_duration, ) return (jwk.get_private_key(), key)
def _validate(self): """ Reference: https://docs.docker.com/registry/spec/manifest-v2-1/#signed-manifests """ if not self._signatures: return payload_str = self._payload for signature in self._signatures: protected = signature[DOCKER_SCHEMA1_PROTECTED_KEY] sig = signature[DOCKER_SCHEMA1_SIGNATURE_KEY] jwk = JsonWebKey.import_key( signature[DOCKER_SCHEMA1_HEADER_KEY]["jwk"]) jws = JsonWebSignature( algorithms=[signature[DOCKER_SCHEMA1_HEADER_KEY]["alg"]]) obj_to_verify = { DOCKER_SCHEMA1_PROTECTED_KEY: protected, DOCKER_SCHEMA1_SIGNATURE_KEY: sig, DOCKER_SCHEMA1_HEADER_KEY: { "alg": signature[DOCKER_SCHEMA1_HEADER_KEY]["alg"] }, "payload": base64url_encode(payload_str), } try: data = jws.deserialize_json(obj_to_verify, jwk.get_public_key()) except (BadSignatureError, UnsupportedAlgorithmError): raise InvalidSchema1Signature() if not data: raise InvalidSchema1Signature()
def fetch_keys(self): if not self.key_url: return response = requests.get(self.key_url) if response.ok: data = response.json() self._keyset = JsonWebKey.import_key_set(data)
def load_key(header, _): alg = header.get("alg") if alg in ["HS256", "HS384", "HS512"]: # For HS256: client secret is used for id_token signing return self.client_secret elif alg in ["RS256", "RS384", "RS512"]: jwk_set = JsonWebKey.import_key_set(self.fetch_jwk_set()) try: return jwk_set.find_by_kid(header.get("kid")) except ValueError: # re-try with new jwk set jwk_set = JsonWebKey.import_key_set( self.fetch_jwk_set(force=True)) return jwk_set.find_by_kid(header.get("kid")) else: raise RuntimeError(f"Unsupported id_token algorithm: '{alg}'")
def verifyToken(self, accessToken=None, jwks=None): """Verify access token :param str accessToken: access token :param dict jwks: JWKs :return: dict """ # Define an access token if not accessToken: accessToken = self.token["access_token"] # Renew a JWKs of an identity provider if needed if not jwks: result = self.updateJWKs() if not result["OK"]: return result jwks = self.jwks if not jwks: return S_ERROR("JWKs not found.") # Try to decode and verify an access token self.log.debug("Try to decode token %s with JWKs:\n" % accessToken, pprint.pformat(jwks)) try: return S_OK(jwt.decode(accessToken, JsonWebKey.import_key_set(jwks))) except Exception as e: self.log.exception(e) return S_ERROR(repr(e))
def dummy_key_pair(): key = ec.generate_private_key(ec.SECP256R1(), backend=default_backend()) private_key = key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()) public_key = key.public_key().public_bytes( serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH) PRIVATE_KEY = JsonWebKey.import_key(private_key, {'kty': 'EC'}) PUBLIC_KEY = JsonWebKey.import_key(public_key, {'kty': 'EC'}) keys = {'PUBLIC_KEY': PUBLIC_KEY, 'PRIVATE_KEY': PRIVATE_KEY} return keys
async def parse_id_token(self, token, nonce, claims_options=None): """Return an instance of UserInfo from token's ``id_token``.""" claims_params = dict( nonce=nonce, client_id=self.client_id, ) if 'access_token' in token: claims_params['access_token'] = token['access_token'] claims_cls = CodeIDToken else: claims_cls = ImplicitIDToken metadata = await self.load_server_metadata() if claims_options is None and 'issuer' in metadata: claims_options = {'iss': {'values': [metadata['issuer']]}} alg_values = metadata.get('id_token_signing_alg_values_supported') if not alg_values: alg_values = ['RS256'] jwt = JsonWebToken(alg_values) jwk_set = await self.fetch_jwk_set() try: claims = jwt.decode( token['id_token'], key=JsonWebKey.import_key_set(jwk_set), claims_cls=claims_cls, claims_options=claims_options, claims_params=claims_params, ) except ValueError: jwk_set = await self.fetch_jwk_set(force=True) claims = jwt.decode( token['id_token'], key=JsonWebKey.import_key_set(jwk_set), claims_cls=claims_cls, claims_options=claims_options, claims_params=claims_params, ) # https://github.com/lepture/authlib/issues/259 if claims.get('nonce_supported') is False: claims.params['nonce'] = None claims.validate(leeway=120) return UserInfo(claims)
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 get_jwk(): LOGGER.debug('Loading jwk from public key...') key_data = None with open(app_context().config['jwk_public_key_path'], 'rb') as _key_file: key_data = _key_file.read() LOGGER.debug(key_data) key = JsonWebKey.import_key(key_data, {'kty': 'RSA'}) return {'keys': [{**key.as_dict(), 'kid': 'demo_key'}]}
def test_loads_okp_public_key(self): obj = { "x": "AD9E0JYnpV-OxZbd8aN1t4z71Vtf6JcJC7TYHT0HDbg", "crv": "Ed25519", "kty": "OKP" } jwk = JsonWebKey(RFC8037_ALGORITHMS) key = jwk.loads(obj) new_obj = jwk.dumps(key) self.assertEqual(obj['x'], new_obj['x'])
def refresh_token(): req = flask.request session_cookie = req.cookies.get(SESSION_COOKIE_NAME) session_id = session_cookie if session_id in sessions: session = sessions[session_id] else: return flask.make_response(flask.redirect(own_url, code=303)) log.info('Refresh token, session {}'.format(session_id)) data = { 'refresh_token': session['refresh_token'], 'grant_type': 'refresh_token' } headers = { 'Authorization': 'Basic ' + encode_client_creds(client_id, client_secret), 'Content-type': 'application/x-www-form-urlencoded' } log.info("Refresh token from url: '{}'".format(oauth2_token_url)) response = requests.post(oauth2_token_url, data=data, headers=headers) log_response('REFRESH-TOKEN', response) if response.status_code != 200: return 'Failed with status {}: {}'.format(response.status_code, response.text) response_json = response.json() for token_type in ['id_token', 'access_token', 'refresh_token']: if token_type in response_json: log.info("Got {} token '{}'".format(token_type, response_json[token_type])) if 'id_token' in response_json: id_token = response_json['id_token'] token_pub_jwk_json = token_get_jwk(id_token) token_pub_jwk = JsonWebKey.import_key(token_pub_jwk_json) claims = jwt.decode(id_token, token_pub_jwk) session['id_token'] = id_token, session['id_token_claims'] = claims if 'access_token' in response_json: session['access_token'] = response_json['access_token'] if 'refresh_token' in response_json: session['refresh_token'] = response_json['refresh_token'] sessions[session_cookie] = session resp = flask.make_response(flask.redirect(own_url, code=303)) return resp
def test_loads_okp_private_key(self): obj = { 'x': '11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo', 'd': 'nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A', 'crv': 'Ed25519', 'kty': 'OKP' } jwk = JsonWebKey(RFC8037_ALGORITHMS) key = jwk.loads(obj) new_obj = jwk.dumps(key) self.assertEqual(obj['d'], new_obj['d'])
def test_dumps_okp_public_key(self): key = read_file_path('ed25519-ssh.pub') 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') key = read_file_path('ed25519-pub.pem') obj = jwk.dumps(key, 'OKP') self.assertEqual(obj['kty'], 'OKP') self.assertEqual(obj['crv'], 'Ed25519')
def signing_key(): jwk = JsonWebKey.generate_key("RSA", 2048, is_private=True) return { "id": "somekey", "private_key": jwk.get_private_key().private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ), "jwk": jwk.as_dict(), }
def readToken(self, token): """Decode self token :param str token: token to decode :return: S_OK(dict)/S_ERROR() """ result = self.db.getKeySet() if not result["OK"]: return result try: return S_OK(jwt.decode(token, JsonWebKey.import_key_set(result["Value"].as_dict()))) except Exception as e: sLog.exception(e) return S_ERROR(repr(e))