From 8fd35ba1f50fb11ac1f853146b969d92e94fed3c Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Mon, 21 Mar 2016 21:12:47 +0100 Subject: Write out encrypted values The Encryption class now has a fields property that lists the fields that should be encrypted when writing the PSKC file. This adds an encrypt_value() function that performs the encryption and various functions to convert the plain value to binary before writing the encrypted XML elements. --- pskc/key.py | 56 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 11 deletions(-) (limited to 'pskc/key.py') diff --git a/pskc/key.py b/pskc/key.py index c332efa..140a194 100644 --- a/pskc/key.py +++ b/pskc/key.py @@ -23,6 +23,7 @@ import array import base64 +import binascii from pskc.policy import Policy @@ -91,18 +92,36 @@ class DataType(object): """Convert the value to an unencrypted string representation.""" raise NotImplementedError # pragma: no cover - def make_xml(self, key, tag): + def make_xml(self, key, tag, field): from pskc.xml import find, mk_elem # skip empty values - value = self.get_value() - if value is None: + if self.value in (None, '') and not self.cipher_value: return # find the data tag and create our tag under it data = find(key, 'pskc:Data') if data is None: data = mk_elem(key, 'pskc:Data', empty=True) element = mk_elem(data, tag, empty=True) - mk_elem(element, 'pskc:PlainValue', self._to_text(value)) + # see if we should encrypt + if field in self.pskc.encryption.fields and not self.cipher_value: + self.cipher_value = self.pskc.encryption.encrypt_value( + self._to_bin(self.value)) + self.algorithm = self.pskc.encryption.algorithm + self.value = None + # write out value + if self.cipher_value: + encrypted_value = mk_elem( + element, 'pskc:EncryptedValue', empty=True) + mk_elem( + encrypted_value, 'xenc:EncryptionMethod', + Algorithm=self.algorithm) + cipher_data = mk_elem( + encrypted_value, 'xenc:CipherData', empty=True) + mk_elem( + cipher_data, 'xenc:CipherValue', + base64.b64encode(self.cipher_value).decode()) + else: + mk_elem(element, 'pskc:PlainValue', self._to_text(self.value)) def get_value(self): """Provide the attribute value, decrypting as needed.""" @@ -150,6 +169,14 @@ class BinaryDataType(DataType): value = value.encode() # pragma: no cover (Python 3 specific) return base64.b64encode(value).decode() + @staticmethod + def _to_bin(value): + """Convert the value to binary representation for encryption.""" + # force conversion to bytestring on Python 3 + if not isinstance(value, type(b'')): + value = value.encode() # pragma: no cover (Python 3 specific) + return value + class IntegerDataType(DataType): """Subclass of DataType for integer types (e.g. counters).""" @@ -182,6 +209,13 @@ class IntegerDataType(DataType): """Convert the value to an unencrypted string representation.""" return str(value) + @staticmethod + def _to_bin(value): + """Convert the value to binary representation for encryption.""" + value = '%x' % value + n = len(value) + return binascii.unhexlify(value.zfill(n + (n & 1))) + class Key(object): """Representation of a single key from a PSKC file. @@ -314,7 +348,7 @@ class Key(object): self.challenge_max_length = getint(challenge_format, 'Max') self.challenge_check = getbool( challenge_format, 'CheckDigits', getbool( - challenge_format, 'CheckDigit')) + challenge_format, 'CheckDigit')) response_format = find( key_package, @@ -324,7 +358,7 @@ class Key(object): self.response_length = getint(response_format, 'Length') self.response_check = getbool( response_format, 'CheckDigits', getbool( - response_format, 'CheckDigit')) + response_format, 'CheckDigit')) self.policy.parse(find(key_package, 'Key/Policy')) @@ -373,11 +407,11 @@ class Key(object): mk_elem(key, 'pskc:KeyProfileId', self.key_profile) mk_elem(key, 'pskc:KeyReference', self.key_reference) mk_elem(key, 'pskc:FriendlyName', self.friendly_name) - self._secret.make_xml(key, 'pskc:Secret') - self._counter.make_xml(key, 'pskc:Counter') - self._time_offset.make_xml(key, 'pskc:Time') - self._time_interval.make_xml(key, 'pskc:TimeInterval') - self._time_drift.make_xml(key, 'pskc:TimeDrift') + self._secret.make_xml(key, 'pskc:Secret', 'secret') + self._counter.make_xml(key, 'pskc:Counter', 'counter') + self._time_offset.make_xml(key, 'pskc:Time', 'time_offset') + self._time_interval.make_xml(key, 'pskc:TimeInterval', 'time_interval') + self._time_drift.make_xml(key, 'pskc:TimeDrift', 'time_drif') mk_elem(key, 'pskc:UserId', self.key_userid) self.policy.make_xml(key) -- 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/key.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