python-pskc branch master updated. ba17976c43e85f17486cd12ec59740a46b807632
[
Date Prev][
Date Next]
[
Thread Prev][
Thread Next]
python-pskc branch master updated. ba17976c43e85f17486cd12ec59740a46b807632
- 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. ba17976c43e85f17486cd12ec59740a46b807632
- Date: Sat, 19 Apr 2014 21:58:26 +0200 (CEST)
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 ba17976c43e85f17486cd12ec59740a46b807632 (commit)
via 64e207db764ec9795177344bb45b9ee02294fdd3 (commit)
via 6becc615b36cd7a3f887563248425145bf9e8bb4 (commit)
via 1d42fbc57ca2e08e53d95baf2f6a832435455aa7 (commit)
via b07d709629739dd2267855b6b814cc24fa780e3e (commit)
via 285860e4bad7ed442be267b3e537ebb11978bfa7 (commit)
via 8c9e03d942397603fa653b96c62ecaa5b52e303f (commit)
from c883d48a4e26760e6dc14be98c0c5acddf4f79fc (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=ba17976c43e85f17486cd12ec59740a46b807632
commit ba17976c43e85f17486cd12ec59740a46b807632
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Apr 19 21:53:07 2014 +0200
Move PSKC class to toplevel module
This also splits the parsing to a parse() function for consistency.
diff --git a/pskc/__init__.py b/pskc/__init__.py
index 32507df..04f6888 100644
--- a/pskc/__init__.py
+++ b/pskc/__init__.py
@@ -30,7 +30,6 @@ for use in an OTP authentication system.
The following prints all keys, decrypting using a password:
-
>>> from pskc import PSKC
>>> pskc = PSKC('tests/rfc6030-figure7.pskc')
>>> pskc.encryption.derive_key('qwerty')
@@ -44,11 +43,52 @@ embedded signatures, asymmetric keys and writing files are
on the wishlist
"""
-from pskc.parse import PSKC
-
-
__all__ = ['PSKC', '__version__']
# the version number of the library
__version__ = '0.1'
+
+
+class PSKC(object):
+ """Wrapper module for parsing a PSKC file.
+
+ Instances of this class provide the following attributes:
+
+ version: the PSKC format version used (1.0)
+ id: identifier
+ encryption: information on used encryption (Encryption instance)
+ mac: information on used MAC method (MAC instance)
+ keys: list of keys (Key instances)
+ """
+
+ def __init__(self, filename):
+ from xml.etree import ElementTree
+ from pskc.encryption import Encryption
+ from pskc.mac import MAC
+ self.version = None
+ self.id = None
+ self.encryption = Encryption()
+ self.mac = MAC(self)
+ self.keys = []
+ tree = ElementTree.parse(filename)
+ self.parse(tree.getroot())
+
+ def parse(self, container):
+ """Read information from the provided <KeyContainer> tree."""
+ from pskc.parse import namespaces
+ from pskc.key import Key
+ # the version of the PSKC schema
+ self.version = container.attrib.get('Version')
+ # unique identifier for the container
+ self.id = container.attrib.get('Id')
+ # handle EncryptionKey entries
+ self.encryption.parse(container.find(
+ 'pskc:EncryptionKey', namespaces=namespaces))
+ # handle MACMethod entries
+ self.mac.parse(container.find(
+ 'pskc:MACMethod', namespaces=namespaces))
+ # handle KeyPackage entries
+ for package in container.findall(
+ 'pskc:KeyPackage', namespaces=namespaces):
+ self.keys.append(Key(self, package))
diff --git a/pskc/parse.py b/pskc/parse.py
index e5818b8..20b85d8 100644
--- a/pskc/parse.py
+++ b/pskc/parse.py
@@ -25,9 +25,6 @@ PSKC files.
"""
-from xml.etree import ElementTree
-
-
# the relevant XML namespaces for PSKC
namespaces = dict(
# the XML namespace URI for version 1.0 of PSKC
@@ -63,38 +60,3 @@ def g_e_d(tree, match):
if element is not None:
import dateutil.parser
return dateutil.parser.parse(element.text.strip())
-
-
-class PSKC(object):
- """Wrapper module for parsing a PSKC file.
-
- Instances of this class provide the following attributes:
-
- version: the PSKC format version used (1.0)
- id: identifier
- encryption: information on used encryption (Encryption instance)
- mac: information on used MAC method (MAC instance)
- keys: list of keys (Key instances)
- """
-
- def __init__(self, filename):
- from pskc.encryption import Encryption
- from pskc.mac import MAC
- from pskc.key import Key
- tree = ElementTree.parse(filename)
- container = tree.getroot()
- # the version of the PSKC schema
- self.version = container.attrib.get('Version')
- # unique identifier for the container
- self.id = container.attrib.get('Id')
- # handle EncryptionKey entries
- self.encryption = Encryption(container.find(
- 'pskc:EncryptionKey', namespaces=namespaces))
- # handle MACMethod entries
- self.mac = MAC(self, container.find(
- 'pskc:MACMethod', namespaces=namespaces))
- # handle KeyPackage entries
- self.keys = []
- for package in container.findall(
- 'pskc:KeyPackage', namespaces=namespaces):
- self.keys.append(Key(self, package))
http://arthurdejong.org/git/python-pskc/commit/?id=64e207db764ec9795177344bb45b9ee02294fdd3
commit 64e207db764ec9795177344bb45b9ee02294fdd3
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Apr 19 21:42:30 2014 +0200
Provide pskc.key docstrings
This documents most of the information that is available per key and
adds a few other minor cosmetic changes.
This also re-organises the key properties to be in a slightly more
logical order and renames the userid key property to key_userid to more
clearly distinguish it from device_userid.
diff --git a/pskc/key.py b/pskc/key.py
index c729497..bf00d0c 100644
--- a/pskc/key.py
+++ b/pskc/key.py
@@ -18,6 +18,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
+"""Module that handles keys stored in PSKC files."""
+
+
import base64
from pskc.encryption import EncryptedValue
@@ -26,15 +29,29 @@ from pskc.policy import Policy
class DataType(object):
+ """Provide access to possibly encrypted, MAC'ed information.
+
+ This class is meant to be subclassed to provide typed access to stored
+ values. Instances of this class provide the following attributes:
+
+ plain_value: raw unencrypted value if present (possibly base64 encoded)
+ encrypted_value: reference to an EncryptedValue instance
+ value_mac: reference to a ValueMAC instance
+ value: the plaintext value (decrypted if necessary)
+ """
def __init__(self, key, element=None):
- self.key = key
self.plain_value = None
- self.encrypted_value = EncryptedValue(self.key.pskc.encryption)
- self.value_mac = ValueMAC(self.key.pskc.mac)
+ self.encrypted_value = EncryptedValue(key.pskc.encryption)
+ self.value_mac = ValueMAC(key.pskc.mac)
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
+ value."""
from pskc.parse import g_e_v, namespaces
if element is None:
return
@@ -46,13 +63,16 @@ class DataType(object):
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)
class BinaryDataType(DataType):
+ """Subclass of DataType for binary data (e.g. keys)."""
@property
def value(self):
+ """Provide the raw binary value."""
# plain value is base64 encoded
value = self.plain_value
if value is not None:
@@ -64,14 +84,16 @@ class BinaryDataType(DataType):
class IntegerDataType(DataType):
+ """Subclass of DataType for integer types (e.g. counters)."""
@property
def value(self):
+ """Provide the raw integer value."""
# plain value is a string representation of the number
value = self.plain_value
if value:
return int(value)
- # decrypted value is a
+ # decrypted value is big endian encoded
value = self.encrypted_value.decrypt()
if value is not None:
# Python3 has int.from_bytes(value, byteorder='big')
@@ -82,11 +104,61 @@ class IntegerDataType(DataType):
class Key(object):
+ """Representation of a single key from a PSKC file.
+
+ Instances of this class provide the following properties:
+
+ id: unique key identifier (should be constant between interchanges)
+ algorithm: identifier of the PSKC algorithm profile (URI)
+ secret: the secret key itself (binary form, automatically decrypted)
+ counter: event counter for event-based OTP
+ time: time offset for time-based OTP algorithms (in intervals)
+ time_interval: time interval for time-based OTP in seconds
+ time_drift: device clock drift (negative means device is slow)
+ issuer: party that issued the key
+ key_profile: reference to pre-shared key profile information
+ key_reference: reference to an external key
+ friendly_name: human-readable name for the secret key
+ key_userid: user distinguished name associated with the key
+ manufacturer: name of the organisation that made the device
+ serial: serial number of the device
+ model: device model description
+ issue_no: issue number per serial number
+ device_binding: device (class) identifier for the key to be loaded upon
+ start_date: key should not be used before this date
+ expiry_date: key or device may expire after this date
+ device_userid: user distinguished name associated with the device
+ crypto_module: id of module to which keys are provisioned within device
+ algorithm_suite: additional algorithm characteristics (e.g. used hash)
+ challenge_encoding: format of the challenge for CR devices
+ challenge_min_length: minimum accepted challenge length by device
+ challenge_max_length: maximum size challenge accepted by the device
+ challenge_check: whether the device will check an embedded check digit
+ response_encoding: format of the response the device will generate
+ response_length: the length of the response of the device
+ response_check: whether the device appends a Luhn check digit
+ policy: reference to policy information (see Policy class)
+ """
def __init__(self, pskc, key_package):
self.pskc = pskc
+ self.id = None
+ self.algorithm = None
+
+ self._secret = BinaryDataType(self)
+ self._counter = IntegerDataType(self)
+ self._time_offset = IntegerDataType(self)
+ self._time_interval = IntegerDataType(self)
+ self._time_drift = IntegerDataType(self)
+
+ self.issuer = None
+ self.key_profile = None
+ self.key_reference = None
+ self.friendly_name = None
+ self.key_userid = None
+
self.manufacturer = None
self.serial = None
self.model = None
@@ -98,15 +170,6 @@ class Key(object):
self.crypto_module = None
- self.id = None
- self.algorithm = None
-
- self.issuer = None
- self.key_profile = None
- self.key_reference = None
- self.friendly_name = None
- self.userid = None
-
self.algorithm_suite = None
self.challenge_encoding = None
@@ -118,47 +181,62 @@ class Key(object):
self.response_length = None
self.response_check = None
- self._secret = BinaryDataType(self)
- self._counter = IntegerDataType(self)
- self._time_offset = IntegerDataType(self)
- self._time_interval = IntegerDataType(self)
- self._time_drift = IntegerDataType(self)
-
self.policy = Policy(self)
self.parse(key_package)
def parse(self, key_package):
+ """Read key information from the provided <KeyPackage> tree."""
from pskc.parse import g_e_v, g_e_d, namespaces
if key_package is None:
return
- self.manufacturer = g_e_v(key_package,
'pskc:DeviceInfo/pskc:Manufacturer')
- self.serial = g_e_v(key_package, 'pskc:DeviceInfo/pskc:SerialNo')
- self.model = g_e_v(key_package, 'pskc:DeviceInfo/pskc:Model')
- self.issue_no = g_e_v(key_package, 'pskc:DeviceInfo/pskc:IssueNo')
- self.device_binding = g_e_v(key_package,
'pskc:DeviceInfo/pskc:DeviceBinding')
- self.start_date = g_e_d(key_package, 'pskc:DeviceInfo/pskc:StartDate')
- self.expiry_date = g_e_d(key_package,
'pskc:DeviceInfo/pskc:ExpiryDate')
- self.device_userid = g_e_v(key_package, 'pskc:DeviceInfo/pskc:UserId')
-
- self.crypto_module = g_e_v(key_package,
'pskc:CryptoModuleInfo/pskc:Id')
-
key = key_package.find('pskc:Key', namespaces=namespaces)
if key is not None:
self.id = key.attrib.get('Id')
self.algorithm = key.attrib.get('Algorithm')
+ data = key_package.find('pskc:Key/pskc:Data', namespaces=namespaces)
+ if data is not None:
+ self._secret.parse(data.find(
+ 'pskc:Secret', namespaces=namespaces))
+ self._counter.parse(data.find(
+ 'pskc:Counter', namespaces=namespaces))
+ self._time_offset.parse(data.find(
+ 'pskc:Time', namespaces=namespaces))
+ self._time_interval.parse(data.find(
+ 'pskc:TimeInterval', namespaces=namespaces))
+ self._time_drift.parse(data.find(
+ 'pskc:TimeDrift', namespaces=namespaces))
+
self.issuer = g_e_v(key_package, 'pskc:Key/pskc:Issuer')
self.key_profile = g_e_v(key_package, 'pskc:Key/pskc:KeyProfileId')
self.key_reference = g_e_v(key_package, 'pskc:Key/pskc:KeyReference')
self.friendly_name = g_e_v(key_package, 'pskc:Key/pskc:FriendlyName')
# TODO: support multi-language values of <FriendlyName>
- self.userid = g_e_v(key_package, 'pskc:Key/pskc:UserId')
+ self.key_userid = g_e_v(key_package, 'pskc:Key/pskc:UserId')
+
+ self.manufacturer = g_e_v(
+ key_package, 'pskc:DeviceInfo/pskc:Manufacturer')
+ self.serial = g_e_v(key_package, 'pskc:DeviceInfo/pskc:SerialNo')
+ self.model = g_e_v(key_package, 'pskc:DeviceInfo/pskc:Model')
+ self.issue_no = g_e_v(key_package, 'pskc:DeviceInfo/pskc:IssueNo')
+ self.device_binding = g_e_v(
+ key_package, 'pskc:DeviceInfo/pskc:DeviceBinding')
+ self.start_date = g_e_d(key_package, 'pskc:DeviceInfo/pskc:StartDate')
+ self.expiry_date = g_e_d(
+ key_package, 'pskc:DeviceInfo/pskc:ExpiryDate')
+ self.device_userid = g_e_v(key_package, 'pskc:DeviceInfo/pskc:UserId')
+
+ self.crypto_module = g_e_v(
+ key_package, 'pskc:CryptoModuleInfo/pskc:Id')
- self.algorithm_suite = g_e_v(key_package,
'pskc:Key/pskc:AlgorithmParameters/pskc:Suite')
+ self.algorithm_suite = g_e_v(
+ key_package, 'pskc:Key/pskc:AlgorithmParameters/pskc:Suite')
- challenge_format =
key_package.find('pskc:Key/pskc:AlgorithmParameters/pskc:ChallengeFormat',
namespaces=namespaces)
+ challenge_format = key_package.find(
+ 'pskc:Key/pskc:AlgorithmParameters/pskc:ChallengeFormat',
+ namespaces=namespaces)
if challenge_format is not None:
self.challenge_encoding = challenge_format.attrib.get('Encoding')
v = challenge_format.attrib.get('Min')
@@ -171,7 +249,9 @@ class Key(object):
if v:
self.challenge_check = v.lower() == 'true'
- response_format =
key_package.find('pskc:Key/pskc:AlgorithmParameters/pskc:ResponseFormat',
namespaces=namespaces)
+ response_format = key_package.find(
+ 'pskc:Key/pskc:AlgorithmParameters/pskc:ResponseFormat',
+ namespaces=namespaces)
if response_format is not None:
self.response_encoding = response_format.attrib.get('Encoding')
v = response_format.attrib.get('Length')
@@ -181,19 +261,6 @@ class Key(object):
if v:
self.response_check = v.lower() == 'true'
- data = key_package.find('pskc:Key/pskc:Data', namespaces=namespaces)
- if data is not None:
- self._secret.parse(data.find(
- 'pskc:Secret', namespaces=namespaces))
- self._counter.parse(data.find(
- 'pskc:Counter', namespaces=namespaces))
- self._time_offset.parse(data.find(
- 'pskc:Time', namespaces=namespaces))
- self._time_interval.parse(data.find(
- 'pskc:TimeInterval', namespaces=namespaces))
- self._time_drift.parse(data.find(
- 'pskc:TimeDrift', namespaces=namespaces))
-
self.policy.parse(key_package.find(
'pskc:Key/pskc:Policy', namespaces=namespaces))
diff --git a/tests/test_rfc6030.doctest b/tests/test_rfc6030.doctest
index 0b8c62b..4530ae1 100644
--- a/tests/test_rfc6030.doctest
+++ b/tests/test_rfc6030.doctest
@@ -68,7 +68,7 @@ parameters.
'12345678901234567890'
>>> key.counter
0
->>> key.userid
+>>> key.key_userid
'UID=jsmith,DC=example-bank,DC=net'
http://arthurdejong.org/git/python-pskc/commit/?id=6becc615b36cd7a3f887563248425145bf9e8bb4
commit 6becc615b36cd7a3f887563248425145bf9e8bb4
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Apr 19 19:13:05 2014 +0200
Provide pskc.parse docstrings
This documents most of the API of the parsing functions and the PSKC
class.
diff --git a/pskc/parse.py b/pskc/parse.py
index a0a28a7..e5818b8 100644
--- a/pskc/parse.py
+++ b/pskc/parse.py
@@ -18,6 +18,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
+"""Module for parsing PSKC files.
+
+This module provides the PSKC class and some utility functions for parsing
+PSKC files.
+"""
+
+
from xml.etree import ElementTree
@@ -59,6 +66,16 @@ def g_e_d(tree, match):
class PSKC(object):
+ """Wrapper module for parsing a PSKC file.
+
+ Instances of this class provide the following attributes:
+
+ version: the PSKC format version used (1.0)
+ id: identifier
+ encryption: information on used encryption (Encryption instance)
+ mac: information on used MAC method (MAC instance)
+ keys: list of keys (Key instances)
+ """
def __init__(self, filename):
from pskc.encryption import Encryption
@@ -78,5 +95,6 @@ class PSKC(object):
'pskc:MACMethod', namespaces=namespaces))
# handle KeyPackage entries
self.keys = []
- for package in container.findall('pskc:KeyPackage',
namespaces=namespaces):
+ for package in container.findall(
+ 'pskc:KeyPackage', namespaces=namespaces):
self.keys.append(Key(self, package))
http://arthurdejong.org/git/python-pskc/commit/?id=1d42fbc57ca2e08e53d95baf2f6a832435455aa7
commit 1d42fbc57ca2e08e53d95baf2f6a832435455aa7
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Apr 19 18:50:42 2014 +0200
Complete pskc.policy docstrings
Also contains small consistency improvement.
diff --git a/pskc/policy.py b/pskc/policy.py
index 664b571..1074fce 100644
--- a/pskc/policy.py
+++ b/pskc/policy.py
@@ -18,6 +18,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
+"""Module that provides PSKC key policy information."""
+
class Policy(object):
"""Representation of a policy that describes key and pin usage.
@@ -88,6 +90,7 @@ class Policy(object):
def __init__(self, key=None, policy=None):
"""Create a new policy, optionally linked to the key and parsed."""
+ self.key = key
self.start_date = None
self.expiry_date = None
self.number_of_transactions = None
@@ -99,11 +102,10 @@ class Policy(object):
self.pin_max_length = None
self.pin_encoding = None
self.unknown_policy_elements = False
- self.key = key
self.parse(policy)
def parse(self, policy):
- """Read key policy information from the provided KeyPackage tree."""
+ """Read key policy information from the provided <Policy> tree."""
from pskc.parse import g_e_v, g_e_i, g_e_d, namespaces
if policy is None:
return
http://arthurdejong.org/git/python-pskc/commit/?id=b07d709629739dd2267855b6b814cc24fa780e3e
commit b07d709629739dd2267855b6b814cc24fa780e3e
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Apr 19 18:48:26 2014 +0200
Provide pskc.mac docstrings
This also hides two properties that are not part of the public API.
diff --git a/pskc/mac.py b/pskc/mac.py
index ca19930..ad7c27c 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -18,6 +18,17 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
+"""Module that provides message authentication for PSKC values.
+
+This module provides a MAC class that is used to store information about
+how the MAC should be calculated (including the MAC key) and a ValueMAC
+class that provides (H)MAC checking for PSKC key data.
+
+The MAC key is generated specifically for each PSKC file and encrypted
+with the PSKC encryption key.
+"""
+
+
import base64
import hashlib
import hmac
@@ -29,47 +40,62 @@ HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
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._value_mac = None
self.parse(value_mac)
def parse(self, value_mac):
+ """Read MAC information from the <ValueMAC> XML tree."""
from pskc.parse import g_e_v
if value_mac is None:
return
value = g_e_v(value_mac, '.')
if value is not None:
- self.value_mac = base64.b64decode(value)
+ self._value_mac = base64.b64decode(value)
def check(self, value):
- if value is None or self.value_mac is None:
+ """Check if the provided value matches the MAC.
+
+ This will return None if the value cannot be checked (no value,
+ no key, etc.) or a boolean otherwise.
+ """
+ if value is None or self._value_mac is None:
return
algorithm = self.mac.algorithm
key = self.mac.key
if algorithm == HMAC_SHA1 and key is not None:
h = hmac.new(key, value, hashlib.sha1).digest()
- return h == self.value_mac
+ return h == self._value_mac
class MAC(object):
+ """Class describing the MAC algorithm to use and how to get the key.
+
+ Instances of this class provide the following attributes:
+
+ algorithm: the name of the HMAC to use (currently only HMAC_SHA1)
+ key: the binary value of the MAC key if it can be decrypted
+ """
def __init__(self, pskc, mac_method=None):
self.algorithm = None
- self.mac_key = EncryptedValue(pskc.encryption)
+ self._mac_key = EncryptedValue(pskc.encryption)
self.parse(mac_method)
def parse(self, mac_method):
- """Read encryption information from the EncryptionKey XML tree."""
+ """Read MAC information from the <MACMethod> XML tree."""
from pskc.parse import g_e_v, namespaces
if mac_method is None:
return
self.algorithm = mac_method.attrib.get('Algorithm')
- self.mac_key.parse(mac_method.find(
+ self._mac_key.parse(mac_method.find(
'pskc:MACKey', namespaces=namespaces))
mac_key_reference = g_e_v(mac_method, 'pskc:MACKeyReference')
@property
def key(self):
- return self.mac_key.decrypt()
+ """Provides access to the MAC key binary value if available."""
+ return self._mac_key.decrypt()
http://arthurdejong.org/git/python-pskc/commit/?id=285860e4bad7ed442be267b3e537ebb11978bfa7
commit 285860e4bad7ed442be267b3e537ebb11978bfa7
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Apr 19 17:58:00 2014 +0200
Provide pskc.encryption docstrings
This documents classes in the pskc.encryption module.
diff --git a/pskc/encryption.py b/pskc/encryption.py
index d98871a..a1412d1 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -18,6 +18,16 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
+"""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.
+
+The encryption key can be derived using the KeyDerivation class.
+"""
+
+
import base64
from Crypto.Cipher import AES
@@ -38,13 +48,13 @@ class EncryptedValue(object):
def __init__(self, encryption, encrypted_value=None):
"""Initialise an encrypted value for the provided Key."""
+ self.encryption = encryption
self.algorithm = None
self.cipher_value = None
- self.encryption = encryption
self.parse(encrypted_value)
def parse(self, encrypted_value):
- """Read encrypted data from the EncryptedValue XML tree."""
+ """Read encrypted data from the <EncryptedValue> XML tree."""
from pskc.parse import g_e_v, namespaces
if encrypted_value is None:
return
@@ -76,7 +86,16 @@ PBKDF2_URIS = [
class KeyDerivation(object):
- """Handle derived keys."""
+ """Handle key derivation.
+
+ The algorithm property contains the key derivation algorithm to use. For
+ PBDKF2 the following parameters are set:
+
+ pbkdf2_salt: salt value
+ pbkdf2_iterations: number of iterations to use
+ pbkdf2_key_length: required key lengt
+ pbkdf2_prf: name of pseudorandom function used (HMAC-SHA1 is assumed)
+ """
def __init__(self, key_deriviation=None):
self.algorithm = None
@@ -88,6 +107,7 @@ class KeyDerivation(object):
self.parse(key_deriviation)
def parse(self, key_deriviation):
+ """Read derivation parameters from a <KeyDerivationMethod> element."""
from pskc.parse import g_e_v, g_e_i, namespaces
if key_deriviation is None:
return
@@ -113,6 +133,7 @@ class KeyDerivation(object):
self.pbkdf2_prf = prf.attrib.get('Algorithm')
def generate(self, password):
+ """Derive a key from the password."""
if self.algorithm in PBKDF2_URIS:
# TODO: support pseudorandom function (prf)
return PBKDF2(
@@ -121,7 +142,20 @@ class KeyDerivation(object):
class Encryption(object):
- """Class for handling encryption keys that are used in the PSKC file."""
+ """Class for handling encryption keys that are used in the PSKC file.
+
+ Encryption generally uses a symmetric key that is used to encrypt some
+ of the information stored in PSKC files (typically the seed). This
+ class provides the following values:
+
+ id: identifier of the key
+ 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)
+
+ The key can either be included in the PSKC file (in that case it
+ automatically picked up) or derived using the derive_key() method.
+ """
def __init__(self, key_info=None):
self.id = None
@@ -131,7 +165,7 @@ class Encryption(object):
self.parse(key_info)
def parse(self, key_info):
- """Read encryption information from the EncryptionKey XML tree."""
+ """Read encryption information from the <EncryptionKey> XML tree."""
from pskc.parse import g_e_v, namespaces
if key_info is None:
return
@@ -148,8 +182,10 @@ class Encryption(object):
@property
def key_name(self):
+ """Provide the name of the (first) key."""
if self.key_names:
return self.key_names[0]
def derive_key(self, password):
+ """Derive a key from the password."""
self.key = self.derivation.generate(password)
http://arthurdejong.org/git/python-pskc/commit/?id=8c9e03d942397603fa653b96c62ecaa5b52e303f
commit 8c9e03d942397603fa653b96c62ecaa5b52e303f
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Apr 19 17:34:42 2014 +0200
Move Key class to separate module
This also allows re-organising the imports a bit.
diff --git a/pskc/parse.py b/pskc/key.py
similarity index 76%
copy from pskc/parse.py
copy to pskc/key.py
index 83554ed..c729497 100644
--- a/pskc/parse.py
+++ b/pskc/key.py
@@ -1,4 +1,4 @@
-# parse.py - module for reading PSKC files
+# key.py - module for handling keys from pskc files
# coding: utf-8
#
# Copyright (C) 2014 Arthur de Jong
@@ -18,55 +18,16 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
-from xml.etree import ElementTree
import base64
-import dateutil.parser
-
+from pskc.encryption import EncryptedValue
+from pskc.mac import ValueMAC
from pskc.policy import Policy
-# the relevant XML namespaces for PSKC
-namespaces = dict(
- # the XML namespace URI for version 1.0 of PSKC
- pskc='urn:ietf:params:xml:ns:keyprov:pskc',
- # the XML Signature namespace
- ds='http://www.w3.org/2000/09/xmldsig#',
- # the XML Encryption namespace
- xenc='http://www.w3.org/2001/04/xmlenc#',
- # the XML Encryption version 1.1 namespace
- xenc11='http://www.w3.org/2009/xmlenc11#',
- # the PKCS #5 namespace
- pkcs5='http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#',
-)
-
-
-def g_e_v(tree, match):
- """Get the text value of an element (or None)."""
- element = tree.find(match, namespaces=namespaces)
- if element is not None:
- return element.text.strip()
-
-
-def g_e_i(tree, match):
- """Return an element value as an int (or None)."""
- element = tree.find(match, namespaces=namespaces)
- if element is not None:
- return int(element.text.strip())
-
-
-def g_e_d(tree, match):
- """Return an element value as a datetime (or None)."""
- element = tree.find(match, namespaces=namespaces)
- if element is not None:
- return dateutil.parser.parse(element.text.strip())
-
-
class DataType(object):
def __init__(self, key, element=None):
- from pskc.encryption import EncryptedValue
- from pskc.mac import ValueMAC
self.key = key
self.plain_value = None
self.encrypted_value = EncryptedValue(self.key.pskc.encryption)
@@ -74,6 +35,7 @@ class DataType(object):
self.parse(element)
def parse(self, element):
+ from pskc.parse import g_e_v, namespaces
if element is None:
return
self.plain_value = g_e_v(element, 'pskc:PlainValue')
@@ -125,6 +87,52 @@ class Key(object):
self.pskc = pskc
+ self.manufacturer = None
+ self.serial = None
+ self.model = None
+ self.issue_no = None
+ self.device_binding = None
+ self.start_date = None
+ self.expiry_date = None
+ self.device_userid = None
+
+ self.crypto_module = None
+
+ self.id = None
+ self.algorithm = None
+
+ self.issuer = None
+ self.key_profile = None
+ self.key_reference = None
+ self.friendly_name = None
+ self.userid = None
+
+ self.algorithm_suite = None
+
+ self.challenge_encoding = None
+ self.challenge_min_length = None
+ self.challenge_max_length = None
+ self.challenge_check = None
+
+ self.response_encoding = None
+ self.response_length = None
+ self.response_check = None
+
+ self._secret = BinaryDataType(self)
+ self._counter = IntegerDataType(self)
+ self._time_offset = IntegerDataType(self)
+ self._time_interval = IntegerDataType(self)
+ self._time_drift = IntegerDataType(self)
+
+ self.policy = Policy(self)
+
+ self.parse(key_package)
+
+ def parse(self, key_package):
+ from pskc.parse import g_e_v, g_e_d, namespaces
+ if key_package is None:
+ return
+
self.manufacturer = g_e_v(key_package,
'pskc:DeviceInfo/pskc:Manufacturer')
self.serial = g_e_v(key_package, 'pskc:DeviceInfo/pskc:SerialNo')
self.model = g_e_v(key_package, 'pskc:DeviceInfo/pskc:Model')
@@ -136,9 +144,6 @@ class Key(object):
self.crypto_module = g_e_v(key_package,
'pskc:CryptoModuleInfo/pskc:Id')
- self.id = None
- self.algorithm = None
-
key = key_package.find('pskc:Key', namespaces=namespaces)
if key is not None:
self.id = key.attrib.get('Id')
@@ -153,11 +158,6 @@ class Key(object):
self.algorithm_suite = g_e_v(key_package,
'pskc:Key/pskc:AlgorithmParameters/pskc:Suite')
- self.challenge_encoding = None
- self.challenge_min_length = None
- self.challenge_max_length = None
- self.challenge_check = None
-
challenge_format =
key_package.find('pskc:Key/pskc:AlgorithmParameters/pskc:ChallengeFormat',
namespaces=namespaces)
if challenge_format is not None:
self.challenge_encoding = challenge_format.attrib.get('Encoding')
@@ -171,10 +171,6 @@ class Key(object):
if v:
self.challenge_check = v.lower() == 'true'
- self.response_encoding = None
- self.response_length = None
- self.response_check = None
-
response_format =
key_package.find('pskc:Key/pskc:AlgorithmParameters/pskc:ResponseFormat',
namespaces=namespaces)
if response_format is not None:
self.response_encoding = response_format.attrib.get('Encoding')
@@ -185,12 +181,6 @@ class Key(object):
if v:
self.response_check = v.lower() == 'true'
- self._secret = BinaryDataType(self)
- self._counter = IntegerDataType(self)
- self._time_offset = IntegerDataType(self)
- self._time_interval = IntegerDataType(self)
- self._time_drift = IntegerDataType(self)
-
data = key_package.find('pskc:Key/pskc:Data', namespaces=namespaces)
if data is not None:
self._secret.parse(data.find(
@@ -204,7 +194,7 @@ class Key(object):
self._time_drift.parse(data.find(
'pskc:TimeDrift', namespaces=namespaces))
- self.policy = Policy(self, key_package.find(
+ self.policy.parse(key_package.find(
'pskc:Key/pskc:Policy', namespaces=namespaces))
@property
@@ -240,26 +230,3 @@ class Key(object):
if all(x is None for x in checks):
return None
return all(x is not False for x in checks)
-
-
-class PSKC(object):
-
- def __init__(self, filename):
- from pskc.encryption import Encryption
- from pskc.mac import MAC
- tree = ElementTree.parse(filename)
- container = tree.getroot()
- # the version of the PSKC schema
- self.version = container.attrib.get('Version')
- # unique identifier for the container
- self.id = container.attrib.get('Id')
- # handle EncryptionKey entries
- self.encryption = Encryption(container.find(
- 'pskc:EncryptionKey', namespaces=namespaces))
- # handle MACMethod entries
- self.mac = MAC(self, container.find(
- 'pskc:MACMethod', namespaces=namespaces))
- # handle KeyPackage entries
- self.keys = []
- for package in container.findall('pskc:KeyPackage',
namespaces=namespaces):
- self.keys.append(Key(self, package))
diff --git a/pskc/mac.py b/pskc/mac.py
index 8fbf39c..ca19930 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -22,6 +22,8 @@ import base64
import hashlib
import hmac
+from pskc.encryption import EncryptedValue
+
HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
@@ -54,7 +56,6 @@ class ValueMAC(object):
class MAC(object):
def __init__(self, pskc, mac_method=None):
- from pskc.encryption import EncryptedValue
self.algorithm = None
self.mac_key = EncryptedValue(pskc.encryption)
self.parse(mac_method)
diff --git a/pskc/parse.py b/pskc/parse.py
index 83554ed..a0a28a7 100644
--- a/pskc/parse.py
+++ b/pskc/parse.py
@@ -19,11 +19,6 @@
# 02110-1301 USA
from xml.etree import ElementTree
-import base64
-
-import dateutil.parser
-
-from pskc.policy import Policy
# the relevant XML namespaces for PSKC
@@ -59,194 +54,16 @@ def g_e_d(tree, match):
"""Return an element value as a datetime (or None)."""
element = tree.find(match, namespaces=namespaces)
if element is not None:
+ import dateutil.parser
return dateutil.parser.parse(element.text.strip())
-class DataType(object):
-
- def __init__(self, key, element=None):
- from pskc.encryption import EncryptedValue
- from pskc.mac import ValueMAC
- self.key = key
- self.plain_value = None
- self.encrypted_value = EncryptedValue(self.key.pskc.encryption)
- self.value_mac = ValueMAC(self.key.pskc.mac)
- self.parse(element)
-
- def parse(self, element):
- if element is None:
- return
- self.plain_value = g_e_v(element, 'pskc:PlainValue')
- self.encrypted_value.parse(element.find(
- 'pskc:EncryptedValue', namespaces=namespaces))
- self.value_mac.parse(element.find(
- 'pskc:ValueMAC', namespaces=namespaces))
-
- def check(self):
- """Check whether the embedded MAC is correct."""
- return self.value_mac.check(self.encrypted_value.cipher_value)
-
-
-class BinaryDataType(DataType):
-
- @property
- def value(self):
- # plain value is base64 encoded
- value = self.plain_value
- if value is not None:
- return base64.b64decode(value)
- # encrypted value is in correct format
- value = self.encrypted_value.decrypt()
- if value is not None:
- return value
-
-
-class IntegerDataType(DataType):
-
- @property
- def value(self):
- # plain value is a string representation of the number
- value = self.plain_value
- if value:
- return int(value)
- # decrypted value is a
- value = self.encrypted_value.decrypt()
- if value is not None:
- # Python3 has int.from_bytes(value, byteorder='big')
- v = 0
- for x in value:
- v = (v << 8) + ord(x)
- return v
-
-
-class Key(object):
-
- def __init__(self, pskc, key_package):
-
- self.pskc = pskc
-
- self.manufacturer = g_e_v(key_package,
'pskc:DeviceInfo/pskc:Manufacturer')
- self.serial = g_e_v(key_package, 'pskc:DeviceInfo/pskc:SerialNo')
- self.model = g_e_v(key_package, 'pskc:DeviceInfo/pskc:Model')
- self.issue_no = g_e_v(key_package, 'pskc:DeviceInfo/pskc:IssueNo')
- self.device_binding = g_e_v(key_package,
'pskc:DeviceInfo/pskc:DeviceBinding')
- self.start_date = g_e_d(key_package, 'pskc:DeviceInfo/pskc:StartDate')
- self.expiry_date = g_e_d(key_package,
'pskc:DeviceInfo/pskc:ExpiryDate')
- self.device_userid = g_e_v(key_package, 'pskc:DeviceInfo/pskc:UserId')
-
- self.crypto_module = g_e_v(key_package,
'pskc:CryptoModuleInfo/pskc:Id')
-
- self.id = None
- self.algorithm = None
-
- key = key_package.find('pskc:Key', namespaces=namespaces)
- if key is not None:
- self.id = key.attrib.get('Id')
- self.algorithm = key.attrib.get('Algorithm')
-
- self.issuer = g_e_v(key_package, 'pskc:Key/pskc:Issuer')
- self.key_profile = g_e_v(key_package, 'pskc:Key/pskc:KeyProfileId')
- self.key_reference = g_e_v(key_package, 'pskc:Key/pskc:KeyReference')
- self.friendly_name = g_e_v(key_package, 'pskc:Key/pskc:FriendlyName')
- # TODO: support multi-language values of <FriendlyName>
- self.userid = g_e_v(key_package, 'pskc:Key/pskc:UserId')
-
- self.algorithm_suite = g_e_v(key_package,
'pskc:Key/pskc:AlgorithmParameters/pskc:Suite')
-
- self.challenge_encoding = None
- self.challenge_min_length = None
- self.challenge_max_length = None
- self.challenge_check = None
-
- challenge_format =
key_package.find('pskc:Key/pskc:AlgorithmParameters/pskc:ChallengeFormat',
namespaces=namespaces)
- if challenge_format is not None:
- self.challenge_encoding = challenge_format.attrib.get('Encoding')
- v = challenge_format.attrib.get('Min')
- if v:
- self.challenge_min_length = int(v)
- v = challenge_format.attrib.get('Max')
- if v:
- self.challenge_max_length = int(v)
- v = challenge_format.attrib.get('CheckDigits')
- if v:
- self.challenge_check = v.lower() == 'true'
-
- self.response_encoding = None
- self.response_length = None
- self.response_check = None
-
- response_format =
key_package.find('pskc:Key/pskc:AlgorithmParameters/pskc:ResponseFormat',
namespaces=namespaces)
- if response_format is not None:
- self.response_encoding = response_format.attrib.get('Encoding')
- v = response_format.attrib.get('Length')
- if v:
- self.response_length = int(v)
- v = response_format.attrib.get('CheckDigits')
- if v:
- self.response_check = v.lower() == 'true'
-
- self._secret = BinaryDataType(self)
- self._counter = IntegerDataType(self)
- self._time_offset = IntegerDataType(self)
- self._time_interval = IntegerDataType(self)
- self._time_drift = IntegerDataType(self)
-
- data = key_package.find('pskc:Key/pskc:Data', namespaces=namespaces)
- if data is not None:
- self._secret.parse(data.find(
- 'pskc:Secret', namespaces=namespaces))
- self._counter.parse(data.find(
- 'pskc:Counter', namespaces=namespaces))
- self._time_offset.parse(data.find(
- 'pskc:Time', namespaces=namespaces))
- self._time_interval.parse(data.find(
- 'pskc:TimeInterval', namespaces=namespaces))
- self._time_drift.parse(data.find(
- 'pskc:TimeDrift', namespaces=namespaces))
-
- self.policy = Policy(self, key_package.find(
- 'pskc:Key/pskc:Policy', namespaces=namespaces))
-
- @property
- def secret(self):
- """The secret key itself."""
- return self._secret.value
-
- @property
- def counter(self):
- """An event counter for event-based OTP."""
- return self._counter.value
-
- @property
- def time_offset(self):
- """A time offset for time-based OTP (number of intervals)."""
- return self._time_offset.value
-
- @property
- def time_interval(self):
- """A time interval in seconds."""
- return self._time_interval.value
-
- @property
- def time_drift(self):
- """Device clock drift value (number of time intervals)."""
- return self._time_drift.value
-
- def check(self):
- """Check if all MACs in the message are valid."""
- checks = (self._secret.check(), self._counter.check(),
- self._time_offset.check(), self._time_interval.check(),
- self._time_drift.check())
- if all(x is None for x in checks):
- return None
- return all(x is not False for x in checks)
-
-
class PSKC(object):
def __init__(self, filename):
from pskc.encryption import Encryption
from pskc.mac import MAC
+ from pskc.key import Key
tree = ElementTree.parse(filename)
container = tree.getroot()
# the version of the PSKC schema
diff --git a/pskc/policy.py b/pskc/policy.py
index 39dd191..664b571 100644
--- a/pskc/policy.py
+++ b/pskc/policy.py
@@ -100,12 +100,13 @@ class Policy(object):
self.pin_encoding = None
self.unknown_policy_elements = False
self.key = key
- if policy is not None:
- self.parse(policy)
+ self.parse(policy)
def parse(self, policy):
"""Read key policy information from the provided KeyPackage tree."""
from pskc.parse import g_e_v, g_e_i, g_e_d, namespaces
+ if policy is None:
+ return
self.start_date = g_e_d(policy, 'pskc:StartDate')
self.expiry_date = g_e_d(policy, 'pskc:ExpiryDate')
-----------------------------------------------------------------------
Summary of changes:
pskc/__init__.py | 48 ++++++++-
pskc/encryption.py | 46 +++++++-
pskc/{parse.py => key.py} | 256 +++++++++++++++++++++++++-------------------
pskc/mac.py | 45 ++++++--
pskc/parse.py | 213 +-----------------------------------
pskc/policy.py | 11 +-
tests/test_rfc6030.doctest | 2 +-
7 files changed, 279 insertions(+), 342 deletions(-)
copy pskc/{parse.py => key.py} (56%)
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. ba17976c43e85f17486cd12ec59740a46b807632,
Commits of the python-pskc project