lists.arthurdejong.org
RSS feed

python-pskc branch master updated. 0.3-12-g91f66f4

[Date Prev][Date Next] [Thread Prev][Thread Next]

python-pskc branch master updated. 0.3-12-g91f66f4



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-pskc".

The branch, master has been updated
       via  91f66f466e6929e306711761e296a6ff794a513b (commit)
       via  9b13d3b78c864028b5e8e892141f09a65c93a68f (commit)
       via  b6eab4723392e398a459f99ccecc9749c857f0c6 (commit)
       via  b5f7de5a932d1de36b58cef56ece39eae1520d97 (commit)
       via  107a836a2686abc4213b57277d6d7ffe0a52ed54 (commit)
      from  a86ff8aa50beea7d1e879a12788d7cfd992a7120 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://arthurdejong.org/git/python-pskc/commit/?id=91f66f466e6929e306711761e296a6ff794a513b

commit 91f66f466e6929e306711761e296a6ff794a513b
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Jan 24 15:20:40 2016 +0100

    Refactor out EncryptedValue and ValueMAC
    
    This removes the EncryptedValue and ValueMAC classes and instead moves
    the XML parsing of these values to the DataType class. This will make it
    easier to support different parsing schemes.
    
    This also includes a small consistency improvement in the subclasses of
    DataType.

diff --git a/pskc/encryption.py b/pskc/encryption.py
index 6007d79..5cdb781 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -20,9 +20,8 @@
 
 """Module that handles encrypted PSKC values.
 
-This module defines an Encryption class that handles the encryption key
-and an EncryptedValue wrapper class that can decrypt values using the
-encryption key.
+This module defines an Encryption class that handles the encryption key,
+algorithms and decryption.
 
 The encryption key can be derived using the KeyDerivation class.
 """
@@ -75,83 +74,6 @@ def unpad(value):
     return value[0:-ord(value[-1:])]
 
 
-class EncryptedValue(object):
-    """Wrapper class to handle encrypted values.
-
-    Instances of this class provide the following attributes:
-
-      algorithm: name of the encryption algorithm used
-      cipher_value: binary encrypted data
-    """
-
-    def __init__(self, encryption, encrypted_value=None):
-        """Initialise an encrypted value for the provided Key."""
-        self.encryption = encryption
-        encryption._encrypted_values.append(self)
-        self.algorithm = None
-        self.cipher_value = None
-        self.parse(encrypted_value)
-
-    def parse(self, encrypted_value):
-        """Read encrypted data from the <EncryptedValue> XML tree."""
-        from pskc.xml import find, findbin
-        if encrypted_value is None:
-            return
-        encryption_method = find(encrypted_value, 'EncryptionMethod')
-        if encryption_method is not None:
-            self.algorithm = normalise_algorithm(
-                encryption_method.attrib.get('Algorithm'))
-        self.cipher_value = findbin(
-            encrypted_value, 'CipherData/CipherValue')
-
-    def decrypt(self):
-        """Decrypt the linked value and return the plaintext value."""
-        from pskc.exceptions import DecryptionError
-        if self.cipher_value is None:
-            return
-        key = self.encryption.key
-        if key is None:
-            raise DecryptionError('No key available')
-        if self.algorithm is None:
-            raise DecryptionError('No algorithm specified')
-        if self.algorithm.endswith('#aes128-cbc') or \
-           self.algorithm.endswith('#aes192-cbc') or \
-           self.algorithm.endswith('#aes256-cbc'):
-            from Crypto.Cipher import AES
-            if len(key) * 8 != int(self.algorithm[-7:-4]) or \
-               len(key) not in AES.key_size:
-                raise DecryptionError('Invalid key length')
-            iv = self.cipher_value[:AES.block_size]
-            ciphertext = self.cipher_value[AES.block_size:]
-            cipher = AES.new(key, AES.MODE_CBC, iv)
-            return unpad(cipher.decrypt(ciphertext))
-        elif self.algorithm.endswith('#tripledes-cbc'):
-            from Crypto.Cipher import DES3
-            if len(key) not in DES3.key_size:
-                raise DecryptionError('Invalid key length')
-            iv = self.cipher_value[:DES3.block_size]
-            ciphertext = self.cipher_value[DES3.block_size:]
-            cipher = DES3.new(key, DES3.MODE_CBC, iv)
-            return unpad(cipher.decrypt(ciphertext))
-        elif self.algorithm.endswith('#kw-aes128') or \
-                self.algorithm.endswith('#kw-aes192') or \
-                self.algorithm.endswith('#kw-aes256'):
-            from pskc.crypto.aeskw import unwrap
-            from Crypto.Cipher import AES
-            if len(key) * 8 != int(self.algorithm[-3:]) or \
-               len(key) not in AES.key_size:
-                raise DecryptionError('Invalid key length')
-            return unwrap(self.cipher_value, key)
-        elif self.algorithm.endswith('#kw-tripledes'):
-            from pskc.crypto.tripledeskw import unwrap
-            from Crypto.Cipher import DES3
-            if len(key) not in DES3.key_size:
-                raise DecryptionError('Invalid key length')
-            return unwrap(self.cipher_value, key)
-        else:
-            raise DecryptionError('Unsupported algorithm: %r' % self.algorithm)
-
-
 class KeyDerivation(object):
     """Handle key derivation.
 
@@ -268,13 +190,8 @@ class Encryption(object):
     @property
     def algorithm(self):
         """Provide the encryption algorithm used."""
-        # if one is explicitly set, use that
         if self._algorithm:
             return self._algorithm
-        # fall back to finding the algorithm in any encrypted value
-        for encrypted_value in self._encrypted_values:
-            if encrypted_value.algorithm:
-                return encrypted_value.algorithm
 
     @algorithm.setter
     def algorithm(self, value):
@@ -283,3 +200,49 @@ class Encryption(object):
     def derive_key(self, password):
         """Derive a key from the password."""
         self.key = self.derivation.derive(password)
+
+    def decrypt_value(self, cipher_value, algorithm=None):
+        """Decrypt the cipher_value and return the plaintext value."""
+        from pskc.exceptions import DecryptionError
+        key = self.key
+        if key is None:
+            raise DecryptionError('No key available')
+        algorithm = algorithm or self.algorithm
+        if algorithm is None:
+            raise DecryptionError('No algorithm specified')
+        if algorithm.endswith('#aes128-cbc') or \
+           algorithm.endswith('#aes192-cbc') or \
+           algorithm.endswith('#aes256-cbc'):
+            from Crypto.Cipher import AES
+            if len(key) * 8 != int(algorithm[-7:-4]) or \
+               len(key) not in AES.key_size:
+                raise DecryptionError('Invalid key length')
+            iv = cipher_value[:AES.block_size]
+            ciphertext = cipher_value[AES.block_size:]
+            cipher = AES.new(key, AES.MODE_CBC, iv)
+            return unpad(cipher.decrypt(ciphertext))
+        elif algorithm.endswith('#tripledes-cbc'):
+            from Crypto.Cipher import DES3
+            if len(key) not in DES3.key_size:
+                raise DecryptionError('Invalid key length')
+            iv = cipher_value[:DES3.block_size]
+            ciphertext = cipher_value[DES3.block_size:]
+            cipher = DES3.new(key, DES3.MODE_CBC, iv)
+            return unpad(cipher.decrypt(ciphertext))
+        elif algorithm.endswith('#kw-aes128') or \
+                algorithm.endswith('#kw-aes192') or \
+                algorithm.endswith('#kw-aes256'):
+            from pskc.crypto.aeskw import unwrap
+            from Crypto.Cipher import AES
+            if len(key) * 8 != int(algorithm[-3:]) or \
+               len(key) not in AES.key_size:
+                raise DecryptionError('Invalid key length')
+            return unwrap(cipher_value, key)
+        elif algorithm.endswith('#kw-tripledes'):
+            from pskc.crypto.tripledeskw import unwrap
+            from Crypto.Cipher import DES3
+            if len(key) not in DES3.key_size:
+                raise DecryptionError('Invalid key length')
+            return unwrap(cipher_value, key)
+        else:
+            raise DecryptionError('Unsupported algorithm: %r' % algorithm)
diff --git a/pskc/key.py b/pskc/key.py
index ca01713..858d12d 100644
--- a/pskc/key.py
+++ b/pskc/key.py
@@ -23,8 +23,6 @@
 
 import base64
 
-from pskc.encryption import EncryptedValue
-from pskc.mac import ValueMAC
 from pskc.policy import Policy
 
 
@@ -34,31 +32,48 @@ class DataType(object):
     This class is meant to be subclassed to provide typed access to stored
     values. Instances of this class provide the following attributes:
 
-      value: unencrypted value if present
-      encrypted_value: reference to an EncryptedValue instance
-      value_mac: reference to a ValueMAC instance
+      value: unencrypted value
+      cipher_value: encrypted value
+      algorithm: encryption algorithm of encrypted value
+      value_mac: MAC of the encrypted value
     """
 
     def __init__(self, key, element=None):
