diff --git a/data/model/service_keys.py b/data/model/service_keys.py index c4dad1828..a02d49b9f 100644 --- a/data/model/service_keys.py +++ b/data/model/service_keys.py @@ -1,10 +1,18 @@ +import json + from calendar import timegm from datetime import datetime, timedelta +from hashlib import sha256 from peewee import JOIN_LEFT_OUTER +from Crypto.PublicKey import RSA +from jwkest.jwk import RSAKey + from data.database import db_for_update, User, ServiceKey, ServiceKeyApproval from data.model import ServiceKeyDoesNotExist, ServiceKeyAlreadyApproved, db_transaction, config from data.model.notification import create_notification, delete_all_notifications_by_path_prefix +from util import canonicalize + def _expired_keys_clause(service): return ((ServiceKey.service == service) & @@ -49,6 +57,18 @@ def create_service_key(name, kid, service, jwk, metadata, expiration_date): _notify_superusers(key) _gc_expired(service) + return key + + +def generate_service_key(service, expiration_date, kid=None, name='', metadata=None): + private_key = RSA.generate(2048) + jwk = RSAKey(key=private_key.publickey()).serialize() + if kid is None: + kid = sha256(json.dumps(canonicalize(jwk), separators=(',', ':'))).hexdigest() + + key = create_service_key(name, kid, service, jwk, metadata or {}, expiration_date) + return (private_key, key) + def replace_service_key(old_kid, kid, jwk, metadata, expiration_date): try: diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index ab91bda00..c0a1b787b 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -1,17 +1,13 @@ """ Superuser API. """ -import json import logging import os import string from datetime import datetime -from hashlib import sha256 from random import SystemRandom -from Crypto.PublicKey import RSA from flask import request, make_response, jsonify -from jwkest.jwk import RSAKey import features @@ -26,7 +22,6 @@ from endpoints.api import (ApiResource, nickname, resource, validate_json_reques from endpoints.api.logs import get_logs, get_aggregate_logs from data import model from data.database import ServiceKeyApprovalType -from util import canonicalize from util.useremails import send_confirmation_email, send_recovery_email @@ -576,22 +571,17 @@ class SuperUserServiceKeyManagement(ApiResource): 'ip': request.remote_addr, }) - # Generate the private key but *do not save it on the server anywhere*. - private_key = RSA.generate(2048) - jwk = RSAKey(key=private_key.publickey()).serialize() - kid = sha256(json.dumps(canonicalize(jwk), separators=(',', ':'))).hexdigest() - - # Create the service key. - model.service_keys.create_service_key(body.get('name', ''), kid, body['service'], jwk, - metadata, expiration_date) - + # Generate a key with a private key that we *never save*. + (private_key, key) = model.service_keys.generate_service_key(body['service'], metadata, + expiration_date, + name=body.get('name', '')) # Auto-approve the service key. - model.service_keys.approve_service_key(kid, user, ServiceKeyApprovalType.SUPERUSER, + model.service_keys.approve_service_key(key.kid, user, ServiceKeyApprovalType.SUPERUSER, notes=body.get('notes', '')) # Log the creation and auto-approval of the service key. key_log_metadata = { - 'kid': kid, + 'kid': key.kid, 'preshared': True, 'service': body['service'], 'name': body.get('name', ''), @@ -603,7 +593,7 @@ class SuperUserServiceKeyManagement(ApiResource): log_action('service_key_approve', None, key_log_metadata) return jsonify({ - 'kid': kid, + 'kid': key.kid, 'name': body.get('name', ''), 'public_key': private_key.publickey().exportKey('PEM'), 'private_key': private_key.exportKey('PEM'), diff --git a/initdb.py b/initdb.py index c4afe8ebc..f72cb7bd2 100644 --- a/initdb.py +++ b/initdb.py @@ -157,20 +157,18 @@ def __create_subtree(with_storage, repo, structure, creator_username, parent, ta def __generate_service_key(kid, name, user, timestamp, approval_type, expiration=None, - metadata=None): - private_key = RSA.generate(1024) - jwk = RSAKey(key=private_key.publickey()).serialize() + metadata=None, service='sample_service'): + _, key = model.service_keys.generate_service_key(service, expiration, kid=kid, + name=name, metadata=metadata) - metadata = metadata or {} - model.service_keys.create_service_key(name, kid, 'sample_service', jwk, metadata, expiration) if approval_type is not None: - model.service_keys.approve_service_key(kid, user, approval_type, + model.service_keys.approve_service_key(key.kid, user, approval_type, notes='The **test** apporval') key_metadata = { 'kid': kid, 'preshared': True, - 'service': 'sample_service', + 'service': service, 'name': name, 'expiration_date': expiration, 'auto_approved': True @@ -664,6 +662,9 @@ def populate_database(minimal=False, with_storage=False): __generate_service_key('kid4', 'autorotatingkey', new_user_1, six_ago, ServiceKeyApprovalType.KEY_ROTATION, today + timedelta(1), dict(rotation_ttl=timedelta(hours=12).total_seconds())) + __generate_service_key('kid5', 'key for another service', new_user_1, today, + ServiceKeyApprovalType.SUPERUSER, today + timedelta(14), + service='different_sample_service') model.log.log_action('org_create_team', org.username, performer=new_user_1, timestamp=week_ago, metadata={'team': 'readers'}) diff --git a/test/data/test.db b/test/data/test.db index 4ac351c8a..17eeccc8b 100644 Binary files a/test/data/test.db and b/test/data/test.db differ