diff --git a/data/model/service_keys.py b/data/model/service_keys.py index 713b48442..868c9bea9 100644 --- a/data/model/service_keys.py +++ b/data/model/service_keys.py @@ -1,3 +1,4 @@ +from calendar import timegm from datetime import datetime, timedelta from app import app @@ -32,8 +33,8 @@ def _notify_superusers(key): 'service': key.service, 'jwk': key.jwk, 'metadata': key.metadata, - 'created_date': key.created_date, - 'expiration_date': key.expiration_date, + 'created_date': timegm(key.created_date.utctimetuple()), + 'expiration_date': timegm(key.created_date.utctimetuple()), } superusers = User.select().where(User.username << app.config['SUPER_USERS']) @@ -136,4 +137,7 @@ def list_service_keys(service): def get_service_key(kid, service=None): - return _list_service_keys_query(kid=kid, service=service).get() + try: + return _list_service_keys_query(kid=kid, service=service).get() + except ServiceKey.DoesNotExist: + raise ServiceKeyDoesNotExist diff --git a/endpoints/key_server.py b/endpoints/key_server.py index 8911ee30f..ce140b689 100644 --- a/endpoints/key_server.py +++ b/endpoints/key_server.py @@ -1,18 +1,24 @@ +import logging + from datetime import datetime import jwt from flask import Blueprint, jsonify, abort, request, make_response from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicNumbers from cryptography.hazmat.backends import default_backend +from jwkest.jwk import keyrep, RSAKey, ECKey import data.model import data.model.service_keys from app import app +from auth.registry_jwt_auth import TOKEN_REGEX from util.security import strictjwt +logger = logging.getLogger(__name__) key_server = Blueprint('key_server', __name__) JWT_HEADER_NAME = 'Authorization' @@ -36,9 +42,18 @@ def _validate_jwk(jwk, kid): abort(400) +def _jwk_dict_to_public_key(jwk): + jwkest_key = keyrep(jwk) + if isinstance(jwkest_key, RSAKey): + pycrypto_key = jwkest_key.key + return RSAPublicNumbers(e=pycrypto_key.e, n=pycrypto_key.n).public_key(default_backend()) + elif isinstance(jwkest_key, ECKey): + x, y = jwkest_key.get_key() + return EllipticCurvePublicNumbers(x, y, jwkest_key.curve).public_key(default_backend()) + + def _validate_jwt(encoded_jwt, jwk, service): - public_key = RSAPublicNumbers(e=jwk.e, - n=jwk.n).public_key(default_backend()) + public_key = _jwk_dict_to_public_key(jwk) try: strictjwt.decode(encoded_jwt, public_key, algorithms=['RS256'], @@ -49,6 +64,7 @@ def _validate_jwt(encoded_jwt, jwk, service): def _signer_kid(encoded_jwt): decoded_jwt = jwt.decode(encoded_jwt, verify=False) + logger.debug(decoded_jwt) return decoded_jwt.get('signer_kid', None) @@ -88,20 +104,24 @@ def put_service_keys(service, kid): abort(400) try: - jwk = request.json() + jwk = request.get_json() except ValueError: abort(400) - encoded_jwt = request.headers.get(JWT_HEADER_NAME, None) - if not encoded_jwt: + logger.debug(jwk) + + jwt_header = request.headers.get(JWT_HEADER_NAME, '') + match = TOKEN_REGEX.match(jwt_header) + if match is None: abort(400) + encoded_jwt = match.group(1) _validate_jwk(jwk, kid) metadata = {'ip': request.remote_addr} signer_kid = _signer_kid(encoded_jwt) - if kid == signer_kid or signer_kid == '': + if kid == signer_kid or signer_kid is None: # The key is self-signed. Create a new instance and await approval. _validate_jwt(encoded_jwt, jwk, service) data.model.service_keys.create_service_key('', kid, service, jwk, metadata, expiration_date) @@ -125,9 +145,11 @@ def put_service_keys(service, kid): @key_server.route('/services//keys/', methods=['DELETE']) def delete_service_key(service, kid): - encoded_jwt = request.headers.get(JWT_HEADER_NAME, None) - if not encoded_jwt: + jwt_header = request.headers.get(JWT_HEADER_NAME, '') + match = TOKEN_REGEX.match(jwt_header) + if match is None: abort(400) + encoded_jwt = match.group(1) signer_kid = _signer_kid(encoded_jwt) signer_key = _signer_key(service, signer_kid)