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