lists.arthurdejong.org
RSS feed

python-pskc branch master updated. 460f335781f8146a04262edb8f2384310118baee

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

python-pskc branch master updated. 460f335781f8146a04262edb8f2384310118baee



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  460f335781f8146a04262edb8f2384310118baee (commit)
       via  a926ddb19ed022f09995aecbf4d572aeaa83339d (commit)
       via  e53e8651052ec9432d7f49c41c47573ee7477c7f (commit)
       via  3fe09195ce4c6b0248c6c3f845d40c4fb3089626 (commit)
      from  591bb5dac7a1faebc59017de71ddb584da3c9586 (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=460f335781f8146a04262edb8f2384310118baee

commit 460f335781f8146a04262edb8f2384310118baee
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Apr 12 18:03:54 2014 +0200

    Add test for Figure 6 from RFC6030
    
    This test key encryption with a pre-shared key and MAC checks.

diff --git a/tests/rfc6030-figure6.pskc b/tests/rfc6030-figure6.pskc
new file mode 100644
index 0000000..950c620
--- /dev/null
+++ b/tests/rfc6030-figure6.pskc
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Figure 6 example from RFC6030 that shows key material encrypted using
+  AES-128-CBC with pre-shared key (12345678901234567890123456789012 hex).
+  The MAC key used (1122334455667788990011223344556677889900 hex) is
+  encrypted with the same key.
+-->
+
+<KeyContainer Version="1.0"
+    xmlns="urn:ietf:params:xml:ns:keyprov:pskc"
+    xmlns:ds="http://www.w3.org/2000/09/xmldsig#";
+    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#";>
+    <EncryptionKey>
+        <ds:KeyName>Pre-shared-key</ds:KeyName>
+    </EncryptionKey>
+    <MACMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1";>
+        <MACKey>
+            <xenc:EncryptionMethod
+            Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <xenc:CipherData>
+                <xenc:CipherValue>
+    ESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSX
+                </xenc:CipherValue>
+            </xenc:CipherData>
+        </MACKey>
+    </MACMethod>
+    <KeyPackage>
+        <DeviceInfo>
+            <Manufacturer>Manufacturer</Manufacturer>
+            <SerialNo>987654321</SerialNo>
+        </DeviceInfo>
+        <CryptoModuleInfo>
+            <Id>CM_ID_001</Id>
+        </CryptoModuleInfo>
+        <Key Id="12345678"
+            Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+            <Issuer>Issuer</Issuer>
+            <AlgorithmParameters>
+                <ResponseFormat Length="8" Encoding="DECIMAL"/>
+            </AlgorithmParameters>
+            <Data>
+                <Secret>
+                    <EncryptedValue>
+                        <xenc:EncryptionMethod
+                        
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+                        <xenc:CipherData>
+                            <xenc:CipherValue>
+    AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
+                            </xenc:CipherValue>
+                        </xenc:CipherData>
+                    </EncryptedValue>
+                    <ValueMAC>Su+NvtQfmvfJzF6bmQiJqoLRExc=
+                    </ValueMAC>
+                </Secret>
+                <Counter>
+                    <PlainValue>0</PlainValue>
+                </Counter>
+            </Data>
+        </Key>
+    </KeyPackage>
+</KeyContainer>
diff --git a/tests/test_rfc6030.doctest b/tests/test_rfc6030.doctest
index 049f3b1..de86724 100644
--- a/tests/test_rfc6030.doctest
+++ b/tests/test_rfc6030.doctest
@@ -138,3 +138,19 @@ False
 '1234'
 >>> key1.policy.pin
 '1234'
+
+
+This tests key encryption based on pre-shared keys as illustrated in
+Figure 6 from RFC6030.
+
+>>> pskc = PSKC('tests/rfc6030-figure6.pskc')
+>>> pskc.encryption.key_name
+'Pre-shared-key'
+>>> pskc.encryption.key = '12345678901234567890123456789012'.decode('hex')
+>>> pskc.mac.key.encode('hex')
+'1122334455667788990011223344556677889900'
+>>> key = pskc.keys[0]
+>>> key.secret.encode('hex')
+'3132333435363738393031323334353637383930'
+>>> key.check()
+True

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

commit a926ddb19ed022f09995aecbf4d572aeaa83339d
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Apr 12 17:52:08 2014 +0200

    Implement MAC checking
    
    This implements message message authentication code checking for the
    encrypted values if MACMethod and ValueMAC are present.

diff --git a/pskc/mac.py b/pskc/mac.py
new file mode 100644
index 0000000..8fbf39c
--- /dev/null
+++ b/pskc/mac.py
@@ -0,0 +1,74 @@
+# mac.py - module for checking value signatures
+# coding: utf-8
+#
+# Copyright (C) 2014 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import base64
+import hashlib
+import hmac
+
+
+HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
+
+
+class ValueMAC(object):
+
+    def __init__(self, mac, value_mac=None):
+        self.mac = mac
+        self.value_mac = None
+        self.parse(value_mac)
+
+    def parse(self, value_mac):
+        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)
+
+    def check(self, value):
+        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
+
+
+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)
+
+    def parse(self, mac_method):
+        """Read encryption information from the EncryptionKey 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(
+            'pskc:MACKey', namespaces=namespaces))
+        mac_key_reference = g_e_v(mac_method, 'pskc:MACKeyReference')
+
+    @property
+    def key(self):
+        return self.mac_key.decrypt()
diff --git a/pskc/parse.py b/pskc/parse.py
index e61a5d7..6509e70 100644
--- a/pskc/parse.py
+++ b/pskc/parse.py
@@ -66,9 +66,11 @@ 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):
@@ -77,6 +79,12 @@ class DataType(object):
         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):
@@ -224,11 +232,21 @@ class Key(object):
         """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
         tree = ElementTree.parse(filename)
         container = tree.getroot()
         # the version of the PSKC schema
@@ -238,6 +256,9 @@ class PSKC(object):
         # 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):

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

commit e53e8651052ec9432d7f49c41c47573ee7477c7f
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Apr 12 12:26:40 2014 +0200

    Support decrypting with a pre-shared key
    
    This adds an encryption module that provides wrappers for handling
    decryption.

diff --git a/pskc/encryption.py b/pskc/encryption.py
new file mode 100644
index 0000000..2c06ae6
--- /dev/null
+++ b/pskc/encryption.py
@@ -0,0 +1,88 @@
+# encryption.py - module for handling encrypted values
+# coding: utf-8
+#
+# Copyright (C) 2014 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import base64
+
+from Crypto.Cipher import AES
+
+
+AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+
+
+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.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."""
+        from pskc.parse import g_e_v, namespaces
+        if encrypted_value is None:
+            return
+        encryption_method = encrypted_value.find(
+            'xenc:EncryptionMethod', namespaces=namespaces)
+        self.algorithm = encryption_method.attrib.get('Algorithm')
+        value = g_e_v(encrypted_value, 'xenc:CipherData/xenc:CipherValue')
+        if value is not None:
+            self.cipher_value = base64.b64decode(value)
+
+    def decrypt(self):
+        """Decrypt the linked value and return the plaintext value."""
+        key = self.encryption.key
+        ciphertext = self.cipher_value
+        if key is None or ciphertext is None:
+            return
+        if self.algorithm == AES128_CBC:
+            iv = ciphertext[:AES.block_size]
+            cipher = AES.new(key, AES.MODE_CBC, iv)
+            plaintext = cipher.decrypt(ciphertext[AES.block_size:])
+            return plaintext[0:-ord(plaintext[-1])]
+
+
+class Encryption(object):
+    """Class for handling encryption keys that are used in the PSKC file."""
+
+    def __init__(self, key_info=None):
+        self.key_names = []
+        self.key = None
+        if key_info is not None:
+            self.parse(key_info)
+
+    def parse(self, key_info):
+        """Read encryption information from the EncryptionKey XML tree."""
+        from pskc.parse import g_e_v, namespaces
+        for name in key_info.findall('ds:KeyName', namespaces=namespaces):
+            self.key_names.append(g_e_v(name, '.'))
+
+    @property
+    def key_name(self):
+        if self.key_names:
+            return self.key_names[0]
diff --git a/pskc/parse.py b/pskc/parse.py
index d205c83..e61a5d7 100644
--- a/pskc/parse.py
+++ b/pskc/parse.py
@@ -65,14 +65,18 @@ def g_e_d(tree, match):
 class DataType(object):
 
     def __init__(self, key, element=None):
+        from pskc.encryption import EncryptedValue
         self.key = key
         self.plain_value = None
+        self.encrypted_value = EncryptedValue(self.key.pskc.encryption)
         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))
 
 
 class BinaryDataType(DataType):
@@ -83,7 +87,10 @@ class BinaryDataType(DataType):
         value = self.plain_value
         if value is not None:
             return base64.b64decode(value)
-        # TODO: else: see if EncryptedValue is present and decode
+        # encrypted value is in correct format
+        value = self.encrypted_value.decrypt()
+        if value is not None:
+            return value
 
 
 class IntegerDataType(DataType):
@@ -94,7 +101,14 @@ class IntegerDataType(DataType):
         value = self.plain_value
         if value:
             return int(value)
-        # TODO: else: see if EncryptedValue is present and decode
+        # 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):
@@ -214,12 +228,16 @@ class Key(object):
 class PSKC(object):
 
     def __init__(self, filename):
+        from pskc.encryption import Encryption
         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 KeyPackage entries
         self.keys = []
         for package in container.findall('pskc:KeyPackage', 
namespaces=namespaces):

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

commit 3fe09195ce4c6b0248c6c3f845d40c4fb3089626
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri Apr 11 23:50:07 2014 +0200

    Refactor DataType value handling
    
    This ensures that DataType values are retrieved dynamically instead of
    at the time the PSKC file was parsed in order to make decryption work.

diff --git a/pskc/parse.py b/pskc/parse.py
index dc980af..d205c83 100644
--- a/pskc/parse.py
+++ b/pskc/parse.py
@@ -64,26 +64,25 @@ def g_e_d(tree, match):
 
 class DataType(object):
 
-    def __init__(self, element, t=str):
-        self.element = element
-        self.t = t
+    def __init__(self, key, element=None):
+        self.key = key
+        self.plain_value = None
+        self.parse(element)
 
-    @property
-    def plain_value(self):
-        if self.element is None:
+    def parse(self, element):
+        if element is None:
             return
-        plain_value = self.element.find('pskc:PlainValue', 
namespaces=namespaces)
-        if plain_value is not None:
-            return plain_value.text.strip()
+        self.plain_value = g_e_v(element, 'pskc:PlainValue')
 
 
 class BinaryDataType(DataType):
 
     @property
     def value(self):
-        plain_value = self.plain_value
-        if plain_value:
-            return base64.b64decode(plain_value)
+        # plain value is base64 encoded
+        value = self.plain_value
+        if value is not None:
+            return base64.b64decode(value)
         # TODO: else: see if EncryptedValue is present and decode
 
 
@@ -91,9 +90,10 @@ class IntegerDataType(DataType):
 
     @property
     def value(self):
-        plain_value = self.plain_value
-        if plain_value:
-            return int(plain_value)
+        # plain value is a string representation of the number
+        value = self.plain_value
+        if value:
+            return int(value)
         # TODO: else: see if EncryptedValue is present and decode
 
 
@@ -163,33 +163,53 @@ class Key(object):
             if v:
                 self.response_check = v.lower() == 'true'
 
-        self.secret = None
-        self.counter = None
-        self.time_offset = None
-        self.time_interval = None
-        self.time_drift = None
+        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:
-            # the secret key itself
-            secret = BinaryDataType(data.find('pskc:Secret', 
namespaces=namespaces))
-            self.secret = secret.value
-            # event counter for event-based OTP
-            counter = IntegerDataType(data.find('pskc:Counter', 
namespaces=namespaces))
-            self.counter = counter.value
-            # time offset for time-based OTP (number of intervals)
-            time_offset = IntegerDataType(data.find('pskc:Time', 
namespaces=namespaces))
-            self.time_offset = time_offset.value
-            # time interval in seconds
-            time_interval = IntegerDataType(data.find('pskc:TimeInterval', 
namespaces=namespaces))
-            self.time_interval = time_interval.value
-            # device clock drift value (number of time intervals)
-            time_drift = IntegerDataType(data.find('pskc:TimeDrift', 
namespaces=namespaces))
-            self.time_drift = time_drift
+            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
+
 
 class PSKC(object):
 

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

Summary of changes:
 pskc/encryption.py         |   88 +++++++++++++++++++++++++++++
 pskc/mac.py                |   74 ++++++++++++++++++++++++
 pskc/parse.py              |  135 +++++++++++++++++++++++++++++++-------------
 tests/rfc6030-figure6.pskc |   62 ++++++++++++++++++++
 tests/test_rfc6030.doctest |   16 ++++++
 5 files changed, 337 insertions(+), 38 deletions(-)
 create mode 100644 pskc/encryption.py
 create mode 100644 pskc/mac.py
 create mode 100644 tests/rfc6030-figure6.pskc


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/