test_write.doctest - tests for writing PSKC files 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 >>> from pskc import PSKC >>> import datetime >>> import sys >>> import tempfile >>> from Crypto import Random >>> 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_attemtps = 3 >>> key.policy.pin_min_length = 4 >>> key.policy.pin_max_length = 4 >>> key.policy.pin_encoding = 'DECIMAL' >>> pin_key = pskc.add_key(id='pinID', secret='1234', ... algorithm='urn:ietf:params:xml:ns:keyprov:pskc:pin', ... response_encoding='DECIMAL', response_length=4) 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 Manufacturer 987654321 Model 2 2006-05-01T00:00:00Z 2014-05-31T00:00:00Z uid=arthur, dc=arthurdejong, dc=org CyrptoId Issuer Clubs key profile id reference to some key a friendly key TheQuickBrownFox 0 cn=Arthur de Jong, dc=arthurdejong, dc=org 2008-05-01T00:00:00Z 2012-06-13T00:00:00Z OTP Verify 42 MTIzNA== 267469811 2006-05-01T00:00:00 2014-05-31T00:00:00 TheQuickBrownFox 0 30 6 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: +REPORT_UDIFF Pre-shared-key ESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSX Manufacturer 987654321 CM_ID_001 Issuer AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv Su+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='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='1234', counter=42) >>> pskc.encryption.setup_pbkdf2( ... 'passphrase', key_name='Passphrase') >>> pskc.write(sys.stdout) #doctest: +ELLIPSIS +REPORT_UDIFF ... 12000 16 Passphrase ... 123456 ... ... 42 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=Random.get_random_bytes(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 12000 16 ... ... 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 TokenVendorAcme 123456 MTIzNA== 42 NTY3OA==