lists.arthurdejong.org
RSS feed

python-pskc branch master updated. 0.5-35-gc365a70

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

python-pskc branch master updated. 0.5-35-gc365a70



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  c365a7004cec756ea573fb4414973ddf50739665 (commit)
       via  418f3dcb4f443d5c20b255aa4341bac70c8cc517 (commit)
       via  a97ac469c7134eee1920c64c90bf597388e3831d (commit)
       via  c0bd21f6b6250b9161613106bb6fab27a3ce4b16 (commit)
       via  ea503d6590e3a3ede8e119da3887d8543731cf9a (commit)
      from  fcc6cdb0320cdae1206f2f231c2f5bca56b9b847 (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 -----------------------------------------------------------------
https://arthurdejong.org/git/python-pskc/commit/?id=c365a7004cec756ea573fb4414973ddf50739665

commit c365a7004cec756ea573fb4414973ddf50739665
Merge: fcc6cdb 418f3dc
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Wed Dec 27 19:05:35 2017 +0100

    Implement XML signature checking


https://arthurdejong.org/git/python-pskc/commit/?id=418f3dcb4f443d5c20b255aa4341bac70c8cc517

commit 418f3dcb4f443d5c20b255aa4341bac70c8cc517
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Dec 17 20:50:56 2017 +0100

    Add documentation for signed PSKC files

diff --git a/docs/encryption.rst b/docs/encryption.rst
index df0fb8a..b1d008a 100644
--- a/docs/encryption.rst
+++ b/docs/encryption.rst
@@ -99,7 +99,7 @@ The Encryption class
 
       Configure pre-shared key encryption when writing the file.
 
-      :param binary key: the encryption key to use
+      :param bytes key: the encryption key to use
       :param str id: encryption key identifier
       :param str algorithm: encryption algorithm
       :param int key_length: encryption key length in bytes
@@ -132,7 +132,7 @@ The Encryption class
       :param str key_name: a name for the key
       :param list key_names: a number of names for the key
       :param list fields: a list of fields to encrypt
-      :param binary salt: PBKDF2 salt
+      :param bytes salt: PBKDF2 salt
       :param int salt_length: used when generating random salt
       :param int iterations: number of PBKDF2 iterations
       :param function prf: PBKDF2 pseudorandom function
diff --git a/docs/index.rst b/docs/index.rst
index 66bcbee..4496de3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -11,6 +11,7 @@ Contents
    usage
    encryption
    mac
+   signatures
    policy
    exceptions
    changes
diff --git a/docs/mac.rst b/docs/mac.rst
index 569bc9c..c9e7813 100644
--- a/docs/mac.rst
+++ b/docs/mac.rst
@@ -48,7 +48,7 @@ The MAC class
       Configure an encrypted MAC key for creating a new PSKC file.
 
       :param str algorithm: encryption algorithm
-      :param binary key: the encryption key to use
+      :param bytes key: the encryption key to use
 
       None of the arguments are required. By default HMAC-SHA1 will be used
       as a MAC algorithm. If no key is configured a random key will be
diff --git a/docs/signatures.rst b/docs/signatures.rst
new file mode 100644
index 0000000..f5adc78
--- /dev/null
+++ b/docs/signatures.rst
@@ -0,0 +1,114 @@
+XML Signature checking
+======================
+
+.. module:: pskc.signature
+
+PSKC files can contain embedded XML signatures that allow integrity and
+authenticity checks of the transmitted information. This signature typically
+covers the whole PSKC file while MAC checking only covers the encrypted
+parts.
+
+   >>> pskc = PSKC('somefile.pskcxml')
+   >>> pskc.signature.verify()
+   >>> pskc = pskc.signature.signed_pskc
+
+When using XML signatures it is important to use the
+:attr:`~pskc.signature.Signature.signed_pskc` attribute after verification
+because that :class:`~pskc.PSKC` instance will only contain the signed
+information.
+
+
+To create a signed PSKC file build up a :class:`~pskc.PSKC` instance as
+usual, configure the signature and save it:
+
+   >>> pskc.signature.sign(key, certificate)
+   >>> pskc.write('output.pskcxml')
+
+
+The Signature class
+--------------------
+
+.. class:: Signature
+
+   .. attribute:: is_signed
+
+      A boolan value that indicates whether an XML signature is present in
+      the PSKC file. This propery does not indicate whether the signature
+      is validated.
+
+   .. attribute:: algorithm
+
+      A URI of the signing algorithm used.
+      Assigned values to this attribute will be converted to the canonical
+      URI for the algorithm if it is known.
+
+   .. attribute:: canonicalization_method
+
+      A URI that is used to identify the XML canonicalization method used.
+
+   .. attribute:: digest_algorithm
+
+      A URI that identifies that hashing algorithm that is used to construct
+      the signature.
+
+   .. attribute:: issuer
+
+      A distinguished name of the issuer of the certificate that belongs to
+      the key that is used for the signature.
+
+   .. attribute:: serial
+
+      A serial number of the certificate that belongs to the key that is used
+      for the signature.
+
+   .. attribute:: key
+
+      A PEM encoded key that will be used to create the signed PSKC file.
+
+   .. attribute:: certificate
+
+      A PEM encoded certificate that is embedded inside the signature that
+      can be used to validate the signature.
+
+   .. attribute:: signed_pskc
+
+      A :class:`~pskc.PSKC` instance that contains the signed contents of the
+      PSKC file. It is usually required to call :func:`verify` before
+      accessing this attribute without raising an exception.
+
+   .. function:: verify(certificate=None, ca_pem_file=None)
+
+      Verify the validity of the embedded XML signature. This function will
+      raise an exception when the validation fails.
+
+      :param bytes certificate: a PEM encoded certificate that is used for 
verification
+      :param str ca_pem_file: the name of a file that contains a CA certificate
+
+      The signature can be verified in three ways:
+
+      * The signature was made with a key that has a certificate that is
+        signed by a CA that is configured in the system CA store. In this
+        case neither `certificate` or `ca_pem_file` need to be specified (but
+        a certificate needs to be embedded inside the PSKC file).
+      * The signature was made with a key and a certificate for the key was
+        transmitted out-of-band. In this case the `certificate` argument
+        needs to be present.
+      * The signature was made with a key and has a certificate that is
+        signed by a specific CA who's certificate was transmitted
+        out-of-band. In this case the `ca_pem_file` is used to point to a CA
+        certificate file (but a certificate needs to be embedded inside the
+        PSKC file).
+
+      After calling this function a verified version of the PSKC file will
+      be present in the :attr:`signed_pskc` attribute.
+
+   .. function:: sign(key, certificate=None)
+
+      Set up a key and optionally a certificate that will be used to create an
+      embedded XML signature when writing the file.
+
+      :param bytes key: PEM encoded key used for signing
+      :param bytes certificate: PEM encoded certificate that will be embedded
+
+      This is a utility function that is used to configure the properties
+      needed to create a signed PSKC file.
diff --git a/docs/usage.rst b/docs/usage.rst
index 2c386ec..ce9fc87 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -110,6 +110,11 @@ The PSKC class
       :class:`~pskc.mac.MAC` instance for handling integrity checking.
       See :doc:`mac` for more information.
 
+   .. attribute:: signature
+
+      :class:`~pskc.signature.Signature` instance for handling embedded XML
+      signatures in the file.
+      See :doc:`signatures` for more information.
 
    .. function:: add_device([**kwargs])
 

https://arthurdejong.org/git/python-pskc/commit/?id=a97ac469c7134eee1920c64c90bf597388e3831d

commit a97ac469c7134eee1920c64c90bf597388e3831d
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Dec 23 23:01:00 2017 +0100

    Implement signature checking
    
    This adds support for creating and verifying embedded XML signatures in
    PSKC files. This uses the third-party signxml library for actual signing
    and verification.
    
    The signxml library has a dependency on lxml and defusedxml (and a few
    others) but all parts of python-pskc still work correctly with our
    without lxml and/or defusedxml and signxml is only required when working
    with embedded signatures.
    
    This modifies the tox configuration to skip the signature checks if
    singxml is not installed and to only require 100% code coverage if the
    signature tests are done.

diff --git a/pskc/parser.py b/pskc/parser.py
index fac302a..11486f8 100644
--- a/pskc/parser.py
+++ b/pskc/parser.py
@@ -23,6 +23,7 @@
 
 import array
 import base64
+import copy
 
 from pskc.exceptions import ParseError
 from pskc.key import EncryptedIntegerValue, EncryptedValue
@@ -60,12 +61,14 @@ class PSKCParser(object):
             tree = parse(filename)
         except Exception:
             raise ParseError('Error parsing XML')
-        remove_namespaces(tree)
+        # save a clean copy of the tree for signature checking
+        pskc.signature.tree = copy.deepcopy(tree)
         cls.parse_document(pskc, tree.getroot())
 
     @classmethod
     def parse_document(cls, pskc, container):
         """Read information from the provided <KeyContainer> tree."""
+        remove_namespaces(container)
         if container.tag not in ('KeyContainer', 'SecretContainer'):
             raise ParseError('Missing KeyContainer')
         # the version of the PSKC schema
diff --git a/pskc/serialiser.py b/pskc/serialiser.py
index 3a89da1..99019c8 100644
--- a/pskc/serialiser.py
+++ b/pskc/serialiser.py
@@ -24,7 +24,7 @@
 import base64
 
 from pskc.key import EncryptedIntegerValue, EncryptedValue
-from pskc.xml import find, mk_elem, tostring
+from pskc.xml import find, mk_elem, move_namespaces, reformat, tostring
 
 
 def my_b64encode(value):
@@ -53,7 +53,7 @@ class PSKCSerialiser(object):
         cls.serialise_mac(pskc.mac, container)
         for device in pskc.devices:
             cls.serialise_key_package(device, container)
-        return container
+        return cls.serialise_signature(pskc.signature, container)
 
     @classmethod
     def serialise_encryption(cls, encryption, container):
@@ -232,3 +232,14 @@ class PSKCSerialiser(object):
             mk_elem(policy_elm, 'pskc:KeyUsage', usage)
         mk_elem(policy_elm, 'pskc:NumberOfTransactions',
                 policy.number_of_transactions)
+
+    @classmethod
+    def serialise_signature(cls, signature, container):
+        if not signature.key:
+            return container
+        # move the namespace to the root element and reformat before signing
+        mk_elem(container, 'ds:Signature', Id='placeholder')
+        container = move_namespaces(container)
+        reformat(container)
+        # sign the document
+        return signature.sign_xml(container)
diff --git a/pskc/signature.py b/pskc/signature.py
index 31c3ab0..601fc48 100644
--- a/pskc/signature.py
+++ b/pskc/signature.py
@@ -25,6 +25,41 @@ keys and certificates.
 """
 
 
+def sign_x509(xml, key, certificate, algorithm=None, digest_algorithm=None,
+              canonicalization_method=None):
+    """Sign PSKC data using X.509 certificate and private key.
+
+    xml: an XML document
+    key: the private key in binary format
+    certificate: the X.509 certificate
+    """
+    import signxml
+    algorithm = algorithm or 'rsa-sha256'
+    digest_algorithm = digest_algorithm or 'sha256'
+    canonicalization_method = (
+        canonicalization_method or
+        signxml.XMLSignatureProcessor.default_c14n_algorithm)
+    return signxml.XMLSigner(
+        method=signxml.methods.enveloped,
+        signature_algorithm=algorithm.rsplit('#', 1)[-1].lower(),
+        digest_algorithm=digest_algorithm.rsplit('#', 1)[-1].lower(),
+        c14n_algorithm=canonicalization_method,
+    ).sign(xml, key=key, cert=certificate)
+
+
+def verify_x509(tree, certificate=None, ca_pem_file=None):
+    """Verify signature in PSKC data against a trusted X.509 certificate.
+
+    If a certificate is supplied it is used to validate the signature,
+    otherwise any embedded certificate is used and validated against a
+    certificate in ca_pem_file if it specified and otherwise the operating
+    system CA certificates.
+    """
+    from signxml import XMLVerifier
+    return XMLVerifier().verify(
+        tree, x509_cert=certificate, ca_pem_file=ca_pem_file).signed_xml
+
+
 class Signature(object):
     """Class for handling signature checking of the PSKC file.
 
@@ -36,7 +71,9 @@ class Signature(object):
       digest_algorithm: algorithm used for creating the hash
       issuer: issuer of the certificate
       serial: serial number of the certificate
+      key: key that will be used when creating a signed PSKC file
       certificate: the certificate that is embedded in the signature
+      signed_pskc: a PSKC instance with the signed information
     """
 
     def __init__(self, pskc):
@@ -46,6 +83,7 @@ class Signature(object):
         self.digest_algorithm = None
         self.issuer = None
         self.serial = None
+        self.key = None
         self.certificate = None
 
     @property
@@ -66,3 +104,34 @@ class Signature(object):
     def algorithm(self, value):
         from pskc.algorithms import normalise_algorithm
         self._algorithm = normalise_algorithm(value)
+
+    @property
+    def signed_pskc(self):
+        """Provide the signed PSKC information."""
+        if not hasattr(self, '_signed_pskc'):
+            self.verify()
+        return self._signed_pskc
+
+    def verify(self, certificate=None, ca_pem_file=None):
+        """Check that the signature was made with the specified certificate.
+        If no certificate is provided the signature is expected to contain a
+        signature that is signed by the CA certificate (or the CA standard CA
+        certificates when ca_pem_file is absent)."""
+        from pskc import PSKC
+        from pskc.parser import PSKCParser
+        signed_xml = verify_x509(self.tree, certificate, ca_pem_file)
+        pskc = PSKC()
+        PSKCParser.parse_document(pskc, signed_xml)
+        self._signed_pskc = pskc
+        return True
+
+    def sign(self, key, certificate=None):
+        """Add an XML signature to the file."""
+        self.key = key
+        self.certificate = certificate
+
+    def sign_xml(self, xml):
+        """Sign an XML document with the configured key and certificate."""
+        return sign_x509(
+            xml, self.key, self.certificate, self.algorithm,
+            self.digest_algorithm, self.canonicalization_method)
diff --git a/pskc/xml.py b/pskc/xml.py
index 43ed34c..c7ed2a8 100644
--- a/pskc/xml.py
+++ b/pskc/xml.py
@@ -208,6 +208,7 @@ def move_namespaces(element):
 
 def reformat(element, indent=''):
     """Reformat the XML tree to have nice wrapping and indenting."""
+    tag = element.tag.split('}')[-1]
     # re-order attributes by alphabet
     attrib = sorted(element.attrib.items())
     element.attrib.clear()
@@ -216,7 +217,9 @@ def reformat(element, indent=''):
         # clean up inner text
         if element.text:
             element.text = element.text.strip()
-    else:
+        if tag in ('X509Certificate', 'SignatureValue'):
+            element.text = ''.join(x for x in element.text if not x.isspace())
+    elif tag != 'SignedInfo':
         # indent children
         element.text = '\n ' + indent
         childred = list(element)
diff --git a/setup.py b/setup.py
index 2472d7f..e066ef7 100755
--- a/setup.py
+++ b/setup.py
@@ -66,5 +66,6 @@ setup(
     extras_require={
         'lxml':  ['lxml'],
         'defuse':  ['defusedxml'],
+        'signature': ['signxml']
     },
 )
diff --git a/tests/certificate/README b/tests/certificate/README
new file mode 100644
index 0000000..05184e0
--- /dev/null
+++ b/tests/certificate/README
@@ -0,0 +1,29 @@
+This directory contains keys and certificates that are used in the
+python-pskc test suite for checking XML signatures.
+
+The CA key and certificate were generated with:
+
+openssl req \
+  -x509 -newkey rsa:2048 -nodes \
+  -keyout ca-key.pem -out ca-certificate.pem \
+  -days 3650 -subj '/C=NL/O=python-pskc/CN=Test CA'
+
+The key used for signing the PSKC files and corresponding self-signed
+certificate were generated with:
+
+openssl req \
+  -x509 -newkey rsa:2048 -nodes \
+  -keyout key.pem -out ss-certificate.pem \
+  -days 3650 -subj '/C=NL/O=python-pskc/CN=Test signing'
+
+The certificate signed by the CA key was generated with:
+
+openssl req \
+  -new \
+  -key key.pem -out request.pem \
+  -subj '/C=NL/O=python-pskc/CN=Test signing'
+
+openssl x509 \
+  -req \
+  -in request.pem -CA ca-certificate.pem -CAkey ca-key.pem -out 
certificate.pem \
+  -days 3650 -set_serial 42
diff --git a/tests/certificate/ca-certificate.pem 
b/tests/certificate/ca-certificate.pem
new file mode 100644
index 0000000..4547ba3
--- /dev/null
+++ b/tests/certificate/ca-certificate.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQDCCAiigAwIBAgIJAM7+vdiCdiCaMA0GCSqGSIb3DQEBCwUAMDUxCzAJBgNV
+BAYTAk5MMRQwEgYDVQQKDAtweXRob24tcHNrYzEQMA4GA1UEAwwHVGVzdCBDQTAe
+Fw0xNzEyMjMyMTQ5MDBaFw0yNzEyMjEyMTQ5MDBaMDUxCzAJBgNVBAYTAk5MMRQw
+EgYDVQQKDAtweXRob24tcHNrYzEQMA4GA1UEAwwHVGVzdCBDQTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANHVvzM2MKXJL7SXKddCl7jrs7FKNHGEUimq
+l1qLa+dF/dsEyqidqLBmmbMxKQcqMcsqovKPpTM7dWUmv7P7Cr8ZR0tVmD6JvQlQ
+4dmWngbR0zkVPKP9ZzZFLW1LEkHZUOvurdLeyo+33xomGnosFVLsDX1mo3h41OUT
+kJkCxnG1HZMudpQXzrOUmdCCMV9qsS5iVeLJItW6BzbjIYYx6vdAVgFELofPqMtn
+vPVmLkMCUyMPEKbqjWP23A93XgnwoFFppsSmhqK9JLRrHPVTaAV8UNvQhN8hdEn3
+biOBmtpH47tUG++RQMsV9E5kA9WoiisyMdTlutcdwnYo40L+Zs0CAwEAAaNTMFEw
+HQYDVR0OBBYEFNpXX0X0olR9QDVSBbyn44jP1wDmMB8GA1UdIwQYMBaAFNpXX0X0
+olR9QDVSBbyn44jP1wDmMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
+ggEBAGzWcea0i5H8MehTL35d8TMkjMp1R5Eoy6t1IhlYzZczphqzXSb76rA71oqX
+Ti17mEBH+SCwR3L+QL6uZYNK5JKHwQPbPmBErxwcymmJXK6Y5sOwliSTjeRaujcN
+r1h6qxKVshQFlMinS3VuQvT+fa2c1FMHnzBx0jN/4edsjJzBYChAs5dYgSKPLSua
+NQGSJKJucTq22JBhu1c2+DwhqM9MbycyvhwZjw4p0yfFSUyTmxFm6DMZaI3P6F+P
+YtIrwjaoZt8Hh+YaOUBM+3AJ4nSAkvgy7BsLPMxKFhCJqdAttQMDKpWmhgl+MtNQ
+ZAisAuYRkr0ZkgqE5+3bIERA70o=
+-----END CERTIFICATE-----
diff --git a/tests/certificate/ca-key.pem b/tests/certificate/ca-key.pem
new file mode 100644
index 0000000..153ed54
--- /dev/null
+++ b/tests/certificate/ca-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDR1b8zNjClyS+0
+lynXQpe467OxSjRxhFIpqpdai2vnRf3bBMqonaiwZpmzMSkHKjHLKqLyj6UzO3Vl
+Jr+z+wq/GUdLVZg+ib0JUOHZlp4G0dM5FTyj/Wc2RS1tSxJB2VDr7q3S3sqPt98a
+Jhp6LBVS7A19ZqN4eNTlE5CZAsZxtR2TLnaUF86zlJnQgjFfarEuYlXiySLVugc2
+4yGGMer3QFYBRC6Hz6jLZ7z1Zi5DAlMjDxCm6o1j9twPd14J8KBRaabEpoaivSS0
+axz1U2gFfFDb0ITfIXRJ924jgZraR+O7VBvvkUDLFfROZAPVqIorMjHU5brXHcJ2
+KONC/mbNAgMBAAECggEAd1IXtrRi93MvGH85ALpScoo9lVw/9CktW5oxFQDhLBNF
+5oyT/Uwhx6WgUyBqVzOsOv6UyF/crgRqnklpi5v+oWprezBCMZW7lBeVRlj6paAd
+f4FPCWWokljGGzcsO5urA26nE5kzEISbblAqkDyPJ6cpiJyb6n6zeUwl1oyYccFn
+Vw7x0PN2QnFcTWzTxfhDAZpZeF/yYBzVR78BrYJ25HXf394MseiC0SWTbmUQlZzU
+0jwkGFEQQ3w+Q9i2GfiBp2801sRF69nFDcAin7RPAFajiKAIrmtaGWZbL8C+OIzO
+I32TPWOiA3swKolFFjsSA80DYFXAOYlHpd9j/FWutQKBgQDs5c6YukeZyq0scLRl
+jFtzeul4n1VOJrKT1xh5ivaMTdwOzNbuaOCpReNQoUsHoFUGdO6lxIQqMjjHNoiA
+/WnK7VHIC3Voc0U8HCvYN82jAuYpc7NswpvEefXB8Hq8jGwvvn/WNiGeqjvhbSdr
+8NZPBrGY3X3W2iUMZeGGWu1BVwKBgQDiwUsrjP3RVRXKTCESak8AoiwzrokutMN5
+zMdbIWPh5q5/yFMIfj6PkThOixI4jY0EedunL+7l5obKsTGs1ArwyU6jL9vtKHT4
+GiUtGN1emPA9TCiLFKEM+fDWtoU3aw2CC6Cy+0lZANpg4FV0wrM4mhBtPZpSTRJP
+3gP2O3TOewKBgQDJFFdEc/mKpOpRKmk1OOn3H4FFDZmO+HHj41O29ylG9l+vgFd/
+ji4EAHpuWjohgwoorOBUfHXiizZ+gd9j/bXqtX4RWwiRXCaMWy0sHlHB6BgNX1QR
+IS6umwbrU4AawuahsAU6gkAPOVgShGBN0uNVuMzOgFLsJ8YCvAetz0hLiQKBgEnn
+lghru44unRuCFIVcAMPEF4QArtV6NPvNsmwRReyqstUTBX0PKHTCcAepbBbii8OZ
+s+2ZwdNMf0Iw3m/l7GruMaMeRh/Vv8ndO6CnKf9a+XIBEHXEE3mXPNe+RUz6pzxr
+bcToAc/wZzyL4U5c5uzbyd7Q5StN01oUklX+quNhAoGAKLS5887ucfeFAjNxeNk4
+hsSFw7CDIHfuJbTpzitf8bTbqy1SqB8DSW1S5zCuz8yxBIO5HjF4rT2VT0F2FN0o
+7OrhXiqU9mAWxObvfObDlvOoLcnPvvayotUdlcboBqCxHAjF8jNBYAQuKXneSLFa
+IEo4FtswiJCmqC9tsMEy7Wo=
+-----END PRIVATE KEY-----
diff --git a/tests/certificate/certificate.pem 
b/tests/certificate/certificate.pem
new file mode 100644
index 0000000..2afff39
--- /dev/null
+++ b/tests/certificate/certificate.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC4zCCAcsCASowDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMCTkwxFDASBgNV
+BAoMC3B5dGhvbi1wc2tjMRAwDgYDVQQDDAdUZXN0IENBMB4XDTE3MTIyMzIxNDkx
+NVoXDTI3MTIyMTIxNDkxNVowOjELMAkGA1UEBhMCTkwxFDASBgNVBAoMC3B5dGhv
+bi1wc2tjMRUwEwYDVQQDDAxUZXN0IHNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDm2d2AodQmxQvK/pSr/6LAdj4U2RM3sUgd0P6f2lAcs5+O
+Tr2s+6SWkMEnpm4xzamfFr4hZrTi687Th404k16+VDgD2Fdlha1ic4ETH1WYAkA0
+01HLXejZ/Jc1tYYb/mGD9L1fm7TliTc+2mL7efTMyRFzIGTLmhC7vfcnoTBRzNBG
+g4uy0z0XK+KrDcnb8S1rbXLQWazsQAEi9FAB3ArYkvE8gYgDs666W3fg5EROb3Oh
+GHcKmqECNoPaw4ZeElHTqbETp6YCda3NGgstio8Yc17OCuNg/xL4NSxosJWZiBQ/
+aDDsXP1zBmF2xthaPAo1glBTACnaf0K37qgRE1XRAgMBAAEwDQYJKoZIhvcNAQEL
+BQADggEBAHWvbyr/oip66z5tlQMxKCOqs6FVgYr3WxT8K3QsQeK2ELp64gfYKRLF
+GqM6r7VHVQIUa/5BdHDnd9B3axeSDRRVQzJC6FNjcWvS/Pu+KIvwtUkH8t5T713u
+q4VbdJwKMYRl3ge2cks4ydmMc7KmMMKVQ1W7EMEP2FPSohKTW9sJaWboRd4L5jMd
+2iwnsfLMQHZIhUOH1gQS4dlnJ4PZaLBjsukChe5AfnA+LPiAGji/aqvi9tTjLVWb
+z95OH5e6V/W1t0jtCp7O+h3ytNHmyIV8KYLn4Hu5tCnYGWTJ62NSYCtjhLkDpQrk
+AL5yqfYZRyOjmtjBaq2tYxDCF76e4ms=
+-----END CERTIFICATE-----
diff --git a/tests/certificate/key.pem b/tests/certificate/key.pem
new file mode 100644
index 0000000..4e18309
--- /dev/null
+++ b/tests/certificate/key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDm2d2AodQmxQvK
+/pSr/6LAdj4U2RM3sUgd0P6f2lAcs5+OTr2s+6SWkMEnpm4xzamfFr4hZrTi687T
+h404k16+VDgD2Fdlha1ic4ETH1WYAkA001HLXejZ/Jc1tYYb/mGD9L1fm7TliTc+
+2mL7efTMyRFzIGTLmhC7vfcnoTBRzNBGg4uy0z0XK+KrDcnb8S1rbXLQWazsQAEi
+9FAB3ArYkvE8gYgDs666W3fg5EROb3OhGHcKmqECNoPaw4ZeElHTqbETp6YCda3N
+Ggstio8Yc17OCuNg/xL4NSxosJWZiBQ/aDDsXP1zBmF2xthaPAo1glBTACnaf0K3
+7qgRE1XRAgMBAAECggEBANe+RIQyuVKCVMMhqVAVWMUFvH8/X/ZseDllWJGMNdKd
+DECy1hEUHH2aSuoDYHiq/Brx5j7Q/bwVJPYLeK+nCqRVkQQG66a1h5S63aZBYnzt
+nZPA6nBlE3il16saOntvATORRmpT2nuqATd8YYUkNOQuBWDSpWCiW4EZyCIBQmcu
+fAzdLtZ2opok/k1ZmAYANDU1pOvOEl0hfd+hTyTeop6HzOhjkm5G3+mgQ6xkkgv5
+bEeiddfM5RNN0jzyzlkS5qvuGH84oPLK/xH9/4iLLn1nNfPNSdhWVF1LWLv9ii3d
+AtNcXErQBDlYjEVg5nfrTU2vWZBP963cpO4EPOH8uQECgYEA9cXHrjR59XI5mawE
+EGanRhS/EDk4O/dYwgJiUBnFhLbJ8Nkq495I9b6XWer4TyzLCYdr4c+RUpnZy9eW
+CmS0uT/Zu9/hP3FjVACCbIrAOSZxDJH08w0DzaVFQbIsLd94PSUSIB5LN5p+NerP
+nC/+ZXL9FXg5rdKoHeL42LwdvhMCgYEA8HUgMlfwcqRhMlDLPXjutaCav4qcxIEg
+HJ1dGyqBOefTyZ24CKcMxRX7CIHsxD43ksGaq7mNu66ArLt3RI0n1DxAjtOrhBP2
+22qy1OldxGNW56fgLjs8n0GvOQ+AomVeA9dUh2mSHHpAMDyYn5J+FwNFIfpvPsgW
+/5q9dH0ciQsCgYEAgy73X0fJVTaMAjdrz+lLHcATH7C7EF79NEkbes/WuvjqHzF7
+kDdk0C7UEH233mL1WPe8oMIvOly+gOxovNF2GW84Y+yjCT8Xmo3PD+qboaWgL+p0
+SYtH3Wt65Q8aP74JKvHvk883rM080sCr4h1H95SB/AS5HMvVSmaNFvqKEZsCgYBT
+M+QJJ36u3Iw5UdWRlsfalxEU2Ay4vglzfUwbO1DtzXbuG6HzdIIprBpp7jMUEIdA
+Q7eWViVh3Nbvbgqt8L3oZVTdVwbRM+tGLxxkKb/lVY5bqC4dMSgf3yWXc/LJvpOS
+dq7sEC6bpf2R81XIWXNB2ULRqAtYSPoUReHqbeQy0wKBgQCA95LWgLzcKH7NxaWv
+4Av/jbA1htqm/XKKm1D0vH/7mAag9QJN2EdUeaHaRYLSUJM5g/pkrpf2WBbWhDQG
+3uJrw6kI4vYED4gNzE5KgoDDDuhxc0Ij7ocguTlz9nt2MMwMlWVSiabJvR41X9rn
+3VGCRC+qCeFYeXYmM60U5W4OuQ==
+-----END PRIVATE KEY-----
diff --git a/tests/certificate/request.pem b/tests/certificate/request.pem
new file mode 100644
index 0000000..a693ce2
--- /dev/null
+++ b/tests/certificate/request.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICfzCCAWcCAQAwOjELMAkGA1UEBhMCTkwxFDASBgNVBAoMC3B5dGhvbi1wc2tj
+MRUwEwYDVQQDDAxUZXN0IHNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDm2d2AodQmxQvK/pSr/6LAdj4U2RM3sUgd0P6f2lAcs5+OTr2s+6SW
+kMEnpm4xzamfFr4hZrTi687Th404k16+VDgD2Fdlha1ic4ETH1WYAkA001HLXejZ
+/Jc1tYYb/mGD9L1fm7TliTc+2mL7efTMyRFzIGTLmhC7vfcnoTBRzNBGg4uy0z0X
+K+KrDcnb8S1rbXLQWazsQAEi9FAB3ArYkvE8gYgDs666W3fg5EROb3OhGHcKmqEC
+NoPaw4ZeElHTqbETp6YCda3NGgstio8Yc17OCuNg/xL4NSxosJWZiBQ/aDDsXP1z
+BmF2xthaPAo1glBTACnaf0K37qgRE1XRAgMBAAGgADANBgkqhkiG9w0BAQsFAAOC
+AQEAB3SpoRLjnd/lWTyi4KLAPr4uZEK17wwtKm2aFHhAEPbQSrzvFUdWI2OjVo9r
+nL9VgysozhmwWfWdVxc8kyYo6pNYWXqJtxFK8dItw06ptL8FETz0+ho+bcmeq6gv
+l3ZKzbY2QKdxW/wP5iLuNPUkVYknP1wWzt1lM3ffou3RL/Vf+/XB+HadCD3w+7TD
+XguTeU34alv050HvulO9gUpY35ZCC6rVO9ZefOFtTkT8zAhUqZFpWrnKxvcMXRSM
+6KK0TWyoP9FGfnGxkW8ij94yiq+Vzyi57ZVC766y1x0j4Qj9ZFxCHn+wpDzxhgVy
+clfeldkX2JtQL5C8IOJ7Kt4H1A==
+-----END CERTIFICATE REQUEST-----
diff --git a/tests/certificate/ss-certificate.pem 
b/tests/certificate/ss-certificate.pem
new file mode 100644
index 0000000..2b90be3
--- /dev/null
+++ b/tests/certificate/ss-certificate.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIJAJsIXUIwU7CcMA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV
+BAYTAk5MMRQwEgYDVQQKDAtweXRob24tcHNrYzEVMBMGA1UEAwwMVGVzdCBzaWdu
+aW5nMB4XDTE3MTIyMzIxNDkwNVoXDTI3MTIyMTIxNDkwNVowOjELMAkGA1UEBhMC
+TkwxFDASBgNVBAoMC3B5dGhvbi1wc2tjMRUwEwYDVQQDDAxUZXN0IHNpZ25pbmcw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm2d2AodQmxQvK/pSr/6LA
+dj4U2RM3sUgd0P6f2lAcs5+OTr2s+6SWkMEnpm4xzamfFr4hZrTi687Th404k16+
+VDgD2Fdlha1ic4ETH1WYAkA001HLXejZ/Jc1tYYb/mGD9L1fm7TliTc+2mL7efTM
+yRFzIGTLmhC7vfcnoTBRzNBGg4uy0z0XK+KrDcnb8S1rbXLQWazsQAEi9FAB3ArY
+kvE8gYgDs666W3fg5EROb3OhGHcKmqECNoPaw4ZeElHTqbETp6YCda3NGgstio8Y
+c17OCuNg/xL4NSxosJWZiBQ/aDDsXP1zBmF2xthaPAo1glBTACnaf0K37qgRE1XR
+AgMBAAGjUzBRMB0GA1UdDgQWBBREYylHi/+XUcO9hxafnxihX7m92TAfBgNVHSME
+GDAWgBREYylHi/+XUcO9hxafnxihX7m92TAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBCwUAA4IBAQACwDauWH7v2tOWa0pWtQLr/6TWmYMdf99oz8adGwioer+m
+oP97dcOsTWXyb1gw2SGuopd5HLrgZab7jC93bVIBLPjSj+8TxUlQpKtkyqPX9mGg
+8kI+090Wz30mkRVA+YBjuUiyIHqX6+2tEkgK3n9QuU0SPb/joXnYphin7XEA7ur0
+EKxJutt8ecBAq6VRRlLE0iKcEsY6MyVvspj5z1Q47vqdVtOR19Dsq73rUljkxu10
+0lLc/nn1+wCz7nCgm7ps9H04nBk8+kxXrnvkWW5nUdSFG69VmRCmPZ74HdUvd61s
+KjCcVuWQB7us2P3q1O9nu754P5K5UNq7mkXXy8cd
+-----END CERTIFICATE-----
diff --git a/tests/rfc6030/figure9.pskcxml b/tests/rfc6030/figure9.pskcxml
index d3b38ce..9b116e4 100644
--- a/tests/rfc6030/figure9.pskcxml
+++ b/tests/rfc6030/figure9.pskcxml
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Figure 9 example from RFC 6030 that has a digital signature.
+  Figure 9 example from RFC 6030 that has a digital signature. The signature
+  is invalid because no certificate can be found for it. The file was
+  modified to put the Signature element in the correct namespace (see errata
+  3418 from RFC 6030).
 -->
 
 <KeyContainer
@@ -32,7 +35,7 @@
             </Data>
         </Key>
     </KeyPackage>
-    <Signature>
+    <ds:Signature>
         <ds:SignedInfo>
             <ds:CanonicalizationMethod
              Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
@@ -61,5 +64,5 @@
                 </ds:X509IssuerSerial>
             </ds:X509Data>
         </ds:KeyInfo>
-    </Signature>
+    </ds:Signature>
 </KeyContainer>
diff --git a/tests/test_draft_ietf_keyprov_pskc_02.doctest 
b/tests/test_draft_ietf_keyprov_pskc_02.doctest
index 63b3040..f4cb74c 100644
--- a/tests/test_draft_ietf_keyprov_pskc_02.doctest
+++ b/tests/test_draft_ietf_keyprov_pskc_02.doctest
@@ -246,8 +246,9 @@ an external mechanism to construct a HOTP key.
 
 
 This tests figure 8 from draft-ietf-keyprov-pskc-02 which uses a a digital
-signature to sign the PSKC file. Note that python-pskc currently does not yet
-support checking this signature so this test is very limited.
+signature to sign the PSKC file. Note that this file does not appear to
+contain a valid signature and if it would it wouldn't have a valid signature
+anyway.
 
 >>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure8.pskcxml')
 >>> pskc.signature.is_signed
diff --git a/tests/test_rfc6030.doctest b/tests/test_rfc6030.doctest
index a3438ac..33d0d01 100644
--- a/tests/test_rfc6030.doctest
+++ b/tests/test_rfc6030.doctest
@@ -241,8 +241,9 @@ DecryptionError: No key available
 
 
 This tests a PSKC file that uses digital signature to sign the PSKC file as
-seen in figure 8 of RFC 6030. Note that python-pskc currently does not yet
-support checking this signature so this test is very limited.
+seen in figure 8 of RFC 6030. Since the RFC does not supply the certificate
+that was used in the signature (which is likely invalid) we cannot verify it
+in this example and can only get some signature properties.
 
 >>> pskc = PSKC('tests/rfc6030/figure9.pskcxml')
 >>> pskc.signature.is_signed
diff --git a/tests/test_signature.doctest b/tests/test_signature.doctest
new file mode 100644
index 0000000..ce6947b
--- /dev/null
+++ b/tests/test_signature.doctest
@@ -0,0 +1,219 @@
+test_signature.doctest - test XML signature checking functions
+
+Copyright (C) 2017 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 sys
+>>> import tempfile
+>>> try:
+...     from StringIO import StringIO
+... except ImportError:
+...     from io import StringIO
+>>> from binascii import a2b_hex, b2a_hex
+>>> def tostr(x):
+...     return str(x.decode())
+>>> def decode(f):
+...     return lambda x: tostr(f(x))
+>>> b2a_hex = decode(b2a_hex)
+
+>>> from pskc import PSKC
+>>> from pskc.encryption import encrypt, decrypt
+
+
+>>> with open('tests/certificate/key.pem', 'rb') as f:
+...     signing_key = f.read()
+>>> with open('tests/certificate/ss-certificate.pem', 'rb') as f:
+...     self_signed_certificate = f.read()
+>>> with open('tests/certificate/certificate.pem', 'rb') as f:
+...     signed_certificate = f.read()
+
+
+Build a simple PSKC structure and sign the file including a self-signed
+certificate.
+
+>>> pskc = PSKC()
+>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
+>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
+>>> pskc.signature.sign(signing_key, self_signed_certificate)
+
+Write the PSKC file (use temporary file to test passing file name as
+argument).
+
+>>> f = tempfile.NamedTemporaryFile()
+>>> pskc.write(f.name)
+>>> with open(f.name, 'r') as r:
+...     x = sys.stdout.write(r.read())  #doctest: +ELLIPSIS +REPORT_UDIFF 
+NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:ds="http://www.w3.org/2000/09/xmldsig#"; 
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" Version="1.0">
+ ...
+ <ds:Signature>
+  <ds:SignedInfo><ds:CanonicalizationMethod 
Algorithm="..."/><ds:SignatureMethod 
Algorithm="..."/><ds:Reference...><ds:DigestMethod 
Algorithm="..."/><ds:DigestValue>...</ds:DigestValue></ds:Reference></ds:SignedInfo>
+  <ds:SignatureValue>...</ds:SignatureValue>
+  <ds:KeyInfo>
+   <ds:X509Data>
+    <ds:X509Certificate>...</ds:X509Certificate>
+   </ds:X509Data>
+  </ds:KeyInfo>
+ </ds:Signature>
+</pskc:KeyContainer>
+
+Read back the PSKC file and verify the signature.
+
+>>> newpskc = PSKC(f.name)
+>>> print(tostr(newpskc.signature.certificate))  #doctest: +ELLIPSIS
+-----BEGIN CERTIFICATE-----
+...
+-----END CERTIFICATE-----
+>>> newpskc.signature.signed_pskc  # we need a certificate for verification
+Traceback (most recent call last):
+    ...
+InvalidCertificate: [18, 0, 'self signed certificate']
+>>> newpskc.signature.verify(self_signed_certificate)
+True
+>>> newpskc = newpskc.signature.signed_pskc
+>>> newpskc.keys[0].secret == pskc.keys[0].secret
+True
+
+We can also use the certificate that is embedded in the PSKC file but that
+does not add any security.
+
+>>> newpskc = PSKC(f.name)
+>>> newpskc.signature.verify(newpskc.signature.certificate)
+True
+>>> newpskc = newpskc.signature.signed_pskc
+>>> newpskc.keys[0].secret == pskc.keys[0].secret
+True
+
+
+We can also sign a PSKC file without a embedding certificate. This should be
+practically the same as using a self-signed certificate.
+
+>>> pskc = PSKC()
+>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
+>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
+>>> pskc.signature.sign(signing_key)
+>>> f = tempfile.NamedTemporaryFile()
+>>> pskc.write(f.name)
+>>> with open(f.name, 'r') as r:
+...     x = sys.stdout.write(r.read())  #doctest: +ELLIPSIS +REPORT_UDIFF 
+NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:ds="http://www.w3.org/2000/09/xmldsig#"; 
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" Version="1.0">
+ ...
+ <ds:Signature>
+  <ds:SignedInfo><ds:CanonicalizationMethod 
Algorithm="..."/><ds:SignatureMethod 
Algorithm="..."/><ds:Reference...><ds:DigestMethod 
Algorithm="..."/><ds:DigestValue>...</ds:DigestValue></ds:Reference></ds:SignedInfo>
+  <ds:SignatureValue>...</ds:SignatureValue>
+  <ds:KeyInfo>
+   <ds:KeyValue>
+    <ds:RSAKeyValue>
+     <ds:Modulus>...</ds:Modulus>
+     <ds:Exponent>...</ds:Exponent>
+    </ds:RSAKeyValue>
+   </ds:KeyValue>
+  </ds:KeyInfo>
+ </ds:Signature>
+</pskc:KeyContainer>
+
+Read the file back in and verify the signature using the self-signed
+certificate.
+
+>>> newpskc = PSKC(f.name)
+>>> newpskc.signature.certificate is None
+True
+>>> newpskc.signature.signed_pskc  # we need a certificate for verification
+Traceback (most recent call last):
+    ...
+InvalidInput: Expected a X.509 certificate based signature
+>>> newpskc.signature.verify(self_signed_certificate)
+True
+>>> newpskc = newpskc.signature.signed_pskc
+>>> newpskc.keys[0].secret == pskc.keys[0].secret
+True
+
+
+We can also sign a PSKC file and include a certificate that can be validated
+using a CA certificate.
+
+>>> pskc = PSKC()
+>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
+>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
+>>> pskc.signature.sign(signing_key, signed_certificate)
+>>> f = tempfile.NamedTemporaryFile()
+>>> pskc.write(f.name)
+>>> with open(f.name, 'r') as r:
+...     x = sys.stdout.write(r.read())  #doctest: +ELLIPSIS +REPORT_UDIFF 
+NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:ds="http://www.w3.org/2000/09/xmldsig#"; 
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" Version="1.0">
+ ...
+ <ds:Signature>
+  <ds:SignedInfo><ds:CanonicalizationMethod 
Algorithm="..."/><ds:SignatureMethod 
Algorithm="..."/><ds:Reference...><ds:DigestMethod 
Algorithm="..."/><ds:DigestValue>...</ds:DigestValue></ds:Reference></ds:SignedInfo>
+  <ds:SignatureValue>...</ds:SignatureValue>
+  <ds:KeyInfo>
+   <ds:X509Data>
+    <ds:X509Certificate>...</ds:X509Certificate>
+   </ds:X509Data>
+  </ds:KeyInfo>
+ </ds:Signature>
+</pskc:KeyContainer>
+
+Read back the PSKC file and verify the signature. This file can be verified
+using the self-signed certificate, the signing certificate or by providing
+the CA certificate.
+
+>>> newpskc = PSKC(f.name)
+>>> newpskc.signature.signed_pskc  # we need a certificate for verification
+Traceback (most recent call last):
+    ...
+InvalidCertificate: [20, 0, 'unable to get local issuer certificate']
+>>> newpskc.signature.verify(self_signed_certificate)
+True
+>>> newpskc.signature.verify(signed_certificate)
+True
+>>> 
newpskc.signature.verify(ca_pem_file='tests/certificate/ca-certificate.pem')
+True
+
+
+We could also sign a PSKC file and include a certificate that is validated by
+the default operating system recorded CAs but that is sadly not appropriate
+for a test suite (the key needs to be included in the test suite so would not
+be private, depending on the CA used there would be costs involved and the
+certificates would expire too quickly).
+
+
+A simple test for parsing an incomplete signature element.
+
+>>> pskc = PSKC(StringIO('''
+... <?xml version="1.0"?>
+... <KeyContainer Version="1.0" xmlns="urn:ietf:params:xml:ns:keyprov:pskc">
+...  <KeyPackage>
+...   
<Key><Data><Secret><PlainValue>TheQuickBrownFox</PlainValue></Secret></Data></Key>
+...  </KeyPackage>
+...  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#";>
+...   <SignedInfo/>
+...   <SignatureValue/>
+...   <KeyInfo/>
+...  </Signature>
+... </KeyContainer>
+... '''.strip()))
+>>> len(pskc.keys)
+1
+>>> pskc.signature.canonicalization_method is None
+True
+>>> pskc.signature.algorithm is None
+True
+>>> pskc.signature.digest_algorithm is None
+True
diff --git a/tox.ini b/tox.ini
index 731fcc6..f16829a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,13 +1,16 @@
 [tox]
-envlist = py{27,34,35,36}{,-lxml}{,-defusedxml},flake8
+envlist = 
py{27,34,35,36}-signxml,py{27,34,35,36}{-legacy,-lxml}{,-defusedxml},flake8
 skip_missing_interpreters = True
 
 [testenv]
 deps = nose
        coverage
+       signxml: signxml
        lxml: lxml
        defusedxml: defusedxml
-commands = nosetests
+commands = signxml: nosetests
+           legacy: nosetests --exclude=test_signature.doctest 
--cover-min-percentage 95
+           lxml: nosetests --exclude=test_signature.doctest 
--cover-min-percentage 95
 setenv=
     PYTHONWARNINGS=all
 

https://arthurdejong.org/git/python-pskc/commit/?id=c0bd21f6b6250b9161613106bb6fab27a3ce4b16

commit c0bd21f6b6250b9161613106bb6fab27a3ce4b16
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri Dec 15 22:53:23 2017 +0100

    Move namespace moving to own function

diff --git a/pskc/xml.py b/pskc/xml.py
index 56f45bc..43ed34c 100644
--- a/pskc/xml.py
+++ b/pskc/xml.py
@@ -191,6 +191,21 @@ def mk_elem(parent, tag=None, text=None, empty=False, 
**kwargs):
     return element
 
 
+def move_namespaces(element):
+    """Move the namespace declarations to the toplevel element."""
+    if hasattr(element, 'nsmap'):  # pragma: no cover (only on lxml)
+        # get all used namespaces
+        nsmap = {}
+        for e in element.iter():
+            nsmap.update(e.nsmap)
+        # replace toplevel element with all namespaces
+        e = Element(element.tag, attrib=element.attrib, nsmap=nsmap)
+        for a in element:
+            e.append(a)
+        element = e
+    return element
+
+
 def reformat(element, indent=''):
     """Reformat the XML tree to have nice wrapping and indenting."""
     # re-order attributes by alphabet
@@ -213,17 +228,7 @@ def reformat(element, indent=''):
 
 def tostring(element):
     """Return a serialised XML document for the element tree."""
-    # if we are using lxml.etree move namespaces to toplevel element
-    if hasattr(element, 'nsmap'):  # pragma: no cover (only on lxml)
-        # get all used namespaces
-        nsmap = {}
-        for e in element.iter():
-            nsmap.update(e.nsmap)
-        # replace toplevel element with all namespaces
-        e = Element(element.tag, attrib=element.attrib, nsmap=nsmap)
-        for a in element:
-            e.append(a)
-        element = e
+    element = move_namespaces(element)
     reformat(element)
     xml = xml_tostring(element, encoding='UTF-8')
     xml_decl = b"<?xml version='1.0' encoding='UTF-8'?>\n"

https://arthurdejong.org/git/python-pskc/commit/?id=ea503d6590e3a3ede8e119da3887d8543731cf9a

commit ea503d6590e3a3ede8e119da3887d8543731cf9a
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri Sep 22 11:53:19 2017 +0200

    Implement basic parsing of signature properties

diff --git a/pskc/__init__.py b/pskc/__init__.py
index 3ceaba2..920e906 100644
--- a/pskc/__init__.py
+++ b/pskc/__init__.py
@@ -72,10 +72,12 @@ class PSKC(object):
 
     def __init__(self, filename=None):
         from pskc.encryption import Encryption
+        from pskc.signature import Signature
         from pskc.mac import MAC
         self.version = None
         self.id = None
         self.encryption = Encryption(self)
+        self.signature = Signature(self)
         self.mac = MAC(self)
         self.devices = []
         if filename is not None:
diff --git a/pskc/parser.py b/pskc/parser.py
index abad663..fac302a 100644
--- a/pskc/parser.py
+++ b/pskc/parser.py
@@ -90,6 +90,8 @@ class PSKCParser(object):
         # handle KeyPackage entries
         for key_package in findall(container, 'KeyPackage', 'Device'):
             cls.parse_key_package(pskc.add_device(), key_package)
+        # handle Signature entries
+        cls.parse_signature(pskc.signature, find(container, 'Signature'))
 
     @classmethod
     def parse_encryption(cls, encryption, key_info):
@@ -360,3 +362,31 @@ class PSKCParser(object):
         for child in policy_elm:
             if child.tag not in known_children:
                 policy.unknown_policy_elements = True
+
+    @classmethod
+    def parse_signature(cls, signature, signature_elm):
+        """Read signature information from the <Signature> element."""
+        if signature_elm is None:
+            return
+        cm_elm = find(signature_elm, 'SignedInfo/CanonicalizationMethod')
+        if cm_elm is not None:
+            signature.canonicalization_method = cm_elm.attrib.get('Algorithm')
+        sm_elm = find(signature_elm, 'SignedInfo/SignatureMethod')
+        if sm_elm is not None:
+            signature.algorithm = sm_elm.attrib.get('Algorithm')
+        dm_elm = find(signature_elm, 'SignedInfo/Reference/DigestMethod')
+        if dm_elm is not None:
+            signature.digest_algorithm = dm_elm.attrib.get('Algorithm')
+        issuer = find(signature_elm, 'KeyInfo/X509Data/X509IssuerSerial')
+        if issuer is not None:
+            signature.issuer = findtext(issuer, 'X509IssuerName')
+            signature.serial = findtext(issuer, 'X509SerialNumber')
+        certificate = findbin(
+            signature_elm, 'KeyInfo/X509Data/X509Certificate')
+        if certificate:
+            certificate = base64.b64encode(certificate)
+            signature.certificate = b'\n'.join(
+                [b'-----BEGIN CERTIFICATE-----'] +
+                [certificate[i:i + 64]
+                 for i in range(0, len(certificate), 64)] +
+                [b'-----END CERTIFICATE-----'])
diff --git a/pskc/signature.py b/pskc/signature.py
new file mode 100644
index 0000000..31c3ab0
--- /dev/null
+++ b/pskc/signature.py
@@ -0,0 +1,68 @@
+# signature.py - module for handling signed XML files
+# coding: utf-8
+#
+# Copyright (C) 2017 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
+
+"""Module for handling signed PSKC files.
+
+This module defines a Signature class that handles the signature checking,
+keys and certificates.
+"""
+
+
+class Signature(object):
+    """Class for handling signature checking of the PSKC file.
+
+    Instances of this class provide the following properties:
+
+      is_signed: boolean to indicate whether a signature is present
+      algorithm: identifier of the signing algorithm used
+      canonicalization_method: identifier of the XML canonicalization used
+      digest_algorithm: algorithm used for creating the hash
+      issuer: issuer of the certificate
+      serial: serial number of the certificate
+      certificate: the certificate that is embedded in the signature
+    """
+
+    def __init__(self, pskc):
+        self.pskc = pskc
+        self._algorithm = None
+        self.canonicalization_method = None
+        self.digest_algorithm = None
+        self.issuer = None
+        self.serial = None
+        self.certificate = None
+
+    @property
+    def is_signed(self):
+        """Test whether the PSKC file contains a signature (not whether the
+        signature is valid)."""
+        return bool(
+            self.algorithm or self.canonicalization_method or
+            self.digest_algorithm or self.issuer or self.certificate)
+
+    @property
+    def algorithm(self):
+        """Provide the signing algorithm used."""
+        if self._algorithm:
+            return self._algorithm
+
+    @algorithm.setter
+    def algorithm(self, value):
+        from pskc.algorithms import normalise_algorithm
+        self._algorithm = normalise_algorithm(value)
diff --git a/tests/test_draft_ietf_keyprov_pskc_02.doctest 
b/tests/test_draft_ietf_keyprov_pskc_02.doctest
index 7a039d5..63b3040 100644
--- a/tests/test_draft_ietf_keyprov_pskc_02.doctest
+++ b/tests/test_draft_ietf_keyprov_pskc_02.doctest
@@ -250,6 +250,18 @@ signature to sign the PSKC file. Note that python-pskc 
currently does not yet
 support checking this signature so this test is very limited.
 
 >>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure8.pskcxml')
+>>> pskc.signature.is_signed
+True
+>>> pskc.signature.canonicalization_method
+'http://www.w3.org/2001/10/xml-exc-c14n#'
+>>> pskc.signature.algorithm
+'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
+>>> pskc.signature.digest_algorithm
+'http://www.w3.org/2000/09/xmldsig#sha1'
+>>> pskc.signature.issuer
+'CN=Example.com,C=US'
+>>> pskc.signature.serial
+'12345678'
 >>> key = pskc.keys[0]
 >>> key.manufacturer
 'TokenVendorAcme'
diff --git a/tests/test_rfc6030.doctest b/tests/test_rfc6030.doctest
index e872a95..a3438ac 100644
--- a/tests/test_rfc6030.doctest
+++ b/tests/test_rfc6030.doctest
@@ -1,6 +1,6 @@
 test_rfc6030.doctest - test for examples from RFC 6030
 
-Copyright (C) 2014-2016 Arthur de Jong
+Copyright (C) 2014-2017 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
@@ -245,6 +245,18 @@ seen in figure 8 of RFC 6030. Note that python-pskc 
currently does not yet
 support checking this signature so this test is very limited.
 
 >>> pskc = PSKC('tests/rfc6030/figure9.pskcxml')
+>>> pskc.signature.is_signed
+True
+>>> pskc.signature.algorithm
+'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
+>>> pskc.signature.canonicalization_method
+'http://www.w3.org/2001/10/xml-exc-c14n#'
+>>> pskc.signature.digest_algorithm
+'http://www.w3.org/2000/09/xmldsig#sha1'
+>>> pskc.signature.issuer
+'CN=Example.com,C=US'
+>>> pskc.signature.serial
+'12345678'
 >>> key = pskc.keys[0]
 >>> key.manufacturer
 'TokenVendorAcme'

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

Summary of changes:
 docs/encryption.rst                           |   4 +-
 docs/index.rst                                |   1 +
 docs/mac.rst                                  |   2 +-
 docs/signatures.rst                           | 114 ++++++++++++++
 docs/usage.rst                                |   5 +
 pskc/__init__.py                              |   2 +
 pskc/parser.py                                |  35 +++-
 pskc/serialiser.py                            |  15 +-
 pskc/signature.py                             | 137 ++++++++++++++++
 pskc/xml.py                                   |  32 ++--
 setup.py                                      |   1 +
 tests/certificate/README                      |  29 ++++
 tests/certificate/ca-certificate.pem          |  20 +++
 tests/certificate/ca-key.pem                  |  28 ++++
 tests/certificate/certificate.pem             |  18 +++
 tests/certificate/key.pem                     |  28 ++++
 tests/certificate/request.pem                 |  16 ++
 tests/certificate/ss-certificate.pem          |  20 +++
 tests/rfc6030/figure9.pskcxml                 |   9 +-
 tests/test_draft_ietf_keyprov_pskc_02.doctest |  17 +-
 tests/test_rfc6030.doctest                    |  19 ++-
 tests/test_signature.doctest                  | 219 ++++++++++++++++++++++++++
 tox.ini                                       |   7 +-
 23 files changed, 750 insertions(+), 28 deletions(-)
 create mode 100644 docs/signatures.rst
 create mode 100644 pskc/signature.py
 create mode 100644 tests/certificate/README
 create mode 100644 tests/certificate/ca-certificate.pem
 create mode 100644 tests/certificate/ca-key.pem
 create mode 100644 tests/certificate/certificate.pem
 create mode 100644 tests/certificate/key.pem
 create mode 100644 tests/certificate/request.pem
 create mode 100644 tests/certificate/ss-certificate.pem
 create mode 100644 tests/test_signature.doctest


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