+        self.pskc = key.pskc
         self.value = None
-        self.encrypted_value = EncryptedValue(key.pskc.encryption)
-        self.value_mac = ValueMAC(key.pskc.mac)
+        self.cipher_value = None
+        self.algorithm = None
+        self.value_mac = None
         self.parse(element)
 
     def parse(self, element):
         """Read information from the provided element.
 
         The element is expected to contain <PlainValue>, <EncryptedValue>
-        and/or ValueMAC elements that contain information on the actual
+        and/or <ValueMAC> elements that contain information on the actual
         value."""
-        from pskc.xml import find, findtext
+        from pskc.xml import find, findtext, findbin
         if element is None:
             return
-        value = findtext(element, 'PlainValue')
-        if value is not None:
-            self.value = self._from_text(value)
-        self.encrypted_value.parse(find(element, 'EncryptedValue'))
-        self.value_mac.parse(find(element, 'ValueMAC'))
+        # read plaintext value from <PlainValue>
+        plain_value = findtext(element, 'PlainValue')
+        if plain_value is not None:
+            self.value = self._from_text(plain_value)
+        # read encrypted data from <EncryptedValue>
+        encrypted_value = find(element, 'EncryptedValue')
+        if encrypted_value is not None:
+            self.cipher_value = findbin(
+                encrypted_value, 'CipherData/CipherValue')
+            encryption_method = find(encrypted_value, 'EncryptionMethod')
+            if encryption_method is not None:
+                self.algorithm = encryption_method.attrib.get('Algorithm')
+                # store the found algorithm in the pskc.encryption property
+                if not self.pskc.encryption.algorithm and self.algorithm:
+                    self.pskc.encryption.algorithm = self.algorithm
+        # read MAC information from <ValueMAC>
+        value_mac = findbin(element, 'ValueMAC')
+        if value_mac is not None:
+            self.value_mac = value_mac
 
     @staticmethod
     def _from_text(value):
@@ -66,14 +81,14 @@ class DataType(object):
         raise NotImplementedError
 
     @staticmethod
-    def _to_text(value):
-        """Convert the value to an unencrypted string representation."""
+    def _from_bin(value):
+        """Convert the unencrypted binary to native representation."""
         raise NotImplementedError
 
     @staticmethod
-    def _from_bin(value):
-        """Convert the unencrypted binary to native representation."""
-        return value
+    def _to_text(value):
+        """Convert the value to an unencrypted string representation."""
+        raise NotImplementedError
 
     def make_xml(self, key, tag):
         from pskc.xml import find, mk_elem
@@ -89,23 +104,28 @@ class DataType(object):
         mk_elem(element, 'pskc:PlainValue', self._to_text(value))
 
     def get_value(self):
-        """Provide the raw binary value."""
+        """Provide the attribute value, decrypting as needed."""
         if self.value is not None:
             return self.value
-        if self.encrypted_value.cipher_value:
+        if self.cipher_value:
             # check MAC and decrypt
             self.check()
-            return self._from_bin(self.encrypted_value.decrypt())
+            return self._from_bin(self.pskc.encryption.decrypt_value(
+                self.cipher_value, self.algorithm))
 
     def set_value(self, value):
         """Set the unencrypted value."""
         self.value = value
-        self.encrypted_value.cipher_value = None
+        self.cipher_value = None
+        self.algorithm = None
+        self.value_mac = None
 
     def check(self):
         """Check whether the embedded MAC is correct."""
         # this checks the encrypted value
-        return self.value_mac.check(self.encrypted_value.cipher_value)
+        if self.cipher_value and self.value_mac:
+            return self.pskc.mac.check_value(
+                self.cipher_value, self.value_mac)
 
 
 class BinaryDataType(DataType):
@@ -117,6 +137,11 @@ class BinaryDataType(DataType):
         return base64.b64decode(value)
 
     @staticmethod
+    def _from_bin(value):
+        """Convert the unencrypted binary to native representation."""
+        return value
+
+    @staticmethod
     def _to_text(value):
         """Convert the value to an unencrypted string representation."""
         # force conversion to bytestring on Python 3
@@ -134,11 +159,6 @@ class IntegerDataType(DataType):
         return int(value)
 
     @staticmethod
-    def _to_text(value):
-        """Convert the value to an unencrypted string representation."""
-        return str(value)
-
-    @staticmethod
     def _from_bin(value):
         """Convert the unencrypted binary to native representation."""
         result = 0
@@ -146,6 +166,11 @@ class IntegerDataType(DataType):
             result = (result << 8) + ord(x)
         return result
 
+    @staticmethod
+    def _to_text(value):
+        """Convert the value to an unencrypted string representation."""
+        return str(value)
+
 
 class Key(object):
     """Representation of a single key from a PSKC file.
diff --git a/pskc/mac.py b/pskc/mac.py
index a20a089..c14003f 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -47,42 +47,6 @@ def get_hmac(algorithm):
             return lambda key, value: hmac.new(key, value, digestmod).digest()
 
 
-class ValueMAC(object):
-    """Provide MAC checking ability to PSKC data values."""
-
-    def __init__(self, mac, value_mac=None):
-        self.mac = mac
-        self._value_mac = None
-        self.parse(value_mac)
-
-    def parse(self, value_mac):
-        """Read MAC information from the <ValueMAC> XML tree."""
-        from pskc.xml import findbin
-        if value_mac is None:
-            return
-        self._value_mac = findbin(value_mac, '.')
-
-    def check(self, value):
-        """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 value is None or self._value_mac is None:
-            return  # no MAC present or nothing to check
-        key = self.mac.key
-        if key is None:
-            raise DecryptionError('No MAC key available')
-        hmacfn = get_hmac(self.mac.algorithm)
-        if hmacfn is None:
-            raise DecryptionError(
-                'Unsupported MAC algorithm: %r' % self.mac.algorithm)
-        if hmacfn(key, value) != self._value_mac:
-            raise DecryptionError('MAC value does not match')
-        return True
-
-
 class MAC(object):
     """Class describing the MAC algorithm to use and how to get the key.
 
@@ -93,21 +57,49 @@ class MAC(object):
     """
 
     def __init__(self, pskc, mac_method=None):
-        from pskc.encryption import EncryptedValue
+        self.pskc = pskc
         self.algorithm = None
-        self._mac_key = EncryptedValue(pskc.encryption)
+        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."""
-        from pskc.xml import find, findtext
+        from pskc.xml import find, findtext, findbin
         if mac_method is None:
             return
         self.algorithm = mac_method.get('Algorithm')
-        self._mac_key.parse(find(mac_method, 'MACKey'))
+        mac_key = find(mac_method, 'MACKey')
+        if mac_key is not None:
+            self.key_cipher_value = findbin(mac_key, 'CipherData/CipherValue')
+            encryption_method = find(mac_key, 'EncryptionMethod')
+            if encryption_method is not None:
+                self.key_algorithm = encryption_method.attrib.get('Algorithm')
         mac_key_reference = findtext(mac_method, 'MACKeyReference')
 
     @property
     def key(self):
         """Provides access to the MAC key binary value if available."""
-        return self._mac_key.decrypt()
+        if 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.
+
+        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 value is None or value_mac is None:
+            return  # no MAC present or nothing to check
+        key = self.key
+        if key is None:
+            raise DecryptionError('No MAC key available')
+        hmacfn = get_hmac(self.algorithm)
+        if hmacfn is None:
+            raise DecryptionError(
+                'Unsupported MAC algorithm: %r' % self.algorithm)
+        if hmacfn(key, value) != value_mac:
+            raise DecryptionError('MAC value does not match')
+        return True

http://arthurdejong.org/git/python-pskc/commit/?id=9b13d3b78c864028b5e8e892141f09a65c93a68f

commit 9b13d3b78c864028b5e8e892141f09a65c93a68f
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Jan 23 00:13:23 2016 +0100

    Normalise algorithm names
    
    This transforms the algorithm URIs that are set to known values when
    parsing or setting the algorithm.

diff --git a/pskc/encryption.py b/pskc/encryption.py
index 2959b1a..6007d79 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -28,6 +28,48 @@ The encryption key can be derived using the KeyDerivation 
class.
 """
 
 
