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
This commit is contained in:
Lance Bragstad 2018-11-02 20:14:01 +00:00
parent 781aea6193
commit 1abe8a2ec0
3 changed files with 83 additions and 0 deletions

View File

@ -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.

View File

@ -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,

View File

@ -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
)
)