#!/usr/bin/env python # Copyright (c) 2016, NORDUnet A/S # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # - Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # - Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # - Neither the name of the NORDUnet nor the names of its contributors may # be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ LibHAL unit tests, using libhal.py and the Python unit_test framework. """ # There's some overlap between these tests and the PKCS #11 unit tests, # because in many cases we're testing the same functionality, just via # different APIs. import unittest import datetime import logging import sys from struct import pack, unpack from cryptech.libhal import * try: from Crypto.Util.number import inverse from Crypto.PublicKey import RSA from Crypto.Cipher import AES from Crypto.Cipher.PKCS1_v1_5 import PKCS115_Cipher from Crypto.Signature.PKCS1_v1_5 import PKCS115_SigScheme from Crypto.Hash.SHA256 import SHA256Hash as SHA256 from Crypto.Hash.SHA384 import SHA384Hash as SHA384 from Crypto.Hash.SHA512 import SHA512Hash as SHA512 pycrypto_loaded = True except ImportError: pycrypto_loaded = False try: from ecdsa import der as ECDSA_DER from ecdsa.keys import SigningKey as ECDSA_SigningKey from ecdsa.keys import VerifyingKey as ECDSA_VerifyingKey from ecdsa.ellipticcurve import Point from ecdsa.curves import NIST256p, NIST384p, NIST521p from ecdsa.curves import find_curve as ECDSA_find_curve from ecdsa.util import oid_ecPublicKey if not pycrypto_loaded: from hashlib import sha256 as SHA256, sha384 as SHA384, sha512 as SHA512 ecdsa_loaded = True except ImportError: ecdsa_loaded = False logger = logging.getLogger("unit-tests") def main(): from sys import argv global args args = parse_arguments(argv[1:]) argv = argv[:1] + args.only_test logging.basicConfig(level = logging.DEBUG if args.debug else logging.INFO, datefmt = "%Y-%m-%d %H:%M:%S", format = "%(asctime)-15s %(name)s[%(process)d]:%(levelname)s: %(message)s",) unittest.main(verbosity = 1 if args.quiet else 2, argv = argv, catchbreak = True, testRunner = TextTestRunner) def parse_arguments(argv = ()): from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter parser = ArgumentParser(description = __doc__, formatter_class = ArgumentDefaultsHelpFormatter) parser.add_argument("--quiet", action = "store_true", help = "suppress chatter") parser.add_argument("--debug", action = "store_true", help = "debug-level logging") parser.add_argument("--io-log", action = "store_true", help = "log HSM I/O stream") parser.add_argument("--wheel-pin", default = "fnord", help = "PIN for wheel user") parser.add_argument("--so-pin", default = "fnord", help = "PIN for security officer") parser.add_argument("--user-pin", default = "fnord", help = "PIN for normal user") parser.add_argument("--all-tests", action = "store_true", help = "enable tests usually skipped") parser.add_argument("--only-test", default = [], nargs = "+", help = "only run tests named here") return parser.parse_args(argv) args = parse_arguments() hsm = None pin_map = { HAL_USER_NORMAL : "user_pin", HAL_USER_SO : "so_pin", HAL_USER_WHEEL : "wheel_pin" } def setUpModule(): global hsm hsm = HSM() hsm.debug_io = args.io_log def tearDownModule(): hsm.logout() #hsm.close() # Subclass a few bits of unittest to add timing reports for individual tests. class TestCase(unittest.TestCase): def setUp(self): super(TestCase, self).setUp() self.startTime = datetime.datetime.now() def tearDown(self): self.endTime = datetime.datetime.now() super(TestCase, self).tearDown() def skipUnlessAll(self, reason): if not args.all_tests: self.skipTest(reason) class TextTestResult(unittest.TextTestResult): def addSuccess(self, test): if self.showAll and hasattr(test, "startTime") and hasattr(test, "endTime"): self.stream.write("runtime {} ... ".format(test.endTime - test.startTime)) self.stream.flush() super(TextTestResult, self).addSuccess(test) def addError(self, test, err): if self.showAll: self.stream.write("exception {!s} ".format(err[0].__name__)) # err[1] self.stream.flush() super(TextTestResult, self).addError(test, err) class TextTestRunner(unittest.TextTestRunner): resultclass = TextTestResult # Tests below here class TestBasic(TestCase): """ Test basic functions that don't involve keys, digests, or PINs. """ def test_get_version(self): version = hsm.get_version() # Might want to inspect the result here self.assertIsInstance(version, int) def test_get_random(self): length = 32 random = hsm.get_random(length) self.assertIsInstance(random, str) self.assertEqual(length, len(random)) class TestPIN(TestCase): """ Test functions involving PINs. """ def setUp(self): hsm.logout() super(TestPIN, self).setUp() def tearDown(self): super(TestPIN, self).tearDown() hsm.logout() def test_is_logged_in(self): for user in pin_map: self.assertRaises(HAL_ERROR_FORBIDDEN, hsm.is_logged_in, user) def login_logout(self, user1): pin = getattr(args, pin_map[user1]) hsm.login(user1, pin) for user2 in pin_map: if user2 == user1: hsm.is_logged_in(user2) else: self.assertRaises(HAL_ERROR_FORBIDDEN, hsm.is_logged_in, user2) hsm.logout() def test_login_wheel(self): self.login_logout(HAL_USER_WHEEL) def test_login_so(self): self.login_logout(HAL_USER_SO) def test_login_user(self): self.login_logout(HAL_USER_NORMAL) # Eventually we will want a test of set_pin(), probably under a # @unittest.skipUnless to prevent it from being run unless the # user requests it. Punt that one for the moment. class TestDigest(TestCase): """ Test digest/HMAC functions. """ def v(*bytes): return "".join(chr(b) for b in bytes) # NIST sample messages. # "abc" nist_512_single = v( 0x61, 0x62, 0x63 ) nist_sha1_single_digest = v( 0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d ) nist_sha256_single_digest = v( 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad ) # "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" nist_512_double = v( 0x61, 0x62, 0x63, 0x64, 0x62, 0x63, 0x64, 0x65, 0x63, 0x64, 0x65, 0x66, 0x64, 0x65, 0x66, 0x67, 0x65, 0x66, 0x67, 0x68, 0x66, 0x67, 0x68, 0x69, 0x67, 0x68, 0x69, 0x6a, 0x68, 0x69, 0x6a, 0x6b, 0x69, 0x6a, 0x6b, 0x6c, 0x6a, 0x6b, 0x6c, 0x6d, 0x6b, 0x6c, 0x6d, 0x6e, 0x6c, 0x6d, 0x6e, 0x6f, 0x6d, 0x6e, 0x6f, 0x70, 0x6e, 0x6f, 0x70, 0x71 ) nist_sha1_double_digest = v( 0x84, 0x98, 0x3e, 0x44, 0x1c, 0x3b, 0xd2, 0x6e, 0xba, 0xae, 0x4a, 0xa1, 0xf9, 0x51, 0x29, 0xe5, 0xe5, 0x46, 0x70, 0xf1 ) nist_sha256_double_digest = v( 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 ) # "abc" nist_1024_single = v( 0x61, 0x62, 0x63 ) nist_sha512_224_single_digest = v( 0x46, 0x34, 0x27, 0x0f, 0x70, 0x7b, 0x6a, 0x54, 0xda, 0xae, 0x75, 0x30, 0x46, 0x08, 0x42, 0xe2, 0x0e, 0x37, 0xed, 0x26, 0x5c, 0xee, 0xe9, 0xa4, 0x3e, 0x89, 0x24, 0xaa ) nist_sha512_256_single_digest = v( 0x53, 0x04, 0x8e, 0x26, 0x81, 0x94, 0x1e, 0xf9, 0x9b, 0x2e, 0x29, 0xb7, 0x6b, 0x4c, 0x7d, 0xab, 0xe4, 0xc2, 0xd0, 0xc6, 0x34, 0xfc, 0x6d, 0x46, 0xe0, 0xe2, 0xf1, 0x31, 0x07, 0xe7, 0xaf, 0x23 ) nist_sha384_single_digest = v( 0xcb, 0x00, 0x75, 0x3f, 0x45, 0xa3, 0x5e, 0x8b, 0xb5, 0xa0, 0x3d, 0x69, 0x9a, 0xc6, 0x50, 0x07, 0x27, 0x2c, 0x32, 0xab, 0x0e, 0xde, 0xd1, 0x63, 0x1a, 0x8b, 0x60, 0x5a, 0x43, 0xff, 0x5b, 0xed, 0x80, 0x86, 0x07, 0x2b, 0xa1, 0xe7, 0xcc, 0x23, 0x58, 0xba, 0xec, 0xa1, 0x34, 0xc8, 0x25, 0xa7 ) nist_sha512_single_digest = v( 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a, 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8, 0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd, 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e, 0x2a, 0x9a, 0xc9, 0x4f, 0x
# This duplicates more of sw/thirdparty/libtfm/Makefile than I
# would like, but it does the job. Prettier makefiles can wait for another day.
# vpath %.c ${LIBTFM_SRC}
# vpath %.h ${LIBTFM_SRC}
BITS := 8192
HDR := ${LIBTFM_SRC}/tomsfastmath/src/headers/tfm.h
LIB := tomsfastmath/libtfm.a
#CFLAGS += -DTFM_X86
#CFLAGS += -DTFM_NO_ASM
CFLAGS += -fPIC -Wall -W -Wshadow -I${LIBTFM_SRC}/tomsfastmath/src/headers -g3 -DFP_MAX_SIZE="(${BITS}*2+(8*DIGIT_BIT))"
TARGETS := $(notdir ${HDR} ${LIB})
all: ${TARGETS}
clean:
rm -rf ${TARGETS} $(notdir ${HDR}.tmp) ${LIB} tomsfastmath/src
distclean: clean
rm -f TAGS
$(notdir ${HDR}): ${HDR}
echo >$@.tmp '/* Configure size of largest bignum we want to handle -- see notes in tfm.pdf */'
echo >>$@.tmp '#define FP_MAX_SIZE (${BITS}*2+(8*DIGIT_BIT))'
echo >>$@.tmp ''
cat >>$@.tmp $^
mv -f $@.tmp $@
$(notdir ${LIB}): ${LIB}
ln -f $^ $@
${LIB}: ${HDR}
(cd ${LIBTFM_SRC} && find tomsfastmath/src -type d) | xargs mkdir -p
cd tomsfastmath; ${MAKE} CFLAGS='${CFLAGS}'