+# cannonical URIs of known encryption algorithms
+_algorithms = {
+    'tripledes-cbc': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
+    'kw-tripledes': 'http://www.w3.org/2001/04/xmlenc#kw-tripledes',
+    'aes128-cbc': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc',
+    'aes192-cbc': 'http://www.w3.org/2001/04/xmlenc#aes192-cbc',
+    'aes256-cbc': 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
+    'kw-aes128': 'http://www.w3.org/2001/04/xmlenc#kw-aes128',
+    'kw-aes192': 'http://www.w3.org/2001/04/xmlenc#kw-aes192',
+    'kw-aes256': 'http://www.w3.org/2001/04/xmlenc#kw-aes256',
+    'camellia128': 'http://www.w3.org/2001/04/xmldsig-more#camellia128',
+    'camellia192': 'http://www.w3.org/2001/04/xmldsig-more#camellia192',
+    'camellia256': 'http://www.w3.org/2001/04/xmldsig-more#camellia256',
+    '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',
+}
+
+# translation table to change old encryption names to new names
+_algorithm_aliases = {
+    '3des-cbc': 'tripledes-cbc',
+    '3des112-cbc': 'tripledes-cbc',
+    '3des168-cbc': 'tripledes-cbc',
+    'kw-3des': 'kw-tripledes',
+    'pbe-3des112-cbc': 'tripledes-cbc',
+    'pbe-3des168-cbc': 'tripledes-cbc',
+    'pbe-aes128-cbc': 'aes128-cbc',
+    'pbe-aes192-cbc': 'aes192-cbc',
+    'pbe-aes256-cbc': 'aes256-cbc',
+    'rsa-1_5': 'rsa-1_5',
+    'rsa-oaep-mgf1p': 'rsa-oaep-mgf1p',
+}
+
+
+def normalise_algorithm(algorithm):
+    """Return the canonical URI for the provided algorithm."""
+    if not algorithm or algorithm.lower() == 'none':
+        return None
+    algorithm = _algorithm_aliases.get(algorithm.lower(), algorithm)
+    return _algorithms.get(algorithm.rsplit('#', 1)[-1].lower(), algorithm)
+
+
 def unpad(value):
     """Remove padding from the plaintext."""
     return value[0:-ord(value[-1:])]
@@ -57,7 +99,8 @@ class EncryptedValue(object):
             return
         encryption_method = find(encrypted_value, 'EncryptionMethod')
         if encryption_method is not None:
-            self.algorithm = encryption_method.attrib.get('Algorithm')
+            self.algorithm = normalise_algorithm(
+                encryption_method.attrib.get('Algorithm'))
         self.cipher_value = findbin(
             encrypted_value, 'CipherData/CipherValue')
 
@@ -235,7 +278,7 @@ class Encryption(object):
 
     @algorithm.setter
     def algorithm(self, value):
-        self._algorithm = value
+        self._algorithm = normalise_algorithm(value)
 
     def derive_key(self, password):
         """Derive a key from the password."""
diff --git a/tests/test_misc.doctest b/tests/test_misc.doctest
index 4e15879..db64b07 100644
--- a/tests/test_misc.doctest
+++ b/tests/test_misc.doctest
@@ -79,7 +79,7 @@ Setting encryption key name and algorithm also works.
 >>> pskc.encryption.key_names
 ['Test encryption key']
 >>> pskc.encryption.algorithm
->>> pskc.encryption.algorithm = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+>>> pskc.encryption.algorithm = 'aes128-cbc'
 >>> pskc.encryption.algorithm
 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
 

http://arthurdejong.org/git/python-pskc/commit/?id=b6eab4723392e398a459f99ccecc9749c857f0c6

commit b6eab4723392e398a459f99ccecc9749c857f0c6
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri Jan 22 17:45:18 2016 +0100

    Add encryption algorithm property
    
    Either determine the encryption algorithm from the PSKC file or from the
    explicitly set value. This also adds support for setting the encryption
    key name.

diff --git a/docs/encryption.rst b/docs/encryption.rst
index 0d5cf3e..9bedf8c 100644
--- a/docs/encryption.rst
+++ b/docs/encryption.rst
@@ -38,6 +38,10 @@ The Encryption class
 
       Optional identifier of the encryption key.
 
+   .. attribute:: algorithm
+
+      A URI of the encryption algorithm used.
+
    .. attribute:: key_names
 
       List of names provided for the encryption key.
@@ -46,7 +50,7 @@ The Encryption class
 
       Since usually only one name is defined for a key but the schema allows
       for multiple names, this is a shortcut for accessing the first value of
-      :attr:`key_names`.
+      :attr:`key_names`. It will return ``None`` if no name is available.
 
    .. attribute:: key
 
diff --git a/pskc/encryption.py b/pskc/encryption.py
index bcf6cb6..2959b1a 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -45,6 +45,7 @@ class EncryptedValue(object):
     def __init__(self, encryption, encrypted_value=None):
         """Initialise an encrypted value for the provided Key."""
         self.encryption = encryption
