From eba541e9d976472582ad3dc31cd69087be102fa6 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 20 Mar 2016 21:54:55 +0100 Subject: Make Encryption and MAC constructors consistent This removes calling parse() from the Encryption and MAC constructors and stores a reference to the PSKC object in both objects so it can be used later on. --- pskc/mac.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'pskc/mac.py') diff --git a/pskc/mac.py b/pskc/mac.py index 4cab63c..5291d3e 100644 --- a/pskc/mac.py +++ b/pskc/mac.py @@ -56,12 +56,11 @@ 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.key_cipher_value = None self.key_algorithm = None - self.parse(mac_method) def parse(self, mac_method): """Read MAC information from the XML tree.""" -- cgit v1.2.3 From ca0fa368f6f60d02d1dfcf41d4f9961d65560c98 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 20 Mar 2016 21:54:55 +0100 Subject: Write MACMethod This also makes the MAC.algorithm a property similarly as what is done for Encryption (normalise algorithm names) and adds a setter for the MAC.key property. --- pskc/__init__.py | 1 + pskc/encryption.py | 9 ++++++++- pskc/mac.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 3 deletions(-) (limited to 'pskc/mac.py') diff --git a/pskc/__init__.py b/pskc/__init__.py index 7c9184b..c6d4aad 100644 --- a/pskc/__init__.py +++ b/pskc/__init__.py @@ -106,6 +106,7 @@ class PSKC(object): container = mk_elem('pskc:KeyContainer', Version=self.version, Id=self.id) self.encryption.make_xml(container) + self.mac.make_xml(container) for key in self.keys: key.make_xml(container) return container diff --git a/pskc/encryption.py b/pskc/encryption.py index 7b2da65..e69db02 100644 --- a/pskc/encryption.py +++ b/pskc/encryption.py @@ -28,7 +28,7 @@ The encryption key can be derived using the KeyDerivation class. import base64 -# cannonical URIs of known encryption algorithms +# cannonical URIs of known algorithms _algorithms = { 'tripledes-cbc': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc', 'kw-tripledes': 'http://www.w3.org/2001/04/xmlenc#kw-tripledes', @@ -44,6 +44,13 @@ _algorithms = { 'kw-camellia128': 'http://www.w3.org/2001/04/xmldsig-more#kw-camellia128', 'kw-camellia192': 'http://www.w3.org/2001/04/xmldsig-more#kw-camellia192', 'kw-camellia256': 'http://www.w3.org/2001/04/xmldsig-more#kw-camellia256', + 'hmac-md5': 'http://www.w3.org/2001/04/xmldsig-more#hmac-md5', + 'hmac-sha1': 'http://www.w3.org/2000/09/xmldsig#hmac-sha1', + 'hmac-sha224': 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224', + 'hmac-sha256': 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256', + 'hmac-sha384': 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha384', + 'hmac-sha512': 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha512', + 'hmac-ripemd160': 'http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160', } # translation table to change old encryption names to new names diff --git a/pskc/mac.py b/pskc/mac.py index 5291d3e..dccabf7 100644 --- a/pskc/mac.py +++ b/pskc/mac.py @@ -29,6 +29,7 @@ with the PSKC encryption key. """ +import base64 import re @@ -58,7 +59,8 @@ class MAC(object): 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 @@ -76,13 +78,52 @@ 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) + @key.setter + def key(self, value): + self.key_plain_value = value + self.key_cipher_value = None + + @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) + def check_value(self, value, value_mac): """Check if the provided value matches the MAC. -- cgit v1.2.3 From 16da531142af8f5052029864df2691ad3fd61858 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 20 Mar 2016 22:11:43 +0100 Subject: Generate MAC values --- pskc/key.py | 7 +++++++ pskc/mac.py | 36 +++++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 13 deletions(-) (limited to 'pskc/mac.py') diff --git a/pskc/key.py b/pskc/key.py index 140a194..d7d1cb0 100644 --- a/pskc/key.py +++ b/pskc/key.py @@ -120,6 +120,13 @@ class DataType(object): mk_elem( cipher_data, 'xenc:CipherValue', base64.b64encode(self.cipher_value).decode()) + if self.value_mac: + mk_elem(element, 'pskc:ValueMAC', base64.b64encode( + self.value_mac).decode()) + elif self.pskc.mac.key: + mk_elem(element, 'pskc:ValueMAC', base64.b64encode( + self.pskc.mac.generate_mac(self.cipher_value) + ).decode()) else: mk_elem(element, 'pskc:PlainValue', self._to_text(self.value)) diff --git a/pskc/mac.py b/pskc/mac.py index dccabf7..0b6c7e6 100644 --- a/pskc/mac.py +++ b/pskc/mac.py @@ -36,16 +36,21 @@ import re _hmac_url_re = re.compile(r'^.*#hmac-(?P[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): @@ -124,12 +129,8 @@ class MAC(object): from pskc.encryption import normalise_algorithm self._algorithm = normalise_algorithm(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. - """ + def generate_mac(self, value): + """Generate the MAC over the specified value.""" from pskc.exceptions import DecryptionError key = self.key if key is None: @@ -138,6 +139,15 @@ 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 -- cgit v1.2.3 From 5ac9d4384252dd22ea5fb9510c367fb7967409b7 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Mon, 21 Mar 2016 18:26:00 +0100 Subject: Allow configuring a MAC key This method will set up a MAC key and algorithm as specified or use reasonable defauts. --- pskc/mac.py | 33 +++++++++++++++++++++++++++++++++ tests/test_encryption.doctest | 27 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) (limited to 'pskc/mac.py') diff --git a/pskc/mac.py b/pskc/mac.py index 0b6c7e6..b4ddd53 100644 --- a/pskc/mac.py +++ b/pskc/mac.py @@ -129,6 +129,17 @@ class MAC(object): 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 @@ -151,3 +162,25 @@ class MAC(object): 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) diff --git a/tests/test_encryption.doctest b/tests/test_encryption.doctest index 398b12e..603da30 100644 --- a/tests/test_encryption.doctest +++ b/tests/test_encryption.doctest @@ -121,3 +121,30 @@ DecryptionError: Invalid key length >>> pskc.encryption.key = a2b_hex('255e0d1c07b646dfb3134cc843ba8aa71f025b7c0838251f') >>> b2a_hex(pskc.keys[0].secret) '2923bf85e06dd6ae529149f1f1bae9eab3a7da3d860d3e98' + + +MAC key and algorithm will use useful defaults but can also be manually +specified. + +>>> pskc = PSKC() +>>> pskc.mac.setup() +>>> pskc.mac.algorithm +'http://www.w3.org/2000/09/xmldsig#hmac-sha1' +>>> len(pskc.mac.key) +20 +>>> pskc.mac.setup(key=a2b_hex('548512684595'), algorithm='unknown') +>>> pskc.mac.algorithm +'unknown' +>>> len(pskc.mac.key) +6 +>>> pskc.mac.algorithm_key_length # this is the default +16 +>>> pskc.mac.algorithm = None +>>> pskc.mac.key = None +>>> pskc.mac.setup(algorithm='hmac-sha224') +>>> pskc.mac.algorithm +'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224' +>>> pskc.mac.algorithm_key_length +28 +>>> len(pskc.mac.key) +28 -- cgit v1.2.3