From 1abe8a2ec056fecadbbd025675eeb32fc18c0fcb Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Fri, 2 Nov 2018 20:14:01 +0000 Subject: [PATCH] Add keystone-manage create_jws_keypair functionality Thw JSON Web Token provider implementation is going to need keys in order to issue and validate tokens, very similar to how the fernet provider operates, but using asymmetric signing instead of symmetric encryption. This commit addes a new subcommand to the keystone-manage binary that creates a ECDSA key pair for creating and validating JWS tokens. bp json-web-tokens Change-Id: I9cf5c168bae2a90aba3d696e3f6ce3028998121a --- doc/source/cli/commands.rst | 1 + keystone/cmd/cli.py | 39 ++++++++++++++++++++++++++++++++ keystone/common/jwt_utils.py | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 keystone/common/jwt_utils.py diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 03e2477096..b27b5e75dd 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -8,6 +8,7 @@ Invoking ``keystone-manage`` by itself will give you some usage information. Available commands: * ``bootstrap``: Perform the basic bootstrap process. +* ``create_jws_keypair``: Create an ECDSA key pair for JWS token signing. * ``credential_migrate``: Encrypt credentials using a new primary key. * ``credential_rotate``: Rotate Fernet keys for credential encryption. * ``credential_setup``: Setup a Fernet key repository for credential encryption. diff --git a/keystone/cmd/cli.py b/keystone/cmd/cli.py index 55b5ff4986..e15df72dfb 100644 --- a/keystone/cmd/cli.py +++ b/keystone/cmd/cli.py @@ -31,6 +31,7 @@ from keystone.cmd import bootstrap from keystone.cmd import doctor from keystone.common import driver_hints from keystone.common import fernet_utils +from keystone.common import jwt_utils from keystone.common import sql from keystone.common.sql import upgrades from keystone.common import utils @@ -478,6 +479,43 @@ class FernetRotate(BasePermissionsSetup): keystone_user_id, keystone_group_id, 'fernet_receipts') +class CreateJWSKeyPair(BasePermissionsSetup): + """Create a key pair for signing and validating JWS tokens. + + This command creates a public and private key pair to use for signing and + validating JWS token signatures. The key pair is written to the directory + where the command is invoked. + + """ + + name = 'create_jws_keypair' + + @classmethod + def add_argument_parser(cls, subparsers): + parser = super(CreateJWSKeyPair, cls).add_argument_parser(subparsers) + + parser.add_argument( + '--force', action='store_true', + help=('Forcibly overwrite keys if they already exist') + ) + return parser + + @classmethod + def main(cls): + current_directory = os.getcwd() + private_key_path = os.path.join(current_directory, 'private.pem') + public_key_path = os.path.join(current_directory, 'public.pem') + + if os.path.isfile(private_key_path) and not CONF.command.force: + raise SystemExit(_('Private key %(path)s already exists') + % {'path': private_key_path}) + if os.path.isfile(public_key_path) and not CONF.command.force: + raise SystemExit(_('Public key %(path)s already exists') + % {'path': public_key_path}) + + jwt_utils.create_jws_keypair(private_key_path, public_key_path) + + class TokenSetup(BasePermissionsSetup): """Setup a key repository for tokens. @@ -1253,6 +1291,7 @@ CMDS = [ DomainConfigUpload, FernetRotate, FernetSetup, + CreateJWSKeyPair, MappingPopulate, MappingPurge, MappingEngineTester, diff --git a/keystone/common/jwt_utils.py b/keystone/common/jwt_utils.py new file mode 100644 index 0000000000..9f0aa9b093 --- /dev/null +++ b/keystone/common/jwt_utils.py @@ -0,0 +1,43 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import serialization + + +def create_jws_keypair(private_key_path, public_key_path): + """Create an ECDSA key pair using an secp256r1, or NIST P-256, curve. + + :param private_key_path: location to save the private key + :param public_key_path: location to save the public key + + """ + private_key = ec.generate_private_key(ec.SECP256R1(), default_backend()) + + with open(private_key_path, 'wb') as f: + f.write( + private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + ) + + public_key = private_key.public_key() + with open(public_key_path, 'wb') as f: + f.write( + public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + )