python-pskc-1.0/ 0000755 0000000 0000000 00000000000 13221476257 013600 5 ustar root root 0000000 0000000 python-pskc-1.0/setup.py 0000755 0000000 0000000 00000005323 13221476207 015313 0 ustar root root 0000000 0000000 #!/usr/bin/env python
# setup.py - python-pskc installation script
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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))
base_dir = os.path.dirname(__file__)
with open(os.path.join(base_dir, 'README'), 'r') as fp:
long_description = fp.read()
setup(
name='python-pskc',
version=pskc.__version__,
description='Python module for handling PSKC files',
long_description=long_description,
author='Arthur de Jong',
author_email='arthur@arthurdejong.org',
keywords=['PSKC', 'RFC 6030', 'key container'],
url='https://arthurdejong.org/python-pskc/',
license='LGPL',
classifiers=[
'Development Status :: 4 - Production/Stable',
'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.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Security :: Cryptography',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Systems Administration :: Authentication/Directory',
'Topic :: Text Processing :: Markup :: XML',
],
packages=find_packages(),
install_requires=['cryptography', 'python-dateutil'],
extras_require={
'lxml': ['lxml'],
'defuse': ['defusedxml'],
'signature': ['signxml']
},
)
python-pskc-1.0/tests/ 0000755 0000000 0000000 00000000000 13221476256 014741 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/test_write.doctest 0000644 0000000 0000000 00000044242 13221476046 020524 0 ustar root root 0000000 0000000 test_write.doctest - tests for writing PSKC files
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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 os
>>> import sys
>>> import tempfile
>>> from binascii import a2b_hex
>>> from dateutil.tz import 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=tzutc())
>>> key.expiry_date = datetime.datetime(2014, 5, 31, 0, 0, tzinfo=tzutc())
>>> key.device_userid = 'uid=arthur, dc=arthurdejong, dc=org'
>>> key.crypto_module = 'CyrptoId'
>>> key.algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> 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=tzutc())
>>> key.policy.expiry_date = datetime.datetime(2012, 6, 13, 0, 0, tzinfo=tzutc())
>>> key.policy.number_of_transactions = 42
>>> key.policy.pin_key_id = 'pinID'
>>> key.policy.pin_usage = 'Local'
>>> key.policy.pin_max_failed_attempts = 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)
Add a second (TOTP) key:
>>> key = pskc.add_key()
>>> key.serial = key.id = '267469811'
>>> key.start_date = datetime.datetime(2006, 5, 1, 0, 0)
>>> key.expiry_date = datetime.datetime(2014, 5, 31, 0, 0)
>>> key.algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:totp'
>>> key.response_encoding = 'DECIMAL'
>>> key.response_length = 6
>>> key.time_offset = 0
>>> key.time_interval = 30
>>> key.time_drift = 6
>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
Write the PSKC file (use temporary file to test passing file name as
argument).
>>> f = tempfile.NamedTemporaryFile()
>>> pskc.write(f.name)
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +REPORT_UDIFF
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==2674698112006-05-01T00:00:002014-05-31T00:00:00TheQuickBrownFox0306
Read an encrypted PSKC file and write it out as an unencrypted file.
>>> pskc = PSKC('tests/encryption/kw-aes128.pskcxml')
>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f')
>>> for key in pskc.keys:
... key.secret = key.secret # force decryption of values
>>> pskc.encryption.key = None
>>> pskc.encryption.key_name = None
>>> pskc.write(sys.stdout) #doctest: +REPORT_UDIFF
ABEiM0RVZneImaq7zN3u/w==
Read an encrypted PSKC file and write it out as-is. This does not require
providing the encryption key.
>>> pskc = PSKC('tests/rfc6030/figure6.pskcxml')
>>> pskc.write(sys.stdout) #doctest: +ELLIPSIS +REPORT_UDIFF
Pre-shared-keyESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSXManufacturer987654321CM_ID_001IssuerAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvSu+NvtQfmvfJzF6bmQiJqoLRExc=0
Set up an encrypted PSKC file and generate a pre-shared key for it.
>>> pskc = PSKC()
>>> key = pskc.add_key(
... id='1', serial='123456', secret=b'1234', counter=42)
>>> pskc.encryption.setup_preshared_key(
... algorithm='aes128-cbc',
... key=a2b_hex('12345678901234567890123456789012'),
... key_name='Pre-shared KEY', fields = ['secret', 'counter'])
>>> f = tempfile.NamedTemporaryFile()
>>> pskc.write(f.name)
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +ELLIPSIS +REPORT_UDIFF
Pre-shared KEY...123456............
Read the generated file back in and verify that it matches the original data.
>>> newpskc = PSKC(f.name)
>>> newpskc.encryption.algorithm == pskc.encryption.algorithm
True
>>> newpskc.encryption.key = pskc.encryption.key
>>> all(newkey.check() for newkey in newpskc.keys)
True
>>> key = pskc.keys[0]
>>> newkey = newpskc.keys[0]
>>> newkey.secret == key.secret
True
>>> newkey.counter == key.counter
True
Use PBKDF2 to derive a key instead of using a pre-shared key.
>>> pskc = PSKC()
>>> key = pskc.add_key(
... id='1', serial='123456', secret=b'1234', counter=42)
>>> pskc.encryption.setup_pbkdf2(
... 'passphrase', key_name='Passphrase')
>>> pskc.write(f.name)
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +ELLIPSIS +REPORT_UDIFF
...1200016Passphrase...123456......42
Read the generated file back in and verify that it matches the original data.
>>> newpskc = PSKC(f.name)
>>> newpskc.encryption.algorithm == pskc.encryption.algorithm
True
>>> newpskc.encryption.derive_key('passphrase')
>>> all(newkey.check() for newkey in newpskc.keys)
True
>>> key = pskc.keys[0]
>>> newkey = newpskc.keys[0]
>>> newkey.secret == key.secret
True
>>> newkey.counter == key.counter
True
Test encryption and decryption of the generated file to test encryption/
decryption combinations.
>>> def test_algorithm(algorithm):
... f = tempfile.NamedTemporaryFile()
... pskc1 = PSKC()
... pskc1.add_key(secret=os.urandom(16))
... pskc1.encryption.setup_preshared_key(algorithm=algorithm)
... pskc1.write(f.name)
... pskc2 = PSKC(f.name)
... pskc2.encryption.key = pskc1.encryption.key
... assert pskc1.keys[0].secret == pskc2.keys[0].secret
... return (pskc1, pskc2)
>>> pskc1, pskc2 = test_algorithm('aes192-cbc')
>>> len(pskc1.encryption.key)
24
>>> pskc1, pskc2 = test_algorithm('aes256-cbc')
>>> len(pskc1.encryption.key)
32
>>> pskc1, pskc2 = test_algorithm('tripledes-cbc')
>>> len(pskc1.encryption.key)
24
>>> pskc1, pskc2 = test_algorithm('kw-aes128')
>>> len(pskc1.encryption.key)
16
>>> pskc1, pskc2 = test_algorithm('kw-aes192')
>>> len(pskc1.encryption.key)
24
>>> pskc1, pskc2 = test_algorithm('kw-aes256')
>>> len(pskc1.encryption.key)
32
>>> pskc1, pskc2 = test_algorithm('kw-tripledes')
>>> len(pskc1.encryption.key)
24
Not having a key and trying encryption will fail.
>>> f = tempfile.NamedTemporaryFile()
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_preshared_key()
>>> pskc.encryption.key = None
>>> pskc.write(f.name)
Traceback (most recent call last):
...
EncryptionError: No key available
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_preshared_key()
>>> pskc.encryption.algorithm = None
>>> pskc.write(f.name)
Traceback (most recent call last):
...
EncryptionError: No algorithm specified
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_preshared_key()
>>> pskc.encryption.algorithm = 'FOOBAR'
>>> pskc.write(f.name)
Traceback (most recent call last):
...
DecryptionError: Unsupported algorithm: 'FOOBAR'
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_preshared_key()
>>> pskc.encryption.algorithm = 'aes256-cbc'
>>> pskc.write(f.name)
Traceback (most recent call last):
...
EncryptionError: Invalid key length
Setting up something else than PBKDF2 as derivation algorithm will just
result in an empty KeyDerivation element.
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_pbkdf2('qwerty')
>>> pskc.encryption.derivation.algorithm = 'unknown'
>>> pskc.write(sys.stdout) #doctest: +ELLIPSIS +REPORT_UDIFF
...
...
We can make the PKKDF2 salt have to be transmitted out-of-bounds:
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_pbkdf2('qwerty', salt=a2b_hex('1234567890'))
>>> pskc.encryption.derivation.pbkdf2_salt = None
>>> pskc.write(sys.stdout) #doctest: +ELLIPSIS +REPORT_UDIFF
1200016
...
...
Write a PSKC file with two keys in onde KeyPackage section. Note that this
is not allowed in the RFC 6030 schema. Note that device properties that are
set on one key end up being applied to both keys.
>>> pskc = PSKC()
>>> device = pskc.add_device(manufacturer='TokenVendorAcme')
>>> key = device.add_key(id='1', serial='123456', secret='1234', counter=42)
>>> key = device.add_key(id='pin0', secret='5678')
>>> pskc.write(sys.stdout) #doctest: +ELLIPSIS +REPORT_UDIFF
TokenVendorAcme123456MTIzNA==42NTY3OA==
python-pskc-1.0/tests/multiotp/ 0000755 0000000 0000000 00000000000 13221476257 016617 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/multiotp/tokens_hotp_pbe.pskc 0000644 0000000 0000000 00000006333 13221434050 022652 0 ustar root root 0000000 0000000
OtMW8Un/HGSlXFr/2kG1cQ==100016Passphrase1FTqUDYQ8Y/7T5bLHBQD6n9bDkgJWBIbAnf3FFKW7HVBs6zJEyPnG7OCzeZoVurdKManufacturerZZ7000000000Issuer0HMAC-SHA1Np1sHx016g2ZxvQtVqDRNMKYv6q+Hg0Nkapeg7RtoqDx8aaEhC1b5VJy9PmW3q8woZw2yK9JoL9DpX3/yogFMn9PYv0=uE85joDN6QDu7CZ5+GkiVQ7JTS2gIJi/2/y9/F5Guos=TJToGTyf/oVMePIVjx5XZDZfg6c=
python-pskc-1.0/tests/multiotp/tokens_totp_aes.pskc 0000644 0000000 0000000 00000012517 13221434050 022671 0 ustar root root 0000000 0000000
Pre-shared-keyrVUagPa+KYZM2hfbHwnpozIL7s8tPQZvsXw67jwIslQNuBEd2862YFEH6+TkY4oEManufacturerZZ8000000001Issuer0HMAC-SHA256cqy6Iab7wSTqANYvAHpFlnD2t/bB/3w0Tr2aPXa5qBAQ119hTzrIMXwa8+NDwlbGGDEZvi3CwhkV1USzNc3ZYg==GWy0oB6y4vjUgVJ0mi1fwVyLNwg=+bvO+gPAjRaCj362X+jY/Kq+96JWEUnTrd01LMh5WXc=E8PxvkoE0+jx31dHHefFK2s+TZ8=jhCd0hvSwh9kjFi6sbtGSyTkos9OEM/DkAhcrxi2pBE=kJvlB/aQYRCExmEoSsGHbHmc8P0=ManufacturerZZ8000000002Issuer0HMAC-SHA512sqFR1kSP7U/Lh4Wmd9J68jM0xzWLYRvyTKrrJ/Zp3YSmkmO9/y/7d4O8TTA10YTdB96Fxtu7sG2Pi585ZOrLo45plWAg29bKKZnOzdfbqHWaFgz+w2NOh5nO8BPfN6HpbgWUXTPVrf+8FqRGozQ24/SqmfU=Fmhdk1NjK+gOtF2sZOV58H8NDQdsgShJDtjtpVofhlE=oCpIL7HUiZ6Tch1SgmgNzpaFylQ=Ux0yGMphj1Y2PT+4TQf3EAMVN9ieF42vc9J7T77HuBE=y6o/A0zvLC3bGQ09EeCHkB6wvIo=
python-pskc-1.0/tests/multiotp/pskc-totp-pbe.txt 0000644 0000000 0000000 00000005754 13221434050 022044 0 ustar root root 0000000 0000000
Ej7/PEpyEpw=100016Passphrase1sMSFumtOPpf+FRlkpLQNBAfLijJmm6L8iU2QgnrEpYpsukP2ewzFmGnTDtWUmtHkManufacturerZZ1000000002CM_ID_007Issuer0+VJbRq6uiXOfcLfiWG6SlN3wtYoQ8LesDU3BolqPeoZe6fQVG2J5F3ryBZTtIZvMfjI2IRbUxUYU24boM6atz+RHRV0=030
python-pskc-1.0/tests/multiotp/tokens_totp_pbe.pskc 0000644 0000000 0000000 00000007357 13221434050 022675 0 ustar root root 0000000 0000000
etmLw9S1kDUeGS8V5e/UAw==100016Passphrase1lF9ReQyHsJ4LTlntuVlESIKbJiYnt5MDseTFqnmNcyHqxB/ZS4BD83ZuvTaSYCu9ManufacturerZZ8000000000Issuer0HMAC-SHA11VNUGh7Jf+8QibELHTDFU8Lj+ZFu836aoFKTEVWtAW+XydPBHYmC9gnty3SJ1Vh0Oy+u0zn74nlGNjMnUe2OTfrFhHI=OcePCm3TGaXtSHQlox1pfLMWQ1dP7boiOG92wv9Jk6g=STDGPo7iRGs30+pI7SZCZ+d0J7w=yUNeDTix60G1T5DxGWC1VtFlKktG9lPoowjiGe6uuyY=yZfFtq6nmW9MoxeD31C6OKVojoQ=
python-pskc-1.0/tests/multiotp/pskc-hotp-pbe.txt 0000644 0000000 0000000 00000006407 13221434050 022024 0 ustar root root 0000000 0000000
Ej7/PEpyEpw=100016Passphrase13Y5KqMW+np7kK8GpMDP+90A6VfM/inEqxoJyy93GUPE+4aaL1fwdjyiiQz/0v9vBManufacturerZZ0000000002CM_ID_007Issuer0o1hcXgyZNxLXiopuyNyZhkbooppb/xUierAPRFKu9eSS0WubkhhOt8iUP5aD0731X/EFKw8NsRV+alKO8VOnQDrK3sc=6HhzB+wO/7/zJbNmLYaFd3awuEHXbck5AFb66rEsaZo=fqp0U8/7qMJEs6GyfnXnACn0Z2Q=
python-pskc-1.0/tests/multiotp/tokens_ocra_aes.pskc 0000644 0000000 0000000 00000174776 13221434050 022647 0 ustar root root 0000000 0000000
Pre-shared-keyU+4mSH23ab4JKjBqJ35favjKfKY3hf9sWKp/P7L/TAqpcxUYGSUeK/RMfL0Fv9hyManufacturerZZ9000000001Issuer0OCRA-1:HOTP-SHA1-8:QN08lrdvTkSeq9hj9QimjKE9hcH/OkmWpmxsmJ3fP57AqHygLTrUs88MVW8KB+h5yb9rwe9aOQ8+UUsWIZAd2I/eqnlJ1Cc=ManufacturerZZ9000000002Issuer0OCRA-1:HOTP-SHA256-8:QA08aYojAzVasgEkxJhbOrBCfIikgi686p4u3TILah2QT2m30SgiT/wCSzTpLXUQBuoirzKfkjbd3Bpco3tHyzc+2A==X4R8LvhswobqWv45doqicRNEtzM=ManufacturerZZ9000000003Issuer0OCRA-1:HOTP-SHA1-6:QA06-PSHA18bxb34b2HoBWaVPbBwdLy5HI+/i036YdZz0h0MyuvIKEvGnHI9GmuGJGUXebfgCYcWN0g8q6Fb3HJNcXla0CSoOMe3A=ManufacturerZZ9900000003Issuer03piq9NJAzfkcZ7z1Iu7IpLB1CjdzY4gNQyiOVnW+kcdhBW4NTTqmHx6o8mmSuE684pQgbgcE1B2lBRhRmqv5HRYcbN0=ManufacturerZZ9000000004Issuer0OCRA-1:HOTP-SHA1-6:C-QA06STKpyutR8Nb3BbjNmG8kf7NoTO66yccq5C9MDCZF2AXMUSDEdUhqMwqjfhg92Qu53T1/FGP1wIAVOB9ViVXs6kBQs/g=xasM5vP1vlZBimdMLWoqJWuZNJMY1iEKDnw9MgB0F9I=xw+qx3/xyXDYosr3n7PU3barLSU=ManufacturerZZ9000000005Issuer0OCRA-1:HOTP-SHA1-8:C-QN08ZCAx2z7qNDvmPRPyXpvQsZkoZjiaSlNnMaz1bh5ZWAdWtYMuvSgMkPtDN0xE8KgZ2loMyhUVMcJFUmMEfdriuPZNgLA=aVN/any99YJCpczoTns7H5JwyPUR8OQwNI2lLf4imhQ=AXc+jkG1uZOHcCXyMcqIdm7QCpA=ManufacturerZZ9000000006Issuer0OCRA-1:HOTP-SHA256-8:C-QA08BZzl+whnM71kkdW1uoY1mjM5mvNWZFxn3f5yGaZ+kuODAqgPrvqq531ipEvke6JkARSwOWddvYyOm34Zm+LtZQ==Ko/kWZbKecJ9S8W+yE86UUcy/Ko=ByzvSK/7/5kXCsu2vfwl5sOIIbiD9pON5TSXFEkuwUE=xqmZLntYdIwYdYmNzB3ZMB3+058=ManufacturerZZ9000000007Issuer0OCRA-1:HOTP-SHA256-8:C-QA08-PSHA256b8LoriGzVqbmb9MFSr6Y/Ryj+ltGY12xWoaBuyD44ZwmwKttlyuMu1+mbSVO1jnC/vKPIy5aiDK3IeFV7/BKFA==k5uW1xUA2FzmmeWPnjazgHJuJEU=tYYVbxkQ4meKaMRNRilYbHw2p3rodjakQT34MTgp9ik=Wh0MFLFLdNYrF1wdz5qQC0bXCAY=ManufacturerZZ9900000007Issuer0WKU7EnTKQqtMTG28HxAbAiZdM8YSnmJPNubxIu/aPU92aN+Mun8CDfJeNMQHcQIz3Etq8VvTn+rZoi9cP13CLw==p3bXVz9GTL+davWpoHLe1wnECLk=ManufacturerZZ9000000008Issuer0OCRA-1:HOTP-SHA1-6:QA06-T30S0H0ol1Cz2cwNLyWM6tm+4qcyZElq6Pax/FgFd/8SVyio5yeyaxCjVe38uA0bqyCmH6ZOVXjnoPCGww/3ol0Sh1Y1sOQ=QLbQdbGX+w0QSRVmBSoxtEdYr2gdzVf8YdlDA1qZ4EM=NZ46EI9Ht/sgJFREVWMFcaDA6M0=Q4mCcvb1/2TAm464+MeCyoZLAZxo20D6QLWYfew71y8=kYnUTH85koj3pVJjYKP6HnCd0Ow=ManufacturerZZ9000000009Issuer0OCRA-1:HOTP-SHA1-8:QN08-T30S2G50v7BqR/c7qYl3ya4dhStvrcMzkM+CMpwT0D3DuTMkI/m43wHM0Ujmbwjxm+6XX6513x4mOmjlU4BedoX1Zha2npQ=m42G5E/Wv102ESFI5u+OGLiMsXW1/ZgH33NTOOH1WCw=8DeEbx1p8Mdbg0eocOHeXFXWCUE=Ay4C6M0IqRKZYpRp9Au+aswdDDorRlalmwicbNF/P94=1C8jh1+FEQsAQvv39iW95broEkU=ManufacturerZZ9000000010Issuer0OCRA-1:HOTP-SHA256-6:QA06-T30SbRVzcE4zXD1bbi4ZBXEMLDxhboBPi4rRvZIvMGfe9it+99OrFumVj0B66VZkP32vLtx4w6hagxZoVm8Gynck6Q==A7mJ6qIw+1TiUm5Prsel3RcrFtI=poGDc45s8KwViudS065t+InpbSLam13d0ivLGmdELSA=lOrmfIiH3Uun4kz+/eTO4OFs3Ww=02GUkSl7MPZKVJcBL0OA6zsA3/ztQknHgaSGNpE6kUI=0Y0n7RthG4WeewJOL9unWxRHW20=ManufacturerZZ9000000011Issuer0OCRA-1:HOTP-SHA256-8:QA08-T30SVkrhf45HuiXuZyxXQ9O+PZI/uj674QzB8O8z6w3M7o+kGjI+jCCXXpeizcHmQMpqY9R6CyP66efXmMlNYbv1Jg==6oKfnDGLVAHCSyikMHr/KMPDxIQ=Wxbolq2j5VjQe5vYVMgcSwMyD7qp2/7eMCesJrWUM/E=ux785A8C7k+JZOUKuUtUspOEBBo=nr9bxUFwrD3dmOCAyjC8J/NzHDkvCap7qqNkyTQBjAY=fWzAHMMua1p1MJ1GDL37SL1KeFU=ManufacturerZZ9000000012Issuer0OCRA-1:HOTP-SHA1-8:QN08-PSHA1-T30SDkhESbLkTy5DwNg45IDIveBsoxvDAk3MDdneVNlDhZOaI0w1QVwSLwJpSsnISD8x93VEBONW7G4UETgF24EPHcvBvMc=oRjGEzmzMgt7hlTc1+pzulebdNovZRRYq8vyfl82M74=3u17msMz3fL4F5cRqHzVla2SExQ=dsyLJivZLCS3F0AznNakR49t5NyQiD1dhHGJGFw5nWY=VevPslQOu3j8iqgMe9djGxOLD1w=ManufacturerZZ9900000012Issuer0A4WAKWpJ7cLE9XiPEbKw/CDmAi7H8rtV9p1shcoo2YrVOgBmiXACT8VUaFLSfMzB41GurI1n2Z6Kk9UKBoTnxH8j+N0=ManufacturerZZ9000000013Issuer0OCRA-1:HOTP-SHA256-8:QA08-PSHA256-T30SzrXIQIx5F03cfaRYwJlnwnDDFzTAwxjtF4KnN3Brs9kISfX/0QfJ80lHtQn45dtHaAcmtJMgfI19DpvlSVPLOw==UhS9tgvwHZnuK1qN8yaZIfxPTFE=2Kmbco73faBtAE1GQGZlW2CudPy+1ttdv4uIhucZJPM=D9ug++7+F8Vgz6EdIZYHW5zgN6E=Dsa8e4kXeccvO5bV1hr3aNgOr9iSlGizp2BvvFkZ7jI=Pdr9y7KRtpkLDYeN6IUQ/Mt7Z0o=ManufacturerZZ9900000013Issuer09Jrxfs1YlWcOLlsnRsC9BCr1jsLeCxuzFMu3fb2UifkAFzpyIJmdGGLrHVgz6XuqTq6R+wvTczNyBlqL6Eec4g==DEd9uUkZS5otw3NoAljFYCiG4M8=ManufacturerZZ9000000014Issuer0OCRA-1:HOTP-SHA1-6:QA0693rMicSI4iJq4/GnE8xCHPCZg6yDxbyhHYMULxfNJTl9o0ej5e0NJai7idXpZ0dygZaMejTDMm7HzBlr2s9JSVYMYuQ=ManufacturerZZ9000000015Issuer0OCRA-1:HOTP-SHA1-8:QA08v/a768VMIE/ILhM0ECvN2SRdQOy/aRAbDGyQ/zhKdhWwxaX3yqWdj7Jf2XfUuccba9c35TuApOMWiTcCvTmqoAz0cTo=ManufacturerZZ9000000016Issuer0OCRA-1:HOTP-SHA256-8:QA08+IwfWgQtRPu7ltWK15F/VO71E6HTGF2qxUcGtxToXRklDD9k2fNuz/XrM1txjV2SVWCUIZc0V38na6h4MJi0Sw==bkKCVLivS0quRvTQg2x1gvX6kY0=ManufacturerZZ9000000017Issuer0OCRA-1:HOTP-SHA1-6:QA06-T30SESnLJlQD85YtFq5sDwLo4mwlMKbNgnCIk1wgVzZlCKH+KVFCr0S2HcdqA18iHklfEsd7kmOtLwGsnfvnEAUW+gcEFMQ=9sem1svGk57xGg9vw68KnUfw1xPJOOmAEFe56FijwLw=qG9EiRdQi9CBD8N6AQBLnbRdrgU=IjjpMqJQ/8PhB8m8CHuOQY30LRtdxZU5I9/DB6yYAZo=v6fPyj34+fDabjOGOMcHs+UYh64=ManufacturerZZ9000000018Issuer0OCRA-1:HOTP-SHA1-8:QA08-T30SFNJtVaoXkbTL55ZiLaJskew4ldRebOAha+SErJfRw6KYeeQDoRvAKoCvJSprpNHQOsA/kz7r9aJFsIi+A9fnZBQMNwA=cX4bi1203JI48hWsftfzNDqd2HEjN4GOLZZZOmcwMBM=PfWAQmkkgDoVHzSuP0z3Zp1y0ME=8f6TlXcEKVozm68TSE0bEzAvN2VmLlKOAB/XJANFFXQ=fo6Psq89kQEFJMVBMP9V7XqVbJM=ManufacturerZZ9000000019Issuer0OCRA-1:HOTP-SHA256-6:QA06-T30SkgcOrgBjm4ko/wb3zauucOANcNJK9pm//dwEs1tVc0DVC9y1k6AihtLVc+T+Qj5O0/ozYwP5AbLxvCK/7l4vKg==wWE/RcUaiD91hdPtIyel0we6acA=D8E4aP+BX+yOrCqbMm6o4nrc1gB4JMVhwp6hUFaIVo8=2XGFP2m0aAL9qjmRECqFPlqLQrk=KwVJ2SykUK4A0/40Oc4S0EsMkNiSoLA5BrVYMV69MhY=cMGUHUA2Aj/724eezVljb351d5E=ManufacturerZZ9000000020Issuer0OCRA-1:HOTP-SHA256-8:QA08-T30SA1uqZaf3GyTvwkx2v6uh9pKRzil05k+Cof3CD8jVZalmWgfp6mnDxGDOEB1fHgN4nHqocpgaNDeukbMNkyEfIg==1pU7gblaKHQMg8M4+OnU1y5z580=bUyYhX5ZXmS7aAmJO3uTuhj3HWuj0FYNMXRPkbtHkrE=GbilU0KldLYm2qzgmerdi/YdmVc=BOpE7cNil/vt29rZZv9oRnXaH6Be5+nZsVr2ld71pew=O4v8BGevBGDlgD96lvM0g+bnndA=ManufacturerZZ9000000021Issuer0OCRA-1:HOTP-SHA1-6:QH40cW7wssP4ofGpmXCMygejt6THgKKK6oE+vZp67VXR1dYZCrH6d5kmgPB4C+0isgl07ez8ijPPcmu5akvkEj3hNQzdzdo=ManufacturerZZ9000000022Issuer0OCRA-1:HOTP-SHA1-8:QA32Atlpw0AmlQZeqZjUbyAx/dGxPOCt+nfmzl0L5D0AvuDuRsRxeE4sOnCYt7EULSk9IKARR2Vs2kdLv4OyaLX5ylKrbyw=ManufacturerZZ9000000023Issuer0OCRA-1:HOTP-SHA1-8:QH40i71y5cubNftGXQelHjkN9OaDfmIxl5e6EDAsPU1czgbJ9EC4VoBL3lWeBAUzKF0ClbaC6WXRCoqcoTyJURdtgHqeqiA=ManufacturerZZ9000000024Issuer0OCRA-1:HOTP-SHA256-8:QA32JTz8SLxWk3KJndIFj8cFStAL4wptD918xF43lkX69CGi3Cv+SIvxcYSFkz+3cudIEitqnWsJeUFWGoPyh8kT/w==XoweMnAqLMh2KWnCDNx0rpoPucA=ManufacturerZZ9000000025Issuer0OCRA-1:HOTP-SHA256-8:QH64EHhmfSBnElpTYcvgx2LgoQaxfQjuKXR7p78BWNpt9ToT5EufihiAhe6CMACVfV/dIeiEJpaFgNtrPQT9/CJ24g==ljgNnKpoBXVOjsQbzPBGcS5ogqw=ManufacturerZZ9000000026Issuer0OCRA-1:HOTP-SHA1-6:QA32-T30SvwpradcA029rA/yj6HcUJwl5ylkrEqp8aNALDwiMMu9A+Oz6jE60mvTAGCZqSSoeN9zH7bQi+euQZ94scr12ZdR5Dek=YAK8r3BxdVd9w3aO37G0ZHQ/sQWAK+GA9RfcFP15pcA=gwItZVroPaJdiZYTfH4p+fX6mZk=DswW6MGFaOUNlJWef9kJ+rmfZqUyhZdn8Hyj+6P+RPw=4iLnj+1VWxtPDSdCTbO8mmffzU4=ManufacturerZZ9000000027Issuer0OCRA-1:HOTP-SHA1-6:QH40-T30SoVP8EpcI8WA0Qd7PPsYSG9TSP9T+Qrj9I4euTDqPkAvGAeVmjagHBibpFs10k8bBVU/O2/FQZw9ZzYLVUJhs1zK+MU4=jL4JpbKmkvtVdthap2Mmsl6DK7H0FM+SAhsSuPmrB9w=UTlNoBXskvQydI+kPJjy5BVDwvA=NGwF8XfW28qcqGlxkim7RxmD8+blsMmV3a5CjQ+b2H8=cIAvElM0Xob5tamSmbTmUts6FO8=ManufacturerZZ9000000028Issuer0OCRA-1:HOTP-SHA1-8:QA32-T30SinBVnKPWyvjYRemW54qbFCkaV8QSdkM+yHnvWh+tc5QMgiv8+3+IjwVV60/7jHI/X+hTYvcTw8bFcMCcXm8YnG5BObA=lnGeD5pFoGu8FMJwblbKKCwTFOitZpFLz+jHlFuKnxw=GxJhXxwDE3E75whjA81ww2xiDbc=HaHrtO8BBYHkXLfXdiQCoemwr/LeYdDvHC+4V+HgniU=VBsN2fam75+cNarN0OfKXYks9Tg=ManufacturerZZ9000000029Issuer0OCRA-1:HOTP-SHA1-8:QH40-T30SI+ifaWEYZ4anCimA5BA1mwGMYkczCgDJGs9jhtTbviNhFUxwxj7ejCMgsf6cEk8X1t7hwN9GMibAcCcN2sJr24tUZcA=pOklWp9UxC4t4zQqsW/xUbWBoChfc1VBNlA8SeKxusA=mwd7xtihR/iBVsHK+UGAZfEDSFk=epsMCzzn6JvhOhYK9tLIxaOR0bEHcFAoTcu5qk0Hcho=+yUkk7ZmmjrjMVc3xPz5ROlKgI8=ManufacturerZZ9000000030Issuer0OCRA-1:HOTP-SHA256-6:QH64-T30SO7+CQ9pcYN0MtbqiZ3g0Tso3CvYzCrDTV9Kh+wwqZkoTV5/jg4l/eZXfRM8E8JkD1K7MlgibA0PhwtlV8DMIIA==aKwqMJLS4MICd4PMGKUsvb0lbIQ=jz2bjkZ03IjwWyl1gZqiHoJjv/AjK7/TOA9qW6fLfhQ=NHfciyj3u1IhENAoBvohcRF9Wx0=KO0Po1NyLSTkHf6niARIocLJUXLrdz7e4qiowd/NPtE=mdbPUCunsrog0KT3uo4MRQZiMqM=ManufacturerZZ9000000031Issuer0OCRA-1:HOTP-SHA256-8:QA32-T30Sd17Oq0cv1HFK/XEAmASgK0DlMHiO8W950Wpf5is4BMXY9D1ot5n5nnZGEftd8IoX95il7K5aBdKfBFnl2ZuFZg==p2yeNXvUOxKsU29oz9b66s2XrkM=TotQApim/RCnT8MhiFkudOaBptHFU9iESxQk9exa7mE=R6Nmk8Gjcx35T4XYIk4I7uG56V4=8O/Hsl1q8sR302k1CJWyFtrmQUWq/ZrE/WwEZY7Bg4g=1K2lB+KnTWAKicqqpEWf91XAIj0=ManufacturerZZ9000000032Issuer0OCRA-1:HOTP-SHA256-8:QH64-T30SjAZ2fmJOcMxeOB/0uc1Oo4egMStEMpPHGDpN11wuCv32WrshyMIuZ8pLaYHE1M0jRWRYIy2bTxSiEt4OvSZoqQ==XdjbNQkRNpkHtXk3Nw9YqFcA4mE=zLRD33LH/x0e4vZTMpeBBRa+1JWmr4J5qNmLh+p1eTo=dyaS1yjyDW4PSt9vGHyn6sBmKas=1or92lj58ODH7gFDQmD9kydoPSMb2oMdDpo5AwP79Zk=z0tP1ak/jhxAyUs3t2Y83DxBnOw=
python-pskc-1.0/tests/multiotp/pskc-hotp-aes.txt 0000644 0000000 0000000 00000010472 13221434050 022023 0 ustar root root 0000000 0000000
Pre-shared-keyq+ZCUiDoHbBbug4XilKtI+9T99F/xZsb1T1ra35qGwLLA97mHDWnptpFIBZu8HKyManufacturerZZ0000000000CM_ID_007Issuer0Ze63bjoRjdZRxFs8RouCE5BMP/ust0gAhCd0O8BWNn1e8JSg74hgv3/QIxv4r3lwPrKAa1V4eIP98Lh5Yv1hcSmJoxs=FErCcdjsEpAu/P4xvt3XbIsAm6FC3HpzZdRsIN/wADo=kuha13YGJLmwKRxt8fDY03IoGxk=ManufacturerZZ0100000000CM_ID_007Issuer0Ce/Mwl6WBZfuPI4OIoWhcY5G46oAznRrsYQ1zBhqMblJyf44+UIyYMHQ5gFY/e9Zqa4SZaEiwoSIDgrnvKI5c0/1nEg=n4g7hX9wq1wrxRX9W21p2FQgAjOwnvYFye8D8n4+Nc0=5mEA0KtQRmpMltb3/AkCUzwbdaw=
python-pskc-1.0/tests/multiotp/tokens_hotp_aes.pskc 0000644 0000000 0000000 00000010447 13221434050 022655 0 ustar root root 0000000 0000000
Pre-shared-keywrjW00DjkG/3Ti5w/+MmSQAiBWho8CPjT7GtcQ59PWaOd00kXObQ0N2DZXEViu4iManufacturerZZ7000000001Issuer0HMAC-SHA256yEJynmqPig2WnnEdBaT7Yq3XkPbPiTNpaGmJFVOyzxzS3aSYEAZRCEf7wHA3Fvk/kDHEvQks3t9eVV0JV7Y/uA==OEPJcjpyjHKZSFheQU551nb0ls4=mZ+p+VX8Trg1lKkndr5p7/O9ZyS2dU5DD7cpZljhQ7o=sFa44n9rrsfWq+KcIffF1Xl3Auw=ManufacturerZZ7000000002Issuer0HMAC-SHA512fE30qGXYpqP6D4k5cCHVMMIkaJr4cJZN7oOjRWZ8DOJ8Wk3e+MoUUO3wyRa3UOz5feOvXMGlhUH9uuS0G88U9QIYcuscHuh6X/BTAaaJyrOQZmZOzfBq9Mky8+/dihHWKxF+McV9MoIwJWZkmVturQ8/GwA=u2U0Zys7h+Le/fE6Y36E3+jvU8501bQaZ9bDnQKoFTo=qIg1lePsFokepjWYGUh14kqzsYQ=
python-pskc-1.0/tests/multiotp/tokens_ocra_pbe.pskc 0000644 0000000 0000000 00000005331 13221434050 022621 0 ustar root root 0000000 0000000
rnTsZXC4HbpfBiJGK11/KQ==100016Passphrase1IAC/XU8Vmm4gORzC8Ugdno/AQ6lz5jpG6y91E3dlAcEjjHzG+m4tTeUeiXPSzwoZManufacturerZZ9000000000Issuer0OCRA-1:HOTP-SHA1-6:QA0683rAhYlnYh6m4EwttqwBszP4oamM+NS/t01EJo1NEFuZtjVfLaE7xgin6Fo/1Bdol383liW+s2jGwCN8OGnlto2hx9U=
python-pskc-1.0/tests/multiotp/pskc-totp-aes.txt 0000644 0000000 0000000 00000015430 13221434050 022036 0 ustar root root 0000000 0000000
Pre-shared-keyaJtbg144FkWB+rAdIZLEYGeBguzImDC/s+u6hvQpbn1HKH4d9okcfRVDE1eKc/emManufacturerZZ1000000000CM_ID_007Issuer01/4klvTYXsM7n35dRXD8DEUmG9msDTFGj7iYPbSWd62eEdnBUDTHvuHSo0H1Oa8xFSJiyiNOXUvDl42te34LPpmQA00=030ManufacturerZZ1100000000CM_ID_007Issuer0LHgaE/1m+DeXjNhWVA5BJsX/yv6KZmAWnVG5q6dQkkWbGu0ZO9QvqhBUxU0qoe4W1HZvOMVLYU52SnE+98BTxDZrDds=030ManufacturerZZ1000000001CM_ID_007Issuer0HMAC-SHA256gDk4Xm6Ab4cABb8oVzDbxbHP2UqjdhwuoV2z9NQvW5uV3dqpU7uApCTF9y20IX8uEViEur80QzgftiNlnX6RRw==H1cru72pCCWvSuL7jES0Rp0nKOM=030ManufacturerZZ1100000001CM_ID_007Issuer0HMAC-SHA256HbKFsvEFtqDd8fOrKbigA+7wv47Fdfv1m8BWclZGupgixFiqY9B5LrZq7e5Vd4QcTNatmMW2fDmNdSVbMs91rw==xQvm4mScVI+7GivxvodoTIy3rLs=030
python-pskc-1.0/tests/test_misc.doctest 0000644 0000000 0000000 00000016773 13221434050 020322 0 ustar root root 0000000 0000000 test_misc.doctest - miscellaneous tests
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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 warnings
>>> 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)
>>> import datetime
>>> now = datetime.datetime(2016, 3, 23, 0, 0, 0)
>>> import dateutil.tz
>>> 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)
>>> len(pskc.keys)
0
>>> len(pskc.devices)
1
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
We can also put device-specific properties in a device:
>>> pskc = PSKC()
>>> device = pskc.add_device(manufacturer='Tokens INC.')
>>> len(pskc.keys)
0
>>> key = device.add_key(id='123', serial='456')
>>> len(pskc.keys)
1
>>> key.id
'123'
>>> key.manufacturer
'Tokens INC.'
>>> device.serial
'456'
Adding a key or device with unknown attributes raises an error.
>>> key = pskc.add_key(foo='bar') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
AttributeError
>>> device.add_key(foo='bar') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
AttributeError
>>> device = pskc.add_device(foo='bar') # doctest: +IGNORE_EXCEPTION_DETAIL
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
Setting encryption key name and algorithm also works.
>>> pskc.encryption.key_name = 'Test encryption key'
>>> pskc.encryption.key_names
['Test encryption key']
>>> pskc.encryption.algorithm is None
True
>>> pskc.encryption.algorithm = 'aes128-cbc'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> pskc.encryption.algorithm_key_lengths
[16]
>>> pskc.encryption.algorithm = '3des-cbc'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'
>>> pskc.encryption.algorithm_key_lengths
[16, 24]
>>> pskc.encryption.algorithm = 'none'
>>> pskc.encryption.algorithm is None
True
>>> pskc.encryption.algorithm_key_lengths # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No algorithm specified
Load an PSKC file with an odd namespace.
>>> pskc = PSKC('tests/misc/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/misc/SampleFullyQualifiedNS.xml')
>>> pskc.encryption.key_name
'PassPhrase'
>>> pskc.encryption.derive_key('3FCA3158035072D6')
>>> key = pskc.keys[0]
>>> b2a_hex(key.secret)
'09fbecfd0bf47910839e2eb05ffa10b95cd0390950ce32ab790583ed134171e0'
>>> key.check()
True
Empty PSKC files should raise a useful exception when trying to derive an
encryption key from a password.
>>> pskc = PSKC()
>>> pskc.encryption.derive_key('123456') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyDerivationError: No algorithm specified
Integers can be represented in different ways in PSKC files.
>>> pskc = PSKC('tests/misc/integers.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> [key.counter for key in pskc.keys]
[831791, 709791, 405834, 298507, 961392]
This tests key policy and unknown policy elements. The first key should have
all known policy elements set while other keys should have extra unknown
information added which should result in rejected usage.
>>> pskc = PSKC('tests/misc/policy.pskcxml')
>>> key = pskc.keys[0]
>>> key.policy.start_date
datetime.datetime(2006, 5, 1, 0, 0, tzinfo=tzutc())
>>> key.policy.expiry_date
datetime.datetime(2026, 5, 31, 0, 0, tzinfo=tzutc())
>>> key.policy.number_of_transactions
4321
>>> key.policy.key_usage
['OTP']
>>> key.policy.unknown_policy_elements
False
>>> key.policy.may_use()
True
>>> key.policy.may_use('OTP', datetime.datetime(2005, 4, 3, 0, 0, 0))
False
>>> key.policy.may_use('OTP', now)
True
>>> key.policy.may_use('OTP', datetime.datetime(2028, 12, 31, 0, 0, 0))
False
>>> key.policy.start_date = datetime.datetime.now() + \
... datetime.timedelta(seconds=10)
>>> key.policy.may_use('OTP')
False
>>> key.policy.start_date = datetime.datetime.now(dateutil.tz.tzlocal()) - \
... datetime.timedelta(seconds=10)
>>> key.policy.may_use('OTP')
True
>>> key = pskc.keys[1]
>>> key.policy.key_usage
['OTP']
>>> key.policy.unknown_policy_elements
True
>>> key.policy.may_use('OTP', now)
False
>>> key.policy.pin
'1234'
>>> key = pskc.keys[2]
>>> key.policy.key_usage
['OTP']
>>> key.policy.unknown_policy_elements
True
>>> key.policy.may_use('OTP', now)
False
>>> key.policy.pin is None
True
>>> key = pskc.keys[3]
>>> key.policy.key_usage
['OTP']
>>> key.policy.unknown_policy_elements
True
>>> key.policy.may_use('OTP', now)
False
>>> key.policy.pin is None
True
This checks the ChallengeFormat and ResponseFormat handling of keys and
specifically the attribute indicating presence of check digits.
>>> pskc = PSKC('tests/misc/checkdigits.pskcxml')
>>> for key in pskc.keys:
... print('challenge %r %r %r %r' % (
... key.challenge_encoding, key.challenge_min_length,
... key.challenge_max_length, key.challenge_check))
... print('response %r %r %r' % (
... key.response_encoding, key.response_length,
... key.response_check)) #doctest: +REPORT_UDIFF
challenge 'DECIMAL' 12 34 True
response 'DECIMAL' 8 False
challenge 'DECIMAL' 56 78 False
response 'DECIMAL' 9 True
challenge 'DECIMAL' 16 87 False
response 'DECIMAL' 3 True
challenge 'HEXADECIMAL' 4 6 None
response 'ALPHANUMERIC' 6 None
This checks an PSKC file with a number of different empty sections that
normally contain data.
>>> pskc = PSKC('tests/misc/partialxml.pskcxml')
>>> all(key.counter is None for key in pskc.keys)
True
A typo was fixed and the old name was provided via a deprecation wrapper
property.
>>> pskc = PSKC()
>>> key = pskc.add_key()
>>> with warnings.catch_warnings(record=True) as w:
... key.policy.pin_max_failed_attemtps = 10
... assert len(w) == 1
... assert issubclass(w[0].category, DeprecationWarning)
>>> key.policy.pin_max_failed_attempts
10
>>> with warnings.catch_warnings(record=True) as w:
... key.policy.pin_max_failed_attemtps
... assert len(w) == 1
... assert issubclass(w[0].category, DeprecationWarning)
10
python-pskc-1.0/tests/test_tripledeskw.doctest 0000644 0000000 0000000 00000005260 12676003033 021717 0 ustar root root 0000000 0000000 test_tripledeskw.doctest - test keywrap functions
Copyright (C) 2014-2016 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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) # doctest: +IGNORE_EXCEPTION_DETAIL
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) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: Ciphertext length wrong
>>> unwrap(ciphertext[:-1] + b'A', key) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: CMS key checksum error
python-pskc-1.0/tests/nagraid/ 0000755 0000000 0000000 00000000000 13221476257 016347 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/nagraid/file1.pskcxml 0000644 0000000 0000000 00000007623 12676003033 020751 0 ustar root root 0000000 0000000
Pre-shared-keyOdudVkgsZywiwE1HqPGOJtHmBl+6HzJkylgDrZU9gcflyCddzO+cxEwzYIlOiwrENagraID Security306EUO4-00960306E880479B6A2CA2080OCRA-1:HOTP-SHA1-6:C-QN08-PSHA1VHdEP8TXnMmE3yiAnB5Fx+SQ85UXCNAxH7IyOixJpUZHMk9GTdFYWNsxZp8jVpfpuQ1Bef+XVXHQoW4ZzyQ/cv/9zYA=0NagraID Security306EUO4-00954306E880489CFA2CA2080OCRA-1:HOTP-SHA1-6:C-QN08-PSHA1YTvA1cSntb4cPJHPFkJwuSZkAsLPo+o1EJPA22DeijZRaKhJAwArQKbwDwSmNrR1N8QGRQ7yKd8suyUgaEVme7f0HrA=0NagraID Security306EUO4-00958306E880497B3A2CA2080OCRA-1:HOTP-SHA1-6:C-QN08-PSHA1BdxW7Pb46LafGV8k2zDQ48ujoyYX7M+JumfS3Wx5dP1E9y5By/97QTMiGkzJrcWjWGhmLhbGn4Dksa7lHKfKOqbsJhU=0
python-pskc-1.0/tests/test_nagraid.doctest 0000644 0000000 0000000 00000003376 13117007614 020775 0 ustar root root 0000000 0000000 test_vendors.doctest - tests for NagraID PSKC files
Copyright (C) 2016 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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 a simple PSKC file from NagraID which is protected by a pre-shared
key. The file contains three OCRA keys.
>>> pskc = PSKC('tests/nagraid/file1.pskcxml')
>>> print('\n'.join(key.serial for key in pskc.keys)) #doctest: +REPORT_UDIFF
306EUO4-00960
306EUO4-00954
306EUO4-00958
>>> key = pskc.keys[0]
>>> bool(key.secret) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No key available
>>> pskc.encryption.key_name
'Pre-shared-key'
>>> pskc.encryption.key = a2b_hex('4A057F6AB6FCB57AB5408E46A9835E68')
>>> bool(key.secret)
True
>>> key.check()
True
>>> print('\n'.join(key.algorithm_suite for key in pskc.keys)) #doctest: +REPORT_UDIFF
OCRA-1:HOTP-SHA1-6:C-QN08-PSHA1
OCRA-1:HOTP-SHA1-6:C-QN08-PSHA1
OCRA-1:HOTP-SHA1-6:C-QN08-PSHA1
python-pskc-1.0/tests/certificate/ 0000755 0000000 0000000 00000000000 13221476256 017223 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/certificate/ca-certificate.pem 0000644 0000000 0000000 00000002244 13221434050 022555 0 ustar root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDQDCCAiigAwIBAgIJAM7+vdiCdiCaMA0GCSqGSIb3DQEBCwUAMDUxCzAJBgNV
BAYTAk5MMRQwEgYDVQQKDAtweXRob24tcHNrYzEQMA4GA1UEAwwHVGVzdCBDQTAe
Fw0xNzEyMjMyMTQ5MDBaFw0yNzEyMjEyMTQ5MDBaMDUxCzAJBgNVBAYTAk5MMRQw
EgYDVQQKDAtweXRob24tcHNrYzEQMA4GA1UEAwwHVGVzdCBDQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANHVvzM2MKXJL7SXKddCl7jrs7FKNHGEUimq
l1qLa+dF/dsEyqidqLBmmbMxKQcqMcsqovKPpTM7dWUmv7P7Cr8ZR0tVmD6JvQlQ
4dmWngbR0zkVPKP9ZzZFLW1LEkHZUOvurdLeyo+33xomGnosFVLsDX1mo3h41OUT
kJkCxnG1HZMudpQXzrOUmdCCMV9qsS5iVeLJItW6BzbjIYYx6vdAVgFELofPqMtn
vPVmLkMCUyMPEKbqjWP23A93XgnwoFFppsSmhqK9JLRrHPVTaAV8UNvQhN8hdEn3
biOBmtpH47tUG++RQMsV9E5kA9WoiisyMdTlutcdwnYo40L+Zs0CAwEAAaNTMFEw
HQYDVR0OBBYEFNpXX0X0olR9QDVSBbyn44jP1wDmMB8GA1UdIwQYMBaAFNpXX0X0
olR9QDVSBbyn44jP1wDmMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggEBAGzWcea0i5H8MehTL35d8TMkjMp1R5Eoy6t1IhlYzZczphqzXSb76rA71oqX
Ti17mEBH+SCwR3L+QL6uZYNK5JKHwQPbPmBErxwcymmJXK6Y5sOwliSTjeRaujcN
r1h6qxKVshQFlMinS3VuQvT+fa2c1FMHnzBx0jN/4edsjJzBYChAs5dYgSKPLSua
NQGSJKJucTq22JBhu1c2+DwhqM9MbycyvhwZjw4p0yfFSUyTmxFm6DMZaI3P6F+P
YtIrwjaoZt8Hh+YaOUBM+3AJ4nSAkvgy7BsLPMxKFhCJqdAttQMDKpWmhgl+MtNQ
ZAisAuYRkr0ZkgqE5+3bIERA70o=
-----END CERTIFICATE-----
python-pskc-1.0/tests/certificate/ca-key.pem 0000644 0000000 0000000 00000003250 13221434050 021061 0 ustar root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDR1b8zNjClyS+0
lynXQpe467OxSjRxhFIpqpdai2vnRf3bBMqonaiwZpmzMSkHKjHLKqLyj6UzO3Vl
Jr+z+wq/GUdLVZg+ib0JUOHZlp4G0dM5FTyj/Wc2RS1tSxJB2VDr7q3S3sqPt98a
Jhp6LBVS7A19ZqN4eNTlE5CZAsZxtR2TLnaUF86zlJnQgjFfarEuYlXiySLVugc2
4yGGMer3QFYBRC6Hz6jLZ7z1Zi5DAlMjDxCm6o1j9twPd14J8KBRaabEpoaivSS0
axz1U2gFfFDb0ITfIXRJ924jgZraR+O7VBvvkUDLFfROZAPVqIorMjHU5brXHcJ2
KONC/mbNAgMBAAECggEAd1IXtrRi93MvGH85ALpScoo9lVw/9CktW5oxFQDhLBNF
5oyT/Uwhx6WgUyBqVzOsOv6UyF/crgRqnklpi5v+oWprezBCMZW7lBeVRlj6paAd
f4FPCWWokljGGzcsO5urA26nE5kzEISbblAqkDyPJ6cpiJyb6n6zeUwl1oyYccFn
Vw7x0PN2QnFcTWzTxfhDAZpZeF/yYBzVR78BrYJ25HXf394MseiC0SWTbmUQlZzU
0jwkGFEQQ3w+Q9i2GfiBp2801sRF69nFDcAin7RPAFajiKAIrmtaGWZbL8C+OIzO
I32TPWOiA3swKolFFjsSA80DYFXAOYlHpd9j/FWutQKBgQDs5c6YukeZyq0scLRl
jFtzeul4n1VOJrKT1xh5ivaMTdwOzNbuaOCpReNQoUsHoFUGdO6lxIQqMjjHNoiA
/WnK7VHIC3Voc0U8HCvYN82jAuYpc7NswpvEefXB8Hq8jGwvvn/WNiGeqjvhbSdr
8NZPBrGY3X3W2iUMZeGGWu1BVwKBgQDiwUsrjP3RVRXKTCESak8AoiwzrokutMN5
zMdbIWPh5q5/yFMIfj6PkThOixI4jY0EedunL+7l5obKsTGs1ArwyU6jL9vtKHT4
GiUtGN1emPA9TCiLFKEM+fDWtoU3aw2CC6Cy+0lZANpg4FV0wrM4mhBtPZpSTRJP
3gP2O3TOewKBgQDJFFdEc/mKpOpRKmk1OOn3H4FFDZmO+HHj41O29ylG9l+vgFd/
ji4EAHpuWjohgwoorOBUfHXiizZ+gd9j/bXqtX4RWwiRXCaMWy0sHlHB6BgNX1QR
IS6umwbrU4AawuahsAU6gkAPOVgShGBN0uNVuMzOgFLsJ8YCvAetz0hLiQKBgEnn
lghru44unRuCFIVcAMPEF4QArtV6NPvNsmwRReyqstUTBX0PKHTCcAepbBbii8OZ
s+2ZwdNMf0Iw3m/l7GruMaMeRh/Vv8ndO6CnKf9a+XIBEHXEE3mXPNe+RUz6pzxr
bcToAc/wZzyL4U5c5uzbyd7Q5StN01oUklX+quNhAoGAKLS5887ucfeFAjNxeNk4
hsSFw7CDIHfuJbTpzitf8bTbqy1SqB8DSW1S5zCuz8yxBIO5HjF4rT2VT0F2FN0o
7OrhXiqU9mAWxObvfObDlvOoLcnPvvayotUdlcboBqCxHAjF8jNBYAQuKXneSLFa
IEo4FtswiJCmqC9tsMEy7Wo=
-----END PRIVATE KEY-----
python-pskc-1.0/tests/certificate/certificate.pem 0000644 0000000 0000000 00000002046 13221434050 022174 0 ustar root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIC4zCCAcsCASowDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMCTkwxFDASBgNV
BAoMC3B5dGhvbi1wc2tjMRAwDgYDVQQDDAdUZXN0IENBMB4XDTE3MTIyMzIxNDkx
NVoXDTI3MTIyMTIxNDkxNVowOjELMAkGA1UEBhMCTkwxFDASBgNVBAoMC3B5dGhv
bi1wc2tjMRUwEwYDVQQDDAxUZXN0IHNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDm2d2AodQmxQvK/pSr/6LAdj4U2RM3sUgd0P6f2lAcs5+O
Tr2s+6SWkMEnpm4xzamfFr4hZrTi687Th404k16+VDgD2Fdlha1ic4ETH1WYAkA0
01HLXejZ/Jc1tYYb/mGD9L1fm7TliTc+2mL7efTMyRFzIGTLmhC7vfcnoTBRzNBG
g4uy0z0XK+KrDcnb8S1rbXLQWazsQAEi9FAB3ArYkvE8gYgDs666W3fg5EROb3Oh
GHcKmqECNoPaw4ZeElHTqbETp6YCda3NGgstio8Yc17OCuNg/xL4NSxosJWZiBQ/
aDDsXP1zBmF2xthaPAo1glBTACnaf0K37qgRE1XRAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBAHWvbyr/oip66z5tlQMxKCOqs6FVgYr3WxT8K3QsQeK2ELp64gfYKRLF
GqM6r7VHVQIUa/5BdHDnd9B3axeSDRRVQzJC6FNjcWvS/Pu+KIvwtUkH8t5T713u
q4VbdJwKMYRl3ge2cks4ydmMc7KmMMKVQ1W7EMEP2FPSohKTW9sJaWboRd4L5jMd
2iwnsfLMQHZIhUOH1gQS4dlnJ4PZaLBjsukChe5AfnA+LPiAGji/aqvi9tTjLVWb
z95OH5e6V/W1t0jtCp7O+h3ytNHmyIV8KYLn4Hu5tCnYGWTJ62NSYCtjhLkDpQrk
AL5yqfYZRyOjmtjBaq2tYxDCF76e4ms=
-----END CERTIFICATE-----
python-pskc-1.0/tests/certificate/key.pem 0000644 0000000 0000000 00000003254 13221434050 020504 0 ustar root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDm2d2AodQmxQvK
/pSr/6LAdj4U2RM3sUgd0P6f2lAcs5+OTr2s+6SWkMEnpm4xzamfFr4hZrTi687T
h404k16+VDgD2Fdlha1ic4ETH1WYAkA001HLXejZ/Jc1tYYb/mGD9L1fm7TliTc+
2mL7efTMyRFzIGTLmhC7vfcnoTBRzNBGg4uy0z0XK+KrDcnb8S1rbXLQWazsQAEi
9FAB3ArYkvE8gYgDs666W3fg5EROb3OhGHcKmqECNoPaw4ZeElHTqbETp6YCda3N
Ggstio8Yc17OCuNg/xL4NSxosJWZiBQ/aDDsXP1zBmF2xthaPAo1glBTACnaf0K3
7qgRE1XRAgMBAAECggEBANe+RIQyuVKCVMMhqVAVWMUFvH8/X/ZseDllWJGMNdKd
DECy1hEUHH2aSuoDYHiq/Brx5j7Q/bwVJPYLeK+nCqRVkQQG66a1h5S63aZBYnzt
nZPA6nBlE3il16saOntvATORRmpT2nuqATd8YYUkNOQuBWDSpWCiW4EZyCIBQmcu
fAzdLtZ2opok/k1ZmAYANDU1pOvOEl0hfd+hTyTeop6HzOhjkm5G3+mgQ6xkkgv5
bEeiddfM5RNN0jzyzlkS5qvuGH84oPLK/xH9/4iLLn1nNfPNSdhWVF1LWLv9ii3d
AtNcXErQBDlYjEVg5nfrTU2vWZBP963cpO4EPOH8uQECgYEA9cXHrjR59XI5mawE
EGanRhS/EDk4O/dYwgJiUBnFhLbJ8Nkq495I9b6XWer4TyzLCYdr4c+RUpnZy9eW
CmS0uT/Zu9/hP3FjVACCbIrAOSZxDJH08w0DzaVFQbIsLd94PSUSIB5LN5p+NerP
nC/+ZXL9FXg5rdKoHeL42LwdvhMCgYEA8HUgMlfwcqRhMlDLPXjutaCav4qcxIEg
HJ1dGyqBOefTyZ24CKcMxRX7CIHsxD43ksGaq7mNu66ArLt3RI0n1DxAjtOrhBP2
22qy1OldxGNW56fgLjs8n0GvOQ+AomVeA9dUh2mSHHpAMDyYn5J+FwNFIfpvPsgW
/5q9dH0ciQsCgYEAgy73X0fJVTaMAjdrz+lLHcATH7C7EF79NEkbes/WuvjqHzF7
kDdk0C7UEH233mL1WPe8oMIvOly+gOxovNF2GW84Y+yjCT8Xmo3PD+qboaWgL+p0
SYtH3Wt65Q8aP74JKvHvk883rM080sCr4h1H95SB/AS5HMvVSmaNFvqKEZsCgYBT
M+QJJ36u3Iw5UdWRlsfalxEU2Ay4vglzfUwbO1DtzXbuG6HzdIIprBpp7jMUEIdA
Q7eWViVh3Nbvbgqt8L3oZVTdVwbRM+tGLxxkKb/lVY5bqC4dMSgf3yWXc/LJvpOS
dq7sEC6bpf2R81XIWXNB2ULRqAtYSPoUReHqbeQy0wKBgQCA95LWgLzcKH7NxaWv
4Av/jbA1htqm/XKKm1D0vH/7mAag9QJN2EdUeaHaRYLSUJM5g/pkrpf2WBbWhDQG
3uJrw6kI4vYED4gNzE5KgoDDDuhxc0Ij7ocguTlz9nt2MMwMlWVSiabJvR41X9rn
3VGCRC+qCeFYeXYmM60U5W4OuQ==
-----END PRIVATE KEY-----
python-pskc-1.0/tests/certificate/ss-certificate.pem 0000644 0000000 0000000 00000002260 13221434050 022615 0 ustar root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIJAJsIXUIwU7CcMA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV
BAYTAk5MMRQwEgYDVQQKDAtweXRob24tcHNrYzEVMBMGA1UEAwwMVGVzdCBzaWdu
aW5nMB4XDTE3MTIyMzIxNDkwNVoXDTI3MTIyMTIxNDkwNVowOjELMAkGA1UEBhMC
TkwxFDASBgNVBAoMC3B5dGhvbi1wc2tjMRUwEwYDVQQDDAxUZXN0IHNpZ25pbmcw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm2d2AodQmxQvK/pSr/6LA
dj4U2RM3sUgd0P6f2lAcs5+OTr2s+6SWkMEnpm4xzamfFr4hZrTi687Th404k16+
VDgD2Fdlha1ic4ETH1WYAkA001HLXejZ/Jc1tYYb/mGD9L1fm7TliTc+2mL7efTM
yRFzIGTLmhC7vfcnoTBRzNBGg4uy0z0XK+KrDcnb8S1rbXLQWazsQAEi9FAB3ArY
kvE8gYgDs666W3fg5EROb3OhGHcKmqECNoPaw4ZeElHTqbETp6YCda3NGgstio8Y
c17OCuNg/xL4NSxosJWZiBQ/aDDsXP1zBmF2xthaPAo1glBTACnaf0K37qgRE1XR
AgMBAAGjUzBRMB0GA1UdDgQWBBREYylHi/+XUcO9hxafnxihX7m92TAfBgNVHSME
GDAWgBREYylHi/+XUcO9hxafnxihX7m92TAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
SIb3DQEBCwUAA4IBAQACwDauWH7v2tOWa0pWtQLr/6TWmYMdf99oz8adGwioer+m
oP97dcOsTWXyb1gw2SGuopd5HLrgZab7jC93bVIBLPjSj+8TxUlQpKtkyqPX9mGg
8kI+090Wz30mkRVA+YBjuUiyIHqX6+2tEkgK3n9QuU0SPb/joXnYphin7XEA7ur0
EKxJutt8ecBAq6VRRlLE0iKcEsY6MyVvspj5z1Q47vqdVtOR19Dsq73rUljkxu10
0lLc/nn1+wCz7nCgm7ps9H04nBk8+kxXrnvkWW5nUdSFG69VmRCmPZ74HdUvd61s
KjCcVuWQB7us2P3q1O9nu754P5K5UNq7mkXXy8cd
-----END CERTIFICATE-----
python-pskc-1.0/tests/certificate/README 0000644 0000000 0000000 00000001543 13221434050 020070 0 ustar root root 0000000 0000000 This directory contains keys and certificates that are used in the
python-pskc test suite for checking XML signatures.
The CA key and certificate were generated with:
openssl req \
-x509 -newkey rsa:2048 -nodes \
-keyout ca-key.pem -out ca-certificate.pem \
-days 3650 -subj '/C=NL/O=python-pskc/CN=Test CA'
The key used for signing the PSKC files and corresponding self-signed
certificate were generated with:
openssl req \
-x509 -newkey rsa:2048 -nodes \
-keyout key.pem -out ss-certificate.pem \
-days 3650 -subj '/C=NL/O=python-pskc/CN=Test signing'
The certificate signed by the CA key was generated with:
openssl req \
-new \
-key key.pem -out request.pem \
-subj '/C=NL/O=python-pskc/CN=Test signing'
openssl x509 \
-req \
-in request.pem -CA ca-certificate.pem -CAkey ca-key.pem -out certificate.pem \
-days 3650 -set_serial 42
python-pskc-1.0/tests/certificate/request.pem 0000644 0000000 0000000 00000001660 13221434050 021403 0 ustar root root 0000000 0000000 -----BEGIN CERTIFICATE REQUEST-----
MIICfzCCAWcCAQAwOjELMAkGA1UEBhMCTkwxFDASBgNVBAoMC3B5dGhvbi1wc2tj
MRUwEwYDVQQDDAxUZXN0IHNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDm2d2AodQmxQvK/pSr/6LAdj4U2RM3sUgd0P6f2lAcs5+OTr2s+6SW
kMEnpm4xzamfFr4hZrTi687Th404k16+VDgD2Fdlha1ic4ETH1WYAkA001HLXejZ
/Jc1tYYb/mGD9L1fm7TliTc+2mL7efTMyRFzIGTLmhC7vfcnoTBRzNBGg4uy0z0X
K+KrDcnb8S1rbXLQWazsQAEi9FAB3ArYkvE8gYgDs666W3fg5EROb3OhGHcKmqEC
NoPaw4ZeElHTqbETp6YCda3NGgstio8Yc17OCuNg/xL4NSxosJWZiBQ/aDDsXP1z
BmF2xthaPAo1glBTACnaf0K37qgRE1XRAgMBAAGgADANBgkqhkiG9w0BAQsFAAOC
AQEAB3SpoRLjnd/lWTyi4KLAPr4uZEK17wwtKm2aFHhAEPbQSrzvFUdWI2OjVo9r
nL9VgysozhmwWfWdVxc8kyYo6pNYWXqJtxFK8dItw06ptL8FETz0+ho+bcmeq6gv
l3ZKzbY2QKdxW/wP5iLuNPUkVYknP1wWzt1lM3ffou3RL/Vf+/XB+HadCD3w+7TD
XguTeU34alv050HvulO9gUpY35ZCC6rVO9ZefOFtTkT8zAhUqZFpWrnKxvcMXRSM
6KK0TWyoP9FGfnGxkW8ij94yiq+Vzyi57ZVC766y1x0j4Qj9ZFxCHn+wpDzxhgVy
clfeldkX2JtQL5C8IOJ7Kt4H1A==
-----END CERTIFICATE REQUEST-----
python-pskc-1.0/tests/test_mac.doctest 0000644 0000000 0000000 00000010652 13221434050 020115 0 ustar root root 0000000 0000000 test_mac.doctest - test various mac schemes
Copyright (C) 2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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)
>>> import hashlib
>>> from pskc.mac import mac, mac_key_length
The module supports a wide variety of HMAC algorithms.
Some test cases from RFC 2202 for HMAC-MD5 and HMAC-SHA-1.
>>> key = a2b_hex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')
>>> b2a_hex(mac('HMAC-MD5', key, b'Hi There'))
'9294727a3638bb1c13f48ef8158bfc9d'
>>> b2a_hex(mac('HMAC-MD5', b'Jefe', b'what do ya want for nothing?'))
'750c783e6ab0b503eaa86e310a5db738'
>>> key = a2b_hex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')
>>> b2a_hex(mac('HMAC-SHA-1', key, b'Hi There'))
'b617318655057264e28bc0b6fb378c8ef146be00'
>>> b2a_hex(mac('HMAC-SHA-1', b'Jefe', b'what do ya want for nothing?'))
'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79'
Some test cases from RFC 4231 for HMAC-SHA-224, HMAC-SHA-256, HMAC-SHA-384
and HMAC-SHA-512.
>>> key = a2b_hex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')
>>> data = b'Hi There'
>>> b2a_hex(mac('HMAC-SHA-224', key, data))
'896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22'
>>> b2a_hex(mac('HMAC-SHA-256', key, data))
'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'
>>> b2a_hex(mac('HMAC-SHA-384', key, data))
'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6'
>>> b2a_hex(mac('HMAC-SHA-512', key, data))
'87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854'
>>> key = b'Jefe'
>>> data = b'what do ya want for nothing?'
>>> b2a_hex(mac('HMAC-SHA-224', key, data))
'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44'
>>> b2a_hex(mac('HMAC-SHA-256', key, data))
'5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843'
>>> b2a_hex(mac('HMAC-SHA-384', key, data))
'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649'
>>> b2a_hex(mac('HMAC-SHA-512', key, data))
'164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737'
Some test cases from RFC 2857 for HMAC-RIPEMD160 but not all versions of
hashlib have RIPEMD-160.
>>> hashlib_algorithms = (
... getattr(hashlib, 'algorithms_available', None) or
... getattr(hashlib, 'algorithms', None))
>>> if 'ripemd160' in hashlib_algorithms:
... key = a2b_hex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')
... b2a_hex(mac('HMAC-RIPEMD160', key, b'Hi There')) == \
... '24cb4bd67d20fc1a5d2ed7732dcc39377f0a5668'
... b2a_hex(mac('HMAC-RIPEMD160', b'Jefe', b'what do ya want for nothing?')) == \
... 'dda6c0213a485a9e24f4742064a7f033b43c4069'
... else:
... True # some hashlib implementations
... True # do not have RIPEMD-160
True
True
Some recommended minimal key lengths for some algorithms.
>>> mac_key_length('HMAC-MD5')
16
>>> mac_key_length('HMAC-SHA-1')
20
>>> mac_key_length('HMAC-SHA-224')
28
>>> mac_key_length('HMAC-SHA-256')
32
>>> mac_key_length('HMAC-SHA-384')
48
>>> mac_key_length('HMAC-SHA-512')
64
Unknown algorithms should raise an exception for the mac() function.
>>> mac('unknown', b'FOO', b'BAR') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: Unsupported MAC algorithm: 'unknown'
>>> mac('hmac-unknown', b'FOO', b'BAR') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: Unsupported MAC algorithm: 'hmac-unknown'
The mac_key_length() function should return a default value for unknown
MACs.
>>> mac_key_length('unknown')
16
>>> mac_key_length('hmac-unknown')
16
python-pskc-1.0/tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/ 0000755 0000000 0000000 00000000000 13221476257 025644 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/securid-aes-counter.pskcxml 0000644 0000000 0000000 00000002116 13035526505 033123 0 ustar root root 0000000 0000000
RSA, The Security Division of EMC123456798Issuer2006-04-14T00:00:00Z2010-09-30T00:00:00ZMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0
python-pskc-1.0/tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/totp.pskcxml 0000644 0000000 0000000 00000002252 13035526505 030231 0 ustar root root 0000000 0000000
TokenVendorAcme987654323Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
304
python-pskc-1.0/tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/ocra.pskcxml 0000644 0000000 0000000 00000002010 13035526505 030157 0 ustar root root 0000000 0000000
TokenVendorAcme987654322IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=0
python-pskc-1.0/tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/actividentity-3des.pskcxml 0000644 0000000 0000000 00000002447 13036777211 032770 0 ustar root root 0000000 0000000
ActivIdentity34567890Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0320
python-pskc-1.0/tests/test_multiotp.doctest 0000644 0000000 0000000 00000005764 13221434050 021242 0 ustar root root 0000000 0000000 test_multiotp.doctest - test for files from multiOTP
Copyright (C) 2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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 some files that are shipped with the multiOTP PHP authentication
solution, https://www.multiotp.net/
>>> pskc = PSKC('tests/multiotp/pskc-hotp-aes.txt')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(pskc.keys[0].secret)
'3132333435363738393031323334353637383930'
>>> pskc = PSKC('tests/multiotp/pskc-hotp-pbe.txt')
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.keys[0].secret)
'3031323334353637383930313233343536373839'
>>> pskc = PSKC('tests/multiotp/pskc-totp-aes.txt')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(pskc.keys[0].secret)
'3132333435363738393031323334353637383930'
>>> pskc = PSKC('tests/multiotp/pskc-totp-pbe.txt')
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.keys[0].secret)
'3031323334353637383930313233343536373839'
>>> pskc = PSKC('tests/multiotp/tokens_hotp_aes.pskc')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(pskc.keys[0].secret)
'91f0dc4e239977e6bcc273e4f5414a8a6cf6d62c6990f58b4914a2d588b3475f'
>>> pskc = PSKC('tests/multiotp/tokens_hotp_pbe.pskc')
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.keys[0].secret)
'5d3a38bf5476d6f0b897f1e62887cb3ce833a5b9'
>>> pskc = PSKC('tests/multiotp/tokens_ocra_aes.pskc')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(pskc.keys[0].secret)
'e65f2c66247355fda360acdf3390972c16a1a164'
>>> pskc = PSKC('tests/multiotp/tokens_ocra_pbe.pskc')
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.keys[0].secret)
'4f40e1c6a7436e84620b170ceddfe110083cbd6d'
>>> pskc = PSKC('tests/multiotp/tokens_totp_aes.pskc')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(pskc.keys[0].secret)
'38c2506a8e0708a5e929c2686b827e0ba7ae28c9de3c83e6d27308345981a3de'
>>> pskc.keys[0].algorithm_suite
'HMAC-SHA256'
>>> pskc = PSKC('tests/multiotp/tokens_totp_pbe.pskc')
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.keys[0].secret)
'2c8792d34a3a8711b7cfc4304bcc84e3e67815a6'
python-pskc-1.0/tests/yubico/ 0000755 0000000 0000000 00000000000 13221476257 016234 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/yubico/example3.pskcxml 0000644 0000000 0000000 00000002755 13037001220 021341 0 ustar root root 0000000 0000000
oath.UB2835972009-01-22T00:25:09Z1ACME Inc.
K34VFiiu0qar9xWICc9PPA==
CN=ekhgjhbctrgn, UID=ca62baca62ba
python-pskc-1.0/tests/yubico/example1.pskcxml 0000644 0000000 0000000 00000002167 13037001220 021334 0 ustar root root 0000000 0000000
oath.UB2835992009-01-22T00:25:11Z1ACME Inc.
K34VFiiu0qar9xWICc9PPA==
CN=ekhgjhbctrgn, UID=ca62baca62ba
python-pskc-1.0/tests/yubico/example2.pskcxml 0000644 0000000 0000000 00000003271 13037001220 021332 0 ustar root root 0000000 0000000
oath.UB2835982009-01-22T00:25:10Z1ACME Inc.
Vpg1bTCGjEIB4m9mxYK7RQ==
CN=ekhgjhbctrgn, UID=ca62baca62baoath.UB2835982009-01-22T00:25:10Z2ACME Inc.
OIkrgqvxgHeIRY/FpRZcgA==
CN=ekhgjhbctrgn, UID=ca62baca62ba
python-pskc-1.0/tests/test_feitian.doctest 0000644 0000000 0000000 00000005207 13117007614 021002 0 ustar root root 0000000 0000000 test_feitian.doctest - tests for Feitian PSKC files
Copyright (C) 2016 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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 a simple non-encrypted PSKC file from Feitian. The PSKC file
contains two HOTP keys and four TOTP keys.
>>> pskc = PSKC('tests/feitian/file1.pskcxml')
>>> pskc.keys[0].manufacturer
'Feitian Technology Co.,Ltd'
>>> print('\n'.join(key.serial for key in pskc.keys)) #doctest: +REPORT_UDIFF
1000133508267
1000133508255
2600124809778
2600124809787
2600135004012
2600135004013
>>> print('\n'.join(key.algorithm for key in pskc.keys)) #doctest: +REPORT_UDIFF
urn:ietf:params:xml:ns:keyprov:pskc:hotp
urn:ietf:params:xml:ns:keyprov:pskc:hotp
urn:ietf:params:xml:ns:keyprov:pskc:totp
urn:ietf:params:xml:ns:keyprov:pskc:totp
urn:ietf:params:xml:ns:keyprov:pskc:totp
urn:ietf:params:xml:ns:keyprov:pskc:totp
>>> pskc.keys[5].time_interval
60
This tests a sample seed file originally provided by GOOZE for Feitian
c100 / c200 hardware tokens. There is one TOTP key and one HTOP key in
the file.
>>> pskc = PSKC('tests/feitian/20120919-test001-4282.xml')
>>> pskc.keys[0].manufacturer
'FeiTian Technology Co.,Ltd'
>>> print('\n'.join(key.serial for key in pskc.keys))
2600215704919
1000117803294
>>> key = pskc.keys[0]
>>> key.algorithm, key.response_length, key.time_offset, key.time_interval
('urn:ietf:params:xml:ns:keyprov:pskc:totp', 6, 0, 60)
>>> key.policy.start_date
datetime.datetime(2012, 9, 19, 0, 0, tzinfo=tzutc())
>>> key.policy.expiry_date
datetime.datetime(2022, 9, 1, 0, 0, tzinfo=tzutc())
>>> key = pskc.keys[1]
>>> key.algorithm, key.response_length, key.counter
('urn:ietf:params:xml:ns:keyprov:pskc:hotp', 6, 0)
>>> key.policy.start_date
datetime.datetime(2012, 9, 19, 0, 0, tzinfo=tzutc())
>>> key.policy.expiry_date
datetime.datetime(2022, 9, 1, 0, 0, tzinfo=tzutc())
python-pskc-1.0/tests/test_draft_hoyer_keyprov_portable_symmetric_key_container.doctest 0000644 0000000 0000000 00000011517 13221434160 032343 0 ustar root root 0000000 0000000 test_draft_hoyer_keyprov_portable_symmetric_key_container.doctest -
tests for examples from various versions of
draft-hoyer-keyprov-portable-symmetric-key-container
Copyright (C) 2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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 reads a simple PSKC file with a single non-encrypted HOTP secret key
example from section 10.1 of
draft-hoyer-keyprov-portable-symmetric-key-container-00.
>>> pskc = PSKC('tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/non-encrypted.pskcxml')
>>> pskc.encryption.algorithm is None
True
>>> key = pskc.keys[0]
>>> key.manufacturer
'Token Manufacturer'
>>> key.serial
'98765432187'
>>> key.id
'98765432187'
>>> key.algorithm
'HOTP'
>>> key.expiry_date
datetime.datetime(2008, 1, 1, 0, 0)
>>> key.issuer
'Credential Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> key.friendly_name
'MyFirstToken'
>>> b2a_hex(key.secret)
'5a57634c7670466f584e4841472b6c78332b5277'
>>> key.counter
12
This reads a simple PSKC file with a single password-based encrypted HOTP
secret key example from section 10.2 of
draft-hoyer-keyprov-portable-symmetric-key-container-00.
>>> pskc = PSKC('tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/password-encrypted.pskcxml')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'
>>> pskc.encryption.derivation.algorithm
'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2'
>>> pskc.encryption.derivation.pbkdf2_iterations
999
>>> b2a_hex(pskc.encryption.derivation.pbkdf2_salt)
'cba4f372478b450c'
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.encryption.key)
'651f8b1cafafa433d8c46ec996b3a274'
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> key = pskc.keys[0]
>>> key.manufacturer
'Token Manufacturer'
>>> key.serial
'98765432187'
>>> key.id
'77654321870'
>>> key.algorithm
'HOTP'
>>> key.expiry_date
datetime.datetime(2008, 1, 1, 0, 0)
>>> key.issuer
'Credential Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> key.friendly_name
'MySecondToken'
>>> b2a_hex(key.secret)
'65670bbe91685cd1c01be971dfe470'
>>> key.counter
100
This reads a simple PSKC file with a single non-encrypted HOTP secret key
example from section 10.1 of
draft-hoyer-keyprov-portable-symmetric-key-container-01.
>>> pskc = PSKC('tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/non-encrypted.pskcxml')
>>> pskc.encryption.algorithm is None
True
>>> key = pskc.keys[0]
>>> key.manufacturer
'Token Manufacturer'
>>> key.serial
'98765432187'
>>> key.id
'98765432187'
>>> key.algorithm
'HOTP'
>>> key.expiry_date
datetime.datetime(2008, 1, 1, 0, 0)
>>> key.issuer
'Credential Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> key.friendly_name
'MyFirstToken'
>>> b2a_hex(key.secret)
'5a57634c7670466f584e4841472b6c78332b5277'
>>> key.counter
42
This reads a simple PSKC file with a single password-based encrypted HOTP
secret key example from section 10.2 of
draft-hoyer-keyprov-portable-symmetric-key-container-01.
>>> pskc = PSKC('tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/password-encrypted.pskcxml')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'
>>> pskc.encryption.derivation.algorithm
'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2'
>>> pskc.encryption.derivation.pbkdf2_iterations
999
>>> b2a_hex(pskc.encryption.derivation.pbkdf2_salt)
'cba4f372478b450c'
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.encryption.key)
'651f8b1cafafa433d8c46ec996b3a274'
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> key = pskc.keys[0]
>>> key.manufacturer
'Token Manufacturer'
>>> key.serial
'98765432187'
>>> key.id
'77654321870'
>>> key.algorithm
'HOTP'
>>> key.expiry_date
datetime.datetime(2008, 1, 1, 0, 0)
>>> key.issuer
'Credential Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> key.friendly_name
'MySecondToken'
>>> b2a_hex(key.secret)
'65670bbe91685cd1c01be971dfe470'
>>> key.counter
100
python-pskc-1.0/tests/test_encryption.doctest 0000644 0000000 0000000 00000026616 13221434050 021556 0 ustar root root 0000000 0000000 test_encryption.doctest - test various encryption schemes
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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)
>>> import base64
>>> from pskc import PSKC
>>> from pskc.encryption import encrypt, decrypt
>>> pskc = PSKC('tests/encryption/aes128-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> 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/encryption/aes192-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes192-cbc'
>>> pskc.keys[0].secret # doctest: +IGNORE_EXCEPTION_DETAIL
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/encryption/aes256-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('1234567890123456789012345678901234567890123456789012345678901234')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
>>> 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/encryption/tripledes-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('1234')
>>> pskc.keys[0].secret # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: Invalid key length
>>> pskc.encryption.key = a2b_hex('11111111111111111111111111111111')
>>> pskc.keys[0].secret # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: Invalid padding
>>> 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/encryption/kw-aes128.pskcxml')
>>> pskc.encryption.key = a2b_hex('1234')
>>> pskc.keys[0].secret # doctest: +IGNORE_EXCEPTION_DETAIL
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/encryption/kw-aes192.pskcxml')
>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f')
>>> pskc.keys[0].secret # doctest: +IGNORE_EXCEPTION_DETAIL
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/encryption/kw-aes256.pskcxml')
>>> pskc.encryption.key = a2b_hex('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f')
>>> b2a_hex(pskc.keys[0].secret)
'00112233445566778899aabbccddeeff0001020304050607'
>>> pskc = PSKC('tests/encryption/kw-tripledes.pskcxml')
>>> pskc.encryption.key = a2b_hex('255e0d1c07b646dfb3134cc843ba8aa71f')
>>> pskc.keys[0].secret # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: Invalid key length
>>> pskc.encryption.key = a2b_hex('255e0d1c07b646dfb3134cc843ba8aa71f025b7c0838251f')
>>> b2a_hex(pskc.keys[0].secret)
'2923bf85e06dd6ae529149f1f1bae9eab3a7da3d860d3e98'
>>> pskc = PSKC('tests/encryption/camellia128-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('200497e673a6fae2256e9749468a67ac')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc'
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> tostr(base64.b64encode(encrypt(pskc.encryption.algorithm, pskc.encryption.key,
... pskc.keys[0].secret, a2b_hex('0efb65152f3f41995edb11bc3e37c0f0'))))
'DvtlFS8/QZle2xG8PjfA8Kg4bsjLlU8kH/sEfXC9VLWib2Z/WU8RDHR+fI9uCqOs'
>>> pskc = PSKC('tests/encryption/camellia192-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('e263279877384c84c987661a9d06766affdb9b3211eae801')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc'
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> tostr(base64.b64encode(encrypt(pskc.encryption.algorithm, pskc.encryption.key,
... pskc.keys[0].secret, a2b_hex('59AE4ACF5FC186EA94729432C3CA9148'))))
'Wa5Kz1/BhuqUcpQyw8qRSDwurIsm2vjUR/PO3w1Q3//PFfHod+DgBhRW2BecWpP5'
>>> pskc = PSKC('tests/encryption/camellia256-cbc.pskcxml')
>>> pskc.encryption.key = a2b_hex('33b37e31c5a0a16f004e7fe727d4ff808fc1f879d85ccd8f06dbb5799565d2f5')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc'
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> tostr(base64.b64encode(encrypt(pskc.encryption.algorithm, pskc.encryption.key,
... pskc.keys[0].secret, a2b_hex('5FB59EF9644400134F6B48E5B141D9A8'))))
'X7We+WREABNPa0jlsUHZqF5CWUQiPYdXJ+7ure96AcNH/7TXcQs4mFuSCOHpiv/W'
>>> pskc = PSKC('tests/encryption/kw-camellia128.pskcxml')
>>> pskc.encryption.key = a2b_hex('e35b135a09bfff8b314a5a0c32193c37')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmldsig-more#kw-camellia128'
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> tostr(base64.b64encode(encrypt(pskc.encryption.algorithm, pskc.encryption.key, pskc.keys[0].secret)))
'WB128TBZ1WGZzPNJNbwNrWRqQceU7M4FQSJPy2nw6iI='
>>> pskc = PSKC('tests/encryption/kw-camellia192.pskcxml')
>>> pskc.encryption.key = a2b_hex('5eb0bccad29abe52d143d5aebc1c1ba174b8d379ce763c28')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmldsig-more#kw-camellia192'
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> tostr(base64.b64encode(encrypt(pskc.encryption.algorithm, pskc.encryption.key, pskc.keys[0].secret)))
'y/wSpn3aNjeXzY1giHLOy0P+WQ+NmkN7EovBtHBXZ14='
>>> pskc = PSKC('tests/encryption/kw-camellia256.pskcxml')
>>> pskc.encryption.key = a2b_hex('0e187c656c36975b0d6bded79d7089142209457114ce8e6f4ae78339d71114e8')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmldsig-more#kw-camellia256'
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> tostr(base64.b64encode(encrypt(pskc.encryption.algorithm, pskc.encryption.key, pskc.keys[0].secret)))
'tCrhhD62tBGCcbsp8GV91+79MhaXTy1MP1SkaT2OLaU='
The IV can also be specified globally.
>>> pskc = PSKC('tests/encryption/aes128-cbc-noiv.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> pskc.encryption.iv = a2b_hex('000102030405060708090a0b0c0d0e0f')
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
>>> tostr(pskc.mac.key)
'MacMacMacMacMacMacMa'
If the PSKC file does not have a MAC key configured, older versions of the
PSKC format allowed using the encryption key for the HMAC function.
>>> pskc = PSKC('tests/encryption/no-mac-key.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(pskc.mac.key)
'12345678901234567890123456789012'
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
Older versions of the PSKC format allowed having the MAC go over the
plaintext instead of the ciphertext.
>>> pskc = PSKC('tests/encryption/mac-over-plaintext.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
Test decryption with tripledes-cbc and a specified IV.
>>> iv = a2b_hex('1010101010101010')
>>> key = a2b_hex('12345678901234567890123456789012')
>>> ciphertext = encrypt('#tripledes-cbc', key, b'FOOBAR', iv)
>>> ciphertext = ciphertext[8:] # strip IV
>>> tostr(decrypt('#tripledes-cbc', key, ciphertext, iv))
'FOOBAR'
>>> tostr(decrypt('#tripledes-cbc', key, iv + ciphertext))
'FOOBAR'
MAC key and algorithm will use useful defaults but can also be manually
specified.
>>> pskc = PSKC()
>>> pskc.mac.setup()
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> len(pskc.mac.key)
20
>>> pskc.mac.setup(key=a2b_hex('548512684595'), algorithm='unknown')
>>> pskc.mac.algorithm
'unknown'
>>> len(pskc.mac.key)
6
>>> pskc.mac.algorithm_key_length # this is the default
16
>>> pskc.mac.algorithm = None
>>> pskc.mac.key = None
>>> pskc.mac.setup(algorithm='hmac-sha224')
>>> pskc.mac.algorithm
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224'
>>> pskc.mac.algorithm_key_length
28
>>> len(pskc.mac.key)
28
Test PBKDF2 key derivation set-up. Only specifying a passphrase picks
reasonable defaults.
>>> pskc = PSKC()
>>> pskc.encryption.setup_pbkdf2('test')
>>> pskc.encryption.derivation.algorithm
'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2'
>>> pskc.encryption.derivation.pbkdf2_iterations
12000
>>> len(pskc.encryption.derivation.pbkdf2_salt)
16
>>> pskc.encryption.derivation.pbkdf2_key_length
16
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> len(pskc.encryption.key)
16
The function will pick up an pre-specified values. If an encryption algorithm
is defined (can also be passed) the key with the correct size will be
generated.
>>> pskc = PSKC()
>>> pskc.encryption.algorithm = 'aes256-cbc'
>>> pskc.encryption.derivation.pbkdf2_iterations = 15000
>>> pskc.encryption.setup_pbkdf2('test')
>>> pskc.encryption.derivation.algorithm
'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2'
>>> pskc.encryption.derivation.pbkdf2_iterations
15000
>>> len(pskc.encryption.derivation.pbkdf2_salt)
16
>>> pskc.encryption.derivation.pbkdf2_key_length
32
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
>>> len(pskc.encryption.key)
32
All properties can also be manually specified.
>>> pskc = PSKC()
>>> pskc.encryption.setup_pbkdf2(
... 'qwerty', iterations=1000, algorithm='aes256-cbc', key_length=24,
... salt=base64.b64decode('Ej7/PEpyEpw='),
... key_name='PBKDF2 passphrase',
... prf='hmac-md5')
>>> pskc.encryption.derivation.algorithm
'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2'
>>> pskc.encryption.derivation.pbkdf2_iterations
1000
>>> b2a_hex(pskc.encryption.derivation.pbkdf2_salt)
'123eff3c4a72129c'
>>> pskc.encryption.derivation.pbkdf2_key_length
24
>>> pskc.encryption.derivation.pbkdf2_prf
'http://www.w3.org/2001/04/xmldsig-more#hmac-md5'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
>>> pskc.encryption.key_name
'PBKDF2 passphrase'
>>> b2a_hex(pskc.encryption.key)
'e8c5fecfb2a5cbb80ff791782ff5e125cc375bb6ba113071'
python-pskc-1.0/tests/test_invalid.doctest 0000644 0000000 0000000 00000015473 13221434301 021010 0 ustar root root 0000000 0000000 test_invalid.doctest - test for invalid PSKC file
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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
Load a number of invalid files.
This file is plain invalid XML.
>>> pskc = PSKC('tests/invalid/notxml.pskcxml') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ParseError: Error parsing XML
This XML file has a wrong top-level element.
>>> pskc = PSKC('tests/invalid/wrongelement.pskcxml') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ParseError: Missing KeyContainer
This file has an unknown PSKC version.
>>> pskc = PSKC('tests/invalid/wrongversion.pskcxml') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ParseError: Unsupported version
This PSKC file has a key with an unknown algorithm specified.
>>> pskc = PSKC('tests/invalid/unknown-encryption.pskcxml')
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No key available
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> key.secret # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: Unsupported algorithm: ...
This PSKC file has a key without an algorithm specified.
>>> pskc = PSKC('tests/invalid/missing-encryption.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> key = pskc.keys[0]
>>> key.id
'45678901'
>>> b2a_hex(key.secret) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No algorithm specified
>>> pskc.encryption.algorithm = 'aes128-cbc'
>>> b2a_hex(key.secret)
'3132333435363738393031323334353637383930'
This PSKC file has a key without an encryption method specified.
>>> pskc = PSKC('tests/invalid/missing-encryption-method.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> key = pskc.keys[0]
>>> key.id
'45678901'
>>> b2a_hex(key.secret) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No algorithm specified
>>> pskc.encryption.algorithm = 'aes128-cbc'
>>> b2a_hex(key.secret)
'3132333435363738393031323334353637383930'
This PSKC file has an incomplete key derivation configuration.
>>> pskc = PSKC('tests/invalid/incomplete-derivation.pskcxml')
>>> pskc.encryption.derive_key('qwerty') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyDerivationError: Incomplete PBKDF2 configuration
Specify an unknown key derivation algorithm.
>>> pskc = PSKC('tests/rfc6030/figure7.pskcxml')
>>> pskc.encryption.derivation.algorithm = 'unknown'
>>> pskc.encryption.derive_key('qwerty') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyDerivationError: Unsupported algorithm: 'unknown'
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') # doctest: +IGNORE_EXCEPTION_DETAIL
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') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyDerivationError: Pseudorandom function unsupported: 'unknown'
Specify an unknown hash for the HMAC for the PBKDF2 PRF.
>>> pskc = PSKC('tests/rfc6030/figure7.pskcxml')
>>> pskc.encryption.derivation.pbkdf2_prf = 'hmac-unknown'
>>> pskc.encryption.derive_key('qwerty') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyDerivationError: Pseudorandom function unsupported: 'hmac-unknown'
There is a ValueMAC element but no MACMethod element.
>>> pskc = PSKC('tests/invalid/no-mac-method.pskcxml')
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> pskc.mac.algorithm is None
True
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No MAC algorithm set
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 # doctest: +IGNORE_EXCEPTION_DETAIL
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')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.secret # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: MAC value does not match
>>> key.check() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: MAC value does not match
Checks to see that invalid values are detected.
>>> pskc = PSKC('tests/invalid/not-integer.pskcxml') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'TWELVE'
>>> pskc = PSKC('tests/invalid/not-integer2.pskcxml') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'FOUR'
>>> pskc = PSKC('tests/invalid/not-boolean.pskcxml') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError: invalid boolean value: 'not really'
python-pskc-1.0/tests/feitian/ 0000755 0000000 0000000 00000000000 13221476257 016361 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/feitian/20120919-test001-4282.xml 0000644 0000000 0000000 00000003306 12676003033 021576 0 ustar root root 0000000 0000000
FeiTian Technology Co.,Ltd2600215704919zSK3gP/9LVNpaAfs039ATa45MnA=602012-09-19T00:00:00Z2022-09-01T00:00:00ZFeiTian Technology Co.,Ltd1000117803294TfpfT+8Jn9s6FYNIySi+uzXkIi0=02012-09-19T00:00:00Z2022-09-01T00:00:00Z
python-pskc-1.0/tests/feitian/file1.pskcxml 0000644 0000000 0000000 00000011251 12676003033 020753 0 ustar root root 0000000 0000000
Feitian Technology Co.,Ltd1000133508267PuMnCivln/14Ii3DNhR4/1zGN5A=02012-08-01T00:00:00Z2037-12-31T00:00:00ZFeitian Technology Co.,Ltd1000133508255wRjcslncyKj//L1oaDVQbAvCNnI=02012-08-01T00:00:00Z2037-12-31T00:00:00ZFeitian Technology Co.,Ltd2600124809778MRffGnGNJKmo8uSW313HCvGNIYM=602012-08-01T00:00:00Z2037-12-31T00:00:00ZFeitian Technology Co.,Ltd26001248097879O9PX9g20x74kIcaLLrGiwMUReM=602012-08-01T00:00:00Z2037-12-31T00:00:00ZFeitian Technology Co.,Ltd2600135004012A0DxFX1zRVTsxJlMKFsDXuNQYcI=602012-08-01T00:00:00Z2037-12-31T00:00:00ZFeitian Technology Co.,Ltd2600135004013NSLuCF/qeQPsqY7Sod4anJMjIBg=602012-08-01T00:00:00Z2037-12-31T00:00:00Z
python-pskc-1.0/tests/test_rfc6030.doctest 0000644 0000000 0000000 00000020411 13221434050 020432 0 ustar root root 0000000 0000000 test_rfc6030.doctest - test for examples from RFC 6030
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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)
>>> import datetime
>>> now = datetime.datetime(2016, 3, 23, 0, 0, 0)
>>> 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 of 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', now)
True
>>> key1.policy.may_use('Encrypt', now)
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
of 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 # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No key available
>>> pskc.encryption.is_encrypted
True
>>> pskc.encryption.key_name
'Pre-shared-key'
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> pskc.encryption.is_encrypted
False
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> b2a_hex(pskc.mac.key)
'1122334455667788990011223344556677889900'
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> b2a_hex(key.secret)
'3132333435363738393031323334353637383930'
>>> key.check()
True
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.response_length
8
>>> key.manufacturer
'Manufacturer'
This tests a derived master key using PBKDF2 as seen in figure 7 of RFC 6030.
>>> pskc = PSKC('tests/rfc6030/figure7.pskcxml')
>>> pskc.encryption.key_name
'My Password 1'
>>> pskc.encryption.is_encrypted
True
>>> pskc.encryption.derive_key('qwerty')
>>> pskc.encryption.is_encrypted
False
>>> b2a_hex(pskc.encryption.key)
'651e63cd57008476af1ff6422cd02e41'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> b2a_hex(pskc.mac.key)
'bdaab8d648e850d25a3289364f7d7eaaf53ce581'
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> key = pskc.keys[0]
>>> tostr(key.secret)
'12345678901234567890'
>>> key.check()
True
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.response_length
8
>>> key.manufacturer
'TokenVendorAcme'
This tests a PSKC file that uses asymmetric encryption as seen in figure 8 of
RFC 6030. Note thet python-pskc does not yet support asymmetric encryption so
this test is really limited.
>>> pskc = PSKC('tests/rfc6030/figure8.pskcxml')
>>> pskc.id
'KC0001'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#rsa_1_5'
>>> key = pskc.keys[0]
>>> key.manufacturer
'TokenVendorAcme'
>>> key.serial
'987654321'
>>> key.id
'MBK000000001'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.issuer
'Example-Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> tostr(key.secret) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No key available
>>> key.counter
0
This tests a PSKC file that uses digital signature to sign the PSKC file as
seen in figure 8 of RFC 6030. Since the RFC does not supply the certificate
that was used in the signature (which is likely invalid) we cannot verify it
in this example and can only get some signature properties.
>>> pskc = PSKC('tests/rfc6030/figure9.pskcxml')
>>> pskc.signature.is_signed
True
>>> pskc.signature.algorithm
'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
>>> pskc.signature.canonicalization_method
'http://www.w3.org/2001/10/xml-exc-c14n#'
>>> pskc.signature.digest_algorithm
'http://www.w3.org/2000/09/xmldsig#sha1'
>>> pskc.signature.issuer
'CN=Example.com,C=US'
>>> pskc.signature.serial
'12345678'
>>> key = pskc.keys[0]
>>> key.manufacturer
'TokenVendorAcme'
>>> key.serial
'0755225266'
>>> key.id
'123'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc:hotp'
>>> key.issuer
'Example-Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> tostr(key.secret)
'12345678901234567890'
>>> key.counter
0
This tests bulk provisioning as shown in figure 10 of 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-1.0/tests/encryption/ 0000755 0000000 0000000 00000000000 13221476257 017134 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/encryption/tripledes-cbc.pskcxml 0000644 0000000 0000000 00000002305 13167760305 023256 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVbkU3i5koQy9wRwmtLzydqFV18QfbCMBR8=SVYxMjM0NTbvR25//t5tAuWfL+6ma90GGESqe3AlrJM=4eM8sZbswb+q4q4qZ18q2Af5LEIzZy4M1Mz7XF6Gnc8KozCp87ykK10uOHZpdKLrc9j8Yz0dw9CtQUVcijQKgA==
python-pskc-1.0/tests/encryption/camellia128-cbc.pskcxml 0000644 0000000 0000000 00000002316 13221434050 023251 0 ustar root root 0000000 0000000
Pre-shared-keyVnWO9OoCQWEuH0qsz1VnywNqjvXC/kUNcMp8cFfCqiw48doVY9XVcMV0GR5vn6g3DvtlFS8/QZle2xG8PjfA8Kg4bsjLlU8kH/sEfXC9VLWib2Z/WU8RDHR+fI9uCqOsRDATcSJh3n8TAvMDoPzKqobgOCPZSluA7Gmvpg==
python-pskc-1.0/tests/encryption/aes192-cbc.pskcxml 0000644 0000000 0000000 00000002310 13167760305 022263 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVZJVklWSVZJVmDaimFqjBwo8MSWUGmwDkqJvsb1xlkf0MHfyqeooZzMAAECAwQFBgcICQoLDA0OD/616ab2do/xcWNKuW1qE3rSzwqoZcpg5ucwpjiZ07tVADfYOligu/3jDK9QhUGO7gGMxNxmrBUy4qtv4HyKF8o=
python-pskc-1.0/tests/encryption/aes128-cbc.pskcxml 0000644 0000000 0000000 00000002264 13167760305 022272 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVZJVklWSVZJViZS3d+rzbWqD74OQPuyiwrD+XlDXK7ef602mwOebfTRAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvCjGsEXpmZYGMyejd8WJdLFRBWE9XGJLiigPObg==
python-pskc-1.0/tests/encryption/mac-over-plaintext.pskcxml 0000644 0000000 0000000 00000002401 13036777211 024251 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVZJVklWSVZJViZS3d+rzbWqD74OQPuyiwrD+XlDXK7ef602mwOebfTRAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvRDATcSJh3n8TAvMDoPzKqobgOCPZSluA7Gmvpg==
python-pskc-1.0/tests/encryption/kw-camellia256.pskcxml 0000644 0000000 0000000 00000001554 13221434050 023150 0 ustar root root 0000000 0000000
Pre-shared-keytCrhhD62tBGCcbsp8GV91+79MhaXTy1MP1SkaT2OLaU=
python-pskc-1.0/tests/encryption/kw-camellia128.pskcxml 0000644 0000000 0000000 00000001514 13221434050 023142 0 ustar root root 0000000 0000000
Pre-shared-keyWB128TBZ1WGZzPNJNbwNrWRqQceU7M4FQSJPy2nw6iI=
python-pskc-1.0/tests/encryption/kw-aes128.pskcxml 0000644 0000000 0000000 00000001463 13167760305 022164 0 ustar root root 0000000 0000000
Pre-shared-keyH6aLCoEStEeu80vY+1p7gp0+hiNx0s/l
python-pskc-1.0/tests/encryption/kw-aes256.pskcxml 0000644 0000000 0000000 00000001544 13167760305 022166 0 ustar root root 0000000 0000000
Pre-shared-keyqPm8FhLGiz/25vT74w5x5Haci4CjLLiVjNXRfWslTaE=
python-pskc-1.0/tests/encryption/camellia256-cbc.pskcxml 0000644 0000000 0000000 00000002356 13221434050 023257 0 ustar root root 0000000 0000000
Pre-shared-keyuj9VFSjENtykiBdHoxF/CZ8y2XnWA/fW89i4xonEH9iYK2AH0/hkH7bVmI2ObcSaX7We+WREABNPa0jlsUHZqF5CWUQiPYdXJ+7ure96AcNH/7TXcQs4mFuSCOHpiv/WRDATcSJh3n8TAvMDoPzKqobgOCPZSluA7Gmvpg==
python-pskc-1.0/tests/encryption/kw-aes192.pskcxml 0000644 0000000 0000000 00000001470 13167760305 022163 0 ustar root root 0000000 0000000
Pre-shared-keylneLJa5spDX5K1uXwFCu0kaKuKF62E5d
python-pskc-1.0/tests/encryption/camellia192-cbc.pskcxml 0000644 0000000 0000000 00000002336 13221434050 023254 0 ustar root root 0000000 0000000
Pre-shared-keyWAL6YRtYa5/rE/DX26NKXaHpDu2qE4q5kdtCZmvhZeHMsV76CuLlO5ybTD9RsLWKWa5Kz1/BhuqUcpQyw8qRSDwurIsm2vjUR/PO3w1Q3//PFfHod+DgBhRW2BecWpP5RDATcSJh3n8TAvMDoPzKqobgOCPZSluA7Gmvpg==
python-pskc-1.0/tests/encryption/aes256-cbc.pskcxml 0000644 0000000 0000000 00000002354 13167760305 022274 0 ustar root root 0000000 0000000
Pre-shared-keySVZJVklWSVZJVklWSVZJVlAHw4GN7cbXseMBjNjUCrR8Lb4syW0I7bbNZbCBRt7TAAECAwQFBgcICQoLDA0OD7mg24krBXvsLMVBhZbLXDVFEWhqNqRTCO8AfowoBFcdJdB5+Ub/VSapUmJq+ZzEbseBPijlOp6BGy3+AAHoM7x17MbqR77xREby+9/65UOG
python-pskc-1.0/tests/encryption/kw-tripledes.pskcxml 0000644 0000000 0000000 00000001576 13167760305 023161 0 ustar root root 0000000 0000000
Pre-shared-keyaQEHYY7wkrO0jKF5ayNK6foz67QVlgQDfbXWqE6zqsJ2jGMndaRn1A==
python-pskc-1.0/tests/encryption/no-mac-key.pskcxml 0000644 0000000 0000000 00000001754 12767245644 022516 0 ustar root root 0000000 0000000
Pre-shared-keyAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvN8Agsfe5A7WQFswmdvL/MHdd3y0=
python-pskc-1.0/tests/encryption/aes128-cbc-noiv.pskcxml 0000644 0000000 0000000 00000002263 13167760305 023242 0 ustar root root 0000000 0000000
Pre-shared-keyDiahu/VzjP5IbRYxRgNYT+YQcIa03s5FLMnHjTM0rSQ=5wgci2UHdatrUNSnFW87HaV5/VWY08IyXyPBmohSoa8=mNUFNm7a8VqhdmoYDX95B/V7HY36hHOKr6F9jQ==
python-pskc-1.0/tests/encryption/kw-camellia192.pskcxml 0000644 0000000 0000000 00000001534 13221434050 023145 0 ustar root root 0000000 0000000
Pre-shared-keyy/wSpn3aNjeXzY1giHLOy0P+WQ+NmkN7EovBtHBXZ14=
python-pskc-1.0/tests/actividentity/ 0000755 0000000 0000000 00000000000 13221476256 017621 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/actividentity/test.pskcxml 0000644 0000000 0000000 00000002510 13117007605 022171 0 ustar root root 0000000 0000000
Xus0lsc+rJLi0nc/ANE0Xg==ActivIdentity0950380269ActivIdentity1HBJThmzYDCU7YCrML6OZKMsRQriSvZpoqeMfBui9dI=SlinEB9YUzcR04MUZDF5dBLtK1c=AAAAADHwRgM=
python-pskc-1.0/tests/test_signature.doctest 0000644 0000000 0000000 00000017231 13221434050 021356 0 ustar root root 0000000 0000000 test_signature.doctest - test XML signature checking functions
Copyright (C) 2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> import sys
>>> import tempfile
>>> try:
... from StringIO import StringIO
... except ImportError:
... from io import StringIO
>>> from binascii import a2b_hex, b2a_hex
>>> def tostr(x):
... return str(x.decode())
>>> def decode(f):
... return lambda x: tostr(f(x))
>>> b2a_hex = decode(b2a_hex)
>>> from pskc import PSKC
>>> from pskc.encryption import encrypt, decrypt
>>> with open('tests/certificate/key.pem', 'rb') as f:
... signing_key = f.read()
>>> with open('tests/certificate/ss-certificate.pem', 'rb') as f:
... self_signed_certificate = f.read()
>>> with open('tests/certificate/certificate.pem', 'rb') as f:
... signed_certificate = f.read()
Build a simple PSKC structure and sign the file including a self-signed
certificate.
>>> pskc = PSKC()
>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
>>> pskc.signature.sign(signing_key, self_signed_certificate)
Write the PSKC file (use temporary file to test passing file name as
argument).
>>> f = tempfile.NamedTemporaryFile()
>>> pskc.write(f.name)
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +ELLIPSIS +REPORT_UDIFF +NORMALIZE_WHITESPACE
...
.........
Read back the PSKC file and verify the signature.
>>> newpskc = PSKC(f.name)
>>> print(tostr(newpskc.signature.certificate)) #doctest: +ELLIPSIS
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
>>> newpskc.signature.signed_pskc # we need a certificate for verification
Traceback (most recent call last):
...
InvalidCertificate: [18, 0, 'self signed certificate']
>>> newpskc.signature.verify(self_signed_certificate)
True
>>> newpskc = newpskc.signature.signed_pskc
>>> newpskc.keys[0].secret == pskc.keys[0].secret
True
We can also use the certificate that is embedded in the PSKC file but that
does not add any security.
>>> newpskc = PSKC(f.name)
>>> newpskc.signature.verify(newpskc.signature.certificate)
True
>>> newpskc = newpskc.signature.signed_pskc
>>> newpskc.keys[0].secret == pskc.keys[0].secret
True
We can also sign a PSKC file without a embedding certificate. This should be
practically the same as using a self-signed certificate.
>>> pskc = PSKC()
>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
>>> pskc.signature.sign(signing_key)
>>> f = tempfile.NamedTemporaryFile()
>>> pskc.write(f.name)
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +ELLIPSIS +REPORT_UDIFF +NORMALIZE_WHITESPACE
...
............
Read the file back in and verify the signature using the self-signed
certificate.
>>> newpskc = PSKC(f.name)
>>> newpskc.signature.certificate is None
True
>>> newpskc.signature.signed_pskc # we need a certificate for verification
Traceback (most recent call last):
...
InvalidInput: Expected a X.509 certificate based signature
>>> newpskc.signature.verify(self_signed_certificate)
True
>>> newpskc = newpskc.signature.signed_pskc
>>> newpskc.keys[0].secret == pskc.keys[0].secret
True
We can also sign a PSKC file and include a certificate that can be validated
using a CA certificate.
>>> pskc = PSKC()
>>> key = pskc.add_key(id='456', manufacturer='Manufacturer')
>>> key.secret = a2b_hex('4e1790ba272406ba309c5a31')
>>> pskc.signature.sign(signing_key, signed_certificate)
>>> f = tempfile.NamedTemporaryFile()
>>> pskc.write(f.name)
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +ELLIPSIS +REPORT_UDIFF +NORMALIZE_WHITESPACE
...
.........
Read back the PSKC file and verify the signature. This file can be verified
using the self-signed certificate, the signing certificate or by providing
the CA certificate.
>>> newpskc = PSKC(f.name)
>>> newpskc.signature.signed_pskc # we need a certificate for verification
Traceback (most recent call last):
...
InvalidCertificate: [20, 0, 'unable to get local issuer certificate']
>>> newpskc.signature.verify(self_signed_certificate)
True
>>> newpskc.signature.verify(signed_certificate)
True
>>> newpskc.signature.verify(ca_pem_file='tests/certificate/ca-certificate.pem')
True
We could also sign a PSKC file and include a certificate that is validated by
the default operating system recorded CAs but that is sadly not appropriate
for a test suite (the key needs to be included in the test suite so would not
be private, depending on the CA used there would be costs involved and the
certificates would expire too quickly).
A simple test for parsing an incomplete signature element.
>>> pskc = PSKC(StringIO('''
...
...
...
... TheQuickBrownFox
...
...
...
...
...
...
...
... '''.strip()))
>>> len(pskc.keys)
1
>>> pskc.signature.canonicalization_method is None
True
>>> pskc.signature.algorithm is None
True
>>> pskc.signature.digest_algorithm is None
True
python-pskc-1.0/tests/test_draft_ietf_keyprov_pskc_02.doctest 0000644 0000000 0000000 00000020146 13221434050 024563 0 ustar root root 0000000 0000000 test_draft_ietf_keyprov_pskc_02.doctest - test for examples from
draft-ietf-keyprov-pskc-02
Copyright (C) 2016 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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 draft-ietf-keyprov-pskc-02. It is a basic key
container example with a simple plain text secret key.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure2.pskcxml')
>>> pskc.id
'exampleID1'
>>> key = pskc.keys[0]
>>> key.id
'12345678'
>>> key.manufacturer
'Manufacturer'
>>> key.serial
'987654321'
>>> 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
This tests figure 3 from draft-ietf-keyprov-pskc-02. It is a basic key
container example with a non-encrypted HOTP secret key protected by a PIN
with some extra policy elements.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure3.pskcxml')
>>> pskc.id
'exampleID1'
>>> device = pskc.devices[0]
>>> device.manufacturer
'Manufacturer'
>>> device.serial
'987654321'
>>> key = pskc.keys[0]
>>> 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.policy.pin_min_length
4
>>> key.policy.pin_max_length
4
>>> key.policy.pin_encoding
'DECIMAL'
>>> key.policy.pin_usage
'Local'
>>> key.policy.key_usage
['OTP']
>>> pinkey = key.policy.pin_key
>>> pinkey == pskc.keys[1]
True
>>> pinkey.id
'123456781'
>>> pinkey.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#pin'
>>> pinkey.issuer
'Issuer'
>>> pinkey.response_encoding
'DECIMAL'
>>> pinkey.response_length
4
>>> tostr(pinkey.secret)
'1234'
This tests figure 4 from draft-ietf-keyprov-pskc-02. It is a basic key
container example using a AES-128-CBC encrypted pre-shared secret key.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure4.pskcxml')
>>> pskc.encryption.key_name
'Pre-shared-key'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> key = pskc.keys[0]
>>> key.manufacturer
'Manufacturer'
>>> key.serial
'987654321'
>>> key.id
'12345678'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#hotp'
>>> key.issuer
'Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
8
>>> key.counter
0
>>> b2a_hex(key.secret) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No key available
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> b2a_hex(key.secret)
'3132333435363738393031323334353637383930'
This tests figure 5 from draft-ietf-keyprov-pskc-02 which uses an encryption
key that is derived from a passphrase. While the PSKC file contains a
element, there is no MAC algorithm specified anywhere.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure5.pskcxml')
>>> pskc.encryption.key_name
'Passphrase1'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> key = pskc.keys[0]
>>> key.manufacturer
'TokenVendorAcme'
>>> key.serial
'987654321'
>>> key.id
'123456'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#hotp'
>>> key.issuer
'Example-Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
8
>>> tostr(key.secret) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No key available
>>> pskc.encryption.derive_key('qwerty')
>>> b2a_hex(pskc.encryption.key)
'651e63cd57008476af1ff6422cd02e41'
>>> tostr(key.secret)
'12345678901234567890'
This tests figure 6 from draft-ietf-keyprov-pskc-02 which uses an encryption
based on asymmetric keys. Note that python-pskc does not yet support
asymmetric encryption so this test is really limited.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure6.pskcxml')
>>> pskc.id
'KC0001'
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#rsa_1_5'
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> key = pskc.keys[0]
>>> key.manufacturer
'TokenVendorAcme'
>>> key.serial
'987654321'
>>> key.id
'MBK000000001'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#hotp'
>>> key.issuer
'Example-Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> tostr(key.secret) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: No key available
>>> key.counter
0
This tests figure 7 from draft-ietf-keyprov-pskc-02 which uses a reference to
an external mechanism to construct a HOTP key.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure7.pskcxml')
>>> pskc.id
'exampleID1'
>>> key = pskc.keys[0]
>>> key.manufacturer
'Manufacturer'
>>> key.serial
'987654321'
>>> key.id
'12345678'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#hotp'
>>> key.issuer
'Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
8
>>> key.key_profile
'keyProfile1'
>>> key.key_reference
'MasterKeyLabel'
>>> key.counter
0
>>> key.policy.key_usage
['OTP']
This tests figure 8 from draft-ietf-keyprov-pskc-02 which uses a a digital
signature to sign the PSKC file. Note that this file does not appear to
contain a valid signature and if it would it wouldn't have a valid signature
anyway.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure8.pskcxml')
>>> pskc.signature.is_signed
True
>>> pskc.signature.canonicalization_method
'http://www.w3.org/2001/10/xml-exc-c14n#'
>>> pskc.signature.algorithm
'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
>>> pskc.signature.digest_algorithm
'http://www.w3.org/2000/09/xmldsig#sha1'
>>> pskc.signature.issuer
'CN=Example.com,C=US'
>>> pskc.signature.serial
'12345678'
>>> key = pskc.keys[0]
>>> key.manufacturer
'TokenVendorAcme'
>>> key.serial
'0755225266'
>>> key.id
'123'
>>> key.algorithm
'urn:ietf:params:xml:ns:keyprov:pskc#hotp'
>>> key.issuer
'Example-Issuer'
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
6
>>> tostr(key.secret)
'12345678901234567890'
>>> key.counter
0
This tests figure 9 from draft-ietf-keyprov-pskc-02 which contains multiple
devices and keys.
>>> pskc = PSKC('tests/draft-ietf-keyprov-pskc-02/figure9.pskcxml')
>>> [device.manufacturer for device in pskc.devices]
['TokenVendorAcme', 'TokenVendorAcme', 'TokenVendorAcme']
>>> [device.serial for device in pskc.devices]
['654321', '123456', '9999999']
>>> [len(device.keys) for device in pskc.devices]
[1, 1, 2]
>>> [key.id for key in pskc.keys]
['1', '2', '3', '4']
>>> all(key.algorithm.endswith('#hotp') for key in pskc.keys)
True
>>> [key.issuer for key in pskc.keys]
['Issuer', 'Issuer', 'Issuer', 'Issuer']
>>> [bool(key.secret) for key in pskc.keys]
[True, True, True, True]
>>> [key.counter for key in pskc.keys]
[0, 0, 0, 0]
>>> 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[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-1.0/tests/test_aeskw.doctest 0000644 0000000 0000000 00000017601 13221434050 020470 0 ustar root root 0000000 0000000 test_keywrap.doctest - test keywrap functions
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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 struct
>>> 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:
>>> unwrap(b'XX' + ciphertext[2:], key) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: IV does not match
>>> unwrap(ciphertext[:-2] + b'XX', key) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: IV does not match
>>> unwrap(ciphertext[2:], key) # doctest: +IGNORE_EXCEPTION_DETAIL
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) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
EncryptionError: Plaintext length wrong
>>> unwrap(ciphertext, key, pad=False) # doctest: +IGNORE_EXCEPTION_DETAIL
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
>>> unwrap(ciphertext, key, a2b_hex('2020202020202020')) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: IV does not match
We can fake padding by specifying an RFC 5649 IV ourselves. The length of 14
works because we have padded the plaintext with two 0 bytes.
>>> key = a2b_hex('5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8')
>>> plaintext = a2b_hex('c37b7e6492584340bed1220765460000')
>>> iv = a2b_hex('a65959a6') + struct.pack('>I', 14)
>>> ciphertext = wrap(plaintext, key, iv=iv)
>>> unwrap(ciphertext, key, iv=iv) == plaintext
True
>>> unwrap(ciphertext, key) == plaintext[:14]
True
If we mangle the IV to look like an RFC 5649 value but with an invalid
padding length we should get an exception.
>>> iv = a2b_hex('a65959a6') + struct.pack('>I', 12)
>>> ciphertext = wrap(plaintext, key, iv=iv)
>>> unwrap(ciphertext, key, iv=iv) == plaintext
True
>>> unwrap(ciphertext, key) == plaintext[:12]
Traceback (most recent call last):
...
DecryptionError: IV does not match
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/ 0000755 0000000 0000000 00000000000 13221476257 021641 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure8.pskcxml 0000644 0000000 0000000 00000004305 13036777211 024615 0 ustar root root 0000000 0000000
TokenVendorAcme0755225266Example-Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0
j6lwx3rvEPO0vKtMup4NbeVu8nk=
j6lwx3rvEPO0vKtMup4NbeVu8nk=
CN=Example.com,C=US
12345678
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure2.pskcxml 0000644 0000000 0000000 00000001651 13036777211 024610 0 ustar root root 0000000 0000000
Manufacturer987654321IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure9.pskcxml 0000644 0000000 0000000 00000007074 13036777211 024624 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:00ZIssuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
02006-04-01T00:00:00Z2006-04-30T00:00:00Z
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure6.pskcxml 0000644 0000000 0000000 00000004521 13036777211 024613 0 ustar root root 0000000 0000000
MIIB5zCCAVCgAwIBAgIESZp/vDANBgkqhkiG9w0BAQUFADA4M
Q0wCwYDVQQKEwRJRVRGMRMwEQYDVQQLEwpLZXlQcm92IFdHMRIwEAYDVQQDEwlQU0tDIF
Rlc3QwHhcNMDkwMjE3MDkxMzMyWhcNMTEwMjE3MDkxMzMyWjA4MQ0wCwYDVQQKEwRJRVR
GMRMwEQYDVQQLEwpLZXlQcm92IFdHMRIwEAYDVQQDEwlQU0tDIFRlc3QwgZ8wDQYJKoZI
hvcNAQEBBQADgY0AMIGJAoGBALCWLDa2ItYJ6su80hd1gL4cggQYdyyKK17btt/aS6Q/e
DsKjsPyFIODsxeKVV/uA3wLT4jQJM5euKJXkDajzGGOy92+ypfzTX4zDJMkh61SZwlHNJ
xBKilAM5aW7C+BQ0RvCxvdYtzx2LTdB+X/KMEBA7uIYxLfXH2Mnub3WIh1AgMBAAEwDQY
JKoZIhvcNAQEFBQADgYEAe875m84sYUJ8qPeZ+NG7REgTvlHTmoCdoByU0LBBLotUKuqf
rnRuXJRMeZXaaEGmzY1kLonVjQGzjAkU4dJ+RPmiDlYuHLZS41Pg6VMwY+03lhk6I5A/w
4rnqdkmwZX/NgXg06alnc2pBsXWhL4O7nk0S2ZrLMsQZ6HcsXgdmHo=
http://www.w3.org/2000/09/xmldsig#hmac-sha1
TokenVendorAcme987654321Example-IssuerhJ+fvpoMPMO9BYpK2rdyQYGIxiATYHTHC7e/sPLKYo5/r1v+4
xTYG3gJolCWuVMydJ7Ta0GaiBPHcWa8ctCVYmHKfSz5fdeV5nqbZApe6dofTqhRwZK6
Yx4ufevi91cjN2vBpSxYafvN3c3+xIgk0EnTV4iVPRCR0rBwyfFrPc4=
0
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure5.pskcxml 0000644 0000000 0000000 00000005407 13036777211 024616 0 ustar root root 0000000 0000000
Passphrase1Ej7/PEpyEpw=100016TokenVendorAcme987654321Example-Issuer
oTvo+S22nsmS2Z/RtcoF8Hfh+jzMe0RkiafpoDpnoZTjPYZu6V+A4aEn032yCr4f
cOpiQ/H7Zlj6ywiYWtwgz9cRaOA=
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure3.pskcxml 0000644 0000000 0000000 00000003101 13036777211 024601 0 ustar root root 0000000 0000000
Manufacturer987654321IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0OTPIssuerMTIzNA==
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure7.pskcxml 0000644 0000000 0000000 00000002110 13036777211 024604 0 ustar root root 0000000 0000000
Manufacturer987654321IssuerkeyProfile1MasterKeyLabel0OTP
python-pskc-1.0/tests/draft-ietf-keyprov-pskc-02/figure4.pskcxml 0000644 0000000 0000000 00000003557 13036777211 024621 0 ustar root root 0000000 0000000
Pre-shared-keyhttp://www.w3.org/2000/09/xmldsig#hmac-sha1
Manufacturer987654321Issuer
pgznhXdDh4LJ2G3mOY2RL/e5cN9M3qjkBZJEE4w+NvVr64LbzkwRh9WHLAIkjXcW
zdrZbGBj9BDZJzunbfAG3kyZyYc=
0
python-pskc-1.0/tests/test_yubico.doctest 0000644 0000000 0000000 00000006550 13117010157 020653 0 ustar root root 0000000 0000000 test_yubico.doctest - tests for Yubico PSKC files
Copyright (C) 2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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 the first example from draft-josefsson-keyprov-pskc-yubikey-00.
>>> pskc = PSKC('tests/yubico/example1.pskcxml')
>>> pskc.id
'yk-pskc-283599'
>>> len(pskc.keys)
1
>>> key = pskc.keys[0]
>>> key.manufacturer
'oath.UB'
>>> key.serial
'283599'
>>> key.start_date
datetime.datetime(2009, 1, 22, 0, 25, 11, tzinfo=tzutc())
>>> key.crypto_module
'1'
>>> key.id
'283599:1'
>>> key.algorithm
'http://www.yubico.com/#yubikey-aes'
>>> key.issuer
'ACME Inc.'
>>> key.response_encoding
'ALPHANUMERIC'
>>> key.response_length
44
>>> b2a_hex(key.secret)
'2b7e151628aed2a6abf7158809cf4f3c'
>>> key.userid
'CN=ekhgjhbctrgn, UID=ca62baca62ba'
This tests the second example from draft-josefsson-keyprov-pskc-yubikey-00.
>>> pskc = PSKC('tests/yubico/example2.pskcxml')
>>> pskc.id
'yk-pskc-283598'
>>> len(pskc.keys)
2
>>> key = pskc.keys[0] # first key
>>> key.manufacturer
'oath.UB'
>>> key.serial
'283598'
>>> key.start_date
datetime.datetime(2009, 1, 22, 0, 25, 10, tzinfo=tzutc())
>>> key.crypto_module
'1'
>>> key.id
'283598:1'
>>> key.algorithm
'http://www.yubico.com/#yubikey-aes'
>>> key.issuer
'ACME Inc.'
>>> key.response_encoding
'ALPHANUMERIC'
>>> key.response_length
44
>>> b2a_hex(key.secret)
'5698356d30868c4201e26f66c582bb45'
>>> key.userid
'CN=ekhgjhbctrgn, UID=ca62baca62ba'
>>> key = pskc.keys[1] # second key
>>> key.manufacturer
'oath.UB'
>>> key.serial
'283598'
>>> key.start_date
datetime.datetime(2009, 1, 22, 0, 25, 10, tzinfo=tzutc())
>>> key.crypto_module
'2'
>>> key.id
'283598:2'
>>> key.algorithm
'http://www.yubico.com/#yubikey-aes'
>>> key.issuer
'ACME Inc.'
>>> key.response_encoding
'ALPHANUMERIC'
>>> key.response_length
44
>>> b2a_hex(key.secret)
'38892b82abf1807788458fc5a5165c80'
>>> key.userid
'CN=ekhgjhbctrgn, UID=ca62baca62ba'
This tests the third example from draft-josefsson-keyprov-pskc-yubikey-00.
>>> pskc = PSKC('tests/yubico/example3.pskcxml')
>>> pskc.id
'yk-pskc-283597'
>>> len(pskc.keys)
1
>>> key = pskc.keys[0]
>>> key.manufacturer
'oath.UB'
>>> key.serial
'283597'
>>> key.start_date
datetime.datetime(2009, 1, 22, 0, 25, 9, tzinfo=tzutc())
>>> key.crypto_module
'1'
>>> key.id
'283597:1'
>>> key.algorithm
'http://www.yubico.com/#yubikey-aes'
>>> key.issuer
'ACME Inc.'
>>> key.response_encoding
'ALPHANUMERIC'
>>> key.response_length
40
>>> b2a_hex(key.secret)
'2b7e151628aed2a6abf7158809cf4f3c'
>>> key.userid
'CN=ekhgjhbctrgn, UID=ca62baca62ba'
# TODO: support extensions
python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/ 0000755 0000000 0000000 00000000000 13221476257 027466 5 ustar root root 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/password-encrypted.pskcxml python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/password-encrypted.psk0000644 0000000 0000000 00000003160 13221434017 034026 0 ustar root root 0000000 0000000
y6TzckeLRQw=999Token Manufacturer9876543218701/01/2008Credential IssuerMySecondTokenF/CY93NYc/SvmxT3oB6PzG7p6zpG92/thN793ZE7GM6yCM6gz9OKNRzibhg=VVBYqRF1QSpetvIB2vBAzw==6clqJvT9l0xIZtWSch2t6zr0IwU=
python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/non-encrypted.pskcxml 0000644 0000000 0000000 00000002677 13221434076 033663 0 ustar root root 0000000 0000000
Token Manufacturer9876543218701/01/2008Credential IssuerMyFirstTokenWldjTHZwRm9YTkhBRytseDMrUnc=WldjTHZwRm9YTkhBRytseDM=AAAAAAAAAAw=WldjTHZwRm9YTkhBRytseDM=
python-pskc-1.0/tests/rfc6030/ 0000755 0000000 0000000 00000000000 13221476257 016025 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/rfc6030/figure8.pskcxml 0000644 0000000 0000000 00000004417 13221435626 021002 0 ustar root root 0000000 0000000
MIIB5zCCAVCgAwIBAgIESZp/vDANBgkqhkiG9w0BAQUFADA4M
Q0wCwYDVQQKEwRJRVRGMRMwEQYDVQQLEwpLZXlQcm92IFdHMRIwEAYDVQQDEwlQU0tDIF
Rlc3QwHhcNMDkwMjE3MDkxMzMyWhcNMTEwMjE3MDkxMzMyWjA4MQ0wCwYDVQQKEwRJRVR
GMRMwEQYDVQQLEwpLZXlQcm92IFdHMRIwEAYDVQQDEwlQU0tDIFRlc3QwgZ8wDQYJKoZI
hvcNAQEBBQADgY0AMIGJAoGBALCWLDa2ItYJ6su80hd1gL4cggQYdyyKK17btt/aS6Q/e
DsKjsPyFIODsxeKVV/uA3wLT4jQJM5euKJXkDajzGGOy92+ypfzTX4zDJMkh61SZwlHNJ
xBKilAM5aW7C+BQ0RvCxvdYtzx2LTdB+X/KMEBA7uIYxLfXH2Mnub3WIh1AgMBAAEwDQY
JKoZIhvcNAQEFBQADgYEAe875m84sYUJ8qPeZ+NG7REgTvlHTmoCdoByU0LBBLotUKuqf
rnRuXJRMeZXaaEGmzY1kLonVjQGzjAkU4dJ+RPmiDlYuHLZS41Pg6VMwY+03lhk6I5A/w
4rnqdkmwZX/NgXg06alnc2pBsXWhL4O7nk0S2ZrLMsQZ6HcsXgdmHo=
TokenVendorAcme987654321Example-IssuerhJ+fvpoMPMO9BYpK2rdyQYGIxiATYHTHC7e/sPLKYo5/r1v+4
xTYG3gJolCWuVMydJ7Ta0GaiBPHcWa8ctCVYmHKfSz5fdeV5nqbZApe6dofTqhRwZK6
Yx4ufevi91cjN2vBpSxYafvN3c3+xIgk0EnTV4iVPRCR0rBwyfFrPc4=
0
python-pskc-1.0/tests/rfc6030/figure10.pskcxml 0000644 0000000 0000000 00000007414 12676002045 021051 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-1.0/tests/rfc6030/figure2.pskcxml 0000644 0000000 0000000 00000001072 12767077756 021012 0 ustar root root 0000000 0000000
Issuer-AMTIzNA==
python-pskc-1.0/tests/rfc6030/figure9.pskcxml 0000644 0000000 0000000 00000004462 13221434050 020771 0 ustar root root 0000000 0000000
TokenVendorAcme0755225266Example-Issuer
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0
j6lwx3rvEPO0vKtMup4NbeVu8nk=
j6lwx3rvEPO0vKtMup4NbeVu8nk=
CN=Example.com,C=US
12345678
python-pskc-1.0/tests/rfc6030/figure6.pskcxml 0000644 0000000 0000000 00000004352 12676002045 020774 0 ustar root root 0000000 0000000
Pre-shared-key
ESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSX
Manufacturer987654321CM_ID_001Issuer
AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
Su+NvtQfmvfJzF6bmQiJqoLRExc=
0
python-pskc-1.0/tests/rfc6030/figure5.pskcxml 0000644 0000000 0000000 00000003704 12676002045 020773 0 ustar root root 0000000 0000000
Manufacturer987654321CM_ID_001IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0OTPManufacturer987654321CM_ID_001IssuerMTIzNA==
python-pskc-1.0/tests/rfc6030/figure3.pskcxml 0000644 0000000 0000000 00000002236 12676002045 020770 0 ustar root root 0000000 0000000
Manufacturer987654321DC=example-bank,DC=netCM_ID_001IssuerMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
0UID=jsmith,DC=example-bank,DC=net
python-pskc-1.0/tests/rfc6030/figure7.pskcxml 0000644 0000000 0000000 00000006104 12676002045 020772 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-1.0/tests/rfc6030/figure4.pskcxml 0000644 0000000 0000000 00000002202 12676002045 020762 0 ustar root root 0000000 0000000
Manufacturer987654321CM_ID_001IssuerkeyProfile1MasterKeyLabel
0OTP
python-pskc-1.0/tests/test_actividentity.doctest 0000644 0000000 0000000 00000003511 13117007614 022237 0 ustar root root 0000000 0000000 test_actividentity.doctest - tests for ActivIdentity PSKC files
Copyright (C) 2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> 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 an ActivIdentity PSKC file that was found in the wild. It does not
follow the RFC 6030 spec but is handled nonetheless.
>>> pskc = PSKC('tests/actividentity/test.pskcxml')
>>> pskc.encryption.algorithm
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
>>> b2a_hex(pskc.encryption.iv)
'5eeb3496c73eac92e2d2773f00d1345e'
>>> pskc.encryption.key = a2b_hex('fe0de6b806c09b762c4b49a666a27b72')
>>> pskc.mac.algorithm
'http://www.w3.org/2000/09/xmldsig#hmac-sha1'
>>> key = pskc.keys[0]
>>> key.manufacturer
'ActivIdentity'
>>> key.serial
'0950380269'
>>> key.algorithm
'http://www.ietf.org/keyprov/pskc#hotp'
>>> key.id
'0950380269'
>>> key.issuer
'ActivIdentity'
>>> key.policy.key_usage
['OTP']
>>> key.response_encoding
'DECIMAL'
>>> key.response_length
8
>>> b2a_hex(key.secret)
'dce70c2a0c1f5806f316ca8d09456eb4765ad053'
>>> key.counter
837830147
python-pskc-1.0/tests/test_pskc2csv.doctest 0000644 0000000 0000000 00000015707 13221434050 021121 0 ustar root root 0000000 0000000 test_pskc2csv.doctest - tests for the pskc2csv script
Copyright (C) 2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
>>> from binascii import a2b_hex
>>> import getpass
>>> import shlex
>>> import sys
>>> import tempfile
>>> from pskc import PSKC
>>> from pskc2csv import main
Sadly we cannot test --help and --version properly because argparse calls
exit(0) which doctest does not like.
>>> sys.argv = shlex.split('pskc2csv --help')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
SystemExit: 0
>>> sys.argv = shlex.split('pskc2csv --version')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
SystemExit: 0
We can output a CSV file with some columns with the just default arguments.
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure5.pskcxml')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
987654321,31323334,urn:ietf:params:xml:ns:keyprov:pskc:pin,4,
We can also save the output to a file.
>>> f = tempfile.NamedTemporaryFile()
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml --output') + [f.name]
>>> main()
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
987654321,31323334,urn:ietf:params:xml:ns:keyprov:pskc:pin,4,
We can specify the columns to output with the --columns option and this also
allows specifying custom CSV file column headers.
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml' +
... ' --columns id:NUMBER,secret,counter,policy.pin_min_length')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
NUMBER,secret,counter,policy.pin_min_length
12345678,3132333435363738393031323334353637383930,0,4
123456781,31323334,,
For password-encrypted files we should be prompted on the command line for a
password if the --password option was not specified. The prompt should
include the key name if one is present in the PSKC file. The --password
option can specify a literal password on the command line of point to a file
containing the password.
>>> getpass.getpass = lambda x: 'qwerty' if 'My Password 1' in x else 'WRONG'
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure7.pskcxml')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
>>> getpass.getpass = lambda x: 'WRONGPASSWORD'
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure7.pskcxml')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: ...
>>> f = tempfile.NamedTemporaryFile()
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_pbkdf2('qwerty')
>>> pskc.write(f.name)
>>> getpass.getpass = lambda x: 'qwerty'
>>> sys.argv = shlex.split('pskc2csv') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
,31323334,,,
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure7.pskcxml --password qwerty')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
>>> f = tempfile.NamedTemporaryFile()
>>> with open(f.name, 'w') as f2: # open second file to keep tempfile
... x = f2.write('qwerty\n')
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure7.pskcxml --password') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
For PSKC files that are encrypted with a pre-shared key we can use --secret
option to either supply a hex-encoded secret or point to a file name that
holds the secret.
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure6.pskcxml')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyDerivationError: ...
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure6.pskcxml' +
... ' --secret 12345678901234567890123456789012')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
>>> f = tempfile.NamedTemporaryFile()
>>> with open(f.name, 'wb') as f2: # open second file to keep tempfile
... x = f2.write(a2b_hex('12345678901234567890123456789012'))
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure6.pskcxml --secret') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
The --secret-encoding option can be used to specify the output encoding of
the secret (HEX, BASE64 or BASE32).
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml' +
... ' -c serial,secret -e base64')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret
987654321,MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
987654321,MTIzNA==
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml' +
... ' -c serial,secret -e base32')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret
987654321,GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
987654321,GEZDGNA=
Corner-case test: we an also handle empty PSKC files.
>>> f = tempfile.NamedTemporaryFile()
>>> with open(f.name, 'w') as f2: # open second file to keep tempfile
... x = f2.write('''
...
...
...
...
... '''.strip())
>>> sys.argv = shlex.split('pskc2csv') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
python-pskc-1.0/tests/misc/ 0000755 0000000 0000000 00000000000 13221476257 015675 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/misc/integers.pskcxml 0000644 0000000 0000000 00000003314 12676002045 021112 0 ustar root root 0000000 0000000
Pre-shared-key831791NzA5NzkxAAYxSg==I9Ro0yqZSyNhPY43+fZue7JvyPxbAqtNPuFiu3HprxY=juW3FkXwJwOoyJJ24vfY+ug/J3qAnaEMrhDeJgAMWMg=
python-pskc-1.0/tests/misc/partialxml.pskcxml 0000644 0000000 0000000 00000001464 13221434050 021443 0 ustar root root 0000000 0000000
python-pskc-1.0/tests/misc/SampleFullyQualifiedNS.xml 0000644 0000000 0000000 00000011454 12767245644 022756 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-1.0/tests/misc/odd-namespace.pskcxml 0000644 0000000 0000000 00000001101 12676002045 021762 0 ustar root root 0000000 0000000
Issuer-AMTIzNA==
python-pskc-1.0/tests/misc/policy.pskcxml 0000644 0000000 0000000 00000005156 12767245644 020616 0 ustar root root 0000000 0000000
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=2006-05-01T00:00:00Z2026-05-31T00:00:00Z4321OTPMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=OTPMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=OTPBarMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=OTPbarMTIzNA==
python-pskc-1.0/tests/misc/checkdigits.pskcxml 0000644 0000000 0000000 00000003344 12676003033 021554 0 ustar root root 0000000 0000000
MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/ 0000755 0000000 0000000 00000000000 13221476257 027467 5 ustar root root 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/password-encrypted.pskcxml python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/password-encrypted.psk0000644 0000000 0000000 00000003100 13221434114 034017 0 ustar root root 0000000 0000000
y6TzckeLRQw=999Token Manufacturer9876543218701/01/2008Credential IssuerMySecondTokenF/CY93NYc/SvmxT3oB6PzG7p6zpG92/thN793ZE7GM6yCM6gz9OKNRzibhg=VVBYqRF1QSpetvIB2vBAzw==6clqJvT9l0xIZtWSch2t6zr0IwU=
python-pskc-1.0/tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/non-encrypted.pskcxml 0000644 0000000 0000000 00000002637 13221434107 033653 0 ustar root root 0000000 0000000
Token Manufacturer9876543218701/01/2008Credential IssuerMyFirstTokenWldjTHZwRm9YTkhBRytseDMrUnc=WldjTHZwRm9YTkhBRytseDM=AAAAAAAAACo=WldjTHZwRm9YTkhBRytseDM=
python-pskc-1.0/tests/invalid/ 0000755 0000000 0000000 00000000000 13221476257 016370 5 ustar root root 0000000 0000000 python-pskc-1.0/tests/invalid/not-integer2.pskcxml 0000644 0000000 0000000 00000000474 12676003033 022304 0 ustar root root 0000000 0000000
python-pskc-1.0/tests/invalid/wrongversion.pskcxml 0000644 0000000 0000000 00000000762 13036777211 022540 0 ustar root root 0000000 0000000
Issuer-AMTIzNA==
python-pskc-1.0/tests/invalid/missing-encryption-method.pskcxml 0000644 0000000 0000000 00000001731 13036777211 025112 0 ustar root root 0000000 0000000
Pre-shared-keyAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
python-pskc-1.0/tests/invalid/mac-algorithm.pskcxml 0000644 0000000 0000000 00000003253 12676002045 022513 0 ustar root root 0000000 0000000
Pre-shared-keyESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSXManufacturer987654321CM_ID_001IssuerAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvSu+NvtQfmvfJzF6bmQiJqoLRExc=0
python-pskc-1.0/tests/invalid/unknown-encryption.pskcxml 0000644 0000000 0000000 00000001637 12676003033 023660 0 ustar root root 0000000 0000000
Pre-shared-key
AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
python-pskc-1.0/tests/invalid/wrongelement.pskcxml 0000644 0000000 0000000 00000000067 12676002045 022475 0 ustar root root 0000000 0000000
python-pskc-1.0/tests/invalid/mac-value.pskcxml 0000644 0000000 0000000 00000003274 13036777211 021651 0 ustar root root 0000000 0000000
Pre-shared-keyESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSXManufacturer987654321CM_ID_001IssuerAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvAu+NvtQfmvfJzF6bmQiJqoLRExc=0
python-pskc-1.0/tests/invalid/notxml.pskcxml 0000644 0000000 0000000 00000000066 12676002045 021307 0 ustar root root 0000000 0000000
python-pskc-1.0/tests/invalid/no-mac-method.pskcxml 0000644 0000000 0000000 00000001717 12767245644 022441 0 ustar root root 0000000 0000000
Pre-shared-keyAAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGvLP6xMvjtypbfT9PdkJhBZ+D6O4w=
python-pskc-1.0/tests/invalid/missing-encryption.pskcxml 0000644 0000000 0000000 00000001545 13026303736 023633 0 ustar root root 0000000 0000000
Pre-shared-key
AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv
python-pskc-1.0/tests/invalid/incomplete-derivation.pskcxml 0000644 0000000 0000000 00000003247 12767245644 024312 0 ustar root root 0000000 0000000
My Password 12GTTnLwM3I4e5IO5FkufoOEiOhNj91fhKRQBtBJYluUDsPOLTfUvoU2dStyOwYZxoTvo+S22nsmS2Z/RtcoF8Hfh+jzMe0RkiafpoDpnoZTjPYZu6V+A4aEn032yCr4fLP6xMvjtypbfT9PdkJhBZ+D6O4w=
python-pskc-1.0/tests/invalid/not-boolean.pskcxml 0000644 0000000 0000000 00000000601 12676003033 022174 0 ustar root root 0000000 0000000
python-pskc-1.0/tests/invalid/not-integer.pskcxml 0000644 0000000 0000000 00000000511 12676003033 022212 0 ustar root root 0000000 0000000
TWELVE
python-pskc-1.0/tests/test_draft_keyprov.doctest 0000644 0000000 0000000 00000007526 13221435626 022254 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-hoyer-keyprov-pskc-algorithm-profiles-01/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-hoyer-keyprov-pskc-algorithm-profiles-01/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-hoyer-keyprov-pskc-algorithm-profiles-01/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-hoyer-keyprov-pskc-algorithm-profiles-01/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-1.0/NEWS 0000644 0000000 0000000 00000007701 13221476207 014277 0 ustar root root 0000000 0000000 changes from 0.5 to 1.0
-----------------------
* fix a bug in writing passphrase encrypted PSKC files on Python3
* fix a typo in the pin_max_failed_attempts attribute (the old name is
available as a deprecated property)
* switch from pycrypto to cryptography as provider for encryption functions
because the latter is better supported
* switch to using the PBKDF2 implementation from hashlib which requires
Python 2.7.8 or newer
* use defusedxml when available (python-pskc now supports both standard
xml.etree and lxml with and without defusedxml)
* support checking and generating embedded XML signatures (this requires the
signxml library which is not required for any other operations)
* add limited support for very old draft PSKC versions (it is speculated that
this resembles the "Verisign PSKC format" that some applications produce)
* support Camellia-CBC and KW-Camellia encryption algorithms
* support any hashing algorithm available in Python
* add a --secret-encoding option to pskc2csv to allow base64 encoded binary
output
* support naming the CSV column headers in pskc2csv
* add a manual page for pskc2csv
* a number of documentation, code style and test suite improvements
changes from 0.4 to 0.5
-----------------------
* numerous compatibility improvements for reading PSKC files that do not
follow the RFC 6030 schema exactly: specifically accept a number of old
Internet Draft specifications that preceded RFC 6030 and support an
ActivIdentity file format
* split device information from key information (keep old API available) to
allow multiple keys per device (this is not allowed by RFC 6030 but was
allowed in older Internet Drafts)
* accept MAC to be over plaintext in addition to ciphertext
* fall back to using encryption key as MAC key
* refactoring of some encryption, parsing and serialising functionality into
separate modules for better maintainability
* add configuration for running test suite via Tox
* addition of a large number of test cases, bringing the branch coverage to
100%
* documentation improvements
* drop official support for Python 2.6 (the module still works but is just no
longer tested with it)
changes from 0.3 to 0.4
-----------------------
* add support for writing encrypted PSKC files (with either a pre-shared key
or PBKDF2 password-based encryption)
* extend may_use() policy checking function to check for unknown policy
elements and key expiry
* add a number of tests for existing vendor PSKC files and have full line
coverage with tests
* be more lenient in handling a number of XML files (e.g. automatically
sanitise encryption algorithm URIs, ignore XML namespaces and support more
spellings of some properties)
* support reading password or key files in pskc2csv
* support Python 3 in the pskc2csv script (thanks Mathias Laurin)
* refactoring and clean-ups to be more easily extendible (thanks Mathias
Laurin)
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-1.0/ChangeLog 0000644 0000000 0000000 00000167602 13221476207 015361 0 ustar root root 0000000 0000000 2017-12-29 Arthur de Jong
* [2651e80] tests/test_write.doctest: Not all XML serialisers
write namespaces in same order
This ignores the namespace declarations in the generated XML
files because not all implementations on all environments write
these in the same order.
2017-12-29 Arthur de Jong
* [44b1353] docs/conf.py, setup.cfg, tox.ini: Add Sphinx
documentation checks
This also slightly tunes the way Sphinx documentation is built.
2017-12-15 Arthur de Jong
* [42be53b] pskc2csv.py, tox.ini: Add support for PyPy
2017-12-15 Arthur de Jong
* [660ed65] setup.py, tox.ini: Add support for Python 3.7
2017-12-15 Arthur de Jong
* [9cd97c9] README, setup.py: Use README as package long description
2017-12-15 Arthur de Jong
* [20bf9c5] docs/encryption.rst, pskc/encryption.py, pskc2csv.py,
tests/test_rfc6030.doctest: Add an is_encrypted property
This property can be use to see whether the PSKC file needs an
additional pre-shared key or passphrase to decrypt any stored
information.
2017-12-27 Arthur de Jong
* [c365a70] : Implement XML signature checking
2017-12-17 Arthur de Jong
* [418f3dc] docs/encryption.rst, docs/index.rst, docs/mac.rst,
docs/signatures.rst, docs/usage.rst: Add documentation for signed
PSKC files
2017-12-23 Arthur de Jong
* [a97ac46] pskc/parser.py, pskc/serialiser.py,
pskc/signature.py, pskc/xml.py, setup.py,
tests/certificate/README, tests/certificate/ca-certificate.pem,
tests/certificate/ca-key.pem, tests/certificate/certificate.pem,
tests/certificate/key.pem, tests/certificate/request.pem,
tests/certificate/ss-certificate.pem,
tests/rfc6030/figure9.pskcxml,
tests/test_draft_ietf_keyprov_pskc_02.doctest,
tests/test_rfc6030.doctest, tests/test_signature.doctest, tox.ini:
Implement signature checking
This adds support for creating and verifying embedded XML
signatures in PSKC files. This uses the third-party signxml
library for actual signing and verification.
The signxml library has a dependency on lxml and defusedxml
(and a few others) but all parts of python-pskc still work
correctly with our without lxml and/or defusedxml and signxml
is only required when working with embedded signatures.
This modifies the tox configuration to skip the signature
checks if singxml is not installed and to only require 100%
code coverage if the signature tests are done.
2017-12-15 Arthur de Jong
* [c0bd21f] pskc/xml.py: Move namespace moving to own function
2017-09-22 Arthur de Jong
* [ea503d6] pskc/__init__.py, pskc/parser.py, pskc/signature.py,
tests/test_draft_ietf_keyprov_pskc_02.doctest,
tests/test_rfc6030.doctest: Implement basic parsing of signature
properties
2017-12-23 Arthur de Jong
* [fcc6cdb] pskc2csv.py: Explicitly close output file in pskc2csv
This ensures that the file descriptor is closed if we opened
the file. This is not a big problem for the script (because
the script exists anyway) but causes problems for the tests.
2017-12-18 Arthur de Jong
* [052f5bc] docs/policy.rst, pskc/parser.py,
pskc/policy.py, pskc/serialiser.py, tests/test_misc.doctest,
tests/test_write.doctest: Fix typo in pin_max_failed_attempts
attribute
This makes the old name (pin_max_failed_attemtps) available as
a deprecated property.
2017-12-15 Arthur de Jong
* [6f0ca70] pskc/parser.py,
tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/non-encrypted.pskcxml,
tests/draft-hoyer-keyprov-portable-symmetric-key-container-00/password-encrypted.pskcxml,
tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/non-encrypted.pskcxml,
tests/draft-hoyer-keyprov-portable-symmetric-key-container-01/password-encrypted.pskcxml,
tests/test_draft_hoyer_keyprov_portable_symmetric_key_container.doctest:
Add limited support for very old draft PSKC versions
This adds basic support for parsing the PSKC files as specified
in draft-hoyer-keyprov-portable-symmetric-key-container-00 and
draft-hoyer-keyprov-portable-symmetric-key-container-01.
It should be able to extract secrets, counters, etc. but not
all properties from the PSKC file are supported.
It is speculated that this format resembles the "Verisign PSKC
format" that some applications produce.
2016-09-19 Arthur de Jong
* [9b85634] tests/multiotp/pskc-hotp-aes.txt,
tests/multiotp/pskc-hotp-pbe.txt, tests/multiotp/pskc-totp-aes.txt,
tests/multiotp/pskc-totp-pbe.txt,
tests/multiotp/tokens_hotp_aes.pskc,
tests/multiotp/tokens_hotp_pbe.pskc,
tests/multiotp/tokens_ocra_aes.pskc,
tests/multiotp/tokens_ocra_pbe.pskc,
tests/multiotp/tokens_totp_aes.pskc,
tests/multiotp/tokens_totp_pbe.pskc, tests/test_multiotp.doctest:
Add test files from multiOTP
This adds tests for parsing the files that are shipped as part
of the multiOTP test suite.
https://www.multiotp.net/
2017-12-15 Arthur de Jong
* [01507af] pskc/key.py, pskc/parser.py, pskc/serialiser.py,
tests/misc/partialxml.pskcxml, tests/test_misc.doctest,
tests/test_write.doctest: Refactor internal storate of encrypted
values
This changes the way encrypted values are stored internally before
being decrypted. For example, the internal _secret property can now
be a decrypted plain value or an EncryptedValue instance instead
of always being a DataType, simplifying some things (e.g. all
XML encoding/decoding is now done in the corresponding module).
This should not change the public API but does have consequences
for those who use custom serialisers or parsers.
2017-12-13 Arthur de Jong
* [dcf1919] pskc/crypto/aeskw.py, pskc/encryption.py,
tests/encryption/kw-camellia128.pskcxml,
tests/encryption/kw-camellia192.pskcxml,
tests/encryption/kw-camellia256.pskcxml,
tests/test_encryption.doctest: Add support for KW-Camellia suite
of algorithms
2017-12-13 Arthur de Jong
* [364e93d] pskc/encryption.py,
tests/encryption/camellia128-cbc.pskcxml,
tests/encryption/camellia192-cbc.pskcxml,
tests/encryption/camellia256-cbc.pskcxml,
tests/test_encryption.doctest: Add support for Camellia-CBC
suite of algorithms
2017-10-11 Arthur de Jong
* [4c5e046] docs/conf.py, docs/pskc2csv.rst, setup.cfg: Add a
manual page for pskc2csv
2017-10-09 Arthur de Jong
* [25cb2fc] setup.cfg: Ignore missing docstring in __init__ in flake
2017-09-30 Arthur de Jong
* [225e569] pskc/crypto/__init__.py, pskc/crypto/aeskw.py,
pskc/crypto/tripledeskw.py, pskc/encryption.py,
pskc/mac.py, setup.cfg, setup.py, tests/test_crypto.doctest,
tests/test_encryption.doctest, tox.ini: Replace pycrypto with
cryptography
The cryptography library is better supported.
This uses the functions from cryptography for AES and Triple
DES encryption, replaces the (un)padding functions that were
previously implemented in python-pskc with cryptography and uses
PBKDF2 implementation from hashlib.
2017-09-30 Arthur de Jong
* [5dff7d4] pskc/encryption.py: Use PBKDF2 from hashlib
This uses pbkdf2_hmac() from hashlib for the PBKDF2 calculation.
The downside of this is that this function is only available
since Python 2.7.8.
2017-09-30 Arthur de Jong
* [2c8a9b7] pskc/crypto/aeskw.py, pskc/crypto/tripledeskw.py,
pskc/encryption.py, pskc/mac.py, tests/test_aeskw.doctest,
tests/test_write.doctest: Replace use of pycrypto utility functions
This uses os.urandom() as a source for random data and replaces
other utility functions. This also removes one import for getting
the lengths of Tripple DES keys.
2017-09-24 Arthur de Jong
* [d0eddf8] pskc/serialiser.py, pskc/xml.py,
tests/test_write.doctest: Implement our own XML formatting
This avoids a using xml.dom.minidom to indent the XML tree and
keep the attributes ordered alphabetically. This also allows
for customisations to the XML formatting.
2017-09-24 Arthur de Jong
* [4ed4e11] tests/test_mac.doctest: Support hashlib from Python 2.7.3
Some Python versions don't have the algorithms_available property
but do have the algorithms property in hashlib.
2017-09-24 Arthur de Jong
* [b90faeb] pskc/xml.py, setup.py, tox.ini: Use defusedxml if
available
This uses the defusedxml library if available to defend agains
a number of XML-based attacks.
2017-09-23 Arthur de Jong
* [7272e54] pskc/serialiser.py, tests/test_write.doctest: Fix bug
in saving PBKDF2 salt on Python3
The PBKDF2 salt was saved in the wrong way (b'base64encodeddata'
instead of base64encodeddata) when using Python 3. This fixes
that problem and tests that saving and loading of a file that
uses PBKDF2 key derivation works.
2017-09-23 Arthur de Jong
* [cd33833] pskc2csv.py, setup.cfg, tests/test_pskc2csv.doctest:
Add tests for the pskc2csv script
This makes minor changes to the pskc2csv script to make it more
easily testable.
2017-09-22 Arthur de Jong
* [6028b8e] pskc2csv.py: Support adding custom CSV file headers
This allows adding an optional label to the --columns option that
can be used to output a label different from the key property
name in the CSV file header.
2017-09-20 Arthur de Jong
* [eef681b] pskc2csv.py: Add --secret-encoding option to pskc2csv
This option can be used to configure the encoding of the secret
in the CSV file (still hex by default).
2017-09-20 Arthur de Jong
* [6f78dd6] pskc/__init__.py, pskc/crypto/aeskw.py,
pskc/crypto/tripledeskw.py, pskc/exceptions.py, pskc/mac.py,
pskc/parser.py, pskc/policy.py, pskc/serialiser.py, setup.cfg,
tox.ini: Run flake8 from tox
This also makes a few small code formatting changes to ensure
that the flake8 tests pass.
2017-09-11 Arthur de Jong
* [cc3acc2] tox.ini: Simplify Tox configuration
2017-06-10 Arthur de Jong
* [0c00c80] pskc/__init__.py, pskc/encryption.py, pskc/parser.py,
pskc/serialiser.py, pskc/xml.py, pskc2csv.py: Various minor code
style improvements
2017-06-10 Arthur de Jong
* [510e6a5] pskc/encryption.py, pskc/parser.py: Normalise key
derivation algorithms
This makes KeyDerivation.algorithm and KeyDerivation.pbkdf2_prf
properties automatically normalise assigned values.
2017-06-10 Arthur de Jong
* [d72e6cc] pskc/xml.py: Switch to using non-deprecated method
This uses ElementTree.iter() instead of ElementTree.getiterator()
for going over all the child elements in the tree because the
latter is deprecated.
2017-06-10 Arthur de Jong
* [7b106ff] docs/usage.rst, pskc/key.py, tests/test_yubico.doctest:
Provide Key.userid convenience property
This provides a read-only userid property on Key objects that uses
the key_userid or device_userid value, whichever one is defined.
2017-06-09 Arthur de Jong
* [f0d2991] docs/conf.py, docs/encryption.rst, docs/exceptions.rst,
docs/mac.rst: Document supported encryption and MAC algorithms
This also includes a few other small documentation improvements.
2017-06-09 Arthur de Jong
* [8b8848d] pskc/encryption.py, pskc/mac.py,
tests/test_invalid.doctest, tests/test_mac.doctest: Refactor
MAC lookups
This switches to using the hashlib.new() function to be able to use
all hashes that are available in Python (specifically RIPEMD160).
This also adds a number of tests for HMACs using test vectors
from RFC 2202, RFC 4231 and RFC 2857.
2017-06-09 Arthur de Jong
* [e10f9c6] pskc/algorithms.py: Handle more algortihm URIs
This adds a number of algorithm URIs defined in RFC 6931 and also
simplifies the definition of the list of URIs. It also adds more
aliases for algorithms.
2017-01-25 Arthur de Jong
* [1fc1a03] README, docs/usage.rst, setup.py: Switch URLs to HTTPS
2017-01-21 Arthur de Jong
* [8de25c2] tests/actividentity/test.pskcxml,
tests/test_actividentity.doctest: Correct name of ActivIdentity
test file
2017-01-21 Arthur de Jong
* [5889df7] ChangeLog, NEWS, README, docs/conf.py, pskc/__init__.py,
pskc2csv.py, setup.py: Get files ready for 0.5 release
2017-01-15 Arthur de Jong
* [29a183d] tests/test_feitian.doctest, tests/test_nagraid.doctest:
Split vendor tests
Have one doctest file per vendor to make tests a little more
manageable.
2017-01-14 Arthur de Jong
* [02eb520] tests/test_yubico.doctest, tests/yubico/example1.pskcxml,
tests/yubico/example2.pskcxml, tests/yubico/example3.pskcxml:
Add tests for Yubikey files
This adds tests from draft-josefsson-keyprov-pskc-yubikey-00.
2017-01-13 Arthur de Jong
* [12dfa64] pskc/parser.py, tests/actividentity/test.pskc,
tests/test_actividentity.doctest: Support legacy ActivIdentity
files
This adds support for parsing ActivIdentity files that conform
to a very old version of an Internet Draft. The implementation
and test were based on a file provided by Jaap Ruijgrok.
2017-01-11 Arthur de Jong
* [a5e2343] pskc/parser.py,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/actividentity-3des.pskcxml,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/ocra.pskcxml,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/securid-aes-counter.pskcxml,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/totp.pskcxml:
Use original examples from old profiles Internet Draft
This updates the tests to use the original examples from
draft-hoyer-keyprov-pskc-algorithm-profiles-01 instead of
modifying them to fit the RFC 6030 schema (but does include some
minor changes to make them valid XML).
This adds a few additions to the parser to handle legacy challenge
and resposne encoding and a few key policy properties.
This also includes a fix for 0b757ec in the handling of the
element under a element.
2016-12-21 Arthur de Jong
* [2f7cb1a] tests/rfc6030/figure8.pskcxml,
tests/rfc6030/figure9.pskcxml, tests/test_rfc6030.doctest:
Add all figures from RFC 6030 to test suite
Note that asymmetric encryption and digital signature checking
has not yet been implemented so the tests are pretty minimal.
2016-12-21 Arthur de Jong
* [0b757ec] pskc/parser.py, pskc/xml.py,
tests/draft-ietf-keyprov-pskc-02/figure2.pskcxml,
tests/draft-ietf-keyprov-pskc-02/figure3.pskcxml,
tests/draft-ietf-keyprov-pskc-02/figure4.pskcxml,
tests/draft-ietf-keyprov-pskc-02/figure5.pskcxml,
tests/draft-ietf-keyprov-pskc-02/figure6.pskcxml,
tests/draft-ietf-keyprov-pskc-02/figure7.pskcxml,
tests/draft-ietf-keyprov-pskc-02/figure8.pskcxml,
tests/draft-ietf-keyprov-pskc-02/figure9.pskcxml,
tests/test_draft_ietf_keyprov_pskc_02.doctest: Add support for
older Internet Draft version
This adds support for parsing most examples from
draft-ietf-keyprov-pskc-02. That file uses a few other names
for elements and attributes of the PSKC file and a few other
minor differences.
The XML parsing has been changed to allow specifying multiple
matches and the find*() functions now return the first found match.
While all examples from draft-ietf-keyprov-pskc-02 are tested
support for verifying digital signatures and asymmetric keys
have not yet been implemented.
2016-12-19 Arthur de Jong
* [09076f8] tests/test_encryption.doctest: Fix typo in test
2016-12-20 Arthur de Jong
* [46fa5f1] setup.cfg: Fail tests on missing coverage
2016-12-20 Arthur de Jong
* [047a2a9] pskc/key.py, pskc/mac.py,
tests/encryption/mac-over-plaintext.pskcxml,
tests/invalid/mac-missing.pskcxml, tests/invalid/mac-value.pskcxml,
tests/invalid/missing-encryption-method.pskcxml,
tests/test_encryption.doctest, tests/test_invalid.doctest:
Allow MAC over plaintext or ciphertext
RFC 6030 implies that the MAC should be performed over the
ciphertext but some earlier drafts implied that the MAC should
be performed on the plaintext. This change accpets the MAC if
either the plaintext or ciphertext match.
Note that this change allows for a padding oracle attack when
CBC encryption modes are used because decryption (and unpadding)
needs to be done before MAC checking. However, this module is
not expected to be available to users to process arbitrary PSKC
files repeatedly.
This removes the tests for a missing MAC key (and replaces it for
tests of missing EncryptionMethod) because falling back to using
the encryption key (implemented in a444f78) in combination with
this change means that decryption is performed before MAC checking
and is no longer possible to trigger a missing MAC key error.
2016-12-19 Arthur de Jong
* [bae7084] pskc/crypto/__init__.py, pskc/encryption.py,
tests/test_crypto.doctest: Add sanity checks to unpadding
2016-12-19 Arthur de Jong
* [d864bc8] pskc/serialiser.py: Ensure XML file ends with a newline
2016-12-19 Arthur de Jong
* [c631628] pskc/xml.py: Adapt coverage pragma annotations
This fixes the pragma directives to be be correct independently
of whether lxml is installed or not.
2016-12-19 Arthur de Jong
* [18d82dc] .gitignore, tox.ini: Add Tox configuration
This sets up Tox with various versions of Python and for each
version a run with and without lxml.
2016-12-19 Arthur de Jong
* [71058e2] tests/test_write.doctest: Close read files in tests
This ensures that the files that are read in the test suite are
properly closed to avoid leaking open file descriptors.
2016-12-18 Arthur de Jong
* [f0a0a3b] pskc/parser.py: Support missing or lowercase version
attribute
2016-09-26 Arthur de Jong
* [3bf4737] docs/usage.rst: Fix copy-pasto in documentation
This accidentally slipped in as part of beafc6b. 2016-09-19
Arthur de Jong
* [02b30a9] pskc/__init__.py, pskc/parser.py, pskc/serialiser.py:
Also move outer writing and parsing to modules
2016-09-17 Arthur de Jong
* [b1f8f87] .gitignore, README, pskc/__init__.py: Add writing
example to toplevel documentation
2016-09-17 Arthur de Jong
* [e23a467] pskc/key.py: Use custom data descriptors for key
properties
This uses a custom data descriptor (property) for secret, counter,
time_offset, time_interval and time_drift.
2016-09-17 Arthur de Jong
* [beafc6b] docs/usage.rst, pskc/__init__.py, pskc/device.py,
pskc/key.py, pskc/parser.py, pskc/policy.py, pskc/serialiser.py,
tests/test_misc.doctest, tests/test_write.doctest: Support
separate device from key
This allows having multiple keys per device while also maintaining
the previous API.
Note that having multiple keys per device is not allowed by the
RFC 6030 schema but is allowed by some older internet drafts.
2016-09-16 Arthur de Jong
* [84bfb8a] pskc/__init__.py, pskc/encryption.py, pskc/key.py,
pskc/mac.py, pskc/policy.py, pskc/serialiser.py: Move XML
generation to own module
Similar to the change for parsing, move the XML serialisation
of PSKC data to a single class in a separate module.
2016-09-14 Arthur de Jong
* [426e821] pskc/__init__.py, pskc/encryption.py, pskc/key.py,
pskc/mac.py, pskc/parser.py, pskc/policy.py: Move document
parsing to own module
This moves all the parse() functions to a single class in a
dedicated module that can be used for parsing PSKC files. This
should make it easier to subclass the parser.
2016-09-14 Arthur de Jong
* [bf34209] tests/invalid/no-mac-method.pskcxml,
tests/test_invalid.doctest, tests/test_rfc6030.doctest: Some
minor improvements to the tests
2016-09-12 Arthur de Jong
* [600ae68] pskc/encryption.py, pskc/key.py, pskc/xml.py,
setup.cfg, tests/invalid/empty-mac-key.pskcxml,
tests/invalid/incomplete-derivation.pskcxml,
tests/invalid/missing-encryption.pskcxml,
tests/misc/SampleFullyQualifiedNS.xml, tests/misc/policy.pskcxml,
tests/test_aeskw.doctest, tests/test_encryption.doctest,
tests/test_invalid.doctest, tests/test_misc.doctest,
tests/test_write.doctest: Improve branch coverage
This enables branch coverage testing and adds tests to improve
coverage.
2016-09-11 Arthur de Jong
* [713d106] pskc/encryption.py, tests/test_encryption.doctest:
Support specifying PRF in setup_pbkdf2()
This also ensures that the PRF URL is normalised.
2016-09-11 Arthur de Jong
* [ff811c9] pskc/encryption.py: Fix bug in passing explicit key
to setup_preshared_key()
2016-09-11 Arthur de Jong
* [fa07aa5] docs/encryption.rst, pskc/encryption.py: Clarify
encryption.setup_*() documentation
This tries to make it clearer that the setup_preshared_key()
and setup_pbkdf2() functions are meant to be used when writing
out PSKC files.
2016-04-23 Arthur de Jong
* [a444f78] pskc/key.py, pskc/mac.py,
tests/encryption/no-mac-key.pskcxml,
tests/invalid/mac-missing.pskcxml, tests/test_encryption.doctest,
tests/test_invalid.doctest: Fall back to encryption key for MAC
This uses the encryption key also as MAC key if no MAC key has
been specified in the PSKC file. Earlier versions of the PSKC
draft specified this behaviour.
2016-04-23 Arthur de Jong
* [9b76135] pskc/encryption.py,
tests/encryption/aes128-cbc-noiv.pskcxml,
tests/test_encryption.doctest: Allow global specification of IV
In older versions of the PSKC standard it was allowed to
have a global initialization vector for CBC based encryption
algorithms. It is probably not a good idea to re-use an IV
in general.
2016-04-23 Arthur de Jong
* [d53f05b] pskc/encryption.py, pskc/mac.py: Move crypto to functions
This makes it much easier to test the encryption, decryption
and HMAC processing separate from the PSKC parsing.
2016-04-05 Arthur de Jong
* [5dbfefd] pskc/__init__.py, pskc/encryption.py, pskc/key.py,
pskc/policy.py: Remove parse call from constructors
This makes the creation if internal instances a litte more
consistent.
2016-04-05 Arthur de Jong
* [0d7caf1] pskc/algorithms.py, pskc/encryption.py, pskc/mac.py:
Move algorithm uri handling to separate module
2016-03-29 Arthur de Jong
* [22ba9f1] pskc/crypto/__init__.py, pskc/encryption.py: Move
padding functions to crypto package
2016-03-28 Arthur de Jong
* [efbe94c] ChangeLog, NEWS, pskc/__init__.py, setup.py: Get files
ready for 0.4 release
2016-03-26 Arthur de Jong
* [0c57335] docs/policy.rst: Document may_use() policy function
2016-03-27 Arthur de Jong
* [b4a6c72] : Implement writing encrypted files
This adds support for setting up encryption keys and password-based
key derivation when writing PSKC files. Also MAC keys are set
up when needed.
2016-03-26 Arthur de Jong
* [59aa65b] README, docs/conf.py, docs/encryption.rst, docs/mac.rst,
docs/usage.rst, pskc/__init__.py: Document writing encrypted files
2016-03-21 Arthur de Jong
* [5f32528] tests/test_write.doctest: Add encryption error tests
2016-03-21 Arthur de Jong
* [7ede4a1] tests/test_write.doctest: Add tests for writing
encrypted PSKC files
2016-03-20 Arthur de Jong
* [1ff3237] pskc/encryption.py: Allow configuring a pre-shared key
This method allows configuring a pre-shared encryption key and
will chose reasonable defaults for needed encryption values
(e.g. it will choose an algorithm, generate a new key of the
appropriate length if needed, etc.).
2016-03-19 Arthur de Jong
* [50414a3] pskc/encryption.py, tests/test_encryption.doctest:
Allow configuring PBKDF2 key derivation
This factors out the PBKDF2 key derivation to a separate function
and introduces a function to configure KeyDerivation instances
with PBKDF2.
2016-03-21 Arthur de Jong
* [5ac9d43] pskc/mac.py, tests/test_encryption.doctest: Allow
configuring a MAC key
This method will set up a MAC key and algorithm as specified or
use reasonable defauts.
2016-03-20 Arthur de Jong
* [16da531] pskc/key.py, pskc/mac.py: Generate MAC values
2016-03-20 Arthur de Jong
* [ca0fa36] pskc/__init__.py, pskc/encryption.py, pskc/mac.py:
Write MACMethod
This also makes the MAC.algorithm a property similarly as what
is done for Encryption (normalise algorithm names) and adds a
setter for the MAC.key property.
2016-03-21 Arthur de Jong
* [8fd35ba] pskc/encryption.py, pskc/key.py: Write out encrypted
values
The Encryption class now has a fields property that lists the
fields that should be encrypted when writing the PSKC file.
This adds an encrypt_value() function that performs the encryption
and various functions to convert the plain value to binary before
writing the encrypted XML elements.
2016-03-20 Arthur de Jong
* [eba541e] pskc/__init__.py, pskc/encryption.py, pskc/mac.py:
Make Encryption and MAC constructors consistent
This removes calling parse() from the Encryption and MAC
constructors and stores a reference to the PSKC object in both
objects so it can be used later on.
2016-03-20 Arthur de Jong
* [fe21231] pskc/__init__.py, pskc/encryption.py,
tests/test_write.doctest: Write encryption key information
This writes information about a pre-shared key or PBKDF2 key
derivation in the PSKC file. This also means that writing
a decrypted version of a previously encrypted file requires
actively removing the encryption.
2016-03-19 Arthur de Jong
* [0893640] pskc/encryption.py, tests/test_misc.doctest: Add
algorithm_key_lengths property
This property on the Encryption object provides a list of key
sizes (in bytes) that the configured encryption algorithm supports.
2016-03-22 Arthur de Jong
* [8b5f6c2] pskc/policy.py, tests/test_misc.doctest,
tests/test_rfc6030.doctest, tests/test_write.doctest: Also check
key expiry in may_use()
2016-03-20 Arthur de Jong
* [dfa57ae] pskc2csv.py: Support reading password or key in pskc2csv
This supports reading the encryption password or key from the
command line or from a file.
2014-06-28 Arthur de Jong
* [0744222] pskc/xml.py: Copy namespaces to toplevel element
Ensure that when writing an XML file all namespace definitions
are on the toplevel KeyContainer element instead of scattered
throughout the XML document.
2016-03-19 Arthur de Jong
* [e8ef157] pskc/__init__.py, tests/test_write.doctest: Support
writing to text streams in Python 3
This supports writing the XML output to binary streams as well
as text streams in Python 3.
2016-03-19 Arthur de Jong
* [cadc6d9] pskc/key.py, pskc/mac.py,
tests/invalid/missing-encryption.pskcxml,
tests/invalid/not-boolean.pskcxml,
tests/invalid/not-integer.pskcxml,
tests/invalid/not-integer2.pskcxml,
tests/invalid/unknown-encryption.pskcxml, tests/test_aeskw.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:
Improve tests and test coverage
This adds tests to ensure that incorrect attribute and value
types in the PSKC file raise a ValueError exception and extends
the tests for invalid encryption options.
This removes some code or adds no cover directives to a few
places that have unreachable code or are Python version specific
and places doctest directives inside the doctests where needed.
2016-03-19 Arthur de Jong
* [b8905e0] pskc/key.py, pskc/xml.py, tests/misc/checkdigits.pskcxml,
tests/test_misc.doctest: Support both CheckDigit and CheckDigits
RFC 6030 is not clear about whether the attribute of
ChallengeFormat and ResponseFormat should be the singular
CheckDigit or the plural CheckDigits. This ensures that both
forms are accepted.
2016-03-19 Arthur de Jong
* [7915c55] pskc/policy.py, tests/misc/policy.pskcxml,
tests/test_misc.doctest: Implement policy checking
This checks for unknown policy elements in the PSKC file and
will cause the key usage policy check to fail.
2016-03-18 Arthur de Jong
* [1687fd6] tests/feitian/20120919-test001-4282.xml,
tests/feitian/file1.pskcxml, tests/nagraid/file1.pskcxml,
tests/test_vendors.doctest: Add a few tests for vendor files
Some vendor-specific files were lifted from the LinOTP test suite
and another Feitian file was found in the oath-toolkit repository.
2016-01-31 Arthur de Jong
* [aae8a18] pskc/key.py, tests/misc/integers.pskcxml,
tests/test_misc.doctest: Support various integer representations
This extends support for handling various encoding methods for
integer values in PSKC files. For encrypted files the decrypted
value is first tried to be evaluated as an ASCII representation
of the number and after that big-endian decoded.
For plaintext values first ASCII decoding is tried after which
base64 decoding is tried which tries the same encodings as for
decrypted values.
There should be no possibility for any base64 encoded value
(either of an ASCII value or a big-endian value) to be interpreted
as an ASCII value for any 32-bit integer.
There is a possibility that a big-endian encoded integer could
be incorrectly interpreted as an ASCII value but this is only
the case for 110 numbers when only considering 6-digit numbers.
2016-01-24 Arthur de Jong
* [c86aaea] README, pskc/__init__.py,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/actividentity-3des.pskcxml,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/ocra.pskcxml,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/securid-aes-counter.pskcxml,
tests/draft-hoyer-keyprov-pskc-algorithm-profiles-01/totp.pskcxml,
tests/encryption/aes128-cbc.pskcxml,
tests/encryption/aes192-cbc.pskcxml,
tests/encryption/aes256-cbc.pskcxml,
tests/encryption/kw-aes128.pskcxml,
tests/encryption/kw-aes192.pskcxml,
tests/encryption/kw-aes256.pskcxml,
tests/encryption/kw-tripledes.pskcxml,
tests/encryption/tripledes-cbc.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/misc/SampleFullyQualifiedNS.xml,
tests/misc/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_draft_keyprov.doctest, tests/test_encryption.doctest,
tests/test_invalid.doctest, tests/test_misc.doctest,
tests/test_rfc6030.doctest, tests/test_write.doctest: Re-organise
test files
This puts the test PSKC files in subdirectories so they can be
organised more cleanly.
2016-01-23 Arthur de Jong
* [1904dc2] tests/test_misc.doctest: Add test for incorrect key
derivation
If no key derivation algorithm has been specified in the PSKC
file an exception should be raised when attempting to perform
key derivation.
2016-01-24 Arthur de Jong
* [91f66f4] pskc/encryption.py, pskc/key.py, pskc/mac.py: Refactor
out EncryptedValue and ValueMAC
This removes the EncryptedValue and ValueMAC classes and instead
moves the XML parsing of these values to the DataType class. This
will make it easier to support different parsing schemes.
This also includes a small consistency improvement in the
subclasses of DataType.
2016-01-23 Arthur de Jong
* [9b13d3b] pskc/encryption.py, tests/test_misc.doctest: Normalise
algorithm names
This transforms the algorithm URIs that are set to known values
when parsing or setting the algorithm.
2016-01-22 Arthur de Jong
* [b6eab47] docs/encryption.rst, pskc/encryption.py,
tests/test_encryption.doctest, tests/test_misc.doctest: Add
encryption algorithm property
Either determine the encryption algorithm from the PSKC file
or from the explicitly set value. This also adds support for
setting the encryption key name.
2016-01-22 Arthur de Jong
* [b5f7de5] pskc/key.py, tests/test_write.doctest: Fix a problem
when writing previously encrypted file
This fixes a problem with writing a PSKC file that is based on
a read file that was encrypted.
2016-01-22 Arthur de Jong
* [107a836] pskc/__init__.py, pskc/encryption.py, pskc/key.py,
pskc/mac.py, pskc/policy.py, pskc/xml.py: Strip XML namespaces
before parsing
This simplifies calls to the find() family of functions and
allows parsing PSKC files that have slightly different namespace
URLs. This is especially common when parsing old draft versions
of the specification.
This also removes passing multiple patterns to the find()
functions that was introduced in 68b20e2.
2015-12-28 Arthur de Jong
* [a86ff8a] README, docs/encryption.rst: Update some documentation
This adds a development notes section to the README and changes
the wording on the encryption page.
2015-12-01 Mathias Laurin
* [0ff4154] docs/encryption.rst: Fix typo in the documentation
2015-12-01 Mathias Laurin
* [3473903] pskc2csv.py: Support Python 3
2015-11-30 Mathias Laurin
* [a82a60b] pskc/key.py: Make value conversion methods static private
- the conversions do not call self: they are static - the
conversions are not to be used out of the class: make private
2015-11-30 Mathias Laurin
* [e711a30] pskc/key.py: Provide abstract methods to clarify API
2015-11-30 Mathias Laurin
* [1577687] pskc/encryption.py: Fix typo in variable name
2015-11-30 Mathias Laurin
* [3aa2a6f] tests/test_invalid.doctest: Fix doctest:
IGNORE_EXCEPTION_DETAL
2015-10-07 Arthur de Jong
* [c155d15] ChangeLog, MANIFEST.in, NEWS, pskc/__init__.py,
setup.py: Get files ready for 0.3 release
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/crypto/__init__.py, pskc/crypto/aeskw.py,
pskc/crypto/tripledeskw.py, pskc/encryption.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/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.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: 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:
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-1.0/tox.ini 0000644 0000000 0000000 00000002002 13221474062 015075 0 ustar root root 0000000 0000000 [tox]
envlist = py{27,34,35,36,37,py}-signxml,py{27,34,35,36,37,py}{-legacy,-lxml}{,-defusedxml},flake8,docs
skip_missing_interpreters = True
[testenv]
deps = nose
coverage
signxml: signxml
lxml: lxml
defusedxml: defusedxml
commands = signxml: nosetests
legacy: nosetests --exclude=test_signature.doctest --cover-min-percentage 95
lxml: nosetests --exclude=test_signature.doctest --cover-min-percentage 95
setenv=
PYTHONWARNINGS=all
[testenv:flake8]
skip_install = true
deps = flake8
flake8-author
flake8-blind-except
py{35,36,37}: flake8-bugbear
flake8-class-newline
flake8-commas
flake8-deprecated
flake8-docstrings
flake8-exact-pin
flake8-import-order
flake8-print
flake8-quotes
flake8-tidy-imports
flake8-tuple
pep8-naming
commands = flake8 pskc *.py
[testenv:docs]
basepython = python
deps = sphinx
commands = python setup.py build_sphinx --build-dir {envtmpdir} -W
python-pskc-1.0/README 0000644 0000000 0000000 00000004755 13221454470 014464 0 ustar root root 0000000 0000000 Python module for handling PSKC files
=====================================
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 (seed files)
to different types of crypto modules, commonly one-time password systems or
other authentication devices.
This module can be used to extract keys from PSKC files for use in an OTP
authentication system. The module can also be used for authoring PSKC files.
This module should be able to handle most common PSKC files.
https://arthurdejong.org/python-pskc/
API
---
The module provides a straightforward API that is mostly geared towards
parsing existing PSKC files.
Extracting key material 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('%s %s' % (key.serial, str(key.secret.decode())))
987654321 12345678901234567890
Writing am encrypted PSKC file is as simple as:
>>> pskc = PSKC()
>>> key = pskc.add_key(
... id='456', secret='987654321', manufacturer='Manufacturer',
... algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:hotp')
>>> pskc.encryption.setup_pbkdf2('passphrase')
>>> pskc.write('output.pskcxml')
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 securely dispose of memory that may have held
private key material.
Copyright
---------
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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-1.0/MANIFEST.in 0000644 0000000 0000000 00000000246 13221476207 015333 0 ustar root root 0000000 0000000 include README NEWS ChangeLog COPYING tox.ini *.py
recursive-include tests *.doctest *.py *.pskcxml *.xml *.pem *.txt *.pskc README
recursive-include docs *.rst *.py
python-pskc-1.0/pskc/ 0000755 0000000 0000000 00000000000 13221476256 014537 5 ustar root root 0000000 0000000 python-pskc-1.0/pskc/encryption.py 0000644 0000000 0000000 00000037212 13221434047 017300 0 ustar root root 0000000 0000000 # encryption.py - module for handling encrypted values
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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,
algorithms and decryption.
The encryption key can be derived using the KeyDerivation class.
"""
import os
import re
def algorithm_key_lengths(algorithm):
"""Return the possible key lengths for the configured algorithm."""
from pskc.exceptions import DecryptionError
if algorithm is None:
raise DecryptionError('No algorithm specified')
elif algorithm.endswith('#aes128-cbc') or \
algorithm.endswith('#aes192-cbc') or \
algorithm.endswith('#aes256-cbc'):
return [int(algorithm[-7:-4]) // 8]
elif algorithm.endswith('#tripledes-cbc') or \
algorithm.endswith('#kw-tripledes'):
return [16, 24]
elif algorithm.endswith('#kw-aes128') or \
algorithm.endswith('#kw-aes192') or \
algorithm.endswith('#kw-aes256'):
return [int(algorithm[-3:]) // 8]
elif (algorithm.endswith('#camellia128-cbc') or
algorithm.endswith('#camellia192-cbc') or
algorithm.endswith('#camellia256-cbc')):
return [int(algorithm[-7:-4]) // 8]
elif (algorithm.endswith('#kw-camellia128') or
algorithm.endswith('#kw-camellia192') or
algorithm.endswith('#kw-camellia256')):
return [int(algorithm[-3:]) // 8]
else:
raise DecryptionError('Unsupported algorithm: %r' % algorithm)
def _decrypt_cbc(algorithm, key, ciphertext, iv=None):
"""Decrypt the ciphertext and return the plaintext value."""
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, modes
from pskc.exceptions import DecryptionError
if not iv:
iv = ciphertext[:algorithm.block_size // 8]
ciphertext = ciphertext[algorithm.block_size // 8:]
cipher = Cipher(
algorithm(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
unpadder = padding.PKCS7(algorithm.block_size).unpadder()
try:
return unpadder.update(
decryptor.update(ciphertext) +
decryptor.finalize()) + unpadder.finalize()
except ValueError:
raise DecryptionError('Invalid padding')
def decrypt(algorithm, key, ciphertext, iv=None):
"""Decrypt the ciphertext and return the plaintext value."""
from cryptography.hazmat.primitives.ciphers import algorithms
from pskc.exceptions import DecryptionError
if key is None:
raise DecryptionError('No key available')
if algorithm is None:
raise DecryptionError('No algorithm specified')
if len(key) not in algorithm_key_lengths(algorithm):
raise DecryptionError('Invalid key length')
if algorithm.endswith('#aes128-cbc') or \
algorithm.endswith('#aes192-cbc') or \
algorithm.endswith('#aes256-cbc'):
return _decrypt_cbc(algorithms.AES, key, ciphertext, iv)
elif algorithm.endswith('#tripledes-cbc'):
return _decrypt_cbc(algorithms.TripleDES, key, ciphertext, iv)
elif algorithm.endswith('#kw-aes128') or \
algorithm.endswith('#kw-aes192') or \
algorithm.endswith('#kw-aes256'):
from pskc.crypto.aeskw import unwrap
return unwrap(ciphertext, key)
elif algorithm.endswith('#kw-tripledes'):
from pskc.crypto.tripledeskw import unwrap
return unwrap(ciphertext, key)
elif (algorithm.endswith('#camellia128-cbc') or
algorithm.endswith('#camellia192-cbc') or
algorithm.endswith('#camellia256-cbc')):
return _decrypt_cbc(algorithms.Camellia, key, ciphertext, iv)
elif (algorithm.endswith('#kw-camellia128') or # pragma: no branch
algorithm.endswith('#kw-camellia192') or
algorithm.endswith('#kw-camellia256')):
from pskc.crypto.aeskw import unwrap
return unwrap(ciphertext, key, algorithm=algorithms.Camellia)
# no fallthrough because algorithm_key_lengths() fails with unknown algo
def _encrypt_cbc(algorithm, key, plaintext, iv=None):
"""Encrypt the provided value with the key using the algorithm."""
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, modes
iv = iv or os.urandom(algorithm.block_size // 8)
cipher = Cipher(
algorithm(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
padder = padding.PKCS7(algorithm.block_size).padder()
return (
iv + encryptor.update(
padder.update(plaintext) + padder.finalize()) +
encryptor.finalize())
def encrypt(algorithm, key, plaintext, iv=None):
"""Encrypt the provided value with the key using the algorithm."""
from cryptography.hazmat.primitives.ciphers import algorithms
from pskc.exceptions import EncryptionError
if key is None:
raise EncryptionError('No key available')
if algorithm is None:
raise EncryptionError('No algorithm specified')
if len(key) not in algorithm_key_lengths(algorithm):
raise EncryptionError('Invalid key length')
if algorithm.endswith('#aes128-cbc') or \
algorithm.endswith('#aes192-cbc') or \
algorithm.endswith('#aes256-cbc'):
return _encrypt_cbc(algorithms.AES, key, plaintext, iv)
elif algorithm.endswith('#tripledes-cbc'):
return _encrypt_cbc(algorithms.TripleDES, key, plaintext, iv)
elif algorithm.endswith('#kw-aes128') or \
algorithm.endswith('#kw-aes192') or \
algorithm.endswith('#kw-aes256'):
from pskc.crypto.aeskw import wrap
return wrap(plaintext, key)
elif algorithm.endswith('#kw-tripledes'):
from pskc.crypto.tripledeskw import wrap
return wrap(plaintext, key)
elif (algorithm.endswith('#camellia128-cbc') or
algorithm.endswith('#camellia192-cbc') or
algorithm.endswith('#camellia256-cbc')):
return _encrypt_cbc(algorithms.Camellia, key, plaintext, iv)
elif (algorithm.endswith('#kw-camellia128') or # pragma: no branch
algorithm.endswith('#kw-camellia192') or
algorithm.endswith('#kw-camellia256')):
from pskc.crypto.aeskw import wrap
return wrap(plaintext, key, algorithm=algorithms.Camellia)
# no fallthrough because algorithm_key_lengths() fails with unknown algo
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 length in bytes
pbkdf2_prf: name of pseudorandom function used
"""
def __init__(self):
self._algorithm = None
# PBKDF2 properties
self.pbkdf2_salt = None
self.pbkdf2_iterations = None
self.pbkdf2_key_length = None
self._pbkdf2_prf = None
@property
def algorithm(self):
"""Provide the key derivation algorithm used."""
if self._algorithm:
return self._algorithm
@algorithm.setter
def algorithm(self, value):
from pskc.algorithms import normalise_algorithm
self._algorithm = normalise_algorithm(value)
@property
def pbkdf2_prf(self):
"""Provide the PBKDF2 pseudorandom function used."""
if self._pbkdf2_prf:
return self._pbkdf2_prf
@pbkdf2_prf.setter
def pbkdf2_prf(self, value):
from pskc.algorithms import normalise_algorithm
self._pbkdf2_prf = normalise_algorithm(value)
def derive_pbkdf2(self, password):
from hashlib import pbkdf2_hmac
from pskc.exceptions import KeyDerivationError
prf = 'sha1'
if self.pbkdf2_prf:
match = re.search(
r'^(.*#)?hmac-(?P[a-z0-9-]+)$', self.pbkdf2_prf)
if match:
prf = match.group('hash')
else:
raise KeyDerivationError(
'Unsupported PRF: %r' % self.pbkdf2_prf)
if not all((password, self.pbkdf2_salt, self.pbkdf2_key_length,
self.pbkdf2_iterations)):
raise KeyDerivationError('Incomplete PBKDF2 configuration')
# force conversion to bytestring on Python 3
if not isinstance(password, type(b'')):
password = password.encode() # pragma: no cover (Py3 specific)
try:
return pbkdf2_hmac(
prf, password, self.pbkdf2_salt, self.pbkdf2_iterations,
self.pbkdf2_key_length)
except ValueError:
raise KeyDerivationError(
'Pseudorandom function unsupported: %r' % self.pbkdf2_prf)
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'):
return self.derive_pbkdf2(password)
else:
raise KeyDerivationError(
'Unsupported algorithm: %r' % self.algorithm)
def setup_pbkdf2(self, password, salt=None, salt_length=16,
key_length=None, iterations=None, prf=None):
self.algorithm = 'pbkdf2'
if salt is None:
salt = os.urandom(salt_length)
self.pbkdf2_salt = salt
if iterations:
self.pbkdf2_iterations = iterations
elif self.pbkdf2_iterations is None:
self.pbkdf2_iterations = 12 * 1000
if key_length: # pragma: no branch (always specified)
self.pbkdf2_key_length = key_length
if prf:
self.pbkdf2_prf = prf
return self.derive_pbkdf2(password)
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
algorithm: the encryption algorithm used
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)
iv: optional initialization vector for CBC based encryption
fields: a list of Key fields that will be encrypted on writing
The key can either be assigned to the key property or derived using the
derive_key() method.
"""
def __init__(self, pskc):
self.pskc = pskc
self.id = None
self._algorithm = None
self.key_names = []
self.key = None
self.iv = None
self.derivation = KeyDerivation()
self.fields = []
@property
def key_name(self):
"""Provide the name of the (first) key."""
if self.key_names:
return self.key_names[0]
@key_name.setter
def key_name(self, value):
self.key_names = [value]
@property
def algorithm(self):
"""Provide the encryption algorithm used."""
if self._algorithm:
return self._algorithm
@algorithm.setter
def algorithm(self, value):
from pskc.algorithms import normalise_algorithm
self._algorithm = normalise_algorithm(value)
@property
def is_encrypted(self):
"""Test whether the PSKC file requires a decryption key."""
from pskc.exceptions import DecryptionError
try:
for key in self.pskc.keys:
key.secret, key.counter, key.time_offset
key.time_interval, key.time_drift
except DecryptionError:
return True
return False
def derive_key(self, password):
"""Derive a key from the password."""
self.key = self.derivation.derive(password)
def _setup_encryption(self, kwargs):
for k in ('id', 'algorithm', 'key_name', 'key_names', 'fields'):
v = kwargs.pop(k, None)
if v is not None:
setattr(self, k, v)
# default encryption to AES128-CBC
if not self.algorithm:
self.algorithm = 'aes128-cbc'
# default to encrypting the secret only
if not self.fields:
self.fields = ['secret', ]
# if we're using a CBC mode of encryption, add a MAC
if self.algorithm.endswith('-cbc'):
self.pskc.mac.setup()
def setup_preshared_key(self, **kwargs):
"""Configure pre-shared key encryption when writing the file.
The following arguments may be supplied:
key: the encryption key to use
id: encryption key identifier
algorithm: encryption algorithm
key_length: encryption key length in bytes
key_name: a name for the key
key_names: a number of names for the key
fields: a list of fields to encrypt
None of the arguments are required, reasonable defaults will be
chosen for missing arguments.
"""
self._setup_encryption(kwargs)
self.key = kwargs.pop('key', self.key)
if not self.key:
self.key = os.urandom(
kwargs.pop('key_length', self.algorithm_key_lengths[-1]))
def setup_pbkdf2(self, password, **kwargs):
"""Configure password-based PSKC encryption when writing the file.
The following arguments may be supplied:
password: the password to use (required)
id: encryption key identifier
algorithm: encryption algorithm
key_length: encryption key length in bytes
key_name: a name for the key
key_names: a number of names for the key
fields: a list of fields to encrypt
salt: PBKDF2 salt
salt_length: used when generating random salt
iterations: number of PBKDF2 iterations
prf: PBKDF2 pseudorandom function
Only password is required, for the other arguments reasonable
defaults will be chosen.
"""
self._setup_encryption(kwargs)
# pass a key length to PBKDF2
kwargs.setdefault('key_length', self.algorithm_key_lengths[-1])
self.key = self.derivation.setup_pbkdf2(password, **kwargs)
@property
def algorithm_key_lengths(self):
"""Provide the possible key lengths for the configured algorithm."""
return algorithm_key_lengths(self.algorithm)
def decrypt_value(self, cipher_value, algorithm=None):
"""Decrypt the cipher_value and return the plaintext value."""
return decrypt(
algorithm or self.algorithm, self.key, cipher_value, self.iv)
def encrypt_value(self, plaintext):
"""Encrypt the provided value and return the cipher_value."""
return encrypt(self.algorithm, self.key, plaintext, self.iv)
python-pskc-1.0/pskc/exceptions.py 0000644 0000000 0000000 00000002777 13221434047 017277 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-1.0/pskc/parser.py 0000644 0000000 0000000 00000040726 13221434047 016406 0 ustar root root 0000000 0000000 # parser.py - PSKC file parsing functions
# coding: utf-8
#
# Copyright (C) 2016-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Module for parsing PSKC files."""
import array
import base64
import copy
from pskc.exceptions import ParseError
from pskc.key import EncryptedIntegerValue, EncryptedValue
from pskc.xml import (
find, findall, findbin, findint, findtext, findtime, getbool, getint,
parse, remove_namespaces)
def plain2int(value):
"""Convert a plain text value to an int."""
# try normal integer string parsing
try:
return int(value)
except ValueError:
pass
# fall back to base64 decoding
value = base64.b64decode(value)
# try to handle value as ASCII representation
if value.isdigit():
return int(value)
# fall back to do big-endian decoding
result = 0
for x in array.array('B', value):
result = (result << 8) + x
return result
class PSKCParser(object):
@classmethod
def parse_file(cls, pskc, filename):
"""Parse the provided file and store information in the existing
PSKC instance."""
try:
tree = parse(filename)
except Exception:
raise ParseError('Error parsing XML')
# save a clean copy of the tree for signature checking
pskc.signature.tree = copy.deepcopy(tree)
cls.parse_document(pskc, tree.getroot())
@classmethod
def parse_document(cls, pskc, container):
"""Read information from the provided tree."""
remove_namespaces(container)
if container.tag not in ('KeyContainer', 'SecretContainer'):
raise ParseError('Missing KeyContainer')
# the version of the PSKC schema
pskc.version = container.get('Version') or container.get('version')
if (container.tag == 'KeyContainer' and
pskc.version and
pskc.version not in ('1', '1.0')):
raise ParseError('Unsupported version %r' % pskc.version)
# unique identifier for the container
pskc.id = (
container.get('Id') or container.get('ID') or container.get('id'))
# handle EncryptionKey entries
cls.parse_encryption(pskc.encryption, find(
container, 'EncryptionKey', 'EncryptionMethod'))
# handle MACMethod entries
cls.parse_mac_method(pskc.mac, find(
container, 'MACMethod', 'DigestMethod'))
# fall back to MACAlgorithm
mac_algorithm = findtext(container, 'MACAlgorithm')
if mac_algorithm:
pskc.mac.algorithm = mac_algorithm
# handle KeyPackage entries
for key_package in findall(container, 'KeyPackage', 'Device'):
cls.parse_key_package(pskc.add_device(), key_package)
# handle Signature entries
cls.parse_signature(pskc.signature, find(container, 'Signature'))
@classmethod
def parse_encryption(cls, encryption, key_info):
"""Read encryption information from the XML tree."""
if key_info is None:
return
encryption.id = key_info.get('Id')
encryption.algorithm = (
key_info.get('Algorithm') or
key_info.get('algorithm') or
encryption.algorithm)
for name in findall(key_info,
'KeyName', 'DerivedKey/MasterKeyName',
'DerivedKey/CarriedKeyName'):
encryption.key_names.append(findtext(name, '.'))
encryption.iv = findbin(key_info, 'IV') or encryption.iv
cls.parse_key_derivation(encryption.derivation, find(
key_info, 'DerivedKey/KeyDerivationMethod'))
encryption.derivation.pbkdf2_salt = (
findbin(key_info, 'PBESalt') or encryption.derivation.pbkdf2_salt)
encryption.derivation.pbkdf2_iterations = (
findint(key_info, 'PBEIterationCount') or
encryption.derivation.pbkdf2_iterations)
algorithm = (
key_info.get('Algorithm') or key_info.get('algorithm') or '')
if (algorithm.lower().startswith('pbe') and
not encryption.derivation.algorithm):
encryption.derivation.algorithm = 'pbkdf2'
encryption.derivation.pbkdf2_key_length = (
encryption.derivation.pbkdf2_key_length or
encryption.algorithm_key_lengths[0])
@classmethod
def parse_key_derivation(cls, derivation, key_derivation):
"""Read derivation parameters from a element."""
if key_derivation is None:
return
derivation.algorithm = key_derivation.get('Algorithm')
# PBKDF2 properties
pbkdf2 = find(key_derivation, 'PBKDF2-params')
if pbkdf2 is not None:
# get used salt
derivation.pbkdf2_salt = findbin(pbkdf2, 'Salt/Specified')
# required number of iterations
derivation.pbkdf2_iterations = findint(pbkdf2, 'IterationCount')
# key length
derivation.pbkdf2_key_length = findint(pbkdf2, 'KeyLength')
# pseudorandom function used
prf = find(pbkdf2, 'PRF')
if prf is not None:
derivation.pbkdf2_prf = prf.get('Algorithm')
@classmethod
def parse_mac_method(cls, mac, mac_method):
"""Read MAC information from the XML tree."""
if mac_method is None:
return
mac.algorithm = (
mac_method.get('Algorithm') or
mac_method.get('algorithm'))
mac_key = find(mac_method, 'MACKey')
if mac_key is not None:
mac.key_algorithm, mac.key_cipher_value = (
cls.parse_encrypted_value(mac_key))
@classmethod
def parse_key_package(cls, device, key_package):
"""Read key information from the provided tree."""
info = find(key_package, 'DeviceInfo', 'DeviceId')
if info is not None:
device.manufacturer = findtext(info, 'Manufacturer')
device.serial = findtext(info, 'SerialNo')
device.model = findtext(info, 'Model')
device.issue_no = findtext(info, 'IssueNo')
device.device_binding = findtext(info, 'DeviceBinding')
device.start_date = findtime(info, 'StartDate')
device.expiry_date = findtime(info, 'ExpiryDate', 'Expiry')
device.device_userid = findtext(info, 'UserId')
device.crypto_module = findtext(key_package, 'CryptoModuleInfo/Id')
for key_elm in findall(key_package, 'Key', 'Secret'):
cls.parse_key(device.add_key(), key_elm)
@classmethod
def parse_key(cls, key, key_elm):
"""Read key information from the provided tree."""
key.id = (
key_elm.get('Id') or key_elm.get('KeyId') or
key_elm.get('SecretId'))
key.algorithm = (
key_elm.get('Algorithm') or key_elm.get('KeyAlgorithm') or
key_elm.get('SecretAlgorithm'))
data = find(key_elm, 'Data')
if data is not None:
cls.parse_data(key, 'secret', find(data, 'Secret'))
cls.parse_data(key, 'counter', find(data, 'Counter'))
cls.parse_data(key, 'time_offset', find(data, 'Time'))
cls.parse_data(key, 'time_interval', find(data, 'TimeInterval'))
cls.parse_data(key, 'time_drift', find(data, 'TimeDrift'))
for data in findall(key_elm, 'Data'):
name = data.get('Name')
if name:
cls.parse_data(key, dict(
secret='secret',
counter='counter',
time='time_offset',
time_interval='time_interval',
).get(name.lower()), data)
key.issuer = findtext(key_elm, 'Issuer')
key.key_profile = findtext(key_elm, 'KeyProfileId')
key.key_reference = findtext(key_elm, 'KeyReference')
key.friendly_name = findtext(key_elm, 'FriendlyName')
# TODO: support multi-language values of
key.key_userid = findtext(key_elm, 'UserId')
key.algorithm_suite = findtext(
key_elm, 'AlgorithmParameters/Suite')
challenge_format = find(
key_elm,
'AlgorithmParameters/ChallengeFormat', 'Usage/ChallengeFormat')
if challenge_format is not None:
key.challenge_encoding = (
challenge_format.get('Encoding') or
challenge_format.get('Format') or
challenge_format.get('format'))
key.challenge_min_length = (
getint(challenge_format, 'Min') or
getint(challenge_format, 'min'))
key.challenge_max_length = (
getint(challenge_format, 'Max') or
getint(challenge_format, 'max'))
key.challenge_check = getbool(
challenge_format, 'CheckDigits', getbool(
challenge_format, 'CheckDigit'))
response_format = find(
key_elm,
'AlgorithmParameters/ResponseFormat', 'Usage/ResponseFormat')
if response_format is not None:
key.response_encoding = (
response_format.get('Encoding') or
response_format.get('Format') or
response_format.get('format'))
key.response_length = (
getint(response_format, 'Length') or
getint(response_format, 'length'))
key.response_check = getbool(
response_format, 'CheckDigits', getbool(
response_format, 'CheckDigit'))
cls.parse_policy(key.policy, find(key_elm, 'Policy'))
usage = find(key_elm, 'Usage')
if usage is not None:
for att in ('OTP', 'CR', 'Integrity', 'Encrypt', 'Unlock'):
if getbool(usage, att):
key.policy.key_usage.append(att)
key.policy.start_date = (
findtime(key_elm, 'StartDate') or key.policy.start_date)
key.policy.expiry_date = (
findtime(key_elm, 'ExpiryDate') or key.policy.expiry_date)
@classmethod
def parse_encrypted_value(cls, encrypted_value):
"""Read encryption value from element."""
algorithm = None
cipher_value = findbin(encrypted_value, 'CipherData/CipherValue')
encryption_method = find(encrypted_value, 'EncryptionMethod')
if encryption_method is not None:
algorithm = encryption_method.attrib.get('Algorithm')
encryption_scheme = find(
encrypted_value, 'EncryptionMethod/EncryptionScheme')
if encryption_scheme is not None:
algorithm = encryption_scheme.attrib.get('Algorithm') or algorithm
return (algorithm, cipher_value)
@classmethod
def parse_data(cls, key, field, element):
"""Read information from the provided element.
The element is expected to contain ,
and/or elements that contain information on the actual
value."""
if element is None:
return
pskc = key.device.pskc
plain_value = None
cipher_value = None
algorithm = None
# get the plain2value function and encryption storage
if field == 'secret':
plain2value = base64.b64decode
encrypted_value_cls = EncryptedValue
else:
plain2value = plain2int
encrypted_value_cls = EncryptedIntegerValue
# read plaintext value from
plain_value = findtext(element, 'PlainValue')
if plain_value is not None:
plain_value = plain2value(plain_value)
# read encrypted data from
encrypted_value = find(element, 'EncryptedValue')
if encrypted_value is not None:
algorithm, cipher_value = cls.parse_encrypted_value(
encrypted_value)
# store the found algorithm in the pskc.encryption property
if not pskc.encryption.algorithm and algorithm:
pskc.encryption.algorithm = algorithm
# read MAC information from
mac_value = findbin(element, 'ValueMAC', 'ValueDigest')
# read legacy elements (can be plain or encrypted)
value = findtext(element, 'Value')
if value is not None:
if pskc.encryption.algorithm and mac_value:
cipher_value = findbin(element, 'Value')
else:
plain_value = plain2value(value)
# store the found information
if plain_value is not None:
setattr(key, field, plain_value)
elif cipher_value:
setattr(key, field,
encrypted_value_cls(cipher_value, mac_value, algorithm))
@classmethod
def parse_policy(cls, policy, policy_elm):
"""Read key policy information from the provided tree."""
if policy_elm is None:
return
policy.start_date = findtime(policy_elm, 'StartDate')
policy.expiry_date = findtime(policy_elm, 'ExpiryDate')
policy.number_of_transactions = findint(
policy_elm, 'NumberOfTransactions')
for key_usage in findall(policy_elm, 'KeyUsage'):
policy.key_usage.append(findtext(key_usage, '.'))
pin_policy_elm = find(policy_elm, 'PINPolicy')
if pin_policy_elm is not None:
policy.pin_key_id = pin_policy_elm.get('PINKeyId')
policy.pin_usage = pin_policy_elm.get('PINUsageMode')
policy.pin_max_failed_attempts = getint(
pin_policy_elm, 'MaxFailedAttempts')
policy.pin_min_length = getint(pin_policy_elm, 'MinLength')
policy.pin_max_length = getint(pin_policy_elm, 'MaxLength')
policy.pin_encoding = pin_policy_elm.get('PINEncoding')
# check for child elements
if list(pin_policy_elm):
policy.unknown_policy_elements = True
# check for unknown attributes
known_attributes = set([
'PINKeyId', 'PINUsageMode', 'MaxFailedAttempts', 'MinLength',
'MaxLength', 'PINEncoding'])
if set(pin_policy_elm.keys()) - known_attributes:
policy.unknown_policy_elements = True
# check for other child elements
known_children = set([
'StartDate', 'ExpiryDate', 'NumberOfTransactions', 'KeyUsage',
'PINPolicy'])
for child in policy_elm:
if child.tag not in known_children:
policy.unknown_policy_elements = True
@classmethod
def parse_signature(cls, signature, signature_elm):
"""Read signature information from the element."""
if signature_elm is None:
return
cm_elm = find(signature_elm, 'SignedInfo/CanonicalizationMethod')
if cm_elm is not None:
signature.canonicalization_method = cm_elm.attrib.get('Algorithm')
sm_elm = find(signature_elm, 'SignedInfo/SignatureMethod')
if sm_elm is not None:
signature.algorithm = sm_elm.attrib.get('Algorithm')
dm_elm = find(signature_elm, 'SignedInfo/Reference/DigestMethod')
if dm_elm is not None:
signature.digest_algorithm = dm_elm.attrib.get('Algorithm')
issuer = find(signature_elm, 'KeyInfo/X509Data/X509IssuerSerial')
if issuer is not None:
signature.issuer = findtext(issuer, 'X509IssuerName')
signature.serial = findtext(issuer, 'X509SerialNumber')
certificate = findbin(
signature_elm, 'KeyInfo/X509Data/X509Certificate')
if certificate:
certificate = base64.b64encode(certificate)
signature.certificate = b'\n'.join(
[b'-----BEGIN CERTIFICATE-----'] +
[certificate[i:i + 64]
for i in range(0, len(certificate), 64)] +
[b'-----END CERTIFICATE-----'])
python-pskc-1.0/pskc/policy.py 0000644 0000000 0000000 00000013631 13221434050 016376 0 ustar root root 0000000 0000000 # policy.py - module for handling PSKC policy information
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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."""
import warnings
def _make_aware(d):
"""Make tge specified datetime timezone aware."""
import dateutil.tz
if not d.tzinfo:
return d.replace(tzinfo=dateutil.tz.tzlocal())
return d
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_attempts: 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):
"""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_attempts = None
self.pin_min_length = None
self.pin_max_length = None
self.pin_encoding = None
self.unknown_policy_elements = False
@property
def pin_max_failed_attemtps(self):
"""Provide access to deprecated name."""
warnings.warn(
'The pin_max_failed_attemtps property has been renamed to '
'pin_max_failed_attempts.', DeprecationWarning, stacklevel=2)
return self.pin_max_failed_attempts
@pin_max_failed_attemtps.setter
def pin_max_failed_attemtps(self, value):
warnings.warn(
'The pin_max_failed_attemtps property has been renamed to '
'pin_max_failed_attempts.', DeprecationWarning, stacklevel=2)
self.pin_max_failed_attempts = value
def may_use(self, usage=None, now=None):
"""Check whether the key may be used for the provided purpose."""
import datetime
import dateutil.tz
if self.unknown_policy_elements:
return False
if usage is not None and self.key_usage:
if usage not in self.key_usage:
return False
# check start_date and expiry_date
if now is None:
now = datetime.datetime.now(dateutil.tz.tzlocal())
if self.start_date:
if _make_aware(self.start_date) > _make_aware(now):
return False # not-yet usable key
if self.expiry_date:
if _make_aware(self.expiry_date) < _make_aware(now):
return False # not-yet usable key
return True
@property
def pin_key(self):
"""Provide the PSKC Key that holds the PIN (if any)."""
if self.pin_key_id and self.key and self.key.device.pskc:
for key in self.key.device.pskc.keys:
if key.id == self.pin_key_id:
return key
@property
def pin(self):
"""Provide the PIN value referenced by PINKeyId if any."""
key = self.pin_key
if key:
return str(key.secret.decode())
python-pskc-1.0/pskc/key.py 0000644 0000000 0000000 00000016537 13221434047 015705 0 ustar root root 0000000 0000000 # key.py - module for handling keys from pskc files
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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 array
import binascii
from pskc.policy import Policy
class EncryptedValue(object):
"""A container for an encrypted value."""
def __init__(self, cipher_value, mac_value, algorithm):
self.cipher_value = cipher_value
self.mac_value = mac_value
self.algorithm = algorithm
@classmethod
def create(cls, pskc, value):
# force conversion to bytestring on Python 3
if not isinstance(value, type(b'')):
value = value.encode() # pragma: no cover (Python 3 specific)
cipher_value = pskc.encryption.encrypt_value(value)
mac_value = None
if pskc.mac.algorithm:
mac_value = pskc.mac.generate_mac(cipher_value)
return cls(cipher_value, mac_value, pskc.encryption.algorithm)
def get_value(self, pskc):
"""Provide the decrypted value."""
from pskc.exceptions import DecryptionError
plaintext = pskc.encryption.decrypt_value(
self.cipher_value, self.algorithm)
# allow MAC over plaintext or cipertext
# (RFC6030 implies MAC over ciphertext but older draft used
# MAC over plaintext)
if self.mac_value and self.mac_value not in (
pskc.mac.generate_mac(self.cipher_value),
pskc.mac.generate_mac(plaintext)):
raise DecryptionError('MAC value does not match')
return plaintext
class EncryptedIntegerValue(EncryptedValue):
@classmethod
def create(cls, pskc, value):
value = '%x' % value
n = len(value)
value = binascii.unhexlify(value.zfill(n + (n & 1)))
return super(EncryptedIntegerValue, cls).create(pskc, value)
def get_value(self, pskc):
"""Provide the decrypted integer value."""
value = super(EncryptedIntegerValue, self).get_value(pskc)
# try to handle value as ASCII representation
if value.isdigit():
return int(value)
# fall back to do big-endian decoding
result = 0
for x in array.array('B', value):
result = (result << 8) + x
return result
class DataTypeProperty(object):
"""A data descriptor that delegates actions to DataType instances."""
def __init__(self, name, doc):
self.name = name
self.__doc__ = doc
def __get__(self, obj, objtype):
value = getattr(obj, '_' + self.name, None)
if hasattr(value, 'get_value'):
return value.get_value(obj.device.pskc)
else:
return value
def __set__(self, obj, val):
setattr(obj, '_' + self.name, val)
class DeviceProperty(object):
"""A data descriptor that delegates actions to the Device instance."""
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype):
return getattr(obj.device, self.name)
def __set__(self, obj, val):
setattr(obj.device, self.name, val)
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
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)
This class also provides access to the manufacturer, serial, model,
issue_no, device_binding, start_date, expiry_date, device_userid and
crypto_module properties of the Device class.
"""
def __init__(self, device):
self.device = device
self.id = None
self.algorithm = None
self.issuer = None
self.key_profile = None
self.key_reference = None
self.friendly_name = None
self.key_userid = 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)
secret = DataTypeProperty(
'secret', 'The secret key itself.')
counter = DataTypeProperty(
'counter', 'An event counter for event-based OTP.')
time_offset = DataTypeProperty(
'time_offset',
'A time offset for time-based OTP (number of intervals).')
time_interval = DataTypeProperty(
'time_interval', 'A time interval in seconds.')
time_drift = DataTypeProperty(
'time_drift', 'Device clock drift value (number of time intervals).')
manufacturer = DeviceProperty('manufacturer')
serial = DeviceProperty('serial')
model = DeviceProperty('model')
issue_no = DeviceProperty('issue_no')
device_binding = DeviceProperty('device_binding')
start_date = DeviceProperty('start_date')
expiry_date = DeviceProperty('expiry_date')
device_userid = DeviceProperty('device_userid')
crypto_module = DeviceProperty('crypto_module')
def check(self):
"""Check if all MACs in the message are valid."""
if all(x is not False for x in (
self.secret, self.counter, self.time_offset,
self.time_interval, self.time_drift)):
return True
@property
def userid(self):
"""User identifier (either the key or device userid)."""
return self.key_userid or self.device_userid
python-pskc-1.0/pskc/__init__.py 0000644 0000000 0000000 00000010612 13221476207 016644 0 ustar root root 0000000 0000000 # __init__.py - main module
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Python module for handling PSKC files
This Python library handles Portable Symmetric Key Container (PSKC) files as
defined in RFC 6030. PSKC files are used to transport and provision symmetric
keys (seed files) to different types of crypto modules, commonly one-time
password tokens or other authentication devices.
This module can be used to extract keys from PSKC files for use in an OTP
authentication system. The module can also be used for authoring PSKC files.
The following prints all keys, decrypting using a password:
>>> from pskc import PSKC
>>> pskc = PSKC('tests/rfc6030/figure7.pskcxml')
>>> pskc.encryption.derive_key('qwerty')
>>> for key in pskc.keys:
... print('%s %s' % (key.serial, str(key.secret.decode())))
987654321 12345678901234567890
The following generates an encrypted PSKC file:
>>> pskc = PSKC()
>>> key = pskc.add_key(
... id='456', secret='987654321', manufacturer='Manufacturer',
... algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:hotp')
>>> pskc.encryption.setup_pbkdf2('passphrase')
>>> pskc.write('output.pskcxml')
The module should be able to handle most common PSKC files.
"""
__all__ = ['PSKC', '__version__']
# the version number of the library
__version__ = '1.0'
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)
devices: list of devices (Device instances)
keys: list of keys (Key instances)
"""
def __init__(self, filename=None):
from pskc.encryption import Encryption
from pskc.signature import Signature
from pskc.mac import MAC
self.version = None
self.id = None
self.encryption = Encryption(self)
self.signature = Signature(self)
self.mac = MAC(self)
self.devices = []
if filename is not None:
from pskc.parser import PSKCParser
PSKCParser.parse_file(self, filename)
else:
self.version = '1.0'
@property
def keys(self):
"""Provide a list of keys."""
return tuple(key for device in self.devices for key in device.keys)
def add_device(self, **kwargs):
"""Create a new device instance for the PSKC file.
The device is initialised with properties from the provided keyword
arguments if any."""
from pskc.device import Device
device = Device(self)
self.devices.append(device)
# assign the kwargs as key properties
for k, v in kwargs.items():
if not hasattr(device, k):
raise AttributeError()
setattr(device, k, v)
return device
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."""
device = self.add_device()
key = device.add_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.serialiser import PSKCSerialiser
if hasattr(filename, 'write'):
PSKCSerialiser.serialise_file(self, filename)
else:
with open(filename, 'wb') as output:
self.write(output)
python-pskc-1.0/pskc/xml.py 0000644 0000000 0000000 00000016676 13221434050 015713 0 ustar root root 0000000 0000000 # xml.py - module for parsing and writing XML for PSKC files
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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
from collections import OrderedDict
# try to find a usable ElementTree implementation
try: # pragma: no cover (different implementations)
from lxml.etree import parse as xml_parse, tostring as xml_tostring
from lxml.etree import register_namespace, Element, SubElement
try:
from defusedxml.lxml import parse as xml_parse # noqa: F811
except ImportError:
pass
except ImportError: # pragma: no cover (different implementations)
from xml.etree.ElementTree import (
parse as xml_parse, tostring as xml_tostring)
from xml.etree.ElementTree import register_namespace, Element, SubElement
try:
from defusedxml.ElementTree import parse as xml_parse # noqa: F811
except ImportError:
pass
# 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#',
)
def register_namespaces():
"""Register the namespaces so the correct short names will be used."""
for ns, namespace in namespaces.items():
register_namespace(ns, namespace)
register_namespaces()
def parse(source):
"""Parse the provided file and return an element tree."""
return xml_parse(source)
def remove_namespaces(tree):
"""Remove namespaces from all elements in the tree."""
import re
for elem in tree.iter():
if isinstance(elem.tag, ''.__class__): # pragma: no branch
elem.tag = re.sub(r'^\{[^}]*\}', '', elem.tag)
def findall(tree, *matches):
"""Find the child elements."""
for match in matches:
for element in tree.findall(match, namespaces=namespaces):
yield element
def find(tree, *matches):
"""Find a child element that matches any of the patterns (or None)."""
try:
return next(findall(tree, *matches))
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, default=None):
"""Return an attribute value as a boolean (or None)."""
value = tree.get(attribute)
if value:
value = value.lower()
if value in ('1', 'true'):
return True
elif value in ('0', 'false'):
return False
else:
raise ValueError('invalid boolean value: %r' % value)
return default
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 = Element(tag, OrderedDict())
else:
element = SubElement(parent, tag, OrderedDict())
# 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 move_namespaces(element):
"""Move the namespace declarations to the toplevel element."""
if hasattr(element, 'nsmap'): # pragma: no cover (only on lxml)
# get all used namespaces
nsmap = {}
for e in element.iter():
nsmap.update(e.nsmap)
# replace toplevel element with all namespaces
e = Element(element.tag, attrib=element.attrib, nsmap=nsmap)
for a in element:
e.append(a)
element = e
return element
def reformat(element, indent=''):
"""Reformat the XML tree to have nice wrapping and indenting."""
tag = element.tag.split('}')[-1]
# re-order attributes by alphabet
attrib = sorted(element.attrib.items())
element.attrib.clear()
element.attrib.update(attrib)
if len(element) == 0:
# clean up inner text
if element.text:
element.text = element.text.strip()
if tag in ('X509Certificate', 'SignatureValue'):
element.text = ''.join(x for x in element.text if not x.isspace())
elif tag != 'SignedInfo':
# indent children
element.text = '\n ' + indent
childred = list(element)
for child in childred:
reformat(child, indent + ' ')
childred[-1].tail = '\n' + indent
element.tail = '\n' + indent
def tostring(element):
"""Return a serialised XML document for the element tree."""
element = move_namespaces(element)
reformat(element)
xml = xml_tostring(element, encoding='UTF-8')
xml_decl = b"\n"
if xml.startswith(xml_decl): # pragma: no cover (only a few cases)
xml = xml[len(xml_decl):]
return (
b'\n' +
xml.replace(b' />', b'/>').strip() + b'\n')
python-pskc-1.0/pskc/algorithms.py 0000644 0000000 0000000 00000010076 13117010157 017252 0 ustar root root 0000000 0000000 # algorithms.py - module for handling algorithm URIs
# coding: utf-8
#
# Copyright (C) 2016-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Utility module that handles algorthm URIs."""
# cannonical URIs of known algorithms
# Note that even if a URI is listed here it does not mean that
# the algorithm is supported in python-pskc.
_algorithms = dict((x.rsplit('#', 1)[-1], x) for x in [
'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
'http://www.w3.org/2001/04/xmlenc#kw-tripledes',
'http://www.w3.org/2001/04/xmlenc#arcfour',
'http://www.w3.org/2001/04/xmlenc#aes128-cbc',
'http://www.w3.org/2001/04/xmlenc#aes192-cbc',
'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
'http://www.w3.org/2001/04/xmlenc#kw-aes128',
'http://www.w3.org/2001/04/xmlenc#kw-aes192',
'http://www.w3.org/2001/04/xmlenc#kw-aes256',
'http://www.w3.org/2009/xmlenc11#aes128-gcm',
'http://www.w3.org/2009/xmlenc11#aes192-gcm',
'http://www.w3.org/2009/xmlenc11#aes256-gcm',
'http://www.w3.org/2009/xmlenc11#kw-aes-128-pad',
'http://www.w3.org/2009/xmlenc11#kw-aes-192-pad',
'http://www.w3.org/2009/xmlenc11#kw-aes-256-pad',
'http://www.w3.org/2001/04/xmldsig-more#camellia128-cbc',
'http://www.w3.org/2001/04/xmldsig-more#camellia192-cbc',
'http://www.w3.org/2001/04/xmldsig-more#camellia256-cbc',
'http://www.w3.org/2001/04/xmldsig-more#kw-camellia128',
'http://www.w3.org/2001/04/xmldsig-more#kw-camellia192',
'http://www.w3.org/2001/04/xmldsig-more#kw-camellia256',
'http://www.w3.org/2007/05/xmldsig-more#seed128-cbc'
'http://www.w3.org/2007/05/xmldsig-more#kw-seed128',
'http://www.w3.org/2001/04/xmldsig-more#hmac-md5',
'http://www.w3.org/2000/09/xmldsig#hmac-sha1',
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224',
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256',
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha384',
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha512',
'http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160',
'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2',
# a newer URI seems to be http://www.w3.org/2009/xmlenc11#pbkdf2
])
# translation table to change old encryption names to new names
_algorithm_aliases = {
'3des-cbc': 'tripledes-cbc',
'3des112-cbc': 'tripledes-cbc',
'3des168-cbc': 'tripledes-cbc',
'rc4': 'arcfour',
'kw-aes128-pad': 'kw-aes-128-pad',
'kw-aes192-pad': 'kw-aes-192-pad',
'kw-aes256-pad': 'kw-aes-256-pad',
'camellia128': 'camellia128-cbc',
'camellia192': 'camellia192-cbc',
'camellia256': 'camellia256-cbc',
'hmac-sha-1': 'hmac-sha1',
'hmac-sha-224': 'hmac-sha224',
'hmac-sha-256': 'hmac-sha256',
'hmac-sha-384': 'hmac-sha384',
'hmac-sha-512': 'hmac-sha512',
'hmac-ripemd-160': 'hmac-ripemd160',
'kw-3des': 'kw-tripledes',
'pbe-3des112-cbc': 'tripledes-cbc',
'pbe-3des168-cbc': 'tripledes-cbc',
'pbe-aes128-cbc': 'aes128-cbc',
'pbe-aes192-cbc': 'aes192-cbc',
'pbe-aes256-cbc': 'aes256-cbc',
'rsa-1_5': 'rsa-1_5',
'rsa-oaep-mgf1p': 'rsa-oaep-mgf1p',
}
def normalise_algorithm(algorithm):
"""Return the canonical URI for the provided algorithm."""
if not algorithm or algorithm.lower() == 'none':
return None
algorithm = _algorithm_aliases.get(algorithm.lower(), algorithm)
return _algorithms.get(algorithm.rsplit('#', 1)[-1].lower(), algorithm)
python-pskc-1.0/pskc/serialiser.py 0000644 0000000 0000000 00000025566 13221434050 017253 0 ustar root root 0000000 0000000 # serialiser.py - PSKC file parsing functions
# coding: utf-8
#
# Copyright (C) 2016-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Module for serialising PSKC files to XML."""
import base64
from pskc.key import EncryptedIntegerValue, EncryptedValue
from pskc.xml import find, mk_elem, move_namespaces, reformat, tostring
def my_b64encode(value):
"""Wrap around b64encode to handle types correctly."""
if not isinstance(value, type(b'')):
value = value.encode() # pragma: no cover (Python 3 specific)
return base64.b64encode(value).decode()
class PSKCSerialiser(object):
@classmethod
def serialise_file(cls, pskc, output):
xml = tostring(cls.serialise_document(pskc))
try:
output.write(xml)
except TypeError: # pragma: no cover (Python 3 specific)
# fall back to writing as string for Python 3
output.write(xml.decode('utf-8'))
@classmethod
def serialise_document(cls, pskc):
container = mk_elem('pskc:KeyContainer', Version=pskc.version,
Id=pskc.id)
cls.serialise_encryption(pskc.encryption, container)
cls.serialise_mac(pskc.mac, container)
for device in pskc.devices:
cls.serialise_key_package(device, container)
return cls.serialise_signature(pskc.signature, container)
@classmethod
def serialise_encryption(cls, encryption, container):
if all(x is None
for x in (encryption.id, encryption.key_name, encryption.key,
encryption.derivation.algorithm)):
return
encryption_key = mk_elem(container, 'pskc:EncryptionKey',
Id=encryption.id, empty=True)
if encryption.derivation.algorithm:
cls.serialise_key_derivation(
encryption.derivation, encryption_key, encryption.key_names)
else:
for name in encryption.key_names:
mk_elem(encryption_key, 'ds:KeyName', name)
@classmethod
def serialise_key_derivation(cls, derivation, encryption_key, key_names):
derived_key = mk_elem(encryption_key, 'xenc11:DerivedKey', empty=True)
key_derivation = mk_elem(derived_key, 'xenc11:KeyDerivationMethod',
Algorithm=derivation.algorithm)
if derivation.algorithm.endswith('#pbkdf2'):
pbkdf2 = mk_elem(key_derivation, 'xenc11:PBKDF2-params',
empty=True)
if derivation.pbkdf2_salt:
salt = mk_elem(pbkdf2, 'Salt', empty=True)
mk_elem(salt, 'Specified',
base64.b64encode(derivation.pbkdf2_salt).decode())
mk_elem(pbkdf2, 'IterationCount', derivation.pbkdf2_iterations)
mk_elem(pbkdf2, 'KeyLength', derivation.pbkdf2_key_length)
mk_elem(pbkdf2, 'PRF', derivation.pbkdf2_prf)
# TODO: serialise ReferenceList/DataReference
for name in key_names:
mk_elem(derived_key, 'xenc11:MasterKeyName', name)
@classmethod
def serialise_mac(cls, mac, container):
if not mac.algorithm and not mac.key:
return
mac_method = mk_elem(
container, 'pskc:MACMethod', Algorithm=mac.algorithm, empty=True)
mac_key = mk_elem(mac_method, 'pskc:MACKey', empty=True)
mk_elem(
mac_key, 'xenc:EncryptionMethod',
Algorithm=mac.pskc.encryption.algorithm)
cipher_data = mk_elem(mac_key, 'xenc:CipherData', empty=True)
if mac.key_cipher_value:
mk_elem(cipher_data, 'xenc:CipherValue',
base64.b64encode(mac.key_cipher_value).decode())
elif mac.key_plain_value:
mk_elem(cipher_data, 'xenc:CipherValue',
base64.b64encode(mac.pskc.encryption.encrypt_value(
mac.key_plain_value)).decode())
@classmethod
def serialise_key_package(cls, device, container):
key_package = mk_elem(container, 'pskc:KeyPackage', empty=True)
if any(x is not None
for x in (device.manufacturer, device.serial, device.model,
device.issue_no, device.device_binding,
device.start_date, device.expiry_date,
device.device_userid)):
device_info = mk_elem(key_package, 'pskc:DeviceInfo', empty=True)
mk_elem(device_info, 'pskc:Manufacturer', device.manufacturer)
mk_elem(device_info, 'pskc:SerialNo', device.serial)
mk_elem(device_info, 'pskc:Model', device.model)
mk_elem(device_info, 'pskc:IssueNo', device.issue_no)
mk_elem(device_info, 'pskc:DeviceBinding', device.device_binding)
mk_elem(device_info, 'pskc:StartDate', device.start_date)
mk_elem(device_info, 'pskc:ExpiryDate', device.expiry_date)
mk_elem(device_info, 'pskc:UserId', device.device_userid)
if device.crypto_module is not None:
crypto_module = mk_elem(key_package, 'pskc:CryptoModuleInfo',
empty=True)
mk_elem(crypto_module, 'pskc:Id', device.crypto_module)
for key in device.keys:
cls.serialise_key(key, key_package)
@classmethod
def serialise_key(cls, key, key_package):
key_elm = mk_elem(key_package, 'pskc:Key', empty=True, Id=key.id,
Algorithm=key.algorithm, )
mk_elem(key_elm, 'pskc:Issuer', key.issuer)
if any((key.algorithm_suite, key.challenge_encoding,
key.response_encoding, key.response_length)):
parameters = mk_elem(key_elm, 'pskc:AlgorithmParameters',
empty=True)
mk_elem(parameters, 'pskc:Suite', key.algorithm_suite)
mk_elem(parameters, 'pskc:ChallengeFormat',
Encoding=key.challenge_encoding,
Min=key.challenge_min_length,
Max=key.challenge_max_length,
CheckDigits=key.challenge_check)
mk_elem(parameters, 'pskc:ResponseFormat',
Encoding=key.response_encoding,
Length=key.response_length,
CheckDigits=key.response_check)
mk_elem(key_elm, 'pskc:KeyProfileId', key.key_profile)
mk_elem(key_elm, 'pskc:KeyReference', key.key_reference)
mk_elem(key_elm, 'pskc:FriendlyName', key.friendly_name)
cls.serialise_data(
key, 'secret', key_elm, 'pskc:Secret')
cls.serialise_data(
key, 'counter', key_elm, 'pskc:Counter')
cls.serialise_data(
key, 'time_offset', key_elm, 'pskc:Time')
cls.serialise_data(
key, 'time_interval', key_elm, 'pskc:TimeInterval')
cls.serialise_data(
key, 'time_drift', key_elm, 'pskc:TimeDrift')
mk_elem(key_elm, 'pskc:UserId', key.key_userid)
cls.serialise_policy(key.policy, key_elm)
@classmethod
def serialise_data(cls, key, field, key_elm, tag):
value = getattr(key, '_%s' % field, None)
pskc = key.device.pskc
# skip empty values
if value in (None, ''):
return
# get the value2text and encryption storage
if field == 'secret':
value2text = my_b64encode
encrypted_value_cls = EncryptedValue
else:
value2text = str
encrypted_value_cls = EncryptedIntegerValue
# find the data tag and create our tag under it
data = find(key_elm, 'pskc:Data')
if data is None:
data = mk_elem(key_elm, 'pskc:Data', empty=True)
element = mk_elem(data, tag, empty=True)
# see if we should encrypt the value
if field in pskc.encryption.fields and not hasattr(
value, 'get_value'):
value = encrypted_value_cls.create(pskc, value)
# write out value
if not hasattr(value, 'get_value'):
# unencrypted value
mk_elem(element, 'pskc:PlainValue', value2text(value))
else:
# encrypted value
encrypted_value = mk_elem(
element, 'pskc:EncryptedValue', empty=True)
mk_elem(encrypted_value, 'xenc:EncryptionMethod',
Algorithm=value.algorithm)
cipher_data = mk_elem(
encrypted_value, 'xenc:CipherData', empty=True)
mk_elem(cipher_data, 'xenc:CipherValue',
base64.b64encode(value.cipher_value).decode())
if value.mac_value:
mk_elem(element, 'pskc:ValueMAC',
base64.b64encode(value.mac_value).decode())
@classmethod
def serialise_policy(cls, policy, key_elm):
# check if any policy attribute is set
if not policy.key_usage and all(x is None for x in (
policy.start_date, policy.expiry_date,
policy.number_of_transactions, policy.pin_key_id,
policy.pin_usage, policy.pin_max_failed_attempts,
policy.pin_min_length, policy.pin_max_length,
policy.pin_encoding)):
return
policy_elm = mk_elem(key_elm, 'pskc:Policy', empty=True)
mk_elem(policy_elm, 'pskc:StartDate', policy.start_date)
mk_elem(policy_elm, 'pskc:ExpiryDate', policy.expiry_date)
mk_elem(policy_elm, 'pskc:PINPolicy',
PINKeyId=policy.pin_key_id,
PINUsageMode=policy.pin_usage,
MaxFailedAttempts=policy.pin_max_failed_attempts,
MinLength=policy.pin_min_length,
MaxLength=policy.pin_max_length,
PINEncoding=policy.pin_encoding)
for usage in policy.key_usage:
mk_elem(policy_elm, 'pskc:KeyUsage', usage)
mk_elem(policy_elm, 'pskc:NumberOfTransactions',
policy.number_of_transactions)
@classmethod
def serialise_signature(cls, signature, container):
if not signature.key:
return container
# move the namespace to the root element and reformat before signing
mk_elem(container, 'ds:Signature', Id='placeholder')
container = move_namespaces(container)
reformat(container)
# sign the document
return signature.sign_xml(container)
python-pskc-1.0/pskc/mac.py 0000644 0000000 0000000 00000011137 13221434047 015644 0 ustar root root 0000000 0000000 # mac.py - module for checking value signatures
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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 os
import re
_hmac_url_re = re.compile(r'^(.*#)?hmac-(?P[a-z0-9-]+)$')
def _get_hash_obj(algorithm, *args):
"""Return an instantiated hash object."""
import hashlib
from pskc.algorithms import normalise_algorithm
from pskc.exceptions import DecryptionError
match = _hmac_url_re.search(normalise_algorithm(algorithm) or '')
if match:
try:
return hashlib.new(match.group('hash'), *args)
except ValueError:
pass
raise DecryptionError('Unsupported MAC algorithm: %r' % algorithm)
def mac(algorithm, key, value):
"""Generate the MAC value over the specified value."""
import hmac
return hmac.new(
key, value,
lambda *args: _get_hash_obj(algorithm, *args)).digest()
def mac_key_length(algorithm):
"""Recommended minimal key length in bytes for the set algorithm."""
# https://tools.ietf.org/html/rfc2104#section-3
# an HMAC key should be at least as long as the hash output length
from pskc.exceptions import DecryptionError
try:
return int(_get_hash_obj(algorithm).digest_size)
except DecryptionError:
return 16 # fallback value
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):
self.pskc = pskc
self._algorithm = None
self.key_plain_value = None
self.key_cipher_value = None
self.key_algorithm = None
@property
def key(self):
"""Provide access to the MAC key binary value if available."""
if self.key_plain_value:
return self.key_plain_value
elif self.key_cipher_value:
return self.pskc.encryption.decrypt_value(
self.key_cipher_value, self.key_algorithm)
# fall back to encryption key
return self.pskc.encryption.key
@key.setter
def key(self, value):
self.key_plain_value = value
self.key_cipher_value = None
@property
def algorithm(self):
"""Provide the MAC algorithm used."""
if self._algorithm:
return self._algorithm
@algorithm.setter
def algorithm(self, value):
from pskc.algorithms import normalise_algorithm
self._algorithm = normalise_algorithm(value)
@property
def algorithm_key_length(self):
"""Recommended minimal key length in bytes for the set algorithm."""
return mac_key_length(self.algorithm)
def generate_mac(self, value):
"""Generate the MAC over the specified value."""
return mac(self.algorithm, self.key, value)
def setup(self, key=None, algorithm=None):
"""Configure an encrypted MAC key.
The following arguments may be supplied:
key: the MAC key to use
algorithm: MAC algorithm
None of the arguments are required, reasonable defaults will be
chosen for missing arguments.
"""
if key:
self.key = key
if algorithm:
self.algorithm = algorithm
# default to HMAC-SHA1
if not self.algorithm:
self.algorithm = 'hmac-sha1'
# generate an HMAC key
if not self.key:
self.key = os.urandom(self.algorithm_key_length)
python-pskc-1.0/pskc/crypto/ 0000755 0000000 0000000 00000000000 13221476256 016057 5 ustar root root 0000000 0000000 python-pskc-1.0/pskc/crypto/aeskw.py 0000644 0000000 0000000 00000011127 13221434047 017535 0 ustar root root 0000000 0000000 # aeskw.py - implementation of AES key wrapping
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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
import struct
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from pskc.exceptions import DecryptionError, EncryptionError
def _strxor(a, b):
"""Return a XOR b."""
if isinstance(b'', str): # pragma: no cover (Python 2 specific)
return b''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b))
else: # pragma: no cover (Python 3 specific)
return bytes(x ^ y for (x, y) in zip(a, b))
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, algorithm=algorithms.AES):
"""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 + struct.pack('>I', mli)
else:
iv = RFC3394_IV
cipher = Cipher(algorithm(key), modes.ECB(), default_backend())
encryptor = cipher.encryptor()
n = len(plaintext) // 8
if n == 1:
# RFC 5649 shortcut
return encryptor.update(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(encryptor.update(A + R[i]))
A = _strxor(A, struct.pack('>Q', n * j + i + 1))
return A + b''.join(R)
def unwrap(ciphertext, key, iv=None, pad=None, algorithm=algorithms.AES):
"""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')
cipher = Cipher(algorithm(key), modes.ECB(), default_backend())
decryptor = cipher.decryptor()
n = len(ciphertext) // 8 - 1
if n == 1:
A, plaintext = _split(decryptor.update(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, struct.pack('>Q', n * j + i + 1))
A, R[i] = _split(decryptor.update(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 = struct.unpack('>I', A[4:])[0]
# 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-1.0/pskc/crypto/__init__.py 0000644 0000000 0000000 00000001554 13221434047 020165 0 ustar root root 0000000 0000000 # __init__.py - general crypto utility functions
# coding: utf-8
#
# Copyright (C) 2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Implement crypto utility functions."""
python-pskc-1.0/pskc/crypto/tripledeskw.py 0000644 0000000 0000000 00000006025 13221434047 020761 0 ustar root root 0000000 0000000 # tripledeskw.py - implementation of Triple DES key wrapping
# coding: utf-8
#
# Copyright (C) 2014-2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# 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
import hashlib
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from pskc.exceptions import DecryptionError, EncryptionError
def _cms_hash(value):
"""Return the key hash algorithm described in RFC 3217 section 2."""
return hashlib.sha1(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 8 * len(plaintext) % algorithms.TripleDES.block_size != 0:
raise EncryptionError('Plaintext length wrong')
if iv is None:
iv = os.urandom(8)
backend = default_backend()
cipher = Cipher(algorithms.TripleDES(key), modes.CBC(iv), backend)
encryptor = cipher.encryptor()
tmp = (
iv + encryptor.update(plaintext + _cms_hash(plaintext)) +
encryptor.finalize())
cipher = Cipher(algorithms.TripleDES(key), modes.CBC(RFC3217_IV), backend)
encryptor = cipher.encryptor()
return encryptor.update(tmp[::-1]) + encryptor.finalize()
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 8 * len(ciphertext) % algorithms.TripleDES.block_size != 0:
raise DecryptionError('Ciphertext length wrong')
backend = default_backend()
cipher = Cipher(algorithms.TripleDES(key), modes.CBC(RFC3217_IV), backend)
decryptor = cipher.decryptor()
tmp = (decryptor.update(ciphertext) + decryptor.finalize())[::-1]
cipher = Cipher(algorithms.TripleDES(key), modes.CBC(tmp[:8]), backend)
decryptor = cipher.decryptor()
tmp = decryptor.update(tmp[8:]) + decryptor.finalize()
if tmp[-8:] == _cms_hash(tmp[:-8]):
return tmp[:-8]
raise DecryptionError('CMS key checksum error')
python-pskc-1.0/pskc/device.py 0000644 0000000 0000000 00000004630 13036777211 016352 0 ustar root root 0000000 0000000 # device.py - module for handling device info from pskc files
# coding: utf-8
#
# Copyright (C) 2016 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Module that handles device information stored in PSKC files."""
class Device(object):
"""Representation of a single key from a PSKC file.
Instances of this class provide the following properties:
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
"""
def __init__(self, pskc):
self.pskc = pskc
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.keys = []
def add_key(self, **kwargs):
"""Create a new key instance for the device.
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
python-pskc-1.0/pskc/signature.py 0000644 0000000 0000000 00000012102 13221434050 017070 0 ustar root root 0000000 0000000 # signature.py - module for handling signed XML files
# coding: utf-8
#
# Copyright (C) 2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
"""Module for handling signed PSKC files.
This module defines a Signature class that handles the signature checking,
keys and certificates.
"""
def sign_x509(xml, key, certificate, algorithm=None, digest_algorithm=None,
canonicalization_method=None):
"""Sign PSKC data using X.509 certificate and private key.
xml: an XML document
key: the private key in binary format
certificate: the X.509 certificate
"""
import signxml
algorithm = algorithm or 'rsa-sha256'
digest_algorithm = digest_algorithm or 'sha256'
canonicalization_method = (
canonicalization_method or
signxml.XMLSignatureProcessor.default_c14n_algorithm)
return signxml.XMLSigner(
method=signxml.methods.enveloped,
signature_algorithm=algorithm.rsplit('#', 1)[-1].lower(),
digest_algorithm=digest_algorithm.rsplit('#', 1)[-1].lower(),
c14n_algorithm=canonicalization_method,
).sign(xml, key=key, cert=certificate)
def verify_x509(tree, certificate=None, ca_pem_file=None):
"""Verify signature in PSKC data against a trusted X.509 certificate.
If a certificate is supplied it is used to validate the signature,
otherwise any embedded certificate is used and validated against a
certificate in ca_pem_file if it specified and otherwise the operating
system CA certificates.
"""
from signxml import XMLVerifier
return XMLVerifier().verify(
tree, x509_cert=certificate, ca_pem_file=ca_pem_file).signed_xml
class Signature(object):
"""Class for handling signature checking of the PSKC file.
Instances of this class provide the following properties:
is_signed: boolean to indicate whether a signature is present
algorithm: identifier of the signing algorithm used
canonicalization_method: identifier of the XML canonicalization used
digest_algorithm: algorithm used for creating the hash
issuer: issuer of the certificate
serial: serial number of the certificate
key: key that will be used when creating a signed PSKC file
certificate: the certificate that is embedded in the signature
signed_pskc: a PSKC instance with the signed information
"""
def __init__(self, pskc):
self.pskc = pskc
self._algorithm = None
self.canonicalization_method = None
self.digest_algorithm = None
self.issuer = None
self.serial = None
self.key = None
self.certificate = None
@property
def is_signed(self):
"""Test whether the PSKC file contains a signature (not whether the
signature is valid)."""
return bool(
self.algorithm or self.canonicalization_method or
self.digest_algorithm or self.issuer or self.certificate)
@property
def algorithm(self):
"""Provide the signing algorithm used."""
if self._algorithm:
return self._algorithm
@algorithm.setter
def algorithm(self, value):
from pskc.algorithms import normalise_algorithm
self._algorithm = normalise_algorithm(value)
@property
def signed_pskc(self):
"""Provide the signed PSKC information."""
if not hasattr(self, '_signed_pskc'):
self.verify()
return self._signed_pskc
def verify(self, certificate=None, ca_pem_file=None):
"""Check that the signature was made with the specified certificate.
If no certificate is provided the signature is expected to contain a
signature that is signed by the CA certificate (or the CA standard CA
certificates when ca_pem_file is absent)."""
from pskc import PSKC
from pskc.parser import PSKCParser
signed_xml = verify_x509(self.tree, certificate, ca_pem_file)
pskc = PSKC()
PSKCParser.parse_document(pskc, signed_xml)
self._signed_pskc = pskc
return True
def sign(self, key, certificate=None):
"""Add an XML signature to the file."""
self.key = key
self.certificate = certificate
def sign_xml(self, xml):
"""Sign an XML document with the configured key and certificate."""
return sign_x509(
xml, self.key, self.certificate, self.algorithm,
self.digest_algorithm, self.canonicalization_method)
python-pskc-1.0/PKG-INFO 0000644 0000000 0000000 00000010542 13221476257 014677 0 ustar root root 0000000 0000000 Metadata-Version: 1.1
Name: python-pskc
Version: 1.0
Summary: Python module for handling PSKC files
Home-page: https://arthurdejong.org/python-pskc/
Author: Arthur de Jong
Author-email: arthur@arthurdejong.org
License: LGPL
Description-Content-Type: UNKNOWN
Description: Python module for handling PSKC files
=====================================
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 (seed files)
to different types of crypto modules, commonly one-time password systems or
other authentication devices.
This module can be used to extract keys from PSKC files for use in an OTP
authentication system. The module can also be used for authoring PSKC files.
This module should be able to handle most common PSKC files.
https://arthurdejong.org/python-pskc/
API
---
The module provides a straightforward API that is mostly geared towards
parsing existing PSKC files.
Extracting key material 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('%s %s' % (key.serial, str(key.secret.decode())))
987654321 12345678901234567890
Writing am encrypted PSKC file is as simple as:
>>> pskc = PSKC()
>>> key = pskc.add_key(
... id='456', secret='987654321', manufacturer='Manufacturer',
... algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:hotp')
>>> pskc.encryption.setup_pbkdf2('passphrase')
>>> pskc.write('output.pskcxml')
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 securely dispose of memory that may have held
private key material.
Copyright
---------
Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
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
Keywords: PSKC,RFC 6030,key container
Platform: UNKNOWN
Classifier: Development Status :: 4 - Production/Stable
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.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
Classifier: Topic :: Text Processing :: Markup :: XML
python-pskc-1.0/docs/ 0000755 0000000 0000000 00000000000 13221476256 014527 5 ustar root root 0000000 0000000 python-pskc-1.0/docs/encryption.rst 0000644 0000000 0000000 00000016405 13221434047 017451 0 ustar root root 0000000 0000000 PSKC encryption
===============
.. module:: pskc.encryption
Some of the information in PSKC files (e.g. key material) 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::
>>> from 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 no key or an incorrect key has been configured, 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.
When writing out a PSKC file, encryption can be configured with the
:func:`~pskc.encryption.Encryption.setup_preshared_key()` or
:func:`~pskc.encryption.Encryption.setup_pbkdf2()` function::
>>> from pskc import PSKC
>>> pskc = PSKC()
>>> pskc.encryption.setup_preshared_key(algorithm='AES256-CBC')
or::
>>> pskc.encryption.setup_pbkdf2(password='verysecure')
The Encryption class
--------------------
.. class:: Encryption
.. attribute:: id
Optional identifier of the encryption key.
.. attribute:: algorithm
A URI of the encryption algorithm used. See the section
:ref:`encryption-algorithms` below for a list of algorithms URIs.
Assigned values to this attribute will be converted to the canonical
URI for the algorithm if it is known. For instance, the value
``3DES-CBC`` will automatically be converted into
``http://www.w3.org/2001/04/xmlenc#tripledes-cbc``.
.. attribute:: is_encrypted
An indicator of whether the PSKC file requires an additional pre-shared
key or passphrase to decrypt the contents of the file. Will be ``True``
if a key or passphrase is needed, ``False`` otherwise.
.. 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`. It will return ``None`` if no name is available.
.. 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.
.. attribute:: fields
A list of :class:`~pskc.key.Key` instance field names that will be
encrypted when the PSKC file is written. List values can contain
``secret``, ``counter``, ``time_offset``, ``time_interval`` and
``time_drift``.
.. function:: setup_preshared_key(...)
Configure pre-shared key encryption when writing the file.
:param bytes key: the encryption key to use
:param str id: encryption key identifier
:param str algorithm: encryption algorithm
:param int key_length: encryption key length in bytes
:param str key_name: a name for the key
:param list key_names: a number of names for the key
:param list fields: a list of fields to encrypt
This is a utility function to easily set up encryption. Encryption can
also be set up by manually by setting the
:class:`~pskc.encryption.Encryption` properties.
This method will generate a key if required and set the passed values.
By default AES128-CBC encryption will be configured and unless a key is
specified one of the correct length will be generated. If the algorithm
does not provide integrity checks (e.g. CBC-mode algorithms) integrity
checking in the PSKC file will be set up using
:func:`~pskc.mac.MAC.setup()`.
By default only the :attr:`~pskc.key.Key.secret` property will be
encrypted when writing the file.
.. function:: setup_pbkdf2(...)
Configure password-based PSKC encryption when writing the file.
:param str password: the password to use (required)
:param str id: encryption key identifier
:param str algorithm: encryption algorithm
:param int key_length: encryption key length in bytes
:param str key_name: a name for the key
:param list key_names: a number of names for the key
:param list fields: a list of fields to encrypt
:param bytes salt: PBKDF2 salt
:param int salt_length: used when generating random salt
:param int iterations: number of PBKDF2 iterations
:param function prf: PBKDF2 pseudorandom function
Defaults for the above parameters are similar to those for
:func:`setup_preshared_key()` but the password parameter is required.
By default 12000 iterations will be used and a random salt with the
length of the to-be-generated encryption key will be used.
.. _encryption-algorithms:
Supported encryption algorithms
-------------------------------
The following encryption algorithms are currently supported by python-pskc.
+----------------------------------------------------+-----------------------------------------------------+
| URI | Description |
+====================================================+=====================================================+
| ``http://www.w3.org/2001/04/xmlenc#aes128-cbc`` | AES encryption in CBC mode with various key lengths |
| ``http://www.w3.org/2001/04/xmlenc#aes192-cbc`` | |
| ``http://www.w3.org/2001/04/xmlenc#aes256-cbc`` | |
+----------------------------------------------------+-----------------------------------------------------+
| ``http://www.w3.org/2001/04/xmlenc#kw-aes128`` | AES key wrap with various key lengths |
| ``http://www.w3.org/2001/04/xmlenc#kw-aes192`` | |
| ``http://www.w3.org/2001/04/xmlenc#kw-aes256`` | |
+----------------------------------------------------+-----------------------------------------------------+
| ``http://www.w3.org/2001/04/xmlenc#tripledes-cbc`` | Triple DES (3DES) encryption in CBC mode |
+----------------------------------------------------+-----------------------------------------------------+
| ``http://www.w3.org/2001/04/xmlenc#kw-tripledes`` | Triple DES (3DES) key wrap |
+----------------------------------------------------+-----------------------------------------------------+
python-pskc-1.0/docs/signatures.rst 0000644 0000000 0000000 00000007737 13221434047 017453 0 ustar root root 0000000 0000000 XML Signature checking
======================
.. module:: pskc.signature
PSKC files can contain embedded XML signatures that allow integrity and
authenticity checks of the transmitted information. This signature typically
covers the whole PSKC file while MAC checking only covers the encrypted
parts.
>>> pskc = PSKC('somefile.pskcxml')
>>> pskc.signature.verify()
>>> pskc = pskc.signature.signed_pskc
When using XML signatures it is important to use the
:attr:`~pskc.signature.Signature.signed_pskc` attribute after verification
because that :class:`~pskc.PSKC` instance will only contain the signed
information.
To create a signed PSKC file build up a :class:`~pskc.PSKC` instance as
usual, configure the signature and save it:
>>> pskc.signature.sign(key, certificate)
>>> pskc.write('output.pskcxml')
The Signature class
--------------------
.. class:: Signature
.. attribute:: is_signed
A boolan value that indicates whether an XML signature is present in
the PSKC file. This propery does not indicate whether the signature
is validated.
.. attribute:: algorithm
A URI of the signing algorithm used.
Assigned values to this attribute will be converted to the canonical
URI for the algorithm if it is known.
.. attribute:: canonicalization_method
A URI that is used to identify the XML canonicalization method used.
.. attribute:: digest_algorithm
A URI that identifies that hashing algorithm that is used to construct
the signature.
.. attribute:: issuer
A distinguished name of the issuer of the certificate that belongs to
the key that is used for the signature.
.. attribute:: serial
A serial number of the certificate that belongs to the key that is used
for the signature.
.. attribute:: key
A PEM encoded key that will be used to create the signed PSKC file.
.. attribute:: certificate
A PEM encoded certificate that is embedded inside the signature that
can be used to validate the signature.
.. attribute:: signed_pskc
A :class:`~pskc.PSKC` instance that contains the signed contents of the
PSKC file. It is usually required to call :func:`verify` before
accessing this attribute without raising an exception.
.. function:: verify(certificate=None, ca_pem_file=None)
Verify the validity of the embedded XML signature. This function will
raise an exception when the validation fails.
:param bytes certificate: a PEM encoded certificate that is used for verification
:param str ca_pem_file: the name of a file that contains a CA certificate
The signature can be verified in three ways:
* The signature was made with a key that has a certificate that is
signed by a CA that is configured in the system CA store. In this
case neither `certificate` or `ca_pem_file` need to be specified (but
a certificate needs to be embedded inside the PSKC file).
* The signature was made with a key and a certificate for the key was
transmitted out-of-band. In this case the `certificate` argument
needs to be present.
* The signature was made with a key and has a certificate that is
signed by a specific CA who's certificate was transmitted
out-of-band. In this case the `ca_pem_file` is used to point to a CA
certificate file (but a certificate needs to be embedded inside the
PSKC file).
After calling this function a verified version of the PSKC file will
be present in the :attr:`signed_pskc` attribute.
.. function:: sign(key, certificate=None)
Set up a key and optionally a certificate that will be used to create an
embedded XML signature when writing the file.
:param bytes key: PEM encoded key used for signing
:param bytes certificate: PEM encoded certificate that will be embedded
This is a utility function that is used to configure the properties
needed to create a signed PSKC file.
python-pskc-1.0/docs/conf.py 0000644 0000000 0000000 00000014300 13221454500 016010 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.coverage',
'sphinx.ext.intersphinx',
]
# 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-2017 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 = '%Y-%m-%d'
# 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 = [
('pskc2csv', 'pskc2csv', u'Convert a PSKC file to CSV',
[u'Arthur de Jong'], 1)
]
# If true, show URL addresses after external links.
man_show_urls = True
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
python-pskc-1.0/docs/policy.rst 0000644 0000000 0000000 00000013042 13221434047 016550 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_attempts
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=None, now=None)
Check whether the key may be used for the provided purpose. The key
:attr:`start_date` and :attr:`expiry_date` are also checked. The `now`
argument can be used to specify another point in time to check against.
.. _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-1.0/docs/_templates/ 0000755 0000000 0000000 00000000000 13221476256 016664 5 ustar root root 0000000 0000000 python-pskc-1.0/docs/_templates/autosummary/ 0000755 0000000 0000000 00000000000 13221476256 021252 5 ustar root root 0000000 0000000 python-pskc-1.0/docs/_templates/autosummary/module.rst 0000644 0000000 0000000 00000000114 12337607021 023256 0 ustar root root 0000000 0000000 {{ fullname }}
{{ underline }}
.. automodule:: {{ fullname }}
:members:
python-pskc-1.0/docs/pskc2csv.rst 0000644 0000000 0000000 00000003366 13221434047 017017 0 ustar root root 0000000 0000000 :orphan:
pskc2csv
========
Synopsis
--------
**pskc2csv** [*options*] <*FILE*>
Description
-----------
:program:`pskc2csv` reads a PSKC file, optionally decrypts any encrypted key
material and outputs a CSV file with information from the PSKC file.
Options
-------
.. program:: pskc2csv
.. option:: -h, --help
Display usage summary.
.. option:: -V, --version
Display version information.
.. option:: -o FILE, --output FILE
By default :program:`pskc2csv` writes a CSV file to stdout. This option
can be used to save the CSV to a file instead.
.. option:: -c COL:LABEL,COL,.., --columns COL:LABEL,COL,..
Specify the columns that should be written to the CSV file. Any
property of :class:`~pskc.key.Key` instances can be used as well
as :class:`~pskc.policy.Policy` properties via ``policy``.
For example: ``serial``, ``secret``, ``counter``, ``time_offset``,
``time_interval``, ``interval``, ``time_drift``, ``issuer``,
``manufacturer``, ``response_length``, ``policy.pin_min_length``.
By default ``serial,secret,algorithm,response_length,time_interval`` is
used.
The column can be followed by an optional label to use in the CSV file in
place of the column specification.
.. option:: -p PASS/FILE, --password PASS/FILE, --passwd PASS/FILE
The password to use for decryption. If the argument refers to a file the
password is read from the file instead.
.. option:: -s KEY/FILE, --secret KEY/FILE
A hex encoded encryption key or a file containing the binary (raw data,
not encoded) key.
.. option:: -e ENCODING, --secret-encoding ENCODING
Specify the encoding to use for writing key material to the CSV file. By
default HEX encoding is used. Valid encodings are: ``base32``, ``base64``
or ``hex``.
python-pskc-1.0/docs/index.rst 0000644 0000000 0000000 00000000440 13221434047 016356 0 ustar root root 0000000 0000000 .. include:: ../README
:end-before: API
Contents
--------
.. toctree::
:maxdepth: 1
usage
encryption
mac
signatures
policy
exceptions
changes
Security considerations
-----------------------
.. include:: ../README
:start-after: -----------------------
python-pskc-1.0/docs/mac.rst 0000644 0000000 0000000 00000006742 13221434047 016022 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
A URI of the MAC algorithm used for message authentication. See the
section :ref:`mac-algorithms` below for a list of algorithm URIs.
Assigned values to this attribute will be converted to the canonical
URI for the algorithm if it is known. For instance, the value
``HMAC-SHA-256`` will automatically be converted into
``http://www.w3.org/2001/04/xmldsig-more#hmac-sha256``.
.. 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`).
.. function:: setup(...)
Configure an encrypted MAC key for creating a new PSKC file.
:param str algorithm: encryption algorithm
:param bytes key: the encryption key to use
None of the arguments are required. By default HMAC-SHA1 will be used
as a MAC algorithm. If no key is configured a random key will be
generated with the length of the output of the configured hash.
This function will automatically be called when the configured
encryption algorithm requires a message authentication code.
.. _mac-algorithms:
Supported MAC algorithms
------------------------
The module should support all HMAC algorithms that can be constructed from
hash algorithms that are available in the standard Python :mod:`hashlib`
module. At the least the following algorithms should be supported:
+-----------------------------------------------------------+--------------------------+
| URI | Description |
+===========================================================+==========================+
| ``http://www.w3.org/2001/04/xmldsig-more#hmac-md5`` | MD5-based HMAC |
+-----------------------------------------------------------+--------------------------+
| ``http://www.w3.org/2000/09/xmldsig#hmac-sha1`` | SHA-1 based HMAC |
+-----------------------------------------------------------+--------------------------+
| ``http://www.w3.org/2001/04/xmldsig-more#hmac-sha224`` | SHA-2 family based HMACs |
| ``http://www.w3.org/2001/04/xmldsig-more#hmac-sha256`` | |
| ``http://www.w3.org/2001/04/xmldsig-more#hmac-sha384`` | |
| ``http://www.w3.org/2001/04/xmldsig-more#hmac-sha512`` | |
+-----------------------------------------------------------+--------------------------+
| ``http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160`` | RIPEMD-160 based HMAC |
+-----------------------------------------------------------+--------------------------+
python-pskc-1.0/docs/exceptions.rst 0000644 0000000 0000000 00000002656 13117010157 017437 0 ustar root root 0000000 0000000 Exceptions
==========
The module and parser will try to interpret any provided PSKC files and will
only raise exceptions on wildly invalid PSKC files.
.. 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-1.0/docs/usage.rst 0000644 0000000 0000000 00000034340 13221434047 016361 0 ustar root root 0000000 0000000 Basic usage
===========
The :mod:`pskc` module implements a simple and efficient API for parsing and
creating 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.device.Device`
and :class:`~pskc.key.Key` instances which contain most of the useful
information of the PSKC file.
Reading 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', secret='987654321', manufacturer='Manufacturer',
... algorithm = 'urn:ietf:params:xml:ns:keyprov:pskc:hotp')
>>> pskc.write('output.pskcxml')
By default an unencrypted PSKC file will be created but an encryption can be
configured using the
:func:`~pskc.encryption.Encryption.setup_preshared_key()` or
:func:`~pskc.encryption.Encryption.setup_pbkdf2()` function.
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:: devices
A list of :class:`~pskc.device.Device` instances that represent the key
containers within the PSKC file.
.. 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.
.. attribute:: signature
:class:`~pskc.signature.Signature` instance for handling embedded XML
signatures in the file.
See :doc:`signatures` for more information.
.. function:: add_device([**kwargs])
Add a new key package to the PSKC instance. The keyword arguments may
refer to any attributes of the :class:`~pskc.device.Device` class with
which the new device is initialised.
.. 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` or
:class:`~pskc.device.Device` 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:`~pskc.device.Device.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:`~pskc.device.Device.device_userid`.
.. attribute:: userid
The distinguished name of the user associated with the key or the device,
taken from :attr:`key_userid` or :attr:`~pskc.device.Device.device_userid`
whichever one is defined.
.. 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