]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add openstack/common/crypto from OSLO
authorElena Ezhova <eezhova@mirantis.com>
Wed, 16 Oct 2013 11:55:21 +0000 (15:55 +0400)
committerElena Ezhova <eezhova@mirantis.com>
Tue, 22 Oct 2013 14:14:02 +0000 (18:14 +0400)
Add cinder/openstack/common/crypto/__init__.py and
cinder/openstack/common/crypto/utils.py.

They are needed in cinder/openstack/common/rpc/securemessage,
which is updated in the following commits.

Oslo version: 4987d09a28c986bdca0921a1d0f062ac9328904f

Change-Id: I15e7ce3b84831fd247ea155a3b15829348087830

cinder/openstack/common/crypto/__init__.py [new file with mode: 0644]
cinder/openstack/common/crypto/utils.py [new file with mode: 0644]

diff --git a/cinder/openstack/common/crypto/__init__.py b/cinder/openstack/common/crypto/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/openstack/common/crypto/utils.py b/cinder/openstack/common/crypto/utils.py
new file mode 100644 (file)
index 0000000..db0240e
--- /dev/null
@@ -0,0 +1,179 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Red Hat, Inc.
+#
+#    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.
+
+import base64
+
+from Crypto.Hash import HMAC
+from Crypto import Random
+
+from cinder.openstack.common.gettextutils import _  # noqa
+from cinder.openstack.common import importutils
+
+
+class CryptoutilsException(Exception):
+    """Generic Exception for Crypto utilities."""
+
+    message = _("An unknown error occurred in crypto utils.")
+
+
+class CipherBlockLengthTooBig(CryptoutilsException):
+    """The block size is too big."""
+
+    def __init__(self, requested, permitted):
+        msg = _("Block size of %(given)d is too big, max = %(maximum)d")
+        message = msg % {'given': requested, 'maximum': permitted}
+        super(CryptoutilsException, self).__init__(message)
+
+
+class HKDFOutputLengthTooLong(CryptoutilsException):
+    """The amount of Key Material asked is too much."""
+
+    def __init__(self, requested, permitted):
+        msg = _("Length of %(given)d is too long, max = %(maximum)d")
+        message = msg % {'given': requested, 'maximum': permitted}
+        super(CryptoutilsException, self).__init__(message)
+
+
+class HKDF(object):
+    """An HMAC-based Key Derivation Function implementation (RFC5869)
+
+    This class creates an object that allows to use HKDF to derive keys.
+    """
+
+    def __init__(self, hashtype='SHA256'):
+        self.hashfn = importutils.import_module('Crypto.Hash.' + hashtype)
+        self.max_okm_length = 255 * self.hashfn.digest_size
+
+    def extract(self, ikm, salt=None):
+        """An extract function that can be used to derive a robust key given
+        weak Input Key Material (IKM) which could be a password.
+        Returns a pseudorandom key (of HashLen octets)
+
+        :param ikm: input keying material (ex a password)
+        :param salt: optional salt value (a non-secret random value)
+        """
+        if salt is None:
+            salt = '\x00' * self.hashfn.digest_size
+
+        return HMAC.new(salt, ikm, self.hashfn).digest()
+
+    def expand(self, prk, info, length):
+        """An expand function that will return arbitrary length output that can
+        be used as keys.
+        Returns a buffer usable as key material.
+
+        :param prk: a pseudorandom key of at least HashLen octets
+        :param info: optional string (can be a zero-length string)
+        :param length: length of output keying material (<= 255 * HashLen)
+        """
+        if length > self.max_okm_length:
+            raise HKDFOutputLengthTooLong(length, self.max_okm_length)
+
+        N = (length + self.hashfn.digest_size - 1) / self.hashfn.digest_size
+
+        okm = ""
+        tmp = ""
+        for block in range(1, N + 1):
+            tmp = HMAC.new(prk, tmp + info + chr(block), self.hashfn).digest()
+            okm += tmp
+
+        return okm[:length]
+
+
+MAX_CB_SIZE = 256
+
+
+class SymmetricCrypto(object):
+    """Symmetric Key Crypto object.
+
+    This class creates a Symmetric Key Crypto object that can be used
+    to encrypt, decrypt, or sign arbitrary data.
+
+    :param enctype: Encryption Cipher name (default: AES)
+    :param hashtype: Hash/HMAC type name (default: SHA256)
+    """
+
+    def __init__(self, enctype='AES', hashtype='SHA256'):
+        self.cipher = importutils.import_module('Crypto.Cipher.' + enctype)
+        self.hashfn = importutils.import_module('Crypto.Hash.' + hashtype)
+
+    def new_key(self, size):
+        return Random.new().read(size)
+
+    def encrypt(self, key, msg, b64encode=True):
+        """Encrypt the provided msg and returns the cyphertext optionally
+        base64 encoded.
+
+        Uses AES-128-CBC with a Random IV by default.
+
+        The plaintext is padded to reach blocksize length.
+        The last byte of the block is the length of the padding.
+        The length of the padding does not include the length byte itself.
+
+        :param key: The Encryption key.
+        :param msg: the plain text.
+
+        :returns encblock: a block of encrypted data.
+        """
+        iv = Random.new().read(self.cipher.block_size)
+        cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv)
+
+        # CBC mode requires a fixed block size. Append padding and length of
+        # padding.
+        if self.cipher.block_size > MAX_CB_SIZE:
+            raise CipherBlockLengthTooBig(self.cipher.block_size, MAX_CB_SIZE)
+        r = len(msg) % self.cipher.block_size
+        padlen = self.cipher.block_size - r - 1
+        msg += '\x00' * padlen
+        msg += chr(padlen)
+
+        enc = iv + cipher.encrypt(msg)
+        if b64encode:
+            enc = base64.b64encode(enc)
+        return enc
+
+    def decrypt(self, key, msg, b64decode=True):
+        """Decrypts the provided ciphertext, optionally base 64 encoded, and
+        returns the plaintext message, after padding is removed.
+
+        Uses AES-128-CBC with an IV by default.
+
+        :param key: The Encryption key.
+        :param msg: the ciphetext, the first block is the IV
+        """
+        if b64decode:
+            msg = base64.b64decode(msg)
+        iv = msg[:self.cipher.block_size]
+        cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv)
+
+        padded = cipher.decrypt(msg[self.cipher.block_size:])
+        l = ord(padded[-1]) + 1
+        plain = padded[:-l]
+        return plain
+
+    def sign(self, key, msg, b64encode=True):
+        """Signs a message string and returns a base64 encoded signature.
+
+        Uses HMAC-SHA-256 by default.
+
+        :param key: The Signing key.
+        :param msg: the message to sign.
+        """
+        h = HMAC.new(key, msg, self.hashfn)
+        out = h.digest()
+        if b64encode:
+            out = base64.b64encode(out)
+        return out