+++ /dev/null
-# 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