Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/pskc/signature.py
blob: b6f845484c57e0e01a08a4d2bbee1fba63044939 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# signature.py - module for handling signed XML files
# coding: utf-8
#
# Copyright (C) 2017-2018 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.
"""


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
        getattr(signxml.XMLSignatureProcessor, 'default_c14n_algorithm', None) or
        'http://www.w3.org/2006/12/xml-c14n11')
    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.

    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
      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):
        self.pskc = pskc
        self._algorithm = None
        self.canonicalization_method = None
        self.digest_algorithm = None
        self.issuer = None
        self.serial = None
        self.key = None
        self.certificate = None

    @property
    def is_signed(self):
        """Test whether the PSKC file contains a signature.

        This method does not check whether the signature is valid but only if
        one was present in the PSKC file.
        """
        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)

    @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)