python-pskc-0.3/ 0000755 0000000 0000000 00000000000 12605273753 013603 5 ustar root root 0000000 0000000 python-pskc-0.3/setup.py 0000755 0000000 0000000 00000004423 12605273737 015325 0 ustar root root 0000000 0000000 #!/usr/bin/env python
# setup.py - python-pskc installation script
#
# Copyright (C) 2014-2015 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-pskc installation script."""
import os
import sys
from setuptools import setup, find_packages
import pskc
# fix permissions for sdist
if 'sdist' in sys.argv:
os.system('chmod -R a+rX .')
os.umask(int('022', 8))
setup(
name='python-pskc',
version=pskc.__version__,
description='Python module for handling PSKC files',
long_description=pskc.__doc__,
author='Arthur de Jong',
author_email='arthur@arthurdejong.org',
keywords=['PSKC', 'RFC 6030', 'key container'],
url='http://arthurdejong.org/python-pskc/',
license='LGPL',
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Processing :: Markup :: XML',
],
packages=find_packages(),
install_requires=['pycrypto', 'python-dateutil'],
)
python-pskc-0.3/tests/ 0000755 0000000 0000000 00000000000 12605273753 014745 5 ustar root root 0000000 0000000 python-pskc-0.3/tests/test_write.doctest 0000644 0000000 0000000 00000012222 12605045077 020520 0 ustar root root 0000000 0000000 test_write.doctest - tests for writing PSKC files
Copyright (C) 2014-2015 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
>>> from pskc import PSKC
>>> import datetime
>>> import sys
>>> import tempfile
>>> from binascii import a2b_hex
>>> from dateutil.tz import tzutc
>>> utc = tzutc()
Build a PSKC structure.
>>> pskc = PSKC()
Add a key with all attributes set.
>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
>>> key.id = '123'
>>> key.serial = '987654321'
>>> key.model = 'Model'
>>> key.issue_no = 2
>>> key.start_date = datetime.datetime(2006, 5, 1, 0, 0, tzinfo=utc)
>>> key.expiry_date = datetime.datetime(2014, 5, 31, 0, 0, tzinfo=utc)
>>> key.device_userid = 'uid=arthur, dc=arthurdejong, dc=org'
>>> key.crypto_module = 'CyrptoId'
>>> key.algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:totp'
>>> key.issuer = 'Issuer'
>>> key.key_profile = 'key profile id'
>>> key.key_reference = 'reference to some key'
>>> key.friendly_name = 'a friendly key'
>>> key.key_userid = 'cn=Arthur de Jong, dc=arthurdejong, dc=org'
>>> key.algorithm_suite = 'Clubs'
>>> key.challenge_encoding = 'DECIMAL'
>>> key.challenge_min_length = 6
>>> key.challenge_max_length = 8
>>> key.challenge_check = True
>>> key.response_encoding = 'DECIMAL'
>>> key.response_length = 8
>>> key.response_check = False
>>> key.counter = 0
>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
Add policy information and a PIN.
>>> key.policy.key_usage.append('OTP')
>>> key.policy.key_usage.append(key.policy.KEY_USE_VERIFY)
>>> key.policy.start_date = datetime.datetime(2008, 5, 1, 0, 0, tzinfo=utc)
>>> key.policy.expiry_date = datetime.datetime(2012, 6, 13, 0, 0, tzinfo=utc)
>>> key.policy.number_of_transactions = 42
>>> key.policy.pin_key_id = 'pinID'
>>> key.policy.pin_usage = 'Local'
>>> key.policy.pin_max_failed_attemtps = 3
>>> key.policy.pin_min_length = 4
>>> key.policy.pin_max_length = 4
>>> key.policy.pin_encoding = 'DECIMAL'
>>> pin_key = pskc.add_key(id='pinID', secret='1234',
... algorithm='urn:ietf:params:xml:ns:keyprov:pskc:pin',
... response_encoding='DECIMAL', response_length=4)
Write the PSKC file (use temporary file to test passing file name as
argument).
>>> f = tempfile.NamedTemporaryFile()
>>> pskc.write(f.name)
>>> x = sys.stdout.write(open(f.name, 'r').read())
Manufacturer987654321Model22006-05-01T00:00:00Z2014-05-31T00:00:00Zuid=arthur, dc=arthurdejong, dc=orgCyrptoIdIssuerClubskey profile idreference to some keya friendly keyTheQuickBrownFox0cn=Arthur de Jong, dc=arthurdejong, dc=org2008-05-01T00:00:00Z2012-06-13T00:00:00ZOTPVerify42MTIzNA==
python-pskc-0.3/tests/rfc6030-figure5.pskcxml 0000644 0000000 0000000 00000003704 12354056573 021003 0 ustar root root 0000000 0000000
Manufacturer987654321CM_ID_001IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0OTPManufacturer987654321CM_ID_001IssuerMTIzNA==
python-pskc-0.3/tests/rfc6030-figure10.pskcxml 0000644 0000000 0000000 00000007414 12354056573 021061 0 ustar root root 0000000 0000000
TokenVendorAcme654321Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
02006-05-01T00:00:00Z2006-05-31T00:00:00ZTokenVendorAcme123456Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
02006-05-01T00:00:00Z2006-05-31T00:00:00ZTokenVendorAcme9999999Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
02006-03-01T00:00:00Z2006-03-31T00:00:00ZTokenVendorAcme9999999Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
02006-04-01T00:00:00Z2006-04-30T00:00:00Z
python-pskc-0.3/tests/test_misc.doctest 0000644 0000000 0000000 00000004672 12605045077 020333 0 ustar root root 0000000 0000000 test_misc.doctest - miscellaneous tests
Copyright (C) 2014-2015 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
>>> from binascii import 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
This tests the most minimal valid PSKC file with one empty key.
>>> try:
... from StringIO import StringIO
... except ImportError:
... from io import StringIO
>>> minimal_pskc = StringIO('''
...
...
...
...
... '''.strip())
>>> pskc = PSKC(minimal_pskc)
>>> [key.id for key in pskc.keys]
[None]
Check creation of empty PSKC structure and adding an empty key to the list.
>>> pskc = PSKC()
>>> key = pskc.add_key(id='123')
>>> key.id
'123'
>>> key.secret is None
True
Adding a key with unknown attributes raises an error.
>>> key = pskc.add_key(foo='bar')
Traceback (most recent call last):
...
AttributeError
Setting secret, counter, etc. also works
>>> key = pskc.add_key(secret='VERYSECRET')
>>> key.counter = 10
>>> key.secret
'VERYSECRET'
>>> key.counter
10
Load an PSKC file with an odd namespace.
>>> pskc = PSKC('tests/odd-namespace.pskcxml')
>>> pskc.version
'1.0'
>>> pskc.id
'exampleID1'
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.issuer
'Issuer-A'
>>> tostr(key.secret)
'1234'
Load a PSKC file that uses the xenc11 namespace for the PBKDF2 parameters.
>>> pskc = PSKC('tests/SampleFullyQualifiedNS.xml')
>>> pskc.encryption.key_name
'PassPhrase'
>>> pskc.encryption.derive_key('3FCA3158035072D6')
>>> key = pskc.keys[0]
>>> b2a_hex(key.secret)
'09fbecfd0bf47910839e2eb05ffa10b95cd0390950ce32ab790583ed134171e0'
>>> key.check()
True
python-pskc-0.3/tests/invalid-mac-algorithm.pskcxml 0000644 0000000 0000000 00000003253 12354056573 022523 0 ustar root root 0000000 0000000
Pre-shared-keyESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSXManufacturer987654321CM_ID_001IssuerAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvSu+NvtQfmvfJzF6bmQiJqoLRExc=0
python-pskc-0.3/tests/invalid-notxml.pskcxml 0000644 0000000 0000000 00000000066 12354056573 021317 0 ustar root root 0000000 0000000
python-pskc-0.3/tests/test_tripledeskw.doctest 0000644 0000000 0000000 00000005101 12605044512 021712 0 ustar root root 0000000 0000000 test_tripledeskw.doctest - test keywrap functions
Copyright (C) 2014-2015 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 re
>>> from binascii import a2b_hex
>>> from pskc.crypto.tripledeskw import wrap, unwrap
>>> def fromhex(value):
... return a2b_hex(re.sub(r'\s', '', value))
Example from RFC 3217 section 3.2 wrapping a 192 bit Triple DES key with
another 192 bit Triple DES key.
>>> plaintext = fromhex('''
... 2923 bf85 e06d d6ae 5291 49f1 f1ba e9ea b3a7 da3d 860d 3e98
... ''')
>>> key = fromhex('''
... 255e 0d1c 07b6 46df b313 4cc8 43ba 8aa7 1f02 5b7c 0838 251f
... ''')
>>> iv = fromhex('5dd4 cbfc 96f5 453b')
>>> ciphertext = fromhex('''
... 6901 0761 8ef0 92b3 b48c a179 6b23 4ae9 fa33 ebb4 1596 0403
... 7db5 d6a8 4eb3 aac2 768c 6327 75a4 67d4
... ''')
>>> wrap(plaintext, key, iv=iv) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
Leaving out the iv ensures that a random iv is used.
>>> c = wrap(plaintext, key)
>>> c == ciphertext
False
>>> unwrap(c, key) == plaintext
True
Wrapping is only specified for Triple DES keys but the algorithms works for
any plaintext that is a multiple of the Triple DES block size but fails
otherwise.
>>> short_plaintext = fromhex('''
... 2923 bf85 e06d d6ae 5291 49f1 f1ba e9ea
... ''')
>>> unwrap(wrap(short_plaintext, key), key) == short_plaintext
True
>>> short_plaintext = fromhex('''
... 2923 bf85 e06d d6ae 5291 49f1 f1ba e9
... ''')
>>> wrap(short_plaintext, key)
Traceback (most recent call last):
...
EncryptionError: Plaintext length wrong
The ciphertext must have the correct length (multiple of Triple DES block
size) and unwrapping is also authenticated.
>>> unwrap(ciphertext, key) == plaintext
True
>>> unwrap(ciphertext[:-1], key)
Traceback (most recent call last):
...
DecryptionError: Ciphertext length wrong
>>> unwrap(ciphertext[:-1] + b'A', key)
Traceback (most recent call last):
...
DecryptionError: CMS key checksum error
python-pskc-0.3/tests/tripledes-cbc.pskcxml 0000644 0000000 0000000 00000002305 12354056574 021071 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVbkU3i5koQy9wRwmtLzydqFV18QfbCMBR8=SVYxMjM0NTbvR25//t5tAuWfL+6ma90GGESqe3AlrJM=4eM8sZbswb+q4q4qZ18q2Af5LEIzZy4M1Mz7XF6Gnc8KozCp87ykK10uOHZpdKLrc9j8Yz0dw9CtQUVcijQKgA==
python-pskc-0.3/tests/aes192-cbc.pskcxml 0000644 0000000 0000000 00000002310 12354056573 020075 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVZJVklWSVZJVmDaimFqjBwo8MSWUGmwDkqJvsb1xlkf0MHfyqeooZzMAAECAwQFBgcICQoLDA0OD/616ab2do/xcWNKuW1qE3rSzwqoZcpg5ucwpjiZ07tVADfYOligu/3jDK9QhUGO7gGMxNxmrBUy4qtv4HyKF8o=
python-pskc-0.3/tests/invalid-wrongversion.pskcxml 0000644 0000000 0000000 00000000762 12354056573 022543 0 ustar root root 0000000 0000000
Issuer-AMTIzNA==
python-pskc-0.3/tests/aes128-cbc.pskcxml 0000644 0000000 0000000 00000002264 12354056573 020104 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVZJVklWSVZJViZS3d+rzbWqD74OQPuyiwrD+XlDXK7ef602mwOebfTRAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvCjGsEXpmZYGMyejd8WJdLFRBWE9XGJLiigPObg==
python-pskc-0.3/tests/kw-aes128.pskcxml 0000644 0000000 0000000 00000001463 12354056573 017776 0 ustar root root 0000000 0000000
Pre-shared-keyH6aLCoEStEeu80vY+1p7gp0+hiNx0s/l
python-pskc-0.3/tests/kw-aes256.pskcxml 0000644 0000000 0000000 00000001544 12354056573 020000 0 ustar root root 0000000 0000000
Pre-shared-keyqPm8FhLGiz/25vT74w5x5Haci4CjLLiVjNXRfWslTaE=
python-pskc-0.3/tests/rfc6030-figure4.pskcxml 0000644 0000000 0000000 00000002202 12354056573 020772 0 ustar root root 0000000 0000000
Manufacturer987654321CM_ID_001IssuerkeyProfile1MasterKeyLabel
0OTP
python-pskc-0.3/tests/draft-keyprov-ocra.pskcxml 0000644 0000000 0000000 00000002027 12354056573 022070 0 ustar root root 0000000 0000000
TokenVendorAcme987654322IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=0CR
python-pskc-0.3/tests/draft-keyprov-actividentity-3des.pskcxml 0000644 0000000 0000000 00000002314 12354056573 024657 0 ustar root root 0000000 0000000
ActivIdentity34567890Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0320OTP
python-pskc-0.3/tests/rfc6030-figure7.pskcxml 0000644 0000000 0000000 00000006104 12354056573 021002 0 ustar root root 0000000 0000000
Ej7/PEpyEpw=100016My Password 1
2GTTnLwM3I4e5IO5FkufoOEiOhNj91fhKRQBtBJYluUDsPOLTfUvoU2dStyOwYZx
TokenVendorAcme987654321CM_ID_001Example-Issuer
oTvo+S22nsmS2Z/RtcoF8Hfh+jzMe0RkiafpoDpnoZTjPYZu6V+A4aEn032yCr4f
LP6xMvjtypbfT9PdkJhBZ+D6O4w=
python-pskc-0.3/tests/draft-keyprov-totp.pskcxml 0000644 0000000 0000000 00000002121 12354056573 022125 0 ustar root root 0000000 0000000
TokenVendorAcme987654323IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=304OTP
python-pskc-0.3/tests/kw-aes192.pskcxml 0000644 0000000 0000000 00000001470 12354056573 017775 0 ustar root root 0000000 0000000
Pre-shared-keylneLJa5spDX5K1uXwFCu0kaKuKF62E5d
python-pskc-0.3/tests/rfc6030-figure3.pskcxml 0000644 0000000 0000000 00000002236 12354056573 021000 0 ustar root root 0000000 0000000
Manufacturer987654321DC=example-bank,DC=netCM_ID_001IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0UID=jsmith,DC=example-bank,DC=net
python-pskc-0.3/tests/test_encryption.doctest 0000644 0000000 0000000 00000007440 12605045077 021566 0 ustar root root 0000000 0000000 test_encryption.doctest - test various encryption schemes
Copyright (C) 2014-2015 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
>>> 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
>>> pskc = PSKC('tests/aes128-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> pskc.mac.algorithm
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224'
>>> tostr(pskc.mac.key)
'MacMacMacMacMacMacMa'
>>> pskc = PSKC('tests/aes192-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> pskc.keys[0].secret
Traceback (most recent call last):
...
DecryptionError: Invalid key length
>>> pskc.encryption.key = a2b_hex('123456789012345678901234567890123456789012345678')
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> pskc.mac.algorithm
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256'
>>> tostr(pskc.mac.key)
'MacMacMacMacMacMacMa'
>>> pskc = PSKC('tests/aes256-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('1234567890123456789012345678901234567890123456789012345678901234')
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> pskc.mac.algorithm
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha384'
>>> tostr(pskc.mac.key)
'MacMacMacMacMacMacMa'
>>> pskc = PSKC('tests/tripledes-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('1234')
>>> pskc.keys[0].secret
Traceback (most recent call last):
...
DecryptionError: Invalid key length
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> pskc.mac.algorithm
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha512'
>>> tostr(pskc.mac.key)
'MacMacMacMacMacMacMa'
>>> pskc = PSKC('tests/kw-aes128.pskcxml')
>>> pskc.encryption.key = a2b_hex('1234')
>>> pskc.keys[0].secret
Traceback (most recent call last):
...
DecryptionError: Invalid key length
>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f')
>>> b2a_hex(pskc.keys[0].secret)
'00112233445566778899aabbccddeeff'
>>> pskc = PSKC('tests/kw-aes192.pskcxml')
>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f')
>>> pskc.keys[0].secret
Traceback (most recent call last):
...
DecryptionError: Invalid key length
>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f1011121314151617')
>>> b2a_hex(pskc.keys[0].secret)
'00112233445566778899aabbccddeeff'
>>> pskc = PSKC('tests/kw-aes256.pskcxml')
>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f')
>>> b2a_hex(pskc.keys[0].secret)
'00112233445566778899aabbccddeeff0001020304050607'
>>> pskc = PSKC('tests/kw-tripledes.pskcxml')
>>> pskc.encryption.key = a2b_hex('255e0d1c07b646dfb3134cc843ba8aa71f')
>>> pskc.keys[0].secret
Traceback (most recent call last):
...
DecryptionError: Invalid key length
>>> pskc.encryption.key = a2b_hex('255e0d1c07b646dfb3134cc843ba8aa71f025b7c0838251f')
>>> b2a_hex(pskc.keys[0].secret)
'2923bf85e06dd6ae529149f1f1bae9eab3a7da3d860d3e98'
python-pskc-0.3/tests/test_invalid.doctest 0000644 0000000 0000000 00000007513 12605044512 021014 0 ustar root root 0000000 0000000 test_invalid.doctest - test for invalid PSKC file
Copyright (C) 2014-2015 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
>>> from binascii import a2b_hex
>>> from pskc import PSKC
Load a number of invalid files.
This file is plain invalid XML.
>>> pskc = PSKC('tests/invalid-notxml.pskcxml')
Traceback (most recent call last):
...
ParseError: Error parsing XML
This XML file has a wrong top-level element.
>>> pskc = PSKC('tests/invalid-wrongelement.pskcxml')
Traceback (most recent call last):
...
ParseError: Missing KeyContainer
This file has an unknown PSKC version.
>>> pskc = PSKC('tests/invalid-wrongversion.pskcxml')
Traceback (most recent call last):
...
ParseError: Unsupported version
This PSKC file has one key with an unknown algorithm and one key without an
algorithm specified.
>>> pskc = PSKC('tests/invalid-encryption.pskcxml')
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret
Traceback (most recent call last):
...
DecryptionError: No key available
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> key.secret
Traceback (most recent call last):
...
DecryptionError: Unsupported algorithm: ...
>>> key = pskc.keys[1]
>>> key.id
'45678901'
>>> key.secret
Traceback (most recent call last):
...
DecryptionError: No algorithm specified
Specify an unknown key derivation algorithm specified.
>>> pskc = PSKC('tests/rfc6030-figure7.pskcxml')
>>> pskc.encryption.derivation.algorithm = 'unknown'
>>> pskc.encryption.derive_key('qwerty')
Traceback (most recent call last):
...
KeyDerivationError: Unsupported algorithm: ...
Figure 6 does use encryption but with a pre-shared key. Attempting key
derivation with such a PSKC file should result in an exception.
>>> pskc = PSKC('tests/rfc6030-figure6.pskcxml')
>>> pskc.encryption.derive_key('qwerty')
Traceback (most recent call last):
...
KeyDerivationError: No algorithm specified
Specify an unknown PBKDF2 PRF (pseudorandom function).
>>> pskc = PSKC('tests/rfc6030-figure7.pskcxml')
>>> pskc.encryption.derivation.pbkdf2_prf = 'unknown'
>>> pskc.encryption.derive_key('qwerty')
Traceback (most recent call last):
...
KeyDerivationError: Pseudorandom function unsupported: ...
There is a ValueMAC element but no MACMethod element.
>>> pskc = PSKC('tests/invalid-no-mac-method.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret
Traceback (most recent call last):
...
DecryptionError: No MAC key available
There is an unknown algorithm specified in MACMethod.
>>> pskc = PSKC('tests/invalid-mac-algorithm.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret
Traceback (most recent call last):
...
DecryptionError: Unsupported MAC algorithm: ...
The MAC value does not match the calculated MAC, something was modified in
transit.
>>> pskc = PSKC('tests/invalid-mac-value.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret
Traceback (most recent call last):
...
DecryptionError: MAC value does not match
python-pskc-0.3/tests/SampleFullyQualifiedNS.xml 0000644 0000000 0000000 00000011506 12605044512 022001 0 ustar root root 0000000 0000000
C8R6xBQu36C7Z1zDXc8rN//pE3ksB2rK24916PassPhraserBdEN+D5lY5511A1isLWvCHzZAhDJ779KFlvoIv48VFT/FJjLfzOpGDSeGonSLjkCompanyXXXX0000001FToken1CompanyHMAC-SHA256PTTVlVTEiH/4HdphmBhxFJ7h5bGu5x476HXYd5jUtELg+MVqv+28/V1qT2bXdZXQ5rISLRNsZV0solNfH5WOSQ==snQNqaSQl8vxksYL9dDPrS0LHxM=0172800OTPCompanyXXXX0000001FToken2CompanyOCRA-1:HOTP-SHA256-9:QN02-T2HgO8BOgs+yOpfbrvdWV7eyi9/LdNkD/YwpXSllE+koMWkx/9n0Ms3D51Q5Av4KbRyrlGHOO61oeRYI0FYSbrdRg==L1Pne7RIEU1oLM+1kok2zfsfNSw=07200CR
python-pskc-0.3/tests/rfc6030-figure6.pskcxml 0000644 0000000 0000000 00000004352 12354056573 021004 0 ustar root root 0000000 0000000
Pre-shared-key
ESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSX
Manufacturer987654321CM_ID_001Issuer
AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
Su+NvtQfmvfJzF6bmQiJqoLRExc=
0
python-pskc-0.3/tests/invalid-no-mac-method.pskcxml 0000644 0000000 0000000 00000001716 12354056573 022431 0 ustar root root 0000000 0000000
Pre-shared-key
xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvLP6xMvjtypbfT9PdkJhBZ+D6O4w=
python-pskc-0.3/tests/aes256-cbc.pskcxml 0000644 0000000 0000000 00000002354 12354056573 020106 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVZJVklWSVZJVlAHw4GN7cbXseMBjNjUCrR8Lb4syW0I7bbNZbCBRt7TAAECAwQFBgcICQoLDA0OD7mg24krBXvsLMVBhZbLXDVFEWhqNqRTCO8AfowoBFcdJdB5+Ub/VSapUmJq+ZzEbseBPijlOp6BGy3+AAHoM7x17MbqR77xREby+9/65UOG
python-pskc-0.3/tests/odd-namespace.pskcxml 0000644 0000000 0000000 00000001101 12354056573 021041 0 ustar root root 0000000 0000000
Issuer-AMTIzNA==
python-pskc-0.3/tests/test_rfc6030.doctest 0000644 0000000 0000000 00000013367 12605044512 020455 0 ustar root root 0000000 0000000 test_rfc6030.doctest - test for examples from RFC 6030
Copyright (C) 2014-2015 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
>>> 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
This tests Figure 2 from RFC 6030. It is a basic key container example with a
simple plain text secret key.
>>> pskc = PSKC('tests/rfc6030-figure2.pskcxml')
>>> [tostr(key.secret) for key in pskc.keys]
['1234']
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.issuer
'Issuer-A'
>>> tostr(key.secret)
'1234'
This tests Figure 3 from RFC 6030. Relative to Figure 2 this includes device,
cryptographic module and user identification as well as some more parameters.
>>> pskc = PSKC('tests/rfc6030-figure3.pskcxml')
>>> pskc.id
'exampleID1'
>>> key = pskc.keys[0]
>>> key.manufacturer
'Manufacturer'
>>> key.serial
'987654321'
>>> key.device_userid
'DC=example-bank,DC=net'
>>> key.crypto_module
'CM_ID_001'
>>> key.id
'12345678'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.issuer
'Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
8
>>> tostr(key.secret)
'12345678901234567890'
>>> key.counter
0
>>> key.key_userid
'UID=jsmith,DC=example-bank,DC=net'
This tests Figure 4 from RFC 6030. In this case the key value itself is not
contained but can be derived using the serial and out-of-band agreements on
the meanings of key_profile and key_reference.
>>> pskc = PSKC('tests/rfc6030-figure4.pskcxml')
>>> key = pskc.keys[0]
>>> key.serial
'987654321'
>>> key.key_profile
'keyProfile1'
>>> key.key_reference
'MasterKeyLabel'
>>> key.counter
0
This tests the key policy properties as illustrated in Figure 5 from RFC
6030.
>>> pskc = PSKC('tests/rfc6030-figure5.pskcxml')
>>> len(pskc.keys)
2
>>> key1, key2 = pskc.keys
>>> key1.serial
'987654321'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.response_length
8
>>> key.response_encoding
'DECIMAL'
>>> tostr(key1.secret)
'12345678901234567890'
>>> key1.counter
0
>>> key1.policy.pin_min_length
4
>>> key1.policy.pin_max_length
4
>>> key1.policy.pin_key_id
'123456781'
>>> key1.policy.pin_encoding
'DECIMAL'
>>> key1.policy.pin_usage
'Local'
>>> key1.policy.key_usage
['OTP']
>>> key1.policy.may_use('OTP')
True
>>> key1.policy.may_use('Encrypt')
False
>>> key1.policy.unknown_policy_elements
False
>>> key2.id
'123456781'
>>> key2.serial
'987654321'
>>> key2.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:pin'
>>> key2.response_length
4
>>> key2.response_encoding
'DECIMAL'
>>> tostr(key2.secret)
'1234'
>>> key1.policy.pin
'1234'
This tests key encryption based on pre-shared keys as illustrated in Figure 6
from RFC 6030. The first attempt at extracting the key will fail due to the
encryption.
>>> pskc = PSKC('tests/rfc6030-figure6.pskcxml')
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret
Traceback (most recent call last):
...
DecryptionError: No key available
>>> pskc.encryption.key_name
'Pre-shared-key'
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(pskc.mac.key)
'1122334455667788990011223344556677889900'
>>> b2a_hex(key.secret)
'3132333435363738393031323334353637383930'
>>> key.check()
True
This tests a derived master key using PBKDF2 as seen in Figure 7 from RFC
6030.
>>> pskc = PSKC('tests/rfc6030-figure7.pskcxml')
>>> pskc.encryption.key_name
'My Password 1'
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.encryption.key)
'651e63cd57008476af1ff6422cd02e41'
>>> b2a_hex(pskc.mac.key)
'bdaab8d648e850d25a3289364f7d7eaaf53ce581'
>>> key = pskc.keys[0]
>>> tostr(key.secret)
'12345678901234567890'
>>> key.check()
True
This tests bulk provisioning as shown in Figure 10 From RFC 6030.
>>> pskc = PSKC('tests/rfc6030-figure10.pskcxml')
>>> all(key.manufacturer == 'TokenVendorAcme' for key in pskc.keys)
True
>>> [key.serial for key in pskc.keys]
['654321', '123456', '9999999', '9999999']
>>> all(key.algorithm == 'urn:ietf:params:xml:ns:keyprov:pskc:hotp' for key in pskc.keys)
True
>>> all(key.issuer == 'Issuer' for key in pskc.keys)
True
>>> all(key.response_length == 8 for key in pskc.keys)
True
>>> all(key.response_encoding == 'DECIMAL' for key in pskc.keys)
True
>>> all(key.secret == b'12345678901234567890' for key in pskc.keys)
True
>>> all(key.counter == 0 for key in pskc.keys)
True
>>> pskc.keys[0].policy.start_date
datetime.datetime(2006, 5, 1, 0, 0, tzinfo=tzutc())
>>> pskc.keys[0].policy.expiry_date
datetime.datetime(2006, 5, 31, 0, 0, tzinfo=tzutc())
>>> pskc.keys[1].policy.start_date
datetime.datetime(2006, 5, 1, 0, 0, tzinfo=tzutc())
>>> pskc.keys[1].policy.expiry_date
datetime.datetime(2006, 5, 31, 0, 0, tzinfo=tzutc())
>>> pskc.keys[2].policy.start_date
datetime.datetime(2006, 3, 1, 0, 0, tzinfo=tzutc())
>>> pskc.keys[2].policy.expiry_date
datetime.datetime(2006, 3, 31, 0, 0, tzinfo=tzutc())
>>> pskc.keys[3].policy.start_date
datetime.datetime(2006, 4, 1, 0, 0, tzinfo=tzutc())
>>> pskc.keys[3].policy.expiry_date
datetime.datetime(2006, 4, 30, 0, 0, tzinfo=tzutc())
python-pskc-0.3/tests/kw-tripledes.pskcxml 0000644 0000000 0000000 00000001576 12354056573 020773 0 ustar root root 0000000 0000000
Pre-shared-keyaQEHYY7wkrO0jKF5ayNK6foz67QVlgQDfbXWqE6zqsJ2jGMndaRn1A==
python-pskc-0.3/tests/rfc6030-figure2.pskcxml 0000644 0000000 0000000 00000001072 12354056573 020774 0 ustar root root 0000000 0000000
Issuer-AMTIzNA==
python-pskc-0.3/tests/test_aeskw.doctest 0000644 0000000 0000000 00000015174 12605044512 020502 0 ustar root root 0000000 0000000 test_keywrap.doctest - test keywrap functions
Copyright (C) 2014-2015 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
>>> from binascii import a2b_hex
>>> from pskc.crypto.aeskw import wrap, unwrap
Wrap 128 bits of Key Data with a 128-bit KEK (test vector 4.1 from RFC 3394).
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F')
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF')
>>> ciphertext = a2b_hex('1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
Wrap 128 bits of Key Data with a 192-bit KEK (test vector 4.2 from RFC 3394).
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F1011121314151617')
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF')
>>> ciphertext = a2b_hex('96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
Wrap 128 bits of Key Data with a 256-bit KEK (test vector 4.3 from RFC 3394).
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F')
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF')
>>> ciphertext = a2b_hex('64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
Wrap 192 bits of Key Data with a 192-bit KEK (test vector 4.4 from RFC 3394).
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F1011121314151617')
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF0001020304050607')
>>> ciphertext = a2b_hex('031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
Wrap 192 bits of Key Data with a 256-bit KEK (test vector 4.5 from RFC 3394).
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F')
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF0001020304050607')
>>> ciphertext = a2b_hex('A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
Wrap 256 bits of Key Data with a 256-bit KEK (test vector 4.6 from RFC 3394).
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F')
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F')
>>> ciphertext = a2b_hex('28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
Mangling the ciphertext and unwrapping results in an exception:
>>> ciphertext = b'XX' + ciphertext[2:]
>>> unwrap(ciphertext, key)
Traceback (most recent call last):
...
DecryptionError: IV does not match
>>> ciphertext = ciphertext[2:]
>>> unwrap(ciphertext, key)
Traceback (most recent call last):
...
DecryptionError: Ciphertext length wrong
Wrap 20 octets with a 192-bit key (first example from section 6 of RFC 5649).
>>> key = a2b_hex('5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8')
>>> plaintext = a2b_hex('c37b7e6492584340bed12207808941155068f738')
>>> ciphertext = a2b_hex('138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
>>> wrap(plaintext, key, pad=False) # disable padding
Traceback (most recent call last):
...
EncryptionError: Plaintext length wrong
>>> unwrap(ciphertext, key, pad=False)
Traceback (most recent call last):
...
DecryptionError: IV does not match
Wrap 7 octets with a 192-bit key (second example from section 6 of RFC 5649).
>>> key = a2b_hex('5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8')
>>> plaintext = a2b_hex('466f7250617369')
>>> ciphertext = a2b_hex('afbeb0f07dfbf5419200f2ccb50bb24f')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
>>> wrap(plaintext, key, pad=False) # disable padding
Traceback (most recent call last):
...
EncryptionError: Plaintext length wrong
>>> unwrap(ciphertext, key, pad=False)
Traceback (most recent call last):
...
DecryptionError: Ciphertext length wrong
Normally padding is only done if needed but it can be forced.
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F')
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF')
>>> ciphertext = a2b_hex('1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
>>> ciphertext = a2b_hex('2cef0c9e30de26016c230cb78bc60d51b1fe083ba0c79cd5')
>>> wrap(plaintext, key, pad=True) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
>>> unwrap(ciphertext, key, pad=False) # disabling padding fails IV check
Traceback (most recent call last):
...
DecryptionError: IV does not match
Padding can also be disabled. This also disables the shortcut for small
plaintexts as described in RFC 5649.
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F')
>>> plaintext = a2b_hex('0011223344556677')
>>> ciphertext = a2b_hex('f4740052e82a225174ce86fbd7b805e7')
>>> wrap(plaintext, key) == ciphertext
True
>>> unwrap(ciphertext, key) == plaintext
True
>>> wrap(plaintext, key, pad=False) # disable padding
Traceback (most recent call last):
...
EncryptionError: Plaintext length wrong
>>> unwrap(ciphertext, key, pad=False)
Traceback (most recent call last):
...
DecryptionError: Ciphertext length wrong
Lastly, an explicit IV can be set but this disables the padding functionality.
>>> key = a2b_hex('000102030405060708090A0B0C0D0E0F')
>>> plaintext = a2b_hex('0011223344556677')
>>> iv = a2b_hex('1010101010101010')
>>> wrap(plaintext, key, iv)
Traceback (most recent call last):
...
EncryptionError: Plaintext length wrong
>>> plaintext = a2b_hex('00112233445566778899AABBCCDDEEFF')
>>> ciphertext = a2b_hex('4cd926c570e19c35ace71d59a1062dae850e6a709066e0bf')
>>> wrap(plaintext, key, iv) == ciphertext
True
>>> unwrap(ciphertext, key, iv) == plaintext
True
python-pskc-0.3/tests/invalid-encryption.pskcxml 0000644 0000000 0000000 00000002634 12354056573 022173 0 ustar root root 0000000 0000000
Pre-shared-key
AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
python-pskc-0.3/tests/invalid-wrongelement.pskcxml 0000644 0000000 0000000 00000000067 12354056573 022505 0 ustar root root 0000000 0000000
python-pskc-0.3/tests/draft-keyprov-securid-aes-counter.pskcxml 0000644 0000000 0000000 00000002152 12354056573 025024 0 ustar root root 0000000 0000000
RSA, The Security Division of EMC123456798IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0OTP2006-04-14T00:00:00Z2010-09-30T00:00:00Z
python-pskc-0.3/tests/invalid-mac-value.pskcxml 0000644 0000000 0000000 00000003261 12354056573 021650 0 ustar root root 0000000 0000000
Pre-shared-keyESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSXManufacturer987654321CM_ID_001IssuerAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGzSu+NvtQfmvfJzF6bmQiJqoLRExc=0
python-pskc-0.3/tests/test_draft_keyprov.doctest 0000644 0000000 0000000 00000007322 12605044512 022243 0 ustar root root 0000000 0000000 test_draft_keyprov.doctest - test for examples from
draft-hoyer-keyprov-pskc-algorithm-profiles-01
Copyright (C) 2014-2015 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
>>> def tostr(x):
... return str(x.decode())
>>> from pskc import PSKC
This tests an OCRA (OATH Challenge Response Algorithm) key contained within
a PSKC file as described in section 3 of
draft-hoyer-keyprov-pskc-algorithm-profiles-01.
>>> pskc = PSKC('tests/draft-keyprov-ocra.pskcxml')
>>> pskc.version
'1.0'
>>> key = pskc.keys[0]
>>> key.manufacturer
'TokenVendorAcme'
>>> key.serial
'987654322'
>>> key.id
'12345678'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#OCRA-1:HOTP-SHA512-8:C-QN08'
>>> key.issuer
'Issuer'
>>> key.challenge_encoding
'DECIMAL'
>>> key.challenge_min_length
8
>>> key.challenge_max_length
8
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
8
>>> tostr(key.secret)
'12345678901234567890'
>>> key.counter
0
>>> key.policy.key_usage
['CR']
This tests an TOTP (OATH Time based OTP) key contained within a PSKC file as
described in section 4 of draft-hoyer-keyprov-pskc-algorithm-profiles-01.
>>> pskc = PSKC('tests/draft-keyprov-totp.pskcxml')
>>> pskc.version
'1.0'
>>> key = pskc.keys[0]
>>> key.manufacturer
'TokenVendorAcme'
>>> key.serial
'987654323'
>>> key.id
'987654323'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#totp'
>>> key.issuer
'Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> tostr(key.secret)
'12345678901234567890'
>>> key.time_offset
0
>>> key.time_interval
30
>>> key.time_drift
4
>>> key.policy.key_usage
['OTP']
This tests an SecurID-AES-Counter key contained within a PSKC file as
described in section 6 of draft-hoyer-keyprov-pskc-algorithm-profiles-01.
>>> pskc = PSKC('tests/draft-keyprov-securid-aes-counter.pskcxml')
>>> pskc.version
'1.0'
>>> key = pskc.keys[0]
>>> key.manufacturer
'RSA, The Security Division of EMC'
>>> key.serial
'123456798'
>>> key.id
'23456789'
>>> key.algorithm
'http://www.rsa.com/names/2008/04/algorithms/SecurID/SecurID-AES128-Counter'
>>> key.issuer
'Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> tostr(key.secret)
'12345678901234567890'
>>> key.counter
0
>>> key.policy.key_usage
['OTP']
>>> key.policy.start_date
datetime.datetime(2006, 4, 14, 0, 0, tzinfo=tzutc())
>>> key.policy.expiry_date
datetime.datetime(2010, 9, 30, 0, 0, tzinfo=tzutc())
This tests an ActivIdentity-3DES key contained within a PSKC file as
described in section 8 of draft-hoyer-keyprov-pskc-algorithm-profiles-01.
>>> pskc = PSKC('tests/draft-keyprov-actividentity-3des.pskcxml')
>>> pskc.version
'1.0'
>>> key = pskc.keys[0]
>>> key.manufacturer
'ActivIdentity'
>>> key.serial
'34567890'
>>> key.id
'12345677'
>>> key.algorithm
'http://www.actividentity.com/2008/04/algorithms/algorithms#ActivIdentity-3DES'
>>> key.issuer
'Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
8
>>> tostr(key.secret)
'12345678901234567890'
>>> key.counter
0
>>> key.time_offset
0
>>> key.time_interval
32
>>> key.time_drift
0
>>> key.policy.key_usage
['OTP']
python-pskc-0.3/NEWS 0000644 0000000 0000000 00000002117 12605273737 014305 0 ustar root root 0000000 0000000 changes from 0.2 to 0.3
-----------------------
* support writing unencrypted PSKC files
* include a sample pskc2csv script in the source code
* fix an issue with XML namespaces for PBKDF2 parameters
* support Python 3
* update documentation
changes from 0.1 to 0.2
-----------------------
* raise exceptions on parsing, decryption and other problems
* support Python 2.6 and multiple ElementTree implementations (lxml is
required when using Python 2.6)
* support more encryption algorithms (AES128-CBC, AES192-CBC, AES256-CBC,
TripleDES-CBC, KW-AES128, KW-AES192, KW-AES256 and KW-TripleDES) and be
more lenient in accepting algorithm URIs
* support all HMAC algorithms that Python's hashlib module has hash functions
for (HMAC-MD5, HMAC-SHA1, HMAC-SHA224, HMAC-SHA256, HMAC-SHA384 and
HMAC-SHA512)
* support PRF attribute of PBKDF2 algorithm
* support creating PSKC objects and keys
* when accessing values for which a MAC is present, a MAC failure will raise
an exception (DecryptionError)
* many code cleanups
* improve test coverage
changes in 0.1
--------------
Initial release
python-pskc-0.3/ChangeLog 0000644 0000000 0000000 00000060237 12605273737 015367 0 ustar root root 0000000 0000000 2015-10-07 Arthur de Jong
* [cf0c9e6] README, docs/conf.py, docs/encryption.rst,
docs/exceptions.rst, docs/mac.rst, docs/policy.rst, docs/usage.rst,
pskc/__init__.py: Update documentation
This updates the documentation with the new features (writing PSKC
files) as well as many editorial improvements, some rewording
and a few typo fixes. Some things were moved around a little in
order to be more easily readable and easier to find.
2015-10-06 Arthur de Jong
* [671b6e2] pskc/__init__.py, pskc/crypto/aeskw.py,
pskc/crypto/tripledeskw.py, pskc/encryption.py, pskc/key.py,
pskc/policy.py, pskc/xml.py, setup.py, tests/test_aeskw.doctest,
tests/test_draft_keyprov.doctest, tests/test_encryption.doctest,
tests/test_invalid.doctest, tests/test_misc.doctest,
tests/test_rfc6030.doctest, tests/test_tripledeskw.doctest,
tests/test_write.doctest: Support Python 3
This enables support for Python 3 together with Python 2 support
with a single codebase.
On Python 3 key data is passed around as bytestrings which makes
the doctests a little harder to maintain across Python versions.
2015-10-06 Arthur de Jong
* [68b20e2] pskc/encryption.py, pskc/xml.py,
tests/SampleFullyQualifiedNS.xml, tests/test_misc.doctest:
Fix issue with namespaced PBKDF2 parameters
The find() utility functions now allow specifying multiple paths
to be searched where the first match is returned.
This allows handling PSKC files where the PBKDF2 salt, iteration
count, key length and PRF elements are prefixed with the xenc11
namespace.
A test including such a PSKC file has been included.
Thanks to Eric Plet for reporting this.
2014-10-12 Arthur de Jong
* [ebe46f2] pskc2csv.py: Provide a sample pskc2csv script
This is a simple command-line utility that reads a PSKC file
and outputs information on keys as CSV.
2014-06-30 Arthur de Jong
* [1363564] pskc/aeskw.py, pskc/crypto/__init__.py,
pskc/crypto/aeskw.py, pskc/crypto/tripledeskw.py,
pskc/encryption.py, pskc/tripledeskw.py, tests/test_aeskw.doctest,
tests/test_tripledeskw.doctest: Move encryption functions in
pskc.crypto package
This moves the encryption functions under the pskc.crypto package
to more clearly separate it from the other code. Ideally this
should be replaced by third-party library code.
2014-06-30 Arthur de Jong
* [e468ebe] pskc/__init__.py, pskc/encryption.py, pskc/key.py,
pskc/mac.py, pskc/parse.py, pskc/policy.py, pskc/xml.py: Rename
pskc.parse to pskc.xml
This renames the parse module to xml to better reflect the
purpose of the module and it's functions.
This also introduces a parse() function that wraps etree.parse().
2014-06-28 Arthur de Jong
* [480e2d0] : Support writing unencrypted PSKC files
2014-06-27 Arthur de Jong
* [37dc64a] tests/test_write.doctest: Add test for writing PSKC files
This makes a simple doctest that checks the writing of the XML
representation of the PSKC data.
2014-06-27 Arthur de Jong
* [865a755] pskc/__init__.py, pskc/parse.py: Add function for
writing XML
This provides a function for pretty-printing the generated
XML document.
2014-06-27 Arthur de Jong
* [61a192f] pskc/__init__.py, pskc/key.py, pskc/policy.py: Construct
XML document with basic PKSC information
This introduces make_xml() functions to build an XML document
that contains the basic PSKC information and keys. This currently
only supports writing unencrypted PSKC files.
2014-06-27 Arthur de Jong
* [69aec9f] pskc/parse.py: Introduce mk_elem() to create elements
This introduces the mk_elem() function that can be used to create
ElementTree elements for building XML documents. This function
transparetly handles namespaces, translation of values into
XML etc.
2014-06-27 Arthur de Jong
* [7591271] pskc/key.py: Simplify DataType value handling
Only store the native value of the property, not the text
representation. This also results in the BinaryDataType and
IntegerDataType subclasses only needing from_text() and from_bin()
functions.
2014-06-19 Arthur de Jong
* [09eb6b3] ChangeLog, NEWS, docs/changes.rst, docs/index.rst,
docs/usage.rst, pskc/__init__.py, setup.py: Get files ready for
0.2 release
2014-06-19 Arthur de Jong
* [62c9af4] pskc/__init__.py: Only catch normal exceptions
2014-06-18 Arthur de Jong
* [deb57d7] pskc/__init__.py: Remove unused import
2014-06-17 Arthur de Jong
* [178ef1c] pskc/encryption.py: PEP8 fix
2014-06-17 Arthur de Jong
* [7435552] pskc/exceptions.py: Remove __str__ from exception
The message property has been deprecated as of Python 2.6 and
printing the first argument is the default.
2014-06-16 Arthur de Jong
* [f084735] README, docs/encryption.rst, docs/exceptions.rst,
docs/index.rst, docs/mac.rst, docs/policy.rst, docs/usage.rst:
Update documentation
This updates the documentation with the current API, adding
information on exceptions raised, HMAC algorithms supported and
changes to the MAC checking.
This also includes some editorial changes to some of the text and
making references shorter by not including the full package path.
2014-06-15 Arthur de Jong
* [d84e761] pskc/parse.py: Simplify finding ElementTree
implementation
These are the only ElementTree implementations that have been
tested to provide the needed functionality (mostly namespaces).
2014-06-15 Arthur de Jong
* [50b429d] pskc/key.py, pskc/parse.py, pskc/policy.py: Refactor
out some functions to parse
This introduces the getint() and getbool() functions in parse
to avoid some code duplication.
2014-06-15 Arthur de Jong
* [9a16ce4] pskc/key.py, tests/test_misc.doctest: Add support for
setting secret
This supports setters for the secret, counter, time_offset,
time_interval and time_drift properties. Setting these values
stores the values unencrypted internally.
2014-06-14 Arthur de Jong
* [1b9ee9f] pskc/encryption.py: Support PBKDF2 PRF argument
Support specifying a pseudorandom function for PBKDF2 key
derivation. It currently supports any HMAC that the MAC checking
also supports.
2014-06-14 Arthur de Jong
* [79b9a7d] pskc/mac.py: Provide a get_hmac() function
Refactor the functionality to find an HMAC function into a
separate function.
2014-06-14 Arthur de Jong
* [1417d4a] tests/invalid-mac-algorithm.pskcxml,
tests/invalid-mac-value.pskcxml,
tests/invalid-no-mac-method.pskcxml, tests/test_invalid.doctest:
Add tests for missing or invalid MAC
This tests for incomplete, unknown or invalid MACs in PSKC files.
2014-06-14 Arthur de Jong
* [9d8aae0] pskc/key.py, pskc/mac.py: Raise exception when MAC
validation fails
This changes the way the check() function works to raise an
exception when the MAC is not correct. The MAC is also now always
checked before attempting decryption.
This also renames the internal DataType.value property to a
get_value() method for clarity.
2014-06-14 Arthur de Jong
* [699ecf8] pskc/encryption.py: Handle missing MAC algorithm properly
2014-06-14 Arthur de Jong
* [01e102b] tests/aes128-cbc.pskcxml, tests/aes192-cbc.pskcxml,
tests/aes256-cbc.pskcxml, tests/test_encryption.doctest,
tests/tripledes-cbc.pskcxml: Add MAC tests to all CBC encrypted
keys
This adds hmac-sha224, hmac-sha256, hmac-sha384 and hmac-sha512
tests for values that are encrypted using CBC block cypher modes.
2014-06-14 Arthur de Jong
* [59e790e] pskc/mac.py: Automatically support all MACs in hashlib
This uses the name of the hash to automatically get the correct
hash object from Python's hashlib.
2014-06-14 Arthur de Jong
* [566e447] pskc/__init__.py, pskc/parse.py, setup.py: Support
various ElementTree implementations
When using a recent enough lxml, even Python 2.6 should work
now. The most important requirement is that the findall()
function supports the namespaces argument.
This also now catches all exceptions when parsing the PSKC file
fails and wraps it in ParseError because various implementations
raise different exceptions, even between versions (Python 2.6's
ElementTree raises ExpatError, lxml raises XMLSyntaxError).
2014-06-13 Arthur de Jong
* [5d60ee2] pskc/__init__.py, pskc/encryption.py, pskc/key.py,
pskc/mac.py, pskc/parse.py, pskc/policy.py: Have parse module
provide find() functions
This changes the parse module functions to better match the
ElementTree API and extends it with findint(), findtime()
and findbin().
It also passes the namespaces to all calls that require it
without duplicating this throughout the normal code.
2014-06-11 Arthur de Jong
* [6a34c01] pskc/__init__.py, pskc/encryption.py, pskc/key.py,
pskc/mac.py, pskc/policy.py: Use get() instead of attrib.get()
(shorter)
2014-05-31 Arthur de Jong
* [4d92b93] pskc/encryption.py, tests/kw-tripledes.pskcxml,
tests/test_encryption.doctest: Support kw-tripledes decryption
This adds support for key unwrapping using the RFC 3217 Triple
DES key wrap algorithm if the PSKC file uses this.
2014-05-31 Arthur de Jong
* [fd71f01] pskc/tripledeskw.py, tests/test_tripledeskw.doctest:
Implement RFC 3217 Triple DES key wrapping
2014-05-31 Arthur de Jong
* [f639318] tests/test_minimal.doctest, tests/test_misc.doctest:
Merge test_minimal into test_misc
2014-05-31 Arthur de Jong
* [1e7f861] tests/draft-keyprov-actividentity-3des.pskcxml,
tests/test_draft_keyprov.doctest: Add an ActivIdentity-3DES test
The test is taken from
draft-hoyer-keyprov-pskc-algorithm-profiles-01 modified to fit
the schema as described in RFC 6030.
2014-05-31 Arthur de Jong
* [b7cb928] tests/draft-keyprov-securid-aes-counter.pskcxml,
tests/test_draft_keyprov.doctest: Add an SecurID-AES-Counter test
The test is taken from
draft-hoyer-keyprov-pskc-algorithm-profiles-01 modified to be
valid XML and to fit the schema as described in RFC 6030.
2014-05-31 Arthur de Jong
* [427319f] tests/draft-keyprov-totp.pskcxml,
tests/test_draft_keyprov.doctest: Add an TOTP test
The test is taken from
draft-hoyer-keyprov-pskc-algorithm-profiles-01 modified to fit
the schema as described in RFC 6030.
2014-05-31 Arthur de Jong
* [ba49d09] tests/draft-keyprov-ocra.pskcxml,
tests/test_draft_keyprov.doctest: Add an OCRA test
The test is taken from
draft-hoyer-keyprov-pskc-algorithm-profiles-01 modified to fit
the schema as described in RFC 6030.
2014-05-31 Arthur de Jong
* [0a66ede] tests/odd-namespace.pskcxml, tests/test_misc.doctest:
Add a test for an odd namespace
2014-05-30 Arthur de Jong
* [287afa7] pskc/encryption.py, tests/kw-aes128.pskcxml,
tests/kw-aes192.pskcxml, tests/kw-aes256.pskcxml,
tests/test_encryption.doctest: Support kw-aes128, kw-aes192
and kw-aes256
This adds support for key unwrapping using the RFC 3394 or RFC
5649 algorithm if the PSKC file uses this.
2014-05-30 Arthur de Jong
* [99ba287] pskc/aeskw.py, tests/test_aeskw.doctest: Implement
padding as specified in RFC 5649
This adds a pad argument with which padding can be forced or
disabled.
2014-05-29 Arthur de Jong
* [ebf8945] pskc/aeskw.py, tests/test_aeskw.doctest: Allow speciying
an initial value for key wrapping
2014-05-29 Arthur de Jong
* [5720fe5] pskc/aeskw.py, pskc/exceptions.py,
tests/test_aeskw.doctest: Provide an RFC 3394 AES key wrapping
algorithm
This also introduces an EncryptionError exception.
2014-05-29 Arthur de Jong
* [7164d89] README, docs/usage.rst, pskc/__init__.py,
tests/rfc6030-figure10.pskcxml, tests/rfc6030-figure2.pskcxml,
tests/rfc6030-figure3.pskcxml, tests/rfc6030-figure4.pskcxml,
tests/rfc6030-figure5.pskcxml, tests/rfc6030-figure6.pskcxml,
tests/rfc6030-figure7.pskcxml, tests/test_rfc6030.doctest:
Always put a space between RFC and number
2014-05-29 Arthur de Jong
* [ccebb69] pskc/encryption.py, tests/test_encryption.doctest,
tests/tripledes-cbc.pskcxml: Support Tripple DES decryption
2014-05-29 Arthur de Jong
* [a11f31f] tests/test_invalid.doctest: Add tests for key derivation
problems
This tests for unknown or missing algorithms and unknown
derivation parameters.
2014-05-29 Arthur de Jong
* [0738c94] pskc/encryption.py, pskc/exceptions.py: Raise exception
when key derivation fails
This also renames the internal function that implements the
derivation.
2014-05-29 Arthur de Jong
* [76ef42b] pskc/encryption.py, pskc/exceptions.py,
tests/invalid-encryption.pskcxml, tests/test_invalid.doctest:
Add test for missing key encryption algorithm
This also introduces a toplevel PSKCError exception that all
exceptions have as parent.
2014-05-29 Arthur de Jong
* [7f26dc6] tests/aes128-cbc.pskcxml, tests/aes192-cbc.pskcxml,
tests/aes256-cbc.pskcxml, tests/test_encryption.doctest: Add
test for all AES-CBC encryption schemes
2014-05-29 Arthur de Jong
* [28f2c1c] pskc/encryption.py: Support more AES-CBC encryption
schemes
This also moves the crypto imports to the places where they are
used to avoid a depenency on pycrypto if no encryption is used.
2014-05-29 Arthur de Jong
* [678b127] tests/test_minimal.doctest: Add test for missing
secret value
2014-05-25 Arthur de Jong
* [bef2f7d] pskc/__init__.py, pskc/key.py,
tests/test_minimal.doctest: Add a function for adding a new key
2014-05-25 Arthur de Jong
* [46f5749] pskc/__init__.py: Consistency improvement
2014-05-25 Arthur de Jong
* [83f5a4b] pskc/__init__.py, tests/test_minimal.doctest: Support
creating an empty PSKC instance
2014-05-25 Arthur de Jong
* [820c83c] pskc/encryption.py, pskc/mac.py: Be more lenient in
accepting algorithms
2014-05-25 Arthur de Jong
* [02bde47] pskc/key.py: Code simplification
2014-05-25 Arthur de Jong
* [b62fec8] pskc/encryption.py, pskc/exceptions.py,
tests/invalid-encryption.pskcxml, tests/test_invalid.doctest,
tests/test_rfc6030.doctest: Raise an exception if decryption fails
2014-05-25 Arthur de Jong
* [7bc2e6b] pskc/encryption.py: Make decryption code better readable
2014-05-23 Arthur de Jong
* [714f387] setup.cfg, tests/invalid-notxml.pskcxml,
tests/invalid-wrongelement.pskcxml,
tests/invalid-wrongversion.pskcxml, tests/test_invalid.doctest:
Add tests for invalid PSKC files
2014-05-23 Arthur de Jong
* [803d24c] pskc/__init__.py, pskc/exceptions.py: Raise exceptions
on some parsing problems
2014-05-23 Arthur de Jong
* [8c37e26] setup.py: Fix install_requires
2014-05-23 Arthur de Jong
* [8e1729e] ChangeLog, MANIFEST.in, NEWS: Get files ready for
0.1 release
2014-05-23 Arthur de Jong
* [15ca643] README, pskc/__init__.py, tests/rfc6030-figure10.pskc,
tests/rfc6030-figure10.pskcxml, tests/rfc6030-figure2.pskc,
tests/rfc6030-figure2.pskcxml, tests/rfc6030-figure3.pskc,
tests/rfc6030-figure3.pskcxml, tests/rfc6030-figure4.pskc,
tests/rfc6030-figure4.pskcxml, tests/rfc6030-figure5.pskc,
tests/rfc6030-figure5.pskcxml, tests/rfc6030-figure6.pskc,
tests/rfc6030-figure6.pskcxml, tests/rfc6030-figure7.pskc,
tests/rfc6030-figure7.pskcxml, tests/test_rfc6030.doctest:
Use pskcxml as file name extension
This is the extension that is suggested in RFC6030.
2014-05-23 Arthur de Jong
* [44c7d2e] docs/policy.rst, docs/usage.rst: Improve IANA links
2014-05-20 Arthur de Jong
* [cda1c5f] tests/test_rfc6030.doctest: Improve test
This tests that, before the PSKC ecnryption is key available,
the secret from the key cannot be extracted.
2014-05-19 Arthur de Jong
* [e96c746] docs/_templates/autosummary/module.rst, docs/conf.py,
docs/encryption.rst, docs/index.rst, docs/mac.rst, docs/policy.rst,
docs/usage.rst: Provide Sphinx documentation
2014-05-18 Arthur de Jong
* [edf4d24] pskc/policy.py: Add missing policy constant
2014-05-18 Arthur de Jong
* [92a994d] pskc/key.py: Fix attribute name in docstring
2014-04-20 Arthur de Jong
* [cc9bbb5] README: Update README
2014-05-17 Arthur de Jong
* [d0a7814] .gitignore, setup.py: Fix dateutil dependency
This also ignores downloaded .egg files.
2014-04-19 Arthur de Jong
* [e0159ba] pskc/parse.py: Fix module description
2014-04-19 Arthur de Jong
* [ba17976] pskc/__init__.py, pskc/parse.py: Move PSKC class to
toplevel module
This also splits the parsing to a parse() function for consistency.
2014-04-19 Arthur de Jong
* [64e207d] pskc/key.py, tests/test_rfc6030.doctest: 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.
2014-04-19 Arthur de Jong
* [6becc61] pskc/parse.py: Provide pskc.parse docstrings
This documents most of the API of the parsing functions and the
PSKC class.
2014-04-19 Arthur de Jong
* [1d42fbc] pskc/policy.py: Complete pskc.policy docstrings
Also contains small consistency improvement.
2014-04-19 Arthur de Jong
* [b07d709] pskc/mac.py: Provide pskc.mac docstrings
This also hides two properties that are not part of the public API.
2014-04-19 Arthur de Jong
* [285860e] pskc/encryption.py: Provide pskc.encryption docstrings
This documents classes in the pskc.encryption module.
2014-04-19 Arthur de Jong
* [8c9e03d] pskc/key.py, pskc/mac.py, pskc/parse.py, pskc/policy.py:
Move Key class to separate module
This also allows re-organising the imports a bit.
2014-04-16 Arthur de Jong
* [c883d48] MANIFEST.in, pskc/__init__.py, setup.cfg, setup.py:
Add initial setup script
2014-04-14 Arthur de Jong
* [3df6849] COPYING: Include a license file (LGPL)
2014-04-13 Arthur de Jong
* [f08cdb5] tests/rfc6030-figure10.pskc, tests/test_rfc6030.doctest:
Add bulk provisioning test from Figure 10
2014-04-13 Arthur de Jong
* [41828cd] pskc/parse.py: Use slightly clearer names
2014-04-12 Arthur de Jong
* [5ab731c] tests/rfc6030-figure7.pskc, tests/test_rfc6030.doctest:
Add test for Figure 7 from RFC6030
This tests encrypted key derivation using PBKDF2 and a pre-shared
passphrase.
2014-04-12 Arthur de Jong
* [a3fd598] pskc/encryption.py: Implement PBKDF2 key derivation
This supports deriving the key from a passphrase and information
present in the DerivedKey and PBKDF2-params XML elements.
2014-04-12 Arthur de Jong
* [2ff470f] pskc/encryption.py: Add id attribute from EncryptionKey
2014-04-12 Arthur de Jong
* [460f335] tests/rfc6030-figure6.pskc, tests/test_rfc6030.doctest:
Add test for Figure 6 from RFC6030
This test key encryption with a pre-shared key and MAC checks.
2014-04-12 Arthur de Jong
* [a926ddb] pskc/mac.py, pskc/parse.py: Implement MAC checking
This implements message message authentication code checking
for the encrypted values if MACMethod and ValueMAC are present.
2014-04-12 Arthur de Jong
* [e53e865] pskc/encryption.py, pskc/parse.py: Support decrypting
with a pre-shared key
This adds an encryption module that provides wrappers for
handling decryption.
2014-04-11 Arthur de Jong
* [3fe0919] pskc/parse.py: 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.
2014-04-11 Arthur de Jong
* [591bb5d] pskc/policy.py: Document key and pin usage values
2014-04-11 Arthur de Jong
* [b952b93] tests/rfc6030-figure5.pskc, tests/test_rfc6030.doctest:
Add test for Figure 5 from RFC6030
This test extraction of key policy information and cross-key
references.
2014-04-11 Arthur de Jong
* [e939a96] pskc/parse.py, pskc/policy.py: Implement key policy
parsing
This parses key policy from PSKC files and provides a few utility
methods to help with policy validation.
2014-04-11 Arthur de Jong
* [8c9ac8c] pskc/parse.py: Support parsing date and integer values
2014-04-11 Arthur de Jong
* [6446f7d] tests/rfc6030-figure4.pskc, tests/test_rfc6030.doctest:
Add test for Figure 4 from RFC6030
This tests for key profile and key reference properties that
can be used to reference external keys.
2014-04-07 Arthur de Jong
* [e72369f] tests/rfc6030-figure3.pskc, tests/test-rfc6030.doctest,
tests/test_rfc6030.doctest: Add test for Figure 3 from RFC6030
This tests Figure 3 from RFC6030 with a very basic plain text
secret key and some supplementary data.
2014-04-07 Arthur de Jong
* [2c111a8] pskc/parse.py: Get more data from KeyPackage
This gets most simple string values from the KeyPackage as well
as some integer and boolean values.
2014-04-07 Arthur de Jong
* [96b4b54] tests/rfc6030-figure2.pskc, tests/test-rfc6030.doctest:
Add test for example from RFC6030
This tests Figure 2 from RFC6030 with a very basic plain text
secret key.
2014-04-07 Arthur de Jong
* [d662cf2] pskc/parse.py: Support getting plaintext key
2014-04-07 Arthur de Jong
* [550630d] tests/test_minimal.doctest: Minimal test
This adds a doctest for the absolute minimum PSKC file that does
not contain any useful information.
2014-04-07 Arthur de Jong
* [bf8e7f6] pskc/__init__.py, pskc/parse.py: Basic implementation
of PSKC class
This class is used for handling PSKC files. It will parse the
file and store relevant properties for easy access. The Key
class corresponds to a single key defined in the PSKC file.
This is a very minimal implementation that only provides some
meta-data from the file and keys (work in progress).
2014-04-04 Arthur de Jong
* [9803dfc] README: Provide an initial README
2014-04-02 Arthur de Jong
* [c912bb4] .gitignore, pskc/__init__.py: Initial project layout
python-pskc-0.3/README 0000644 0000000 0000000 00000004074 12605273737 014472 0 ustar root root 0000000 0000000 Python PSKC module
==================
A Python module to handle Portable Symmetric Key Container (PSKC) files as
defined in `RFC 6030 `_. PSKC files are
used to transport and provision symmetric keys and key meta data to different
types of crypto modules. The format is commonly used for one-time password
tokens or other authentication devices.
The goal of this module is mainly to provide parsing of PSKC files in order
to extract secret keys for use in an OTP authentication system.
http://arthurdejong.org/python-pskc/
API
---
The module provides a straightforward API that is mostly geared towards
parsing existing PSKC files.
Extracting key matarial from encrypted PSKC files is as simple as.
>>> from pskc import PSKC
>>> pskc = PSKC('tests/rfc6030-figure7.pskcxml')
>>> pskc.encryption.derive_key('qwerty')
>>> for key in pskc.keys:
... print key.serial, key.secret
987654321 12345678901234567890
The key object has a number of properties. See the pskc.key.Key documentation
for details.
Security considerations
-----------------------
This code handles private key material and is written in Python. No
precautions have been taken to lock pages in memory to prevent swapping. Also
no attempt is currently made to security dispose of memory that may have held
private key material.
Copyright
---------
Copyright (C) 2014-2015 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-pskc-0.3/MANIFEST.in 0000644 0000000 0000000 00000000204 12605273737 015337 0 ustar root root 0000000 0000000 include README NEWS ChangeLog COPYING *.py
recursive-include tests *.doctest *.py *.pskcxml *.xml
recursive-include docs *.rst *.py
python-pskc-0.3/pskc/ 0000755 0000000 0000000 00000000000 12605273753 014543 5 ustar root root 0000000 0000000 python-pskc-0.3/pskc/encryption.py 0000644 0000000 0000000 00000021265 12605045077 017311 0 ustar root root 0000000 0000000 # encryption.py - module for handling encrypted values
# coding: utf-8
#
# Copyright (C) 2014-2015 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 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.
"""
def unpad(value):
"""Remove padding from the plaintext."""
return value[0:-ord(value[-1:])]
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.encryption = encryption
self.algorithm = None
self.cipher_value = None
self.parse(encrypted_value)
def parse(self, encrypted_value):
"""Read encrypted data from the XML tree."""
from pskc.xml import find, findbin
if encrypted_value is None:
return
encryption_method = find(encrypted_value, 'xenc:EncryptionMethod')
if encryption_method is not None:
self.algorithm = encryption_method.attrib.get('Algorithm')
self.cipher_value = findbin(
encrypted_value, 'xenc:CipherData/xenc:CipherValue')
def decrypt(self):
"""Decrypt the linked value and return the plaintext value."""
from pskc.exceptions import DecryptionError
if self.cipher_value is None:
return
key = self.encryption.key
if key is None:
raise DecryptionError('No key available')
if self.algorithm is None:
raise DecryptionError('No algorithm specified')
if self.algorithm.endswith('#aes128-cbc') or \
self.algorithm.endswith('#aes192-cbc') or \
self.algorithm.endswith('#aes256-cbc'):
from Crypto.Cipher import AES
if len(key) * 8 != int(self.algorithm[-7:-4]) or \
len(key) not in AES.key_size:
raise DecryptionError('Invalid key length')
iv = self.cipher_value[:AES.block_size]
ciphertext = self.cipher_value[AES.block_size:]
cipher = AES.new(key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(ciphertext))
elif self.algorithm.endswith('#tripledes-cbc'):
from Crypto.Cipher import DES3
if len(key) not in DES3.key_size:
raise DecryptionError('Invalid key length')
iv = self.cipher_value[:DES3.block_size]
ciphertext = self.cipher_value[DES3.block_size:]
cipher = DES3.new(key, DES3.MODE_CBC, iv)
return unpad(cipher.decrypt(ciphertext))
elif self.algorithm.endswith('#kw-aes128') or \
self.algorithm.endswith('#kw-aes192') or \
self.algorithm.endswith('#kw-aes256'):
from pskc.crypto.aeskw import unwrap
from Crypto.Cipher import AES
if len(key) * 8 != int(self.algorithm[-3:]) or \
len(key) not in AES.key_size:
raise DecryptionError('Invalid key length')
return unwrap(self.cipher_value, key)
elif self.algorithm.endswith('#kw-tripledes'):
from pskc.crypto.tripledeskw import unwrap
from Crypto.Cipher import DES3
if len(key) not in DES3.key_size:
raise DecryptionError('Invalid key length')
return unwrap(self.cipher_value, key)
else:
raise DecryptionError('Unsupported algorithm: %r' % self.algorithm)
class KeyDerivation(object):
"""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
"""
def __init__(self, key_deriviation=None):
self.algorithm = None
# PBKDF2 properties
self.pbkdf2_salt = None
self.pbkdf2_iterations = None
self.pbkdf2_key_length = None
self.pbkdf2_prf = None
self.parse(key_deriviation)
def parse(self, key_deriviation):
"""Read derivation parameters from a element."""
from pskc.xml import find, findint, findbin
if key_deriviation is None:
return
self.algorithm = key_deriviation.get('Algorithm')
# PBKDF2 properties
pbkdf2 = find(
key_deriviation, 'xenc11:PBKDF2-params', 'pkcs5:PBKDF2-params')
if pbkdf2 is not None:
# get used salt
self.pbkdf2_salt = findbin(
pbkdf2, 'Salt/Specified', 'xenc11:Salt/xenc11:Specified')
# required number of iterations
self.pbkdf2_iterations = findint(
pbkdf2, 'IterationCount', 'xenc11:IterationCount')
# key length
self.pbkdf2_key_length = findint(
pbkdf2, 'KeyLength', 'xenc11:KeyLength')
# pseudorandom function used
prf = find(pbkdf2, 'PRF', 'xenc11:PRF')
if prf is not None:
self.pbkdf2_prf = prf.get('Algorithm')
def derive(self, password):
"""Derive a key from the password."""
from pskc.exceptions import KeyDerivationError
if self.algorithm is None:
raise KeyDerivationError('No algorithm specified')
if self.algorithm.endswith('#pbkdf2'):
from Crypto.Protocol.KDF import PBKDF2
from pskc.mac import get_hmac
prf = None
if self.pbkdf2_prf:
prf = get_hmac(self.pbkdf2_prf)
if prf is None:
raise KeyDerivationError(
'Pseudorandom function unsupported: %r' %
self.pbkdf2_prf)
return PBKDF2(
password, self.pbkdf2_salt, dkLen=self.pbkdf2_key_length,
count=self.pbkdf2_iterations, prf=prf)
else:
raise KeyDerivationError(
'Unsupported algorithm: %r' % self.algorithm)
class Encryption(object):
"""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
self.key_names = []
self.key = None
self.derivation = KeyDerivation()
self.parse(key_info)
def parse(self, key_info):
"""Read encryption information from the XML tree."""
from pskc.xml import find, findall, findtext
if key_info is None:
return
self.id = key_info.get('Id')
for name in findall(key_info, 'ds:KeyName'):
self.key_names.append(findtext(name, '.'))
for name in findall(
key_info, 'xenc11:DerivedKey/xenc11:MasterKeyName'):
self.key_names.append(findtext(name, '.'))
self.derivation.parse(find(
key_info, 'xenc11:DerivedKey/xenc11:KeyDerivationMethod'))
@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.derive(password)
python-pskc-0.3/pskc/exceptions.py 0000644 0000000 0000000 00000002760 12354352405 017274 0 ustar root root 0000000 0000000 # exceptions.py - collection of pskc exceptions
# 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
"""Collection of exceptions."""
class PSKCError(Exception):
"""General top-level exception."""
pass
class ParseError(PSKCError):
"""Something went wrong with parsing the PSKC file.
Either the file is invalid XML or required elements or attributes are
missing."""
pass
class EncryptionError(PSKCError):
"""There was a problem encrypting the value."""
pass
class DecryptionError(PSKCError):
"""There was a problem decrypting the value.
The encrypted value as available but something went wrong with decrypting
it."""
pass
class KeyDerivationError(PSKCError):
"""There was a problem performing the key derivation."""
pass
python-pskc-0.3/pskc/policy.py 0000644 0000000 0000000 00000016135 12605044512 016407 0 ustar root root 0000000 0000000 # policy.py - module for handling PSKC policy information
# coding: utf-8
#
# Copyright (C) 2014-2015 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 that provides PSKC key policy information."""
class Policy(object):
"""Representation of a policy that describes key and pin usage.
Instances of this class provide attributes that describe limits that
are placed on key usage and requirements for key PIN protection. The
policy provides the following attributes:
start_date: the key MUST not be used before this datetime
expiry_date: the key MUST not be used after this datetime
number_of_transactions: maximum number of times the key may be used
key_usage: list of valid usage scenarios for the key (e.g. OTP)
pin_key_id: id of to the key that holds the PIN
pin_key: reference to the key that holds the PIN
pin: value of the PIN to use
pin_usage: define how the PIN is used in relation to the key
pin_max_failed_attemtps: max. number of times a wrong PIN may be entered
pin_min_length: minimum length of a PIN that may be set
pin_max_length: maximum length of a PIN that may be set
pin_encoding: DECIMAL/HEXADECIMAL/ALPHANUMERIC/BASE64/BINARY
unknown_policy_elements: True if the policy contains unsupported rules
If unknown_policy_elements is True the recipient MUST assume that key
usage is not permitted.
"""
# Key is used for OTP generation.
KEY_USE_OTP = 'OTP'
# Key is used for Challenge/Response purposes.
KEY_USE_CR = 'CR'
# Key is used for data encryption purposes.
KEY_USE_ENCRYPT = 'Encrypt'
# For generating keyed message digests.
KEY_USE_INTEGRITY = 'Integrity'
# For checking keyed message digests.
KEY_USE_VERIFY = 'Verify'
# Unlocking device when wrong PIN has been entered too many times.
KEY_USE_UNLOCK = 'Unlock'
# Key is used for data decryption purposes.
KEY_USE_DECRYPT = 'Decrypt'
# The key is used for key wrap purposes.
KEY_USE_KEYWRAP = 'KeyWrap'
# The key is used for key unwrap purposes.
KEY_USE_UNWRAP = 'Unwrap'
# Use in a key derivation function to derive a new key.
KEY_USE_DERIVE = 'Derive'
# Generate a new key based on a random number and the previous value.
KEY_USE_GENERATE = 'Generate'
# The PIN is checked on the device before the key is used.
PIN_USE_LOCAL = 'Local'
# The response has the PIN prepanded and needs to be checked.
PIN_USE_PREPEND = 'Prepend'
# The response has the PIN appended and needs to be checked.
PIN_USE_APPEND = 'Append'
# The PIN is used in the algorithm computation.
PIN_USE_ALGORITHMIC = 'Algorithmic'
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
self.key_usage = []
self.pin_key_id = None
self.pin_usage = None
self.pin_max_failed_attemtps = None
self.pin_min_length = None
self.pin_max_length = None
self.pin_encoding = None
self.unknown_policy_elements = False
self.parse(policy)
def parse(self, policy):
"""Read key policy information from the provided tree."""
from pskc.xml import (
find, findall, findtext, findint, findtime, getint)
if policy is None:
return
self.start_date = findtime(policy, 'pskc:StartDate')
self.expiry_date = findtime(policy, 'pskc:ExpiryDate')
self.number_of_transactions = findint(
policy, 'pskc:NumberOfTransactions')
for key_usage in findall(policy, 'pskc:KeyUsage'):
self.key_usage.append(findtext(key_usage, '.'))
pin_policy = find(policy, 'pskc:PINPolicy')
if pin_policy is not None:
self.pin_key_id = pin_policy.get('PINKeyId')
self.pin_usage = pin_policy.get('PINUsageMode')
self.pin_max_failed_attemtps = getint(
pin_policy, 'MaxFailedAttempts')
self.pin_min_length = getint(pin_policy, 'MinLength')
self.pin_max_length = getint(pin_policy, 'MaxLength')
self.pin_encoding = pin_policy.get('PINEncoding')
# TODO: check if there are any other attributes set for PINPolicy
# of if there are any children and set unknown_policy_elementss
# TODO: check if there are other children and make sure
# policy rejects any key usage (set unknown_policy_elements)
def make_xml(self, key):
from pskc.xml import mk_elem
# check if any policy attribute is set
if not self.key_usage and all(x is None for x in (
self.start_date, self.expiry_date,
self.number_of_transactions, self.pin_key_id, self.pin_usage,
self.pin_max_failed_attemtps, self.pin_min_length,
self.pin_max_length, self.pin_encoding)):
return
# TODO: raise exception if unknown_policy_elements is set
policy = mk_elem(key, 'pskc:Policy', empty=True)
mk_elem(policy, 'pskc:StartDate', self.start_date)
mk_elem(policy, 'pskc:ExpiryDate', self.expiry_date)
mk_elem(policy, 'pskc:PINPolicy',
PINKeyId=self.pin_key_id,
PINUsageMode=self.pin_usage,
MaxFailedAttempts=self.pin_max_failed_attemtps,
MinLength=self.pin_min_length,
MaxLength=self.pin_max_length,
PINEncoding=self.pin_encoding)
for usage in self.key_usage:
mk_elem(policy, 'pskc:KeyUsage', usage)
mk_elem(policy, 'pskc:NumberOfTransactions',
self.number_of_transactions)
def may_use(self, usage):
"""Check whether the key may be used for the provided purpose."""
if self.unknown_policy_elements:
return False
return not self.key_usage or usage in self.key_usage
@property
def pin_key(self):
"""Reference to the PSKC Key that holds the PIN (if any)."""
if self.pin_key_id and self.key and self.key.pskc:
for key in self.key.pskc.keys:
if key.id == self.pin_key_id:
return key
@property
def pin(self):
"""PIN value referenced by PINKeyId if any."""
key = self.pin_key
if key:
return str(key.secret.decode())
python-pskc-0.3/pskc/key.py 0000644 0000000 0000000 00000035075 12605045077 015713 0 ustar root root 0000000 0000000 # key.py - module for handling keys from pskc files
# coding: utf-8
#
# Copyright (C) 2014-2015 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 that handles keys stored in PSKC files."""
import base64
from pskc.encryption import EncryptedValue
from pskc.mac import ValueMAC
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:
value: unencrypted value if present
encrypted_value: reference to an EncryptedValue instance
value_mac: reference to a ValueMAC instance
"""
def __init__(self, key, element=None):
self.value = None
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 ,
and/or ValueMAC elements that contain information on the actual
value."""
from pskc.xml import find, findtext
if element is None:
return
value = findtext(element, 'pskc:PlainValue')
if value is not None:
self.value = self.from_text(value)
self.encrypted_value.parse(find(element, 'pskc:EncryptedValue'))
self.value_mac.parse(find(element, 'pskc:ValueMAC'))
def make_xml(self, key, tag):
from pskc.xml import find, mk_elem
# skip empty values
value = self.get_value()
if value is None:
return
# find the data tag and create our tag under it
data = find(key, 'pskc:Data')
if data is None:
data = mk_elem(key, 'pskc:Data', empty=True)
element = mk_elem(data, tag, empty=True)
mk_elem(element, 'pskc:PlainValue', self.to_text(self.value))
def get_value(self):
"""Provide the raw binary value."""
if self.value is not None:
return self.value
if self.encrypted_value.cipher_value:
# check MAC and decrypt
self.check()
return self.from_bin(self.encrypted_value.decrypt())
def set_value(self, value):
"""Set the unencrypted value."""
self.value = value
self.encrypted_value.cipher_value = None
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)."""
def from_text(self, value):
"""Convert the plain value to native representation."""
return base64.b64decode(value)
def to_text(self, value):
"""Convert the value to an unencrypted string representation."""
# force conversion to bytestring on Python 3
if not isinstance(value, type(b'')):
value = value.encode()
return base64.b64encode(value).decode()
def from_bin(self, value):
"""Convert the unencrypted binary to native representation."""
return value
class IntegerDataType(DataType):
"""Subclass of DataType for integer types (e.g. counters)."""
def from_text(self, value):
"""Convert the plain value to native representation."""
return int(value)
def to_text(self, value):
"""Convert the value to an unencrypted string representation."""
return str(value)
def from_bin(self, value):
"""Convert the unencrypted binary to native representation."""
result = 0
for x in value:
result = (result << 8) + ord(x)
return result
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_offset: 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=None):
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
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.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.policy = Policy(self)
self.parse(key_package)
def parse(self, key_package):
"""Read key information from the provided tree."""
from pskc.xml import find, findtext, findtime, getint, getbool
if key_package is None:
return
key = find(key_package, 'pskc:Key')
if key is not None:
self.id = key.get('Id')
self.algorithm = key.get('Algorithm')
data = find(key_package, 'pskc:Key/pskc:Data')
if data is not None:
self._secret.parse(find(data, 'pskc:Secret'))
self._counter.parse(find(data, 'pskc:Counter'))
self._time_offset.parse(find(data, 'pskc:Time'))
self._time_interval.parse(find(data, 'pskc:TimeInterval'))
self._time_drift.parse(find(data, 'pskc:TimeDrift'))
self.issuer = findtext(key_package, 'pskc:Key/pskc:Issuer')
self.key_profile = findtext(key_package, 'pskc:Key/pskc:KeyProfileId')
self.key_reference = findtext(
key_package, 'pskc:Key/pskc:KeyReference')
self.friendly_name = findtext(
key_package, 'pskc:Key/pskc:FriendlyName')
# TODO: support multi-language values of
self.key_userid = findtext(key_package, 'pskc:Key/pskc:UserId')
self.manufacturer = findtext(
key_package, 'pskc:DeviceInfo/pskc:Manufacturer')
self.serial = findtext(key_package, 'pskc:DeviceInfo/pskc:SerialNo')
self.model = findtext(key_package, 'pskc:DeviceInfo/pskc:Model')
self.issue_no = findtext(key_package, 'pskc:DeviceInfo/pskc:IssueNo')
self.device_binding = findtext(
key_package, 'pskc:DeviceInfo/pskc:DeviceBinding')
self.start_date = findtime(
key_package, 'pskc:DeviceInfo/pskc:StartDate')
self.expiry_date = findtime(
key_package, 'pskc:DeviceInfo/pskc:ExpiryDate')
self.device_userid = findtext(
key_package, 'pskc:DeviceInfo/pskc:UserId')
self.crypto_module = findtext(
key_package, 'pskc:CryptoModuleInfo/pskc:Id')
self.algorithm_suite = findtext(
key_package, 'pskc:Key/pskc:AlgorithmParameters/pskc:Suite')
challenge_format = find(
key_package,
'pskc:Key/pskc:AlgorithmParameters/pskc:ChallengeFormat')
if challenge_format is not None:
self.challenge_encoding = challenge_format.get('Encoding')
self.challenge_min_length = getint(challenge_format, 'Min')
self.challenge_max_length = getint(challenge_format, 'Max')
self.challenge_check = getbool(challenge_format, 'CheckDigits')
response_format = find(
key_package,
'pskc:Key/pskc:AlgorithmParameters/pskc:ResponseFormat')
if response_format is not None:
self.response_encoding = response_format.get('Encoding')
self.response_length = getint(response_format, 'Length')
self.response_check = getbool(response_format, 'CheckDigits')
self.policy.parse(find(key_package, 'pskc:Key/pskc:Policy'))
def make_xml(self, container):
from pskc.xml import mk_elem
key_package = mk_elem(container, 'pskc:KeyPackage', empty=True)
if any(x is not None
for x in (self.manufacturer, self.serial, self.model,
self.issue_no, self.device_binding, self.start_date,
self.expiry_date, self.device_userid)):
device_info = mk_elem(key_package, 'pskc:DeviceInfo', empty=True)
mk_elem(device_info, 'pskc:Manufacturer', self.manufacturer)
mk_elem(device_info, 'pskc:SerialNo', self.serial)
mk_elem(device_info, 'pskc:Model', self.model)
mk_elem(device_info, 'pskc:IssueNo', self.issue_no)
mk_elem(device_info, 'pskc:DeviceBinding', self.device_binding)
mk_elem(device_info, 'pskc:StartDate', self.start_date)
mk_elem(device_info, 'pskc:ExpiryDate', self.expiry_date)
mk_elem(device_info, 'pskc:UserId', self.device_userid)
if self.crypto_module is not None:
crypto_module = mk_elem(key_package, 'pskc:CryptoModuleInfo',
empty=True)
mk_elem(crypto_module, 'pskc:Id', self.crypto_module)
key = mk_elem(key_package, 'pskc:Key', empty=True, Id=self.id,
Algorithm=self.algorithm, )
mk_elem(key, 'pskc:Issuer', self.issuer)
if any((self.algorithm_suite, self.challenge_encoding,
self.response_encoding, self.response_length)):
parameters = mk_elem(key, 'pskc:AlgorithmParameters', empty=True)
mk_elem(parameters, 'pskc:Suite', self.algorithm_suite)
mk_elem(parameters, 'pskc:ChallengeFormat',
Encoding=self.challenge_encoding,
Min=self.challenge_min_length,
Max=self.challenge_max_length,
CheckDigits=self.challenge_check)
mk_elem(parameters, 'pskc:ResponseFormat',
Encoding=self.response_encoding,
Length=self.response_length,
CheckDigits=self.response_check)
mk_elem(key, 'pskc:KeyProfileId', self.key_profile)
mk_elem(key, 'pskc:KeyReference', self.key_reference)
mk_elem(key, 'pskc:FriendlyName', self.friendly_name)
self._secret.make_xml(key, 'pskc:Secret')
self._counter.make_xml(key, 'pskc:Counter')
self._time_offset.make_xml(key, 'pskc:Time')
self._time_interval.make_xml(key, 'pskc:TimeInterval')
self._time_drift.make_xml(key, 'pskc:TimeDrift')
mk_elem(key, 'pskc:UserId', self.key_userid)
self.policy.make_xml(key)
secret = property(
fget=lambda self: self._secret.get_value(),
fset=lambda self, x: self._secret.set_value(x),
doc="The secret key itself.")
counter = property(
fget=lambda self: self._counter.get_value(),
fset=lambda self, x: self._counter.set_value(x),
doc="An event counter for event-based OTP.")
time_offset = property(
fget=lambda self: self._time_offset.get_value(),
fset=lambda self, x: self._time_offset.set_value(x),
doc="A time offset for time-based OTP (number of intervals).")
time_interval = property(
fget=lambda self: self._time_interval.get_value(),
fset=lambda self, x: self._time_interval.set_value(x),
doc="A time interval in seconds.")
time_drift = property(
fget=lambda self: self._time_drift.get_value(),
fset=lambda self, x: self._time_drift.set_value(x),
doc="Device clock drift value (number of time intervals).")
def check(self):
"""Check if all MACs in the message are valid."""
if any((self._secret.check(), self._counter.check(),
self._time_offset.check(), self._time_interval.check(),
self._time_drift.check())):
return True
python-pskc-0.3/pskc/__init__.py 0000644 0000000 0000000 00000011204 12605273737 016654 0 ustar root root 0000000 0000000 # __init__.py - main module
# coding: utf-8
#
# Copyright (C) 2014-2015 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.
The main goal of this module is to be able to extract keys from PSKC files
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.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.3'
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.mac = MAC(self)
self.keys = []
if filename is not None:
from pskc.xml import parse
try:
tree = parse(filename)
except Exception:
raise ParseError('Error parsing XML')
self.parse(tree.getroot())
else:
self.version = '1.0'
def parse(self, container):
"""Read information from the provided tree."""
from pskc.exceptions import ParseError
from pskc.key import Key
from pskc.xml import find, findall
if not container.tag.endswith('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, 'pskc:EncryptionKey'))
# handle MACMethod entries
self.mac.parse(find(container, 'pskc:MACMethod'))
# handle KeyPackage entries
for key_package in findall(container, 'pskc: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)
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'):
filename.write(tostring(self.make_xml()))
else:
with open(filename, 'wb') as output:
self.write(output)
python-pskc-0.3/pskc/xml.py 0000644 0000000 0000000 00000011461 12605261114 015704 0 ustar root root 0000000 0000000 # xml.py - module for parsing and writing XML for PSKC files
# coding: utf-8
#
# Copyright (C) 2014-2015 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 parsing XML in PSKC files.
This module provides some utility functions for parsing XML files.
"""
from __future__ import absolute_import
# try to find a usable ElementTree module
try:
from lxml import etree
except ImportError: # pragma: no cover (different implementations)
import xml.etree.ElementTree as etree
# 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#',
)
# register the namespaces so the correct short names will be used
for ns, namespace in namespaces.items():
etree.register_namespace(ns, namespace)
def parse(source):
"""Parse the provided file and return an element tree."""
return etree.parse(source)
def findall(tree, match):
"""Find the child elements."""
return tree.findall(match, namespaces=namespaces)
def find(tree, *matches):
"""Find a child element that matches any of the patterns (or None)."""
for match in matches:
try:
return next(iter(findall(tree, match)))
except StopIteration:
pass
def findtext(tree, *matches):
"""Get the text value of an element (or None)."""
element = find(tree, *matches)
if element is not None:
return element.text.strip()
def findint(tree, *matches):
"""Return an element value as an int (or None)."""
value = findtext(tree, *matches)
if value:
return int(value)
def findtime(tree, *matches):
"""Return an element value as a datetime (or None)."""
value = findtext(tree, *matches)
if value:
import dateutil.parser
return dateutil.parser.parse(value)
def findbin(tree, *matches):
"""Return the binary element value base64 decoded."""
value = findtext(tree, *matches)
if value:
import base64
return base64.b64decode(value)
def getint(tree, attribute):
"""Return an attribute value as an integer (or None)."""
value = tree.get(attribute)
if value:
return int(value)
def getbool(tree, attribute):
"""Return an attribute value as a boolean (or None)."""
value = tree.get(attribute)
if value:
return value.lower() == 'true'
def _format(value):
import datetime
if isinstance(value, datetime.datetime):
value = value.isoformat()
if value.endswith('+00:00'):
value = value[:-6] + 'Z'
return value
elif value is True:
return 'true'
elif value is False:
return 'false'
return str(value)
def mk_elem(parent, tag=None, text=None, empty=False, **kwargs):
"""Add element as a child of parent."""
# special-case the top-level element
if tag is None:
tag = parent
parent = None
empty = True
# don't create empty elements
if not empty and text is None and \
all(x is None for x in kwargs.values()):
return
# replace namespace identifier with URL
if ':' in tag:
ns, name = tag.split(':', 1)
tag = '{%s}%s' % (namespaces[ns], name)
if parent is None:
element = etree.Element(tag)
else:
element = etree.SubElement(parent, tag)
# set text of element
if text is not None:
element.text = _format(text)
# set kwargs as attributes
for k, v in kwargs.items():
if v is not None:
element.set(k, _format(v))
return element
def tostring(element):
"""Return a serialised XML document for the element tree."""
from xml.dom import minidom
xml = etree.tostring(element, encoding='UTF-8')
return minidom.parseString(xml).toprettyxml(
indent=' ', encoding='UTF-8').strip()
python-pskc-0.3/pskc/mac.py 0000644 0000000 0000000 00000007624 12605045077 015662 0 ustar root root 0000000 0000000 # 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
"""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 re
_hmac_url_re = re.compile(r'^.*#hmac-(?P[a-z0-9]+)$')
def get_hmac(algorithm):
"""Return an HMAC function that takes a secret and a value and returns a
digest."""
import hashlib
import hmac
match = _hmac_url_re.search(algorithm)
if match:
digestmod = getattr(hashlib, match.group('hash'), None)
if digestmod is not None:
return lambda key, value: hmac.new(key, value, digestmod).digest()
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.parse(value_mac)
def parse(self, value_mac):
"""Read MAC information from the XML tree."""
from pskc.xml import findbin
if value_mac is None:
return
self._value_mac = findbin(value_mac, '.')
def check(self, value):
"""Check if the provided value matches the MAC.
This will return None if there is no MAC to be checked. It will
return True if the MAC matches and raise an exception if it fails.
"""
from pskc.exceptions import DecryptionError
if value is None or self._value_mac is None:
return # no MAC present or nothing to check
key = self.mac.key
if key is None:
raise DecryptionError('No MAC key available')
hmacfn = get_hmac(self.mac.algorithm)
if hmacfn is None:
raise DecryptionError(
'Unsupported MAC algorithm: %r' % self.mac.algorithm)
if hmacfn(key, value) != self._value_mac:
raise DecryptionError('MAC value does not match')
return True
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):
from pskc.encryption import EncryptedValue
self.algorithm = None
self._mac_key = EncryptedValue(pskc.encryption)
self.parse(mac_method)
def parse(self, mac_method):
"""Read MAC information from the XML tree."""
from pskc.xml import find, findtext
if mac_method is None:
return
self.algorithm = mac_method.get('Algorithm')
self._mac_key.parse(find(mac_method, 'pskc:MACKey'))
mac_key_reference = findtext(mac_method, 'pskc:MACKeyReference')
@property
def key(self):
"""Provides access to the MAC key binary value if available."""
return self._mac_key.decrypt()
python-pskc-0.3/pskc/crypto/ 0000755 0000000 0000000 00000000000 12605273753 016063 5 ustar root root 0000000 0000000 python-pskc-0.3/pskc/crypto/aeskw.py 0000644 0000000 0000000 00000010065 12605044512 017536 0 ustar root root 0000000 0000000 # aeskw.py - implementation of AES key wrapping
# coding: utf-8
#
# Copyright (C) 2014-2015 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
"""Implement key wrapping as described in RFC 3394 and RFC 5649."""
import binascii
from Crypto.Cipher import AES
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util.strxor import strxor
from pskc.exceptions import EncryptionError, DecryptionError
def _split(value):
return value[:8], value[8:]
RFC3394_IV = binascii.a2b_hex('a6a6a6a6a6a6a6a6')
RFC5649_IV = binascii.a2b_hex('a65959a6')
def wrap(plaintext, key, iv=None, pad=None):
"""Apply the AES key wrap algorithm to the plaintext.
The iv can specify an initial value, otherwise the value from RFC 3394 or
RFC 5649 will be used, depending on the plaintext length and the value of
pad.
If pad is True, padding as described in RFC 5649 will always be used. If
pad is False, padding is disabled. Other values automatically enable RFC
5649 padding when needed."""
if iv is not None:
pad = False
mli = len(plaintext)
if pad is False and (mli % 8 != 0 or mli < 16):
raise EncryptionError('Plaintext length wrong')
if mli % 8 != 0 and pad is not False:
r = (mli + 7) // 8
plaintext += ((r * 8) - mli) * b'\0'
if iv is None:
if len(plaintext) != mli or pad is True:
iv = RFC5649_IV + long_to_bytes(mli, 4)
else:
iv = RFC3394_IV
encrypt = AES.new(key).encrypt
n = len(plaintext) // 8
if n == 1:
# RFC 5649 shortcut
return encrypt(iv + plaintext)
A = iv
R = [plaintext[i * 8:i * 8 + 8]
for i in range(n)]
for j in range(6):
for i in range(n):
A, R[i] = _split(encrypt(A + R[i]))
A = strxor(A, long_to_bytes(n * j + i + 1, 8))
return A + b''.join(R)
def unwrap(ciphertext, key, iv=None, pad=None):
"""Apply the AES key unwrap algorithm to the ciphertext.
The iv can specify an initial value, otherwise the value from RFC 3394 or
RFC 5649 will be used, depending on the value of pad.
If pad is False, unpadding as described in RFC 5649 will be disabled,
otherwise checking and removing the padding is automatically done."""
if iv is not None:
pad = False
if len(ciphertext) % 8 != 0 or (pad is False and len(ciphertext) < 24):
raise DecryptionError('Ciphertext length wrong')
decrypt = AES.new(key).decrypt
n = len(ciphertext) // 8 - 1
if n == 1:
A, plaintext = _split(decrypt(ciphertext))
else:
A = ciphertext[:8]
R = [ciphertext[(i + 1) * 8:(i + 2) * 8]
for i in range(n)]
for j in reversed(range(6)):
for i in reversed(range(n)):
A = strxor(A, long_to_bytes(n * j + i + 1, 8))
A, R[i] = _split(decrypt(A + R[i]))
plaintext = b''.join(R)
if iv is None:
if A == RFC3394_IV and pad is not True:
return plaintext
elif A[:4] == RFC5649_IV and pad is not False:
mli = bytes_to_long(A[4:])
# check padding length is valid and plaintext only contains zeros
if 8 * (n - 1) < mli <= 8 * n and \
plaintext.endswith((len(plaintext) - mli) * b'\0'):
return plaintext[:mli]
elif A == iv:
return plaintext
raise DecryptionError('IV does not match')
python-pskc-0.3/pskc/crypto/__init__.py 0000644 0000000 0000000 00000000000 12415565135 020157 0 ustar root root 0000000 0000000 python-pskc-0.3/pskc/crypto/tripledeskw.py 0000644 0000000 0000000 00000004765 12605044512 020773 0 ustar root root 0000000 0000000 # tripledeskw.py - implementation of Triple DES key wrapping
# coding: utf-8
#
# Copyright (C) 2014-2015 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
"""Implement Triple DES key wrapping as described in RFC 3217."""
import binascii
from Crypto import Random
from Crypto.Cipher import DES3
from Crypto.Hash import SHA
from pskc.exceptions import EncryptionError, DecryptionError
def _cms_hash(value):
"""The key checksum algorithm described in RFC 3217 section 2."""
return SHA.new(value).digest()[:8]
RFC3217_IV = binascii.a2b_hex('4adda22c79e82105')
def wrap(plaintext, key, iv=None):
"""Wrap one key (typically a Triple DES key) with another Triple DES key.
This uses the algorithm from RFC 3217 to encrypt the plaintext (the key
to wrap) using the provided key. If the iv is None, it is randomly
generated."""
if len(plaintext) % DES3.block_size != 0:
raise EncryptionError('Plaintext length wrong')
if iv is None:
iv = Random.get_random_bytes(8)
cipher = DES3.new(key, DES3.MODE_CBC, iv)
tmp = iv + cipher.encrypt(plaintext + _cms_hash(plaintext))
cipher = DES3.new(key, DES3.MODE_CBC, RFC3217_IV)
return cipher.encrypt(tmp[::-1])
def unwrap(ciphertext, key):
"""Unwrap a key (typically Triple DES key ) with another Triple DES key.
This uses the algorithm from RFC 3217 to decrypt the ciphertext (the
previously wrapped key) using the provided key."""
if len(ciphertext) % DES3.block_size != 0:
raise DecryptionError('Ciphertext length wrong')
cipher = DES3.new(key, DES3.MODE_CBC, RFC3217_IV)
tmp = cipher.decrypt(ciphertext)[::-1]
cipher = DES3.new(key, DES3.MODE_CBC, tmp[:8])
tmp = cipher.decrypt(tmp[8:])
if tmp[-8:] == _cms_hash(tmp[:-8]):
return tmp[:-8]
raise DecryptionError('CMS key checksum error')
python-pskc-0.3/PKG-INFO 0000644 0000000 0000000 00000004005 12605273753 014677 0 ustar root root 0000000 0000000 Metadata-Version: 1.1
Name: python-pskc
Version: 0.3
Summary: Python module for handling PSKC files
Home-page: http://arthurdejong.org/python-pskc/
Author: Arthur de Jong
Author-email: arthur@arthurdejong.org
License: LGPL
Description: 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.
The main goal of this module is to be able to extract keys from PSKC files
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.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.
Keywords: PSKC,RFC 6030,key container
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: XML
python-pskc-0.3/docs/ 0000755 0000000 0000000 00000000000 12605273753 014533 5 ustar root root 0000000 0000000 python-pskc-0.3/docs/encryption.rst 0000644 0000000 0000000 00000004053 12605273737 017463 0 ustar root root 0000000 0000000 PSKC encryption
===============
.. module:: pskc.encryption
The keys (and some embedded data) in PSKC files can be encrypted with either
pre-shared keys, passphrase-based keys or asymmetric keys (asymmetric keys
are currently unimplemented).
Embedded PSKC encryption is handled inside the :class:`Encryption` class that
defines encryption key or means of deriving keys. It is accessed from the
:attr:`~pskc.PSKC.encryption` attribute of a :class:`~pskc.PSKC` instance::
>>> rom binascii import a2b_hex
>>> from pskc import PSKC
>>> pskc = PSKC('somefile.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
or::
>>> pskc.encryption.derive_key('qwerty')
Once the encryption key has been set up, any encrypted key values from the
PSKC file are available transparently.
If an incorrect key has been set up, upon accessing encrypted
information (e.g. the :attr:`~pskc.key.Key.secret` attribute of a
:class:`~pskc.key.Key` instance) a :exc:`~pskc.exceptions.DecryptionError`
exception will be raised.
The Encryption class
--------------------
.. class:: Encryption
.. attribute:: id
Optional identifier of the encryption key.
.. attribute:: key_names
List of names provided for the encryption key.
.. attribute:: key_name
Since usually only one name is defined for a key but the schema allows
for multiple names, this is a shortcut for accessing the first value of
:attr:`key_names`.
.. attribute:: key
The binary value of the encryption key. In the case of pre-shared keys
this value should be set before trying to access encrypted information
in the PSKC file.
When using key derivation the secret key is available in this attribute
after calling :func:`derive_key`.
.. function:: derive_key(password)
Derive a key from the supplied password and information in the PSKC
file (generally algorithm, salt, etc.).
This function may raise a :exc:`~pskc.exceptions.KeyDerivationError`
exception if key derivation fails for some reason.
python-pskc-0.3/docs/conf.py 0000644 0000000 0000000 00000014216 12605273737 016040 0 ustar root root 0000000 0000000 # -*- coding: utf-8 -*-
#
# python-pksc documentation build configuration file, created by
# sphinx-quickstart
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
import pskc
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
'sphinx.ext.coverage', 'sphinx.ext.autosummary'
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'python-pskc'
copyright = u'2014-2015 Arthur de Jong'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = pskc.__version__
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_*', '.svn', '.git']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['pskc.', ]
# Automatically generate stub pages for autosummary entries.
autosummary_generate = True
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Suffix for generated links to HTML files.
#html_link_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'python-pskcdoc'
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'python-pskc', u'python-pskc Documentation',
[u'Arthur de Jong'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
python-pskc-0.3/docs/policy.rst 0000644 0000000 0000000 00000012633 12605273737 016573 0 ustar root root 0000000 0000000 Key usage policy
================
.. module:: pskc.policy
The PSKC format allows for specifying `key and pin usage policy `__
per key.
Instances of the :class:`Policy` class provide attributes that describe
limits that are placed on key usage and requirements for key PIN protection::
>>> key = pskc.keys[0]
>>> key.policy.may_use(key.policy.KEY_USE_OTP)
True
The Policy class
----------------
.. class:: Policy
.. attribute:: start_date
:class:`datetime.datetime` value that indicates that the key must not
be used before this date.
.. attribute:: expiry_date
:class:`datetime.datetime` value that indicates that the key must not
be used after this date. Systems should not rely upon the device to
enforce key usage date restrictions, as some devices do not have an
internal clock.
.. attribute:: number_of_transactions
The value indicates the maximum number of times a key carried within
the PSKC document may be used by an application after having received
it.
.. attribute:: key_usage
A list of `valid usage scenarios
`__ for the
key that the recipient should check against the intended usage of the
key. Also see :func:`may_use` and :ref:`key-use-constants` below.
.. attribute:: pin_key_id
The unique `id` of the key within the PSKC file that contains the value
of the PIN that protects this key.
.. attribute:: pin_key
Instance of the :class:`~pskc.key.Key` (if any) that contains the value
of the PIN referenced by :attr:`pin_key_id`.
.. attribute:: pin
PIN value referenced by :attr:`pin_key_id` (if any). The value is
transparently decrypted if possible.
.. attribute:: pin_usage
Describe how the PIN is used during the usage of the key. See
:ref:`pin-use-constants` below.
.. attribute:: pin_max_failed_attemtps
The maximum number of times the PIN may be entered wrongly before it
MUST NOT be possible to use the key any more.
.. attribute:: pin_min_length
The minimum length of a PIN that can be set to protect the associated
key.
.. attribute:: pin_max_length
The maximum length of a PIN that can be set to protect this key.
.. attribute:: pin_encoding
The encoding of the PIN which is one of ``DECIMAL``, ``HEXADECIMAL``,
``ALPHANUMERIC``, ``BASE64``, or ``BINARY`` (see
:attr:`~pskc.key.Key.challenge_encoding`).
.. attribute:: unknown_policy_elements
Boolean that is set to ``True`` if the PSKC policy information contains
unknown or unsupported definitions or values. A conforming
implementation must assume that key usage is not permitted if this
value is ``True`` to ensure that the lack of understanding of certain
extensions does not lead to unintended key usage.
.. function:: may_use(usage)
Check whether the key may be used for the provided purpose. See
:ref:`key-use-constants` below.
.. _key-use-constants:
Key usage constants
-------------------
The :class:`Policy` class provides the following key use constants (see
:attr:`~Policy.key_usage` and :func:`~Policy.may_use`):
.. autoattribute:: Policy.KEY_USE_OTP
Key is used for OTP generation.
.. autoattribute:: Policy.KEY_USE_CR
The key is used for challenge-response purposes.
.. autoattribute:: Policy.KEY_USE_ENCRYPT
The key is used for data encryption purposes.
.. autoattribute:: Policy.KEY_USE_INTEGRITY
The key is used to generate a keyed message digest for data integrity or
authentication purposes.
.. autoattribute:: Policy.KEY_USE_VERIFY
The key is used to verify a keyed message digest for data integrity or
authentication purposes (this is the opposite of
:attr:`KEY_USE_INTEGRITY`).
.. autoattribute:: Policy.KEY_USE_UNLOCK
The key is used for an inverse challenge-response in the case where a
user has locked the device by entering a wrong PIN too many times (for
devices with PIN-input capability).
.. autoattribute:: Policy.KEY_USE_DECRYPT
The key is used for data decryption purposes.
.. autoattribute:: Policy.KEY_USE_KEYWRAP
The key is used for key wrap purposes.
.. autoattribute:: Policy.KEY_USE_UNWRAP
The key is used for key unwrap purposes.
.. autoattribute:: Policy.KEY_USE_DERIVE
The key is used with a key derivation function to derive a new key.
.. autoattribute:: Policy.KEY_USE_GENERATE
The key is used to generate a new key based on a random number and the
previous value of the key.
.. _pin-use-constants:
Pin usage constants
-------------------
The following constants for PIN use are defined in the :class:`Policy`
class (see :attr:`~Policy.pin_usage`):
.. autoattribute:: Policy.PIN_USE_LOCAL
The PIN is checked locally on the device before allowing the key to be
used in executing the algorithm.
.. autoattribute:: Policy.PIN_USE_PREPEND
The PIN is prepended to the algorithm response. It must be checked by
the party validating the response.
.. autoattribute:: Policy.PIN_USE_APPEND
The PIN is appended to the algorithm response. It must be checked by
the party validating the response.
.. autoattribute:: Policy.PIN_USE_ALGORITHMIC
The PIN is used as part of the algorithm computation.
python-pskc-0.3/docs/_templates/ 0000755 0000000 0000000 00000000000 12605273753 016670 5 ustar root root 0000000 0000000 python-pskc-0.3/docs/_templates/autosummary/ 0000755 0000000 0000000 00000000000 12605273753 021256 5 ustar root root 0000000 0000000 python-pskc-0.3/docs/_templates/autosummary/module.rst 0000644 0000000 0000000 00000000114 12337607021 023260 0 ustar root root 0000000 0000000 {{ fullname }}
{{ underline }}
.. automodule:: {{ fullname }}
:members:
python-pskc-0.3/docs/index.rst 0000644 0000000 0000000 00000000422 12353625504 016365 0 ustar root root 0000000 0000000 .. include:: ../README
:end-before: API
Contents
--------
.. toctree::
:maxdepth: 1
usage
encryption
mac
policy
exceptions
changes
Security considerations
-----------------------
.. include:: ../README
:start-after: -----------------------
python-pskc-0.3/docs/mac.rst 0000644 0000000 0000000 00000002323 12605273737 016027 0 ustar root root 0000000 0000000 Integrity checking
==================
.. module:: pskc.mac
The PSKC format allows for `message authentication and integrity checking
`_ for some of the values
stored within the PSKC file.
Integrity checking is done transparently when accessing attributes that
are encrypted and contain a ValueMAC.
Once the PSKC encryption key has been set up, key values can be explicitly
checked using the :func:`~pskc.key.Key.check` method::
>>> pskc = PSKC('somefile.pskcxml')
>>> pskc.encryption.derive_key('qwerty')
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> all(key.check() for key in pskc.keys)
True
The MAC class
-------------
.. class:: MAC
.. attribute:: algorithm
The name of the MAC algorithm to use (currently ``HMAC-MD5``,
``HMAC-SHA1``, ``HMAC-SHA224``, ``HMAC-SHA256``, ``HMAC-SHA384`` and
``HMAC-SHA512`` are supported).
.. attribute:: key
For HMAC checking, this contains the binary value of the MAC key. The
MAC key is generated specifically for each PSKC file and encrypted with
the PSKC encryption key, so the PSKC file should be decrypted first
(see :doc:`encryption`).
python-pskc-0.3/docs/exceptions.rst 0000644 0000000 0000000 00000002454 12605273737 017455 0 ustar root root 0000000 0000000 Exceptions
==========
.. module:: pskc.exceptions
.. exception:: PSKCError
The base class for all exceptions that the module will raise. In some
cases third-party code may raise additional exceptions.
.. exception:: ParseError
Raised when the PSKC file cannot be correctly read due to invalid XML or
some required element or attribute is missing. This exception should only
be raised when parsing the file (i.e. when the :class:`~pskc.PSKC` class is
instantiated).
.. .. exception:: EncryptionError
Raised when encrypting a value is not possible due to key length issues,
missing or wrong length plain text, or other issues.
.. exception:: DecryptionError
Raised when decrypting a value fails due to missing or incorrect key,
unsupported decryption or MAC algorithm, failed message authentication
check or other error.
This exception is generally raised when accessing encrypted information
(i.e. the :attr:`~pskc.key.Key.secret`, :attr:`~pskc.key.Key.counter`,
:attr:`~pskc.key.Key.time_offset`, :attr:`~pskc.key.Key.time_interval` or
:attr:`~pskc.key.Key.time_drift` attributes of the :class:`~pskc.key.Key`
class).
.. exception:: KeyDerivationError
Raised when key derivation fails due to an unsupported algorithm or
missing information in the PSKC file.
python-pskc-0.3/docs/usage.rst 0000644 0000000 0000000 00000030765 12605273737 016406 0 ustar root root 0000000 0000000 Basic usage
===========
The :mod:`pskc` module implements a simple and efficient API for parsing PSKC
files. The :class:`~pskc.PSKC` class is used to access the file as a whole
which provides access to a list of :class:`~pskc.key.Key` instances which
contain most of the useful information from the PSKC file.
Opening a PSKC file
-------------------
Importing data from a PSKC file can be done by instantiating the
:class:`~pskc.PSKC` class with a file name argument::
>>> from pskc import PSKC
>>> pskc = PSKC('somefile.pskcxml')
>>> pskc.version
'1.0'
The :attr:`~pskc.PSKC.keys` attribute contains a list of keys in the PSKC
file. :class:`~pskc.key.Key` instances have a number of attributes that
provide information on the transmitted keys::
>>> key = pskc.keys[0]
>>> key.id
'some-id'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.secret
'SOME_SECRET_VALUE'
Attribute values will be ``None`` if it the value is not present in the PSKC
file.
The :attr:`~pskc.key.Key.secret`, :attr:`~pskc.key.Key.counter`,
:attr:`~pskc.key.Key.time_offset`, :attr:`~pskc.key.Key.time_interval` or
:attr:`~pskc.key.Key.time_drift` attributes may be stored in encrypted form
in the PSKC file. Decryption of these properties is done when they are
accessed. If decryption is unsuccessful a
:exc:`~pskc.exceptions.DecryptionError` exception is raised. See
:doc:`encryption` for more information.
Writing a PSKC file
-------------------
Creating a PSKC file can be done by creating a :class:`~pskc.PSKC` instance,
adding keys with :func:`~pskc.PSKC.add_key()` and writing the result::
>>> from pskc import PSKC
>>> pskc = PSKC()
>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
>>> key.id
'456'
>>> key.secret = '987654321'
>>> key.algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> pskc.write('output.pskcxml')
Writing the data in encrypted form in the PSKC file is not yet supported so
currently opening an encrypted PSKC file, providing the encryption key and
writing the file should result in the same file but with encryption removed.
The PSKC class
--------------
.. module:: pskc
.. class:: PSKC([filename])
The :class:`PSKC` class is used as a wrapper to access information from a
PSKC file.
The `filename` argument can be either the name of a file or a file-like
object. The whole file is parsed in one go. If parsing the PSKC file
fails, a :exc:`~pskc.exceptions.ParseError` exception is raised.
If no argument is provided, an instance without any keys is created.
Instances of this class provide the following attributes and functions:
.. attribute:: version
The PSKC format version used. Only version ``1.0`` is currently
specified in
`RFC 6030 `__
and supported.
.. attribute:: id
A unique identifier for the container.
.. attribute:: keys
A list of :class:`~pskc.key.Key` instances that represent the keys
within the PSKC file.
.. attribute:: encryption
:class:`~pskc.encryption.Encryption` instance that handles PSKC file
encryption. See :doc:`encryption` for more information.
.. attribute:: mac
:class:`~pskc.mac.MAC` instance for handling integrity checking.
See :doc:`mac` for more information.
.. function:: add_key([**kwargs])
Add a new key to the PSKC instance. The keyword arguments may refer to
any attributes of the :class:`~pskc.key.Key` class with which the new
key is initialised.
.. function:: write(filename)
Write the PSKC object to the provided file. The `filename` argument can
be either the name of a file or a file-like object.
The Key class
-------------
.. module:: pskc.key
.. class:: Key()
Instances of this class provide the following attributes and functions:
.. attribute:: id
A unique identifier for the key. If there are multiple interactions
with the same key in multiple instances of PSKC files the `id` is
supposed to remain the same.
.. attribute:: algorithm
A URI that identifies the PSKC algorithm profile. The algorithm profile
associates specific semantics to the key. Some `known profiles
`__ are:
+------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| URI | Purpose |
+================================================+=============================================================================================================================+
| ``urn:ietf:params:xml:ns:keyprov:pskc:pin`` | `Symmetric static credential comparison `_ |
+------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| ``urn:ietf:params:xml:ns:keyprov:pskc:hotp`` | `OATH event-based OTP `_ |
+------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| ``urn:ietf:params:xml:ns:keyprov:pskc#totp`` | `OATH time-based OTP `_ |
| ``urn:ietf:params:xml:ns:keyprov:pskc:totp`` | |
+------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| ``urn:ietf:params:xml:ns:keyprov:pskc#OCRA-1`` | `OATH challenge-response algorithm `_ |
+------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
.. attribute:: secret
The binary value of the transported secret key. If the key information
is encrypted in the PSKC file it is transparently decrypted if
possible. Accessing the value may raise
:exc:`~pskc.exceptions.DecryptionError` if decryption fails.
.. attribute:: counter
The event counter (integer) for event-based OTP algorithms. Will also be
transparently decrypted and may also raise
:exc:`~pskc.exceptions.DecryptionError`.
.. attribute:: time_offset
The time offset (integer) for time-based OTP algorithms. If time
intervals are used it carries the number of time intervals passed from
an algorithm-dependent start point. Will also be transparently decrypted
and may also raise :exc:`~pskc.exceptions.DecryptionError`.
.. attribute:: time_interval
The time interval in seconds (integer) for time-based OTP algorithms
(usually ``30`` or ``60``). Will also be transparently decrypted and may
also raise :exc:`~pskc.exceptions.DecryptionError`.
.. attribute:: time_drift
For time-based OTP algorithms this contains the device clock drift in
number of intervals (integer). Will also be transparently decrypted and
may also raise :exc:`~pskc.exceptions.DecryptionError`.
.. attribute:: issuer
The name of the party that issued the key. This may be different from
the :attr:`manufacturer` of the device.
.. attribute:: key_profile
A reference to a pre-shared key profile agreed upon between the sending
and receiving parties. The profile information itself is not
transmitted within the container.
See `RFC 6030 `__.
.. attribute:: key_reference
A reference to an external key that is not contained within the PSKC
file (e.g., a PKCS #11 key label). If this attribute is present, the
:attr:`secret` attribute will generally be missing.
.. attribute:: friendly_name
A human-readable name for the secret key.
.. attribute:: key_userid
The distinguished name of the user associated with the key.
Also see :attr:`device_userid`.
.. attribute:: manufacturer
The name of the manufacturer of the device to which the key is
provisioned.
`RFC 6030 `__
prescribes that the value is of the form ``oath.prefix`` for `OATH
Manufacturer Prefixes `_
or ``iana.organisation`` for `IANA Private Enterprise Numbers
`_
however, it is generally just a string. The value may be different from
the :attr:`issuer` of the key on the device.
.. attribute:: serial
The serial number of the device to which the key is provisioned.
Together with :attr:`manufacturer` (and possibly :attr:`issue_no`) this
should uniquely identify the device.
.. attribute:: model
A manufacturer-specific description of the model of the device.
.. attribute:: issue_no
The issue number in case there are devices with the same :attr:`serial`
number so that they can be distinguished by different issue numbers.
.. attribute:: device_binding
Reference to a device identifier (e.g. IMEI) that allows a provisioning
server to ensure that the key is going to be loaded into a specific
device.
.. attribute:: start_date
:class:`datetime.datetime` value that indicates that the device should
only be used after this date.
.. attribute:: expiry_date
:class:`datetime.datetime` value that indicates that the device should
only be used before this date. Systems should not rely upon the device
to enforce key usage date restrictions, as some devices do not have an
internal clock.
.. attribute:: device_userid
The distinguished name of the user associated with the device.
Also see :attr:`key_userid`.
.. attribute:: crypto_module
Implementation specific unique identifier of the cryptographic module
on the device to which the keys have been (or will be) provisioned.
.. attribute:: algorithm_suite
Additional algorithm-specific characteristics. For example, in an
HMAC-based algorithm it could specify the hash algorithm used (SHA1
or SHA256).
.. attribute:: challenge_encoding
Encoding of the challenge accepted by the device for challenge-response
authentication. One of:
* ``DECIMAL``: only numerical digits
* ``HEXADECIMAL``: hexadecimal
* ``ALPHANUMERIC``: all letters and numbers (case sensitive)
* ``BASE64``: base-64 encoded
* ``BINARY``: binary data
.. attribute:: challenge_min_length
The minimum size of the challenge accepted by the device.
.. attribute:: challenge_max_length
The maximum size of the challenge accepted by the device.
.. attribute:: challenge_check
Boolean that indicates whether the device will check an embedded
`Luhn check digit `_
contained in the challenge.
.. attribute:: response_encoding
Format of the response that is generated by the device. If must be one
of the values as described under :attr:`challenge_encoding`.
.. attribute:: response_length
The length of the response generated by the device.
.. attribute:: response_check
Boolean that indicates whether the device will append a
`Luhn check digit `_
to the response.
.. attribute:: policy
:class:`~pskc.policy.Policy` instance that provides key and PIN policy
information. See :doc:`policy`.
.. function:: check()
Check if any MACs in the key data embedded in the PSKC file are valid.
This will return None if there is no MAC to be checked. It will return
True if all the MACs match. If any MAC fails a
:exc:`~pskc.exceptions.DecryptionError` exception is raised.
python-pskc-0.3/docs/changes.rst 0000644 0000000 0000000 00000000104 12353625504 016663 0 ustar root root 0000000 0000000 Changes in python-pskc
======================
.. include:: ../NEWS
python-pskc-0.3/pskc2csv.py 0000755 0000000 0000000 00000010004 12605044512 015676 0 ustar root root 0000000 0000000 #!/usr/bin/env python
# coding: utf-8
# pskc2csv.py - script to convert a PSKC file to CSV
#
# 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 argparse
import csv
import operator
import sys
import getpass
from binascii import b2a_hex
import pskc
from pskc.exceptions import DecryptionError
version_string = '''
pskc2csv (python-pskc) %s
Written by Arthur de Jong.
Copyright (C) 2014 Arthur de Jong
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
'''.strip() % pskc.__version__
class VersionAction(argparse.Action):
def __init__(self, option_strings, dest,
help='output version information and exit'):
super(VersionAction, self).__init__(
option_strings=option_strings,
dest=argparse.SUPPRESS,
default=argparse.SUPPRESS,
nargs=0,
help=help)
def __call__(self, parser, namespace, values, option_string=None):
print version_string
parser.exit()
epilog = '''
supported columns:
id, serial, secret, counter, time_offset, time_interval, interval,
time_drift, issuer, manufacturer, response_length, algorithm
And any other properties of pskc.key.Key instances.
Report bugs to .
'''.strip()
# set up command line parser
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description='Convert a PSKC file to CSV.', epilog=epilog)
parser.add_argument(
'input', metavar='FILE', help='the PSKC file to read')
parser.add_argument(
'-V', '--version', action=VersionAction)
parser.add_argument(
'-o', '--output', metavar='FILE',
help='write CSV to file instead of stdout')
parser.add_argument(
'-c', '--columns', metavar='COL,COL', type=lambda x: x.split(','),
help='list of columns to export',
default='serial,secret,algorithm,response_length,time_interval')
def password_prompt(pskcfile):
"""Prompt for a password and use the password to decrypt."""
prompt = 'Password: '
if pskcfile.encryption.key_name:
prompt = '%s: ' % pskcfile.encryption.key_name
passwd = getpass.getpass(prompt)
pskcfile.encryption.derive_key(passwd)
def get_column(key, column):
"""Return a string value for the given column."""
value = operator.attrgetter(column)(key)
if column == 'secret':
return b2a_hex(value)
return value
def is_encrypted(pskcfile):
"""Check whether the PSKC file is encrypted."""
try:
pskcfile.keys[0].secret
except DecryptionError:
return True
except IndexError:
pass
return False
if __name__ == '__main__':
# parse command-line arguments
args = parser.parse_args()
# open and parse input PSKC file
pskcfile = pskc.PSKC(args.input)
# see if we should prompt for a password
if sys.stdin.isatty() and is_encrypted(pskcfile):
password_prompt(pskcfile)
# open output CSV file, write header and keys
with open(args.output, 'wb') if args.output else sys.stdout as output:
csvfile = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
csvfile.writerow(args.columns)
for key in pskcfile.keys:
csvfile.writerow([
get_column(key, column) for column in args.columns])
python-pskc-0.3/python_pskc.egg-info/ 0000755 0000000 0000000 00000000000 12605273753 017636 5 ustar root root 0000000 0000000 python-pskc-0.3/python_pskc.egg-info/top_level.txt 0000644 0000000 0000000 00000000005 12605273753 022363 0 ustar root root 0000000 0000000 pskc
python-pskc-0.3/python_pskc.egg-info/PKG-INFO 0000644 0000000 0000000 00000004005 12605273753 020732 0 ustar root root 0000000 0000000 Metadata-Version: 1.1
Name: python-pskc
Version: 0.3
Summary: Python module for handling PSKC files
Home-page: http://arthurdejong.org/python-pskc/
Author: Arthur de Jong
Author-email: arthur@arthurdejong.org
License: LGPL
Description: 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.
The main goal of this module is to be able to extract keys from PSKC files
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.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.
Keywords: PSKC,RFC 6030,key container
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: XML
python-pskc-0.3/python_pskc.egg-info/SOURCES.txt 0000644 0000000 0000000 00000003263 12605273753 021526 0 ustar root root 0000000 0000000 COPYING
ChangeLog
MANIFEST.in
NEWS
README
pskc2csv.py
setup.cfg
setup.py
docs/changes.rst
docs/conf.py
docs/encryption.rst
docs/exceptions.rst
docs/index.rst
docs/mac.rst
docs/policy.rst
docs/usage.rst
docs/_templates/autosummary/module.rst
pskc/__init__.py
pskc/encryption.py
pskc/exceptions.py
pskc/key.py
pskc/mac.py
pskc/policy.py
pskc/xml.py
pskc/crypto/__init__.py
pskc/crypto/aeskw.py
pskc/crypto/tripledeskw.py
python_pskc.egg-info/PKG-INFO
python_pskc.egg-info/SOURCES.txt
python_pskc.egg-info/dependency_links.txt
python_pskc.egg-info/pbr.json
python_pskc.egg-info/requires.txt
python_pskc.egg-info/top_level.txt
tests/SampleFullyQualifiedNS.xml
tests/aes128-cbc.pskcxml
tests/aes192-cbc.pskcxml
tests/aes256-cbc.pskcxml
tests/draft-keyprov-actividentity-3des.pskcxml
tests/draft-keyprov-ocra.pskcxml
tests/draft-keyprov-securid-aes-counter.pskcxml
tests/draft-keyprov-totp.pskcxml
tests/invalid-encryption.pskcxml
tests/invalid-mac-algorithm.pskcxml
tests/invalid-mac-value.pskcxml
tests/invalid-no-mac-method.pskcxml
tests/invalid-notxml.pskcxml
tests/invalid-wrongelement.pskcxml
tests/invalid-wrongversion.pskcxml
tests/kw-aes128.pskcxml
tests/kw-aes192.pskcxml
tests/kw-aes256.pskcxml
tests/kw-tripledes.pskcxml
tests/odd-namespace.pskcxml
tests/rfc6030-figure10.pskcxml
tests/rfc6030-figure2.pskcxml
tests/rfc6030-figure3.pskcxml
tests/rfc6030-figure4.pskcxml
tests/rfc6030-figure5.pskcxml
tests/rfc6030-figure6.pskcxml
tests/rfc6030-figure7.pskcxml
tests/test_aeskw.doctest
tests/test_draft_keyprov.doctest
tests/test_encryption.doctest
tests/test_invalid.doctest
tests/test_misc.doctest
tests/test_rfc6030.doctest
tests/test_tripledeskw.doctest
tests/test_write.doctest
tests/tripledes-cbc.pskcxml python-pskc-0.3/python_pskc.egg-info/requires.txt 0000644 0000000 0000000 00000000031 12605273753 022230 0 ustar root root 0000000 0000000 pycrypto
python-dateutil
python-pskc-0.3/python_pskc.egg-info/pbr.json 0000644 0000000 0000000 00000000056 12605273753 021315 0 ustar root root 0000000 0000000 {"is_release": true, "git_version": "c155d15"} python-pskc-0.3/python_pskc.egg-info/dependency_links.txt 0000644 0000000 0000000 00000000001 12605273753 023704 0 ustar root root 0000000 0000000
python-pskc-0.3/setup.cfg 0000644 0000000 0000000 00000000515 12605273753 015425 0 ustar root root 0000000 0000000 [sdist]
owner = root
group = root
[nosetests]
with-doctest = true
doctest-extension = doctest
doctest-options = +IGNORE_EXCEPTION_DETAIL
with-coverage = true
cover-package = pskc
cover-erase = true
cover-html = true
cover-html-dir = coverage
[build_sphinx]
all_files = 1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
python-pskc-0.3/COPYING 0000644 0000000 0000000 00000063504 12323560741 014637 0 ustar root root 0000000 0000000 GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
Copyright (C)
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
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!