From dc8c9d091c47a44a99fefe71db77eb37eb48f0e5 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 16 Aug 2013 17:01:18 -0400 Subject: [PATCH] Split out crypt stuff into it's own file --- confluent/config.py | 122 ----------------------------------- confluent/cryptutils.py | 139 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 122 deletions(-) create mode 100644 confluent/cryptutils.py diff --git a/confluent/config.py b/confluent/config.py index 1dfc2666..1b4d58b2 100644 --- a/confluent/config.py +++ b/confluent/config.py @@ -32,9 +32,6 @@ import math import os -from Crypto.Cipher import AES -from Crypto.Hash import HMAC -from Crypto.Hash import SHA25sterkey = None _masterintegritykey = None _cfgstore = {} @@ -102,125 +99,6 @@ def _expand_expression(attribute, nodeobj): pass -def unlock_config_keys(passphrase=None): - _init_masterkey(passphrase) - - -def _pbkdf2(passphrase, salt, iterations, size): - blocks = int(math.ceil(size/32.0)) # Hardcoded to SHA256 behavior - retkey = "" - for block in xrange(blocks): - citerations = iterations - tsalt = salt + chr(block) - currval = HMAC.new(passphrase, tsalt, SHA256).digest() - currarray = array.array('L',currval) - while citerations > 1: - currval = HMAC.new(passphrase, currval).digest() - nextarray = array.array('L',currval) - for index in range(nextarray): - currarray[index] = currarray[index] ^ nextarray[index] - currval = currarray.tostring() - currarray = nextarray - citerations = citerations - 1 - retkey += currval - return retkey[:size] - - -def _derive_keys(passphrase, salt): - tmpkey = _pbkdf2(passphrase, salt, 50000, 32) - finalkey = _pbkdf2(tmpkey, salt, 50000, 96) - return (finalkey[:32],finalkey[32:]) - - -def _get_protected_key(keydict, passphrase): - if keydict['unencryptedvalue']: - return keydict['unencryptedvalue'] - # TODO(jbjohnso): check for TPM sealing - if 'passphraseprotected' in keydict: - if passphrase is None: - raise Exception("Passphrase protected secret requires passhrase") - for pp in keydict['passphraseprotected']: - salt = pp[0] - privkey, integkey = _derive_keys(passphrase, salt) - return _decrypt_value(pp[1:], key=privkey, integritykey=integkey) - else: - raise Exception("No available decryption key") - - -def _format_key(key, passphrase=None): - if passphrase is not None: - salt = os.urandom(32) - privkey, integkey = _derive_keys(passphrase, salt) - cval = _crypt_value(key, key=privkey, integritykey=integkey) - return {"passphraseprotected": cval} - else: - return {"unencryptedvalue": key} - - -def _init_masterkey(passphrase=None): - if 'master_privacy_key' in _cfgstore['globals']: - _masterkey = _get_protected_key( - _cfgstore['globals']['master_privacy_key'], - passphrase=passphrase) - else: - _masterkey = os.urandom(32) - _cfgstore['globals']['master_privacy_key'] = _format_key(_masterkey, - passphrase=passphrase) - if 'master_integrity_key' in _cfgstore['globals']: - _masterintegritykey = _get_protected_key( - _cfgstore['globals']['master_integrity_key'], - passphrase=passphrase - ) - else: - _masterintegritykey = os.urandom(64) - _cfgstore['globals']['master_integrity_key'] = _format_key( - _masterintegritykey, - passphrase=passphrase - ) - - - -def _decrypt_value(cryptvalue, - key=_masterkey, - integritykey=_masterintegritykey): - iv, cipherdata, hmac = cryptvalue - if _masterkey is None or _masterintegritykey is None: - _init_masterkey() - check_hmac = HMAC.new(_masterintegritykey, cryptvalue, SHA256).digest() - if hmac != check_hmac: - raise Exception("bad HMAC value on crypted value") - decrypter = AES.new(_masterkey, AES.MODE_CBC, iv) - value = decrypter.decrypt(cryptvalue) - padsize = ord(value[-1]) - pad = value[-padsize:] - # Note that I cannot grasp what could be done with a subliminal - # channel in padding in this case, but check the padding anyway - for padbyte in pad: - if ord(padbyte) != padsize: - raise Exception("bad padding in encrypted value") - return value[0:-padsize] - - -def _crypt_value(value, - key=_masterkey, - integritykey=_masterintegritykey): - # encrypt given value - # PKCS7 is the padding scheme to employ, if no padded needed, pad with 16 - # check HMAC prior to attempting decrypt - if key is None or integritykey is None: - _init_masterkey() - key=_masterkey - integritykey=_masterintegritykey - iv = os.urandom(16) - crypter = AES.new(key, ASE.MOD_CBC, iv) - neededpad = 16 - (len(value) % 16) - pad = chr(neededpad) * neededpad - value = value + pad - cryptval = crypter.encrypt(value) - hmac = HMAC.new(integritykey, cryptval, SHA256).digest() - return (iv, cryptval, hmac) - - class NodeAttribs(object): def __init__(self, nodes=[], attributes=[], tenant=0): self._nodelist = collecitons.dequeue(nodes) diff --git a/confluent/cryptutils.py b/confluent/cryptutils.py new file mode 100644 index 00000000..8516315a --- /dev/null +++ b/confluent/cryptutils.py @@ -0,0 +1,139 @@ +# IBM(c) 2013 + +# This module provides cryptographic convenience functions, largely to be +# used by config.py to protect/unlock configuration as appropriopriate. +# The default behavior provides no meaningful protection, all encrypted +# values are linked to a master key that is stored in the clear. +# meanigful protection comes when the user elects to protect the key +# by passphrase and optionally TPM + +import array +import math +import os + +from Crypto.Cipher import AES +from Crypto.Hash import HMAC +from Crypto.Hash import SHA256 + +_masterkey = None +_masterintegritykey = None + + + +def unlock_config_keys(passphrase=None): + _init_masterkey(passphrase) + + +def _pbkdf2(passphrase, salt, iterations, size): + blocks = int(math.ceil(size/32.0)) # Hardcoded to SHA256 behavior + retkey = "" + for block in xrange(blocks): + citerations = iterations + tsalt = salt + chr(block) + currval = HMAC.new(passphrase, tsalt, SHA256).digest() + currarray = array.array('L',currval) + while citerations > 1: + currval = HMAC.new(passphrase, currval).digest() + nextarray = array.array('L',currval) + for index in range(len(nextarray)): + currarray[index] = currarray[index] ^ nextarray[index] + currval = currarray.tostring() + currarray = nextarray + citerations = citerations - 1 + retkey += currval + return retkey[:size] + + +def _derive_keys(passphrase, salt): + tmpkey = _pbkdf2(passphrase, salt, 50000, 32) + finalkey = _pbkdf2(tmpkey, salt, 50000, 96) + return (finalkey[:32],finalkey[32:]) + + +def _get_protected_key(keydict, passphrase): + if keydict['unencryptedvalue']: + return keydict['unencryptedvalue'] + # TODO(jbjohnso): check for TPM sealing + if 'passphraseprotected' in keydict: + if passphrase is None: + raise Exception("Passphrase protected secret requires passhrase") + for pp in keydict['passphraseprotected']: + salt = pp[0] + privkey, integkey = _derive_keys(passphrase, salt) + return _decrypt_value(pp[1:], key=privkey, integritykey=integkey) + else: + raise Exception("No available decryption key") + + +def _format_key(key, passphrase=None): + if passphrase is not None: + salt = os.urandom(32) + privkey, integkey = _derive_keys(passphrase, salt) + cval = _crypt_value(key, key=privkey, integritykey=integkey) + return {"passphraseprotected": cval} + else: + return {"unencryptedvalue": key} + + +def _init_masterkey(passphrase=None): + if 'master_privacy_key' in _cfgstore['globals']: + _masterkey = _get_protected_key( + _cfgstore['globals']['master_privacy_key'], + passphrase=passphrase) + else: + _masterkey = os.urandom(32) + _cfgstore['globals']['master_privacy_key'] = _format_key(_masterkey, + passphrase=passphrase) + if 'master_integrity_key' in _cfgstore['globals']: + _masterintegritykey = _get_protected_key( + _cfgstore['globals']['master_integrity_key'], + passphrase=passphrase + ) + else: + _masterintegritykey = os.urandom(64) + _cfgstore['globals']['master_integrity_key'] = _format_key( + _masterintegritykey, + passphrase=passphrase + ) + + + +def _decrypt_value(cryptvalue, + key=_masterkey, + integritykey=_masterintegritykey): + iv, cipherdata, hmac = cryptvalue + if _masterkey is None or _masterintegritykey is None: + _init_masterkey() + check_hmac = HMAC.new(_masterintegritykey, cryptvalue, SHA256).digest() + if hmac != check_hmac: + raise Exception("bad HMAC value on crypted value") + decrypter = AES.new(_masterkey, AES.MODE_CBC, iv) + value = decrypter.decrypt(cryptvalue) + padsize = ord(value[-1]) + pad = value[-padsize:] + # Note that I cannot grasp what could be done with a subliminal + # channel in padding in this case, but check the padding anyway + for padbyte in pad: + if ord(padbyte) != padsize: + raise Exception("bad padding in encrypted value") + return value[0:-padsize] + + +def _crypt_value(value, + key=_masterkey, + integritykey=_masterintegritykey): + # encrypt given value + # PKCS7 is the padding scheme to employ, if no padded needed, pad with 16 + # check HMAC prior to attempting decrypt + if key is None or integritykey is None: + _init_masterkey() + key=_masterkey + integritykey=_masterintegritykey + iv = os.urandom(16) + crypter = AES.new(key, ASE.MOD_CBC, iv) + neededpad = 16 - (len(value) % 16) + pad = chr(neededpad) * neededpad + value = value + pad + cryptval = crypter.encrypt(value) + hmac = HMAC.new(integritykey, cryptval, SHA256).digest() + return (iv, cryptval, hmac)