Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/pskc/__init__.py
blob: 843a9191abe1b07cedd7730657b8be63361ca187 (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
# __init__.py - main module
# coding: utf-8
#
# Copyright (C) 2014-2016 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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

"""Python module for handling PSKC files

This Python library handles Portable Symmetric Key Container (PSKC) files as
defined in RFC 6030. PSKC files are used to transport and provision symmetric
keys (seed files) to different types of crypto modules, commonly one-time
password tokens or other authentication devices.

This module can be used to extract keys from PSKC files for use in an OTP
authentication system. The module can also be used for authoring PSKC files.

The following prints all keys, decrypting using a password:

>>> from pskc import PSKC
>>> pskc = PSKC('tests/rfc6030/figure7.pskcxml')
>>> pskc.encryption.derive_key('qwerty')
>>> for key in pskc.keys:
...     print('%s %s' % (key.serial, str(key.secret.decode())))
987654321 12345678901234567890

The module should be able to handle most common PSKC files.
"""


__all__ = ['PSKC', '__version__']


# the version number of the library
__version__ = '0.4'


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=None):
        from pskc.encryption import Encryption
        from pskc.exceptions import ParseError
        from pskc.mac import MAC
        self.version = None
        self.id = None
        self.encryption = Encryption(self)
        self.mac = MAC(self)
        self.keys = []
        if filename is not None:
            from pskc.xml import parse, remove_namespaces
            try:
                tree = parse(filename)
            except Exception:
                raise ParseError('Error parsing XML')
            remove_namespaces(tree)
            self.parse(tree.getroot())
        else:
            self.version = '1.0'

    def parse(self, container):
        """Read information from the provided <KeyContainer> tree."""
        from pskc.exceptions import ParseError
        from pskc.key import Key
        from pskc.xml import find, findall
        if container.tag != 'KeyContainer':
            raise ParseError('Missing KeyContainer')
        # the version of the PSKC schema
        self.version = container.get('Version')
        if self.version != '1.0':
            raise ParseError('Unsupported version %r' % self.version)
        # unique identifier for the container
        self.id = container.get('Id')
        # handle EncryptionKey entries
        self.encryption.parse(find(container, 'EncryptionKey'))
        # handle MACMethod entries
        self.mac.parse(find(container, 'MACMethod'))
        # handle KeyPackage entries
        for key_package in findall(container, 'KeyPackage'):
            self.keys.append(Key(self, key_package))

    def make_xml(self):
        from pskc.xml import mk_elem
        container = mk_elem('pskc:KeyContainer', Version=self.version,
                            Id=self.id)
        self.encryption.make_xml(container)
        self.mac.make_xml(container)
        for key in self.keys:
            key.make_xml(container)
        return container

    def add_key(self, **kwargs):
        """Create a new key instance for the PSKC file.

        The new key is initialised with properties from the provided keyword
        arguments if any."""
        from pskc.key import Key
        key = Key(self)
        self.keys.append(key)
        # assign the kwargs as key properties
        for k, v in kwargs.items():
            if not hasattr(key, k):
                raise AttributeError()
            setattr(key, k, v)
        return key

    def write(self, filename):
        """Write the PSKC file to the provided file."""
        from pskc.xml import tostring
        if hasattr(filename, 'write'):
            xml = tostring(self.make_xml())
            try:
                filename.write(xml)
            except TypeError:  # pragma: no cover (Python 3 specific)
                # fall back to writing as string for Python 3
                filename.write(xml.decode('utf-8'))
        else:
            with open(filename, 'wb') as output:
                self.write(output)