+        encryption._encrypted_values.append(self)
         self.algorithm = None
         self.cipher_value = None
         self.parse(encrypted_value)
@@ -180,6 +181,7 @@ class Encryption(object):
     class provides the following values:
 
       id: identifier of the key
+      algorithm: the encryption algorithm used
       key_names: list of names for the key
       key_name: (first) name of the key (usually there is only one)
       key: the key value itself (binary form)
@@ -192,6 +194,8 @@ class Encryption(object):
         self.id = None
         self.key_names = []
         self.key = None
+        self._algorithm = None
+        self._encrypted_values = []
         self.derivation = KeyDerivation()
         self.parse(key_info)
 
@@ -214,6 +218,25 @@ class Encryption(object):
         if self.key_names:
             return self.key_names[0]
 
+    @key_name.setter
+    def key_name(self, value):
+        self.key_names = [value]
+
+    @property
+    def algorithm(self):
+        """Provide the encryption algorithm used."""
+        # if one is explicitly set, use that
+        if self._algorithm:
+            return self._algorithm
+        # fall back to finding the algorithm in any encrypted value
+        for encrypted_value in self._encrypted_values:
+            if encrypted_value.algorithm:
+                return encrypted_value.algorithm
+
+    @algorithm.setter
+    def algorithm(self, value):
+        self._algorithm = value
+
     def derive_key(self, password):
         """Derive a key from the password."""
         self.key = self.derivation.derive(password)
diff --git a/tests/test_encryption.doctest b/tests/test_encryption.doctest
index 928d2d3..cd549c3 100644
--- a/tests/test_encryption.doctest
+++ b/tests/test_encryption.doctest
@@ -1,6 +1,6 @@
 test_encryption.doctest - test various encryption schemes
 
-Copyright (C) 2014-2015 Arthur de Jong
+Copyright (C) 2014-2016 Arthur de Jong
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -30,6 +30,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 >>> pskc = PSKC('tests/aes128-cbc.pskcxml')
 >>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
+>>> pskc.encryption.algorithm
+'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
 >>> tostr(pskc.keys[0].secret)
 '12345678901234567890'
 >>> pskc.mac.algorithm
@@ -55,6 +57,8 @@ DecryptionError: Invalid key length
 
 >>> pskc = PSKC('tests/aes256-cbc.pskcxml')
 >>> pskc.encryption.key = 
 >>> a2b_hex('1234567890123456789012345678901234567890123456789012345678901234')
+>>> pskc.encryption.algorithm
+'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
 >>> tostr(pskc.keys[0].secret)
 '12345678901234567890'
 >>> pskc.mac.algorithm
diff --git a/tests/test_misc.doctest b/tests/test_misc.doctest
index ea20a03..4e15879 100644
--- a/tests/test_misc.doctest
+++ b/tests/test_misc.doctest
@@ -1,6 +1,6 @@
 test_misc.doctest - miscellaneous tests
 
-Copyright (C) 2014-2015 Arthur de Jong
+Copyright (C) 2014-2016 Arthur de Jong
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -73,6 +73,17 @@ Setting secret, counter, etc. also works
 10
 
 
+Setting encryption key name and algorithm also works.
+
+>>> pskc.encryption.key_name = 'Test encryption key'
+>>> pskc.encryption.key_names
+['Test encryption key']
+>>> pskc.encryption.algorithm
+>>> pskc.encryption.algorithm = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+>>> pskc.encryption.algorithm
+'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+
+
 Load an PSKC file with an odd namespace.
 
 >>> pskc = PSKC('tests/odd-namespace.pskcxml')

http://arthurdejong.org/git/python-pskc/commit/?id=b5f7de5a932d1de36b58cef56ece39eae1520d97

commit b5f7de5a932d1de36b58cef56ece39eae1520d97
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri Jan 22 18:44:06 2016 +0100

    Fix a problem when writing previously encrypted file
    
    This fixes a problem with writing a PSKC file that is based on a read
    file that was encrypted.

diff --git a/pskc/key.py b/pskc/key.py
index 83231b5..ca01713 100644
--- a/pskc/key.py
+++ b/pskc/key.py
@@ -86,7 +86,7 @@ class DataType(object):
         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(self.value))
+        mk_elem(element, 'pskc:PlainValue', self._to_text(value))
 
     def get_value(self):
         """Provide the raw binary value."""
diff --git a/tests/test_write.doctest b/tests/test_write.doctest
index 7dc585e..795aa86 100644
--- a/tests/test_write.doctest
+++ b/tests/test_write.doctest
@@ -143,3 +143,24 @@ argument).
   </pskc:Key>
  </pskc:KeyPackage>
 </pskc:KeyContainer>
+
+
+Read an encrypted PSKC file and write it out as an unencrypted file.
+
+>>> pskc = PSKC('tests/kw-aes128.pskcxml')
+>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f')
+>>> f = tempfile.NamedTemporaryFile()
+>>> pskc.write(f.name)
+>>> x = sys.stdout.write(open(f.name, 'r').read())
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer Version="1.0" 
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc">
+ <pskc:KeyPackage>
+  <pskc:Key>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>ABEiM0RVZneImaq7zN3u/w==</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>

http://arthurdejong.org/git/python-pskc/commit/?id=107a836a2686abc4213b57277d6d7ffe0a52ed54

commit 107a836a2686abc4213b57277d6d7ffe0a52ed54
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri Jan 22 18:12:57 2016 +0100

    Strip XML namespaces before parsing
    
    This simplifies calls to the find() family of functions and allows
    parsing PSKC files that have slightly different namespace URLs. This is
    especially common when parsing old draft versions of the specification.
    
    This also removes passing multiple patterns to the find() functions that
    was introduced in 68b20e2.

diff --git a/pskc/__init__.py b/pskc/__init__.py
index 685843b..ea7c4c2 100644
--- a/pskc/__init__.py
+++ b/pskc/__init__.py
@@ -1,7 +1,7 @@
 # __init__.py - main module
 # coding: utf-8
 #
-# Copyright (C) 2014-2015 Arthur de Jong
+# Copyright (C) 2014-2016 Arthur de Jong
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -70,11 +70,12 @@ class PSKC(object):
         self.mac = MAC(self)
         self.keys = []
         if filename is not None:
-            from pskc.xml import parse
+            from pskc.xml import parse, remove_namespaces
             try:
                 tree = parse(filename)
             except Exception:
                 raise ParseError('Error parsing XML')
+            remove_namespaces(tree)
             self.parse(tree.getroot())
         else:
             self.version = '1.0'
@@ -84,7 +85,7 @@ class PSKC(object):
         from pskc.exceptions import ParseError
         from pskc.key import Key
         from pskc.xml import find, findall
-        if not container.tag.endswith('KeyContainer'):
+        if container.tag != 'KeyContainer':
             raise ParseError('Missing KeyContainer')
         # the version of the PSKC schema
         self.version = container.get('Version')
@@ -93,11 +94,11 @@ class PSKC(object):
         # unique identifier for the container
         self.id = container.get('Id')
         # handle EncryptionKey entries
-        self.encryption.parse(find(container, 'pskc:EncryptionKey'))
+        self.encryption.parse(find(container, 'EncryptionKey'))
         # handle MACMethod entries
-        self.mac.parse(find(container, 'pskc:MACMethod'))
+        self.mac.parse(find(container, 'MACMethod'))
         # handle KeyPackage entries
-        for key_package in findall(container, 'pskc:KeyPackage'):
+        for key_package in findall(container, 'KeyPackage'):
             self.keys.append(Key(self, key_package))
 
     def make_xml(self):
diff --git a/pskc/encryption.py b/pskc/encryption.py
index eeda41a..bcf6cb6 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -1,7 +1,7 @@
 # encryption.py - module for handling encrypted values
 # coding: utf-8
 #
-# Copyright (C) 2014-2015 Arthur de Jong
+# Copyright (C) 2014-2016 Arthur de Jong
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -54,11 +54,11 @@ class EncryptedValue(object):
         from pskc.xml import find, findbin
         if encrypted_value is None:
             return
-        encryption_method = find(encrypted_value, 'xenc:EncryptionMethod')
+        encryption_method = find(encrypted_value, 'EncryptionMethod')
         if encryption_method is not None:
             self.algorithm = encryption_method.attrib.get('Algorithm')
         self.cipher_value = findbin(
-            encrypted_value, 'xenc:CipherData/xenc:CipherValue')
+            encrypted_value, 'CipherData/CipherValue')
 
     def decrypt(self):
         """Decrypt the linked value and return the plaintext value."""
@@ -136,20 +136,16 @@ class KeyDerivation(object):
             return
         self.algorithm = key_derivation.get('Algorithm')
         # PBKDF2 properties
-        pbkdf2 = find(
-            key_derivation, 'xenc11:PBKDF2-params', 'pkcs5:PBKDF2-params')
+        pbkdf2 = find(key_derivation, 'PBKDF2-params')
         if pbkdf2 is not None:
             # get used salt
-            self.pbkdf2_salt = findbin(
-                pbkdf2, 'Salt/Specified', 'xenc11:Salt/xenc11:Specified')
+            self.pbkdf2_salt = findbin(pbkdf2, 'Salt/Specified')
             # required number of iterations
-            self.pbkdf2_iterations = findint(
-                pbkdf2, 'IterationCount', 'xenc11:IterationCount')
+            self.pbkdf2_iterations = findint(pbkdf2, 'IterationCount')
             # key length
-            self.pbkdf2_key_length = findint(
-                pbkdf2, 'KeyLength', 'xenc11:KeyLength')
+            self.pbkdf2_key_length = findint(pbkdf2, 'KeyLength')
             # pseudorandom function used
-            prf = find(pbkdf2, 'PRF', 'xenc11:PRF')
+            prf = find(pbkdf2, 'PRF')
             if prf is not None:
                 self.pbkdf2_prf = prf.get('Algorithm')
 
@@ -205,13 +201,12 @@ class Encryption(object):
         if key_info is None:
             return
         self.id = key_info.get('Id')
-        for name in findall(key_info, 'ds:KeyName'):
+        for name in findall(key_info, 'KeyName'):
             self.key_names.append(findtext(name, '.'))
-        for name in findall(
-                key_info, 'xenc11:DerivedKey/xenc11:MasterKeyName'):
+        for name in findall(key_info, 'DerivedKey/MasterKeyName'):
             self.key_names.append(findtext(name, '.'))
         self.derivation.parse(find(
-            key_info, 'xenc11:DerivedKey/xenc11:KeyDerivationMethod'))
+            key_info, 'DerivedKey/KeyDerivationMethod'))
 
     @property
     def key_name(self):
diff --git a/pskc/key.py b/pskc/key.py
index 00b1225..83231b5 100644
--- a/pskc/key.py
+++ b/pskc/key.py
@@ -1,7 +1,7 @@
 # key.py - module for handling keys from pskc files
 # coding: utf-8
 #
-# Copyright (C) 2014-2015 Arthur de Jong
+# Copyright (C) 2014-2016 Arthur de Jong
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -54,12 +54,11 @@ class DataType(object):
         from pskc.xml import find, findtext
         if element is None:
             return
-        value = findtext(element, 'pskc:PlainValue')
+        value = findtext(element, 'PlainValue')
         if value is not None:
-
             self.value = self._from_text(value)
-        self.encrypted_value.parse(find(element, 'pskc:EncryptedValue'))
-        self.value_mac.parse(find(element, 'pskc:ValueMAC'))
+        self.encrypted_value.parse(find(element, 'EncryptedValue'))
+        self.value_mac.parse(find(element, 'ValueMAC'))
 
     @staticmethod
     def _from_text(value):
@@ -236,51 +235,43 @@ class Key(object):
         if key_package is None:
             return
 
-        key = find(key_package, 'pskc:Key')
+        key = find(key_package, 'Key')
         if key is not None:
             self.id = key.get('Id')
             self.algorithm = key.get('Algorithm')
 
-        data = find(key_package, 'pskc:Key/pskc:Data')
+        data = find(key_package, 'Key/Data')
         if data is not None:
-            self._secret.parse(find(data, 'pskc:Secret'))
-            self._counter.parse(find(data, 'pskc:Counter'))
-            self._time_offset.parse(find(data, 'pskc:Time'))
-            self._time_interval.parse(find(data, 'pskc:TimeInterval'))
-            self._time_drift.parse(find(data, 'pskc:TimeDrift'))
-
-        self.issuer = findtext(key_package, 'pskc:Key/pskc:Issuer')
-        self.key_profile = findtext(key_package, 'pskc:Key/pskc:KeyProfileId')
-        self.key_reference = findtext(
-            key_package, 'pskc:Key/pskc:KeyReference')
-        self.friendly_name = findtext(
-            key_package, 'pskc:Key/pskc:FriendlyName')
+            self._secret.parse(find(data, 'Secret'))
+            self._counter.parse(find(data, 'Counter'))
+            self._time_offset.parse(find(data, 'Time'))
+            self._time_interval.parse(find(data, 'TimeInterval'))
+            self._time_drift.parse(find(data, 'TimeDrift'))
+
+        self.issuer = findtext(key_package, 'Key/Issuer')
+        self.key_profile = findtext(key_package, 'Key/KeyProfileId')
+        self.key_reference = findtext(key_package, 'Key/KeyReference')
+        self.friendly_name = findtext(key_package, 'Key/FriendlyName')
         # TODO: support multi-language values of <FriendlyName>
-        self.key_userid = findtext(key_package, 'pskc:Key/pskc:UserId')
+        self.key_userid = findtext(key_package, 'Key/UserId')
 
-        self.manufacturer = findtext(
-            key_package, 'pskc:DeviceInfo/pskc:Manufacturer')
-        self.serial = findtext(key_package, 'pskc:DeviceInfo/pskc:SerialNo')
-        self.model = findtext(key_package, 'pskc:DeviceInfo/pskc:Model')
-        self.issue_no = findtext(key_package, 'pskc:DeviceInfo/pskc:IssueNo')
+        self.manufacturer = findtext(key_package, 'DeviceInfo/Manufacturer')
+        self.serial = findtext(key_package, 'DeviceInfo/SerialNo')
+        self.model = findtext(key_package, 'DeviceInfo/Model')
+        self.issue_no = findtext(key_package, 'DeviceInfo/IssueNo')
         self.device_binding = findtext(
-            key_package, 'pskc:DeviceInfo/pskc:DeviceBinding')
-        self.start_date = findtime(
-            key_package, 'pskc:DeviceInfo/pskc:StartDate')
-        self.expiry_date = findtime(
-            key_package, 'pskc:DeviceInfo/pskc:ExpiryDate')
-        self.device_userid = findtext(
-            key_package, 'pskc:DeviceInfo/pskc:UserId')
+            key_package, 'DeviceInfo/DeviceBinding')
+        self.start_date = findtime(key_package, 'DeviceInfo/StartDate')
+        self.expiry_date = findtime(key_package, 'DeviceInfo/ExpiryDate')
+        self.device_userid = findtext(key_package, 'DeviceInfo/UserId')
 
-        self.crypto_module = findtext(
-            key_package, 'pskc:CryptoModuleInfo/pskc:Id')
+        self.crypto_module = findtext(key_package, 'CryptoModuleInfo/Id')
 
         self.algorithm_suite = findtext(
-            key_package, 'pskc:Key/pskc:AlgorithmParameters/pskc:Suite')
+            key_package, 'Key/AlgorithmParameters/Suite')
 
         challenge_format = find(
-            key_package,
-            'pskc:Key/pskc:AlgorithmParameters/pskc:ChallengeFormat')
+            key_package, 'Key/AlgorithmParameters/ChallengeFormat')
         if challenge_format is not None:
             self.challenge_encoding = challenge_format.get('Encoding')
             self.challenge_min_length = getint(challenge_format, 'Min')
@@ -289,13 +280,13 @@ class Key(object):
 
         response_format = find(
             key_package,
-            'pskc:Key/pskc:AlgorithmParameters/pskc:ResponseFormat')
+            'Key/AlgorithmParameters/ResponseFormat')
         if response_format is not None:
             self.response_encoding = response_format.get('Encoding')
             self.response_length = getint(response_format, 'Length')
             self.response_check = getbool(response_format, 'CheckDigits')
 
-        self.policy.parse(find(key_package, 'pskc:Key/pskc:Policy'))
+        self.policy.parse(find(key_package, 'Key/Policy'))
 
     def make_xml(self, container):
         from pskc.xml import mk_elem
diff --git a/pskc/mac.py b/pskc/mac.py
index 961c934..a20a089 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -1,7 +1,7 @@
 # mac.py - module for checking value signatures
 # coding: utf-8
 #
-# Copyright (C) 2014 Arthur de Jong
+# Copyright (C) 2014-2016 Arthur de Jong
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -104,8 +104,8 @@ class MAC(object):
         if mac_method is None:
             return
         self.algorithm = mac_method.get('Algorithm')
-        self._mac_key.parse(find(mac_method, 'pskc:MACKey'))
-        mac_key_reference = findtext(mac_method, 'pskc:MACKeyReference')
+        self._mac_key.parse(find(mac_method, 'MACKey'))
+        mac_key_reference = findtext(mac_method, 'MACKeyReference')
 
     @property
     def key(self):
diff --git a/pskc/policy.py b/pskc/policy.py
index 94b7c06..af04de1 100644
--- a/pskc/policy.py
+++ b/pskc/policy.py
@@ -1,7 +1,7 @@
 # policy.py - module for handling PSKC policy information
 # coding: utf-8
 #
-# Copyright (C) 2014-2015 Arthur de Jong
+# Copyright (C) 2014-2016 Arthur de Jong
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -114,14 +114,14 @@ class Policy(object):
         if policy is None:
             return
 
-        self.start_date = findtime(policy, 'pskc:StartDate')
-        self.expiry_date = findtime(policy, 'pskc:ExpiryDate')
+        self.start_date = findtime(policy, 'StartDate')
+        self.expiry_date = findtime(policy, 'ExpiryDate')
         self.number_of_transactions = findint(
-            policy, 'pskc:NumberOfTransactions')
-        for key_usage in findall(policy, 'pskc:KeyUsage'):
+            policy, 'NumberOfTransactions')
+        for key_usage in findall(policy, 'KeyUsage'):
             self.key_usage.append(findtext(key_usage, '.'))
 
-        pin_policy = find(policy, 'pskc:PINPolicy')
+        pin_policy = find(policy, 'PINPolicy')
         if pin_policy is not None:
             self.pin_key_id = pin_policy.get('PINKeyId')
             self.pin_usage = pin_policy.get('PINUsageMode')
diff --git a/pskc/xml.py b/pskc/xml.py
index 1de2485..d7c7f47 100644
--- a/pskc/xml.py
+++ b/pskc/xml.py
@@ -1,7 +1,7 @@
 # xml.py - module for parsing and writing XML for PSKC files
 # coding: utf-8
 #
-# Copyright (C) 2014-2015 Arthur de Jong
+# Copyright (C) 2014-2016 Arthur de Jong
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -57,45 +57,52 @@ def parse(source):
     return etree.parse(source)
 
 
+def remove_namespaces(tree):
+    """Remove namespaces from all elements in the tree."""
+    import re
+    for elem in tree.getiterator():
+        if isinstance(elem.tag, ''.__class__):
+            elem.tag = re.sub(r'^\{[^}]*\}', '', elem.tag)
+
+
 def findall(tree, match):
     """Find the child elements."""
     return tree.findall(match, namespaces=namespaces)
 
 
-def find(tree, *matches):
+def find(tree, match):
     """Find a child element that matches any of the patterns (or None)."""
-    for match in matches:
-        try:
-            return next(iter(findall(tree, match)))
-        except StopIteration:
-            pass
+    try:
+        return next(iter(findall(tree, match)))
+    except StopIteration:
+        pass
 
 
-def findtext(tree, *matches):
+def findtext(tree, match):
     """Get the text value of an element (or None)."""
-    element = find(tree, *matches)
+    element = find(tree, match)
     if element is not None:
         return element.text.strip()
 
 
-def findint(tree, *matches):
+def findint(tree, match):
     """Return an element value as an int (or None)."""
-    value = findtext(tree, *matches)
+    value = findtext(tree, match)
     if value:
         return int(value)
 
 
-def findtime(tree, *matches):
+def findtime(tree, match):
     """Return an element value as a datetime (or None)."""
-    value = findtext(tree, *matches)
+    value = findtext(tree, match)
     if value:
         import dateutil.parser
         return dateutil.parser.parse(value)
 
 
-def findbin(tree, *matches):
+def findbin(tree, match):
     """Return the binary element value base64 decoded."""
-    value = findtext(tree, *matches)
+    value = findtext(tree, match)
     if value:
         import base64
         return base64.b64decode(value)

-----------------------------------------------------------------------

Summary of changes:
 docs/encryption.rst           |   6 +-
 pskc/__init__.py              |  13 +--
 pskc/encryption.py            | 208 +++++++++++++++++++++++-------------------
 pskc/key.py                   | 148 ++++++++++++++++--------------
 pskc/mac.py                   |  78 +++++++---------
 pskc/policy.py                |  12 +--
 pskc/xml.py                   |  37 +++++---
 tests/test_encryption.doctest |   6 +-
 tests/test_misc.doctest       |  13 ++-
 tests/test_write.doctest      |  21 +++++
 10 files changed, 311 insertions(+), 231 deletions(-)


hooks/post-receive
-- 
python-pskc
-- 
To unsubscribe send an email to
python-pskc-commits-unsubscribe@lists.arthurdejong.org or see
http://lists.arthurdejong.org/python-pskc-commits/