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
- From: Commits of the python-pskc project <python-pskc-commits [at] lists.arthurdejong.org>
- To: python-pskc-commits [at] lists.arthurdejong.org
- Reply-to: python-pskc-users [at] lists.arthurdejong.org
- Subject: python-pskc branch master updated. 0.3-12-g91f66f4
- Date: Sun, 24 Jan 2016 17:10:08 +0100 (CET)
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/
- python-pskc branch master updated. 0.3-12-g91f66f4,
Commits of the python-pskc project