summaryrefslogtreecommitdiffstats
path: root/base/common/python
diff options
context:
space:
mode:
authorAde Lee <alee@redhat.com>2017-04-03 12:56:48 -0400
committerAde Lee <alee@redhat.com>2017-04-06 21:46:08 -0400
commit8463f5f791ced714d64ff891dc015666a971454b (patch)
tree36cc1fa1b305bbcc54055812376ded16eeef3e8d /base/common/python
parent4ab0608cbda0c9336c5eb9ea40a7d3ca769ab17b (diff)
downloadpki-8463f5f791ced714d64ff891dc015666a971454b.tar.gz
pki-8463f5f791ced714d64ff891dc015666a971454b.tar.xz
pki-8463f5f791ced714d64ff891dc015666a971454b.zip
Add python-cryptography crypto provider
The python-cryptography provider is added. It will use AES mechanisms by default. The eventual goal is to use this provider by default, and to obsolete the NSS CryptoProvider. Added some methods to determine which crypto keyset levels are supported by the crypto provider. Change-Id: Ifd47f0de765a9f0d157e8be678d5d06437bda819
Diffstat (limited to 'base/common/python')
-rw-r--r--base/common/python/pki/crypto.py206
-rw-r--r--base/common/python/pki/util.py6
-rw-r--r--base/common/python/setup.py2
3 files changed, 197 insertions, 17 deletions
diff --git a/base/common/python/pki/crypto.py b/base/common/python/pki/crypto.py
index 86fa16eb7..b767abd2e 100644
--- a/base/common/python/pki/crypto.py
+++ b/base/common/python/pki/crypto.py
@@ -23,13 +23,21 @@ Module containing crypto classes.
"""
from __future__ import absolute_import
import abc
-import nss.nss as nss
import os
-import six
import shutil
import subprocess
import tempfile
+import nss.nss as nss
+import six
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.ciphers import (
+ Cipher, algorithms, modes
+)
+from cryptography.hazmat.primitives import padding
+from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
+import cryptography.x509
+
class CryptoProvider(six.with_metaclass(abc.ABCMeta, object)):
"""
@@ -43,30 +51,32 @@ class CryptoProvider(six.with_metaclass(abc.ABCMeta, object)):
@abc.abstractmethod
def initialize(self):
""" Initialization code """
- pass
- @staticmethod
@abc.abstractmethod
- def generate_nonce_iv(mechanism):
+ def get_supported_algorithm_keyset(self):
+ """ returns highest supported algorithm keyset """
+
+ @abc.abstractmethod
+ def set_algorithm_keyset(self, level):
+ """ sets required keyset """
+
+ @abc.abstractmethod
+ def generate_nonce_iv(self, mechanism):
""" Create a random initialization vector """
- pass
@abc.abstractmethod
def generate_symmetric_key(self, mechanism=None, size=0):
""" Generate and return a symmetric key """
- pass
@abc.abstractmethod
def generate_session_key(self):
""" Generate a session key to be used for wrapping data to the DRM
This must return a 3DES 168 bit key """
- pass
@abc.abstractmethod
def symmetric_wrap(self, data, wrapping_key, mechanism=None,
nonce_iv=None):
""" encrypt data using a symmetric key (wrapping key)"""
- pass
@abc.abstractmethod
def symmetric_unwrap(self, data, wrapping_key, mechanism=None,
@@ -77,7 +87,6 @@ class CryptoProvider(six.with_metaclass(abc.ABCMeta, object)):
The mechanism is the type of key used to do the wrapping. It defaults
to a 56 bit DES3 key.
"""
- pass
@abc.abstractmethod
def asymmetric_wrap(self, data, wrapping_cert, mechanism=None):
@@ -86,12 +95,10 @@ class CryptoProvider(six.with_metaclass(abc.ABCMeta, object)):
The mechanism is the type of symmetric key, which defaults to a 56 bit
DES3 key.
"""
- pass
# abc.abstractmethod
def get_cert(self, cert_nick):
""" Get the certificate for the specified cert_nick. """
- pass
class NSSCryptoProvider(CryptoProvider):
@@ -152,6 +159,18 @@ class NSSCryptoProvider(CryptoProvider):
"""
nss.nss_init(self.certdb_dir)
+ def get_supported_algorithm_keyset(self):
+ """ returns highest supported algorithm keyset """
+ return 0
+
+ def set_algorithm_keyset(self, level):
+ """ sets required keyset """
+ if level > 0:
+ raise Exception("Invalid keyset")
+
+ # basically, do what we have always done, no need to set anything
+ # special here.
+
def import_cert(self, cert_nick, cert, trust=',,'):
""" Import a certificate into the nss database
"""
@@ -170,8 +189,7 @@ class NSSCryptoProvider(CryptoProvider):
'-i', cert_file.name]
subprocess.check_call(command)
- @staticmethod
- def generate_nonce_iv(mechanism=nss.CKM_DES3_CBC_PAD):
+ def generate_nonce_iv(self, mechanism=nss.CKM_DES3_CBC_PAD):
""" Create a random initialization vector """
iv_length = nss.get_iv_length(mechanism)
if iv_length > 0:
@@ -237,6 +255,8 @@ class NSSCryptoProvider(CryptoProvider):
"""
:param data Data to be wrapped
:param wrapping_key Symmetric key to wrap data
+ :param mechanism Mechanism to user when wrapping
+ :param nonce_iv Nonce to use when wrapping
Wrap (encrypt) data using the supplied symmetric key
"""
@@ -255,6 +275,7 @@ class NSSCryptoProvider(CryptoProvider):
"""
:param data Data to be unwrapped
:param wrapping_key Symmetric key to unwrap data
+ :param mechanism Mechanism to use when wrapping
:param nonce_iv iv data
Unwrap (decrypt) data using the supplied symmetric key
@@ -288,3 +309,160 @@ class NSSCryptoProvider(CryptoProvider):
Searches NSS database and returns SecItem object for this certificate.
"""
return nss.find_cert_from_nickname(cert_nick)
+
+
+class CryptographyCryptoProvider(CryptoProvider):
+ """
+ Class that defines python-cryptography implementation of CryptoProvider.
+ Requires a PEM file containing the agent cert to be initialized.
+
+ Note that all inputs and outputs are unencoded.
+ """
+
+ def __init__(self, transport_cert_nick, transport_cert,
+ backend=default_backend()):
+ """ Initialize python-cryptography
+ """
+ super(CryptographyCryptoProvider, self).__init__()
+ self.certs = {}
+
+ if not isinstance(transport_cert, cryptography.x509.Certificate):
+ # it's a file name
+ with open(transport_cert, 'r') as f:
+ transport_pem = f.read()
+ transport_cert = cryptography.x509.load_pem_x509_certificate(
+ transport_pem,
+ backend)
+
+ self.certs[transport_cert_nick] = transport_cert
+
+ # default to AES
+ self.encrypt_alg = algorithms.AES
+ self.encrypt_mode = modes.CBC
+ self.encrypt_size = 128
+ self.backend = backend
+
+ def initialize(self):
+ """
+ Any operations here that need to be performed before crypto
+ operations.
+ """
+ pass
+
+ def get_supported_algorithm_keyset(self):
+ """ returns highest supported algorithm keyset """
+ return 1
+
+ def set_algorithm_keyset(self, level):
+ """ sets required keyset """
+ if level > 1:
+ raise ValueError("Invalid keyset")
+ elif level == 1:
+ self.encrypt_alg = algorithms.AES
+ self.encrypt_mode = modes.CBC
+ self.encrypt_size = 128
+ elif level == 0:
+ self.encrypt_alg = algorithms.TripleDES
+ self.encrypt_mode = modes.CBC
+ self.encrypt_size = 168
+
+ def generate_nonce_iv(self, mechanism='AES'):
+ """ Create a random initialization vector """
+ return os.urandom(self.encrypt_alg.block_size // 8)
+
+ def generate_symmetric_key(self, mechanism=None, size=0):
+ """ Returns a symmetric key.
+ """
+ if mechanism is None:
+ size = self.encrypt_size // 8
+ return os.urandom(size)
+
+ def generate_session_key(self):
+ """ Returns a session key to be used when wrapping secrets for the DRM.
+ """
+ return self.generate_symmetric_key()
+
+ def symmetric_wrap(self, data, wrapping_key, mechanism=None,
+ nonce_iv=None):
+ """
+ :param data Data to be wrapped
+ :param wrapping_key Symmetric key to wrap data
+ :param mechanism Mechanism to use for wrapping key
+ :param nonce_iv Nonce for initialization vector
+
+ Wrap (encrypt) data using the supplied symmetric key
+ """
+ # TODO(alee) Not sure yet how to handle non-default mechanisms
+ # For now, lets just ignore them
+
+ if wrapping_key is None:
+ raise ValueError("Wrapping key must be provided")
+
+ if self.encrypt_mode.name == "CBC":
+ padder = padding.PKCS7(self.encrypt_alg.block_size).padder()
+ padded_data = padder.update(data) + padder.finalize()
+ data = padded_data
+ else:
+ raise ValueError('Only CBC mode is currently supported')
+
+ cipher = Cipher(self.encrypt_alg(wrapping_key),
+ self.encrypt_mode(nonce_iv),
+ backend=self.backend)
+
+ encryptor = cipher.encryptor()
+ ct = encryptor.update(data) + encryptor.finalize()
+ return ct
+
+ def symmetric_unwrap(self, data, wrapping_key,
+ mechanism=None, nonce_iv=None):
+ """
+ :param data Data to be unwrapped
+ :param wrapping_key Symmetric key to unwrap data
+ :param mechanism Mechanism to use when unwrapping
+ :param nonce_iv iv data
+
+ Unwrap (decrypt) data using the supplied symmetric key
+ """
+
+ # TODO(alee) As above, no idea what to do with mechanism
+ # ignoring for now.
+
+ if wrapping_key is None:
+ raise ValueError("Wrapping key must be provided")
+
+ cipher = Cipher(self.encrypt_alg(wrapping_key),
+ self.encrypt_mode(nonce_iv),
+ backend=self.backend)
+
+ decryptor = cipher.decryptor()
+ unwrapped = decryptor.update(data) + decryptor.finalize()
+
+ if self.encrypt_mode.name == 'CBC':
+ unpadder = padding.PKCS7(self.encrypt_alg.block_size).unpadder()
+ unpadded = unpadder.update(unwrapped) + unpadder.finalize()
+ unwrapped = unpadded
+ else:
+ raise ValueError('Only CBC mode is currently supported')
+
+ return unwrapped
+
+ def asymmetric_wrap(self, data, wrapping_cert,
+ mechanism=None):
+ """
+ :param data Data to be wrapped
+ :param wrapping_cert Public key to wrap data
+ :param mechanism algorithm of symmetric key to be wrapped
+
+ Wrap (encrypt) data using the supplied asymmetric key
+ """
+ public_key = wrapping_cert.public_key()
+ return public_key.encrypt(
+ data,
+ PKCS1v15()
+ )
+
+ def get_cert(self, cert_nick):
+ """
+ :param cert_nick Nickname for the certificate to be returned.
+ """
+ return self.certs[cert_nick]
diff --git a/base/common/python/pki/util.py b/base/common/python/pki/util.py
index 0765bcf06..0de13fd93 100644
--- a/base/common/python/pki/util.py
+++ b/base/common/python/pki/util.py
@@ -34,8 +34,10 @@ except ImportError:
import subprocess
-DEFAULT_PKI_ENV_LIST = ['/usr/share/pki/etc/pki.conf',
- '/etc/pki/pki.conf']
+DEFAULT_PKI_ENV_LIST = [
+ '/usr/share/pki/etc/pki.conf',
+ '/etc/pki/pki.conf',
+]
def copy(source, dest):
diff --git a/base/common/python/setup.py b/base/common/python/setup.py
index e0920c1e6..03391a602 100644
--- a/base/common/python/setup.py
+++ b/base/common/python/setup.py
@@ -124,7 +124,7 @@ and set up in less than an hour.""",
keywords='pki x509 cert certificate',
url='http://pki.fedoraproject.org/',
packages=['pki', 'pki.cli'],
- install_requires=['python-nss', 'requests', 'six'],
+ install_requires=['python-nss', 'requests', 'six', 'cryptography'],
cmdclass={'version_info': VersionInfo},
classifiers=[
'Development Status :: 5 - Production/Stable',