Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/pskc/mac.py
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2016-03-27 17:53:26 +0200
committerArthur de Jong <arthur@arthurdejong.org>2016-03-27 17:53:26 +0200
commitb4a6c720cb202f44b07ad2d0f9d8812ab7212ea5 (patch)
treefe056384a6a3112dd6ca9f34f3d722413fe931e1 /pskc/mac.py
parent8b5f6c27e0dde5f8b995b89dd2e3c9fa3caed3d5 (diff)
parent59aa65be6d3349e78d2f17e94ae34f6767113a87 (diff)
Implement writing encrypted files
This adds support for setting up encryption keys and password-based key derivation when writing PSKC files. Also MAC keys are set up when needed.
Diffstat (limited to 'pskc/mac.py')
-rw-r--r--pskc/mac.py115
1 files changed, 99 insertions, 16 deletions
diff --git a/pskc/mac.py b/pskc/mac.py
index 4cab63c..b4ddd53 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -29,22 +29,28 @@ with the PSKC encryption key.
"""
+import base64
import re
_hmac_url_re = re.compile(r'^.*#hmac-(?P<hash>[a-z0-9]+)$')
+def get_hash(algorithm):
+ """Return the hash function for the specifies HMAC algorithm."""
+ import hashlib
+ match = _hmac_url_re.search(algorithm)
+ if match:
+ return getattr(hashlib, match.group('hash'), None)
+
+
def get_hmac(algorithm):
"""Return an HMAC function that takes a secret and a value and returns a
digest."""
- import hashlib
import hmac
- match = _hmac_url_re.search(algorithm)
- if match:
- digestmod = getattr(hashlib, match.group('hash'), None)
- if digestmod is not None:
- return lambda key, value: hmac.new(key, value, digestmod).digest()
+ digestmod = get_hash(algorithm)
+ if digestmod is not None:
+ return lambda key, value: hmac.new(key, value, digestmod).digest()
class MAC(object):
@@ -56,12 +62,12 @@ class MAC(object):
key: the binary value of the MAC key if it can be decrypted
"""
- def __init__(self, pskc, mac_method=None):
+ def __init__(self, pskc):
self.pskc = pskc
- self.algorithm = None
+ self._algorithm = None
+ self.key_plain_value = None
self.key_cipher_value = None
self.key_algorithm = None
- self.parse(mac_method)
def parse(self, mac_method):
"""Read MAC information from the <MACMethod> XML tree."""
@@ -77,19 +83,65 @@ class MAC(object):
self.key_algorithm = encryption_method.attrib.get('Algorithm')
mac_key_reference = findtext(mac_method, 'MACKeyReference')
+ def make_xml(self, container):
+ from pskc.xml import mk_elem
+ if not self.algorithm and not self.key:
+ return
+ mac_method = mk_elem(
+ container, 'pskc:MACMethod', Algorithm=self.algorithm, empty=True)
+ mac_key = mk_elem(mac_method, 'pskc:MACKey', empty=True)
+ mk_elem(
+ mac_key, 'xenc:EncryptionMethod',
+ Algorithm=self.pskc.encryption.algorithm)
+ cipher_data = mk_elem(mac_key, 'xenc:CipherData', empty=True)
+ if self.key_cipher_value:
+ mk_elem(
+ cipher_data, 'xenc:CipherValue',
+ base64.b64encode(self.key_cipher_value).decode())
+ elif self.key_plain_value:
+ mk_elem(
+ cipher_data, 'xenc:CipherValue', base64.b64encode(
+ self.pskc.encryption.encrypt_value(self.key_plain_value)
+ ).decode())
+
@property
def key(self):
"""Provides access to the MAC key binary value if available."""
- if self.key_cipher_value:
+ if self.key_plain_value:
+ return self.key_plain_value
+ elif self.key_cipher_value:
return self.pskc.encryption.decrypt_value(
self.key_cipher_value, self.key_algorithm)
- def check_value(self, value, value_mac):
- """Check if the provided value matches the MAC.
+ @key.setter
+ def key(self, value):
+ self.key_plain_value = value
+ self.key_cipher_value = None
- This will return None if there is no MAC to be checked. It will
- return True if the MAC matches and raise an exception if it fails.
- """
+ @property
+ def algorithm(self):
+ """Provide the MAC algorithm used."""
+ if self._algorithm:
+ return self._algorithm
+
+ @algorithm.setter
+ def algorithm(self, value):
+ from pskc.encryption import normalise_algorithm
+ self._algorithm = normalise_algorithm(value)
+
+ @property
+ def algorithm_key_length(self):
+ """Recommended minimal key length in bytes for the set algorithm."""
+ # https://tools.ietf.org/html/rfc2104#section-3
+ # an HMAC key should be at least as long as the hash output length
+ hashfn = get_hash(self.algorithm)
+ if hashfn is not None:
+ return int(hashfn().digest_size)
+ else:
+ return 16
+
+ def generate_mac(self, value):
+ """Generate the MAC over the specified value."""
from pskc.exceptions import DecryptionError
key = self.key
if key is None:
@@ -98,6 +150,37 @@ class MAC(object):
if hmacfn is None:
raise DecryptionError(
'Unsupported MAC algorithm: %r' % self.algorithm)
- if hmacfn(key, value) != value_mac:
+ return hmacfn(key, value)
+
+ def check_value(self, value, value_mac):
+ """Check if the provided value matches the MAC.
+
+ This will return None if there is no MAC to be checked. It will
+ return True if the MAC matches and raise an exception if it fails.
+ """
+ from pskc.exceptions import DecryptionError
+ if self.generate_mac(value) != value_mac:
raise DecryptionError('MAC value does not match')
return True
+
+ def setup(self, key=None, algorithm=None):
+ """Configure an encrypted MAC key.
+
+ The following arguments may be supplied:
+ key: the MAC key to use
+ algorithm: MAC algorithm
+
+ None of the arguments are required, reasonable defaults will be
+ chosen for missing arguments.
+ """
+ if key:
+ self.key = key
+ if algorithm:
+ self.algorithm = algorithm
+ # default to HMAC-SHA1
+ if not self.algorithm:
+ self.algorithm = 'hmac-sha1'
+ # generate an HMAC key
+ if not self.key:
+ from Crypto import Random
+ self.key = Random.get_random_bytes(self.algorithm_key_length)