From f7b03f7fb59f549e8bf112d041dc6fba9ea2e274 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 4 Apr 2018 16:34:07 +0900 Subject: [PATCH] More tests. --- tests/README | 2 +- tests/openpgp_card.py | 96 +++++++++++++++++++++++++++++- tests/test_001_personalize_card.py | 12 ++-- tests/test_004_reset_pw3.py | 2 +- tests/test_007_kdf.py | 14 +++-- tool/kdf_calc.py | 6 +- 6 files changed, 117 insertions(+), 15 deletions(-) diff --git a/tests/README b/tests/README index 8aefe70..8155758 100644 --- a/tests/README +++ b/tests/README @@ -6,7 +6,7 @@ Gnuk Token is supported as well. You need to install: - $ sudo apt-get install python3-pytest python3-usb + $ sudo apt install python3-pytest python3-usb python3-cffi Please run test by typing: diff --git a/tests/openpgp_card.py b/tests/openpgp_card.py index 1b63b29..6dc0c73 100644 --- a/tests/openpgp_card.py +++ b/tests/openpgp_card.py @@ -1,7 +1,7 @@ """ openpgp_card.py - a library for OpenPGP card -Copyright (C) 2011, 2012, 2013, 2015, 2016 +Copyright (C) 2011, 2012, 2013, 2015, 2016, 2018 Free Software Initiative of Japan Author: NIIBE Yutaka @@ -21,7 +21,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -from struct import pack +from struct import pack, unpack +from kdf_calc import kdf_calc def iso7816_compose(ins, p1, p2, data, cls=0x00, le=None): data_len = len(data) @@ -54,6 +55,52 @@ class OpenPGP_Card(object): """ self.__reader = reader + self.__kdf_iters = None + self.__kdf_salt_user = None + self.__kdf_salt_reset = None + self.__kdf_salt_admin = None + + def configure_with_kdf(self): + kdf_data = self.cmd_get_data(0x00, 0xf9) + if kdf_data != b"": + algo, subalgo, iters, salt_user, salt_reset, salt_admin, hash_user, hash_admin = parse_kdf_data(kdf_data) + self.__kdf_iters = iters + self.__kdf_salt_user = salt_user + self.__kdf_salt_reset = salt_reset + self.__kdf_salt_admin = salt_admin + else: + self.__kdf_iters = None + self.__kdf_salt_user = None + self.__kdf_salt_reset = None + self.__kdf_salt_admin = None + + # Higher layer VERIFY possibly using KDF Data Object + def verify(self, who, passwd): + if self.__kdf_iters: + salt = self.__kdf_salt_user + if who == 3 and self.__kdf_salt_admin: + salt = self.__kdf_salt_admin + pw_hash = kdf_calc(passwd, salt, self.__kdf_iters) + return self.cmd_verify(who, pw_hash) + else: + return self.cmd_verify(who, passwd) + + # Higher layer CHANGE_PASSWD possibly using KDF Data Object + def change_passwd(self, who, passwd_old, passwd_new): + if self.__kdf_iters: + salt = self.__kdf_salt_user + if who == 3 and self.__kdf_salt_admin: + salt = self.__kdf_salt_admin + hash_old = kdf_calc(passwd_old, salt, self.__kdf_iters) + if passwd_new: + hash_new = kdf_calc(passwd_new, salt, self.__kdf_iters) + else: + hash_new = b"" + return self.cmd_change_reference_data(who, hash_old + hash_new) + else: + if not passwd_new: + passwd_new = b"" + return self.cmd_change_reference_data(who, passwd_old + passwd_new) def cmd_get_response(self, expected_len): result = b"" @@ -336,3 +383,48 @@ class OpenPGP_Card(object): raise ValueError(sw) if not (sw[0] == 0x90 and sw[1] == 0x00): raise ValueError("%02x%02x" % (sw[0], sw[1])) + +def parse_kdf_data(kdf_data): + if len(kdf_data) == 90: + single_salt = True + elif len(kdf_data) == 110: + single_salt = False + else: + raise ValueError("length does not much", kdf_data) + + if kdf_data[0:2] != b'\x81\x01': + raise ValueError("data does not much") + algo = kdf_data[2] + if kdf_data[3:5] != b'\x82\x01': + raise ValueError("data does not much") + subalgo = kdf_data[5] + if kdf_data[6:8] != b'\x83\x04': + raise ValueError("data does not much") + iters = unpack(">I", kdf_data[8:12])[0] + if kdf_data[12:14] != b'\x84\x08': + raise ValueError("data does not much") + salt = kdf_data[14:22] + if single_salt: + salt_reset = None + salt_admin = None + if kdf_data[22:24] != b'\x87\x20': + raise ValueError("data does not much") + hash_user = kdf_data[24:56] + if kdf_data[56:58] != b'\x88\x20': + raise ValueError("data does not much") + hash_admin = kdf_data[58:90] + else: + if kdf_data[22:24] != b'\x85\x08': + raise ValueError("data does not much") + salt_reset = kdf_data[24:32] + if kdf_data[32:34] != b'\x86\x08': + raise ValueError("data does not much") + salt_admin = kdf_data[34:42] + if kdf_data[42:44] != b'\x87\x20': + raise ValueError("data does not much") + hash_user = kdf_data[44:76] + if kdf_data[76:78] != b'\x88\x20': + raise ValueError("data does not much") + hash_admin = kdf_data[78:110] + return ( algo, subalgo, iters, salt, salt_reset, salt_admin, + hash_user, hash_admin ) diff --git a/tests/test_001_personalize_card.py b/tests/test_001_personalize_card.py index 928e9ae..c168c98 100644 --- a/tests/test_001_personalize_card.py +++ b/tests/test_001_personalize_card.py @@ -28,7 +28,7 @@ from card_const import * from constants_for_test import * def test_setup_pw3_0(card): - r = card.cmd_change_reference_data(3, FACTORY_PASSPHRASE_PW3 + PW3_TEST0) + r = card.change_passwd(3, FACTORY_PASSPHRASE_PW3, PW3_TEST0) assert r def test_verify_pw3_0(card): @@ -154,7 +154,7 @@ def test_public_key_3(card): assert rsa_keys.key[2][0] == pk[9:9+256] def test_setup_pw1_0(card): - r = card.cmd_change_reference_data(1, FACTORY_PASSPHRASE_PW1 + PW1_TEST0) + r = card.change_passwd(1, FACTORY_PASSPHRASE_PW1, PW1_TEST0) assert r def test_verify_pw1_0(card): @@ -166,7 +166,7 @@ def test_verify_pw1_0_2(card): assert v def test_setup_pw1_1(card): - r = card.cmd_change_reference_data(1, PW1_TEST0 + PW1_TEST1) + r = card.change_passwd(1, PW1_TEST0, PW1_TEST1) assert r def test_verify_pw1_1(card): @@ -194,7 +194,7 @@ def test_verify_pw1_2_2(card): assert v def test_setup_pw3_1(card): - r = card.cmd_change_reference_data(3, PW3_TEST0 + PW3_TEST1) + r = card.change_passwd(3, PW3_TEST0, PW3_TEST1) assert r def test_verify_pw3_1(card): @@ -214,7 +214,7 @@ def test_verify_pw1_3_2(card): assert v def test_setup_pw1_4(card): - r = card.cmd_change_reference_data(1, PW1_TEST3 + PW1_TEST4) + r = card.change_passwd(1, PW1_TEST3, PW1_TEST4) assert r def test_verify_pw1_4(card): @@ -226,7 +226,7 @@ def test_verify_pw1_4_2(card): assert v def test_setup_pw3_2(card): - r = card.cmd_change_reference_data(3, PW3_TEST1 + PW3_TEST0) + r = card.change_passwd(3, PW3_TEST1, PW3_TEST0) assert r def test_verify_pw3_2(card): diff --git a/tests/test_004_reset_pw3.py b/tests/test_004_reset_pw3.py index 0d72169..7477d8a 100644 --- a/tests/test_004_reset_pw3.py +++ b/tests/test_004_reset_pw3.py @@ -24,7 +24,7 @@ from card_const import * # Gnuk specific feature of clear PW3 def test_setup_pw3_null(card): - r = card.cmd_change_reference_data(3, FACTORY_PASSPHRASE_PW3) + r = card.change_passwd(3, FACTORY_PASSPHRASE_PW3, None) assert r def test_verify_pw3(card): diff --git a/tests/test_007_kdf.py b/tests/test_007_kdf.py index 37c34e8..e587c82 100644 --- a/tests/test_007_kdf.py +++ b/tests/test_007_kdf.py @@ -24,29 +24,35 @@ from card_const import * from constants_for_test import * def test_verify_pw3_0(card): - v = card.cmd_verify(3, FACTORY_PASSPHRASE_PW3) + v = card.verify(3, FACTORY_PASSPHRASE_PW3) assert v def test_kdf_put_full(card): r = card.cmd_put_data(0x00, 0xf9, KDF_FULL) + if r: + card.configure_with_kdf() assert r def test_verify_pw3_1(card): - v = card.cmd_verify(3, KDF_FULL_HASH_PW3) + v = card.verify(3, FACTORY_PASSPHRASE_PW3) assert v def test_kdf_put_single(card): r = card.cmd_put_data(0x00, 0xf9, KDF_SINGLE) + if r: + card.configure_with_kdf() assert r def test_verify_pw3_2(card): - v = card.cmd_verify(3, KDF_SINGLE_HASH_PW3) + v = card.verify(3, FACTORY_PASSPHRASE_PW3) assert v def test_kdf_put_none(card): r = card.cmd_put_data(0x00, 0xf9, b"") + if r: + card.configure_with_kdf() assert r def test_verify_pw3_3(card): - v = card.cmd_verify(3, FACTORY_PASSPHRASE_PW3) + v = card.verify(3, FACTORY_PASSPHRASE_PW3) assert v diff --git a/tool/kdf_calc.py b/tool/kdf_calc.py index ff4a643..aceef6c 100644 --- a/tool/kdf_calc.py +++ b/tool/kdf_calc.py @@ -37,7 +37,11 @@ def kdf_calc(pw_string, salt_byte, iterations): ffi = FFI() ffi.cdef(DEF_gcry_kdf_derive) libgcrypt = ffi.dlopen("libgcrypt.so") - pw=ffi.new("char []", pw_string.encode('UTF-8')) + if isinstance(pw_string, str): + pw_byte = pw_string.encode('UTF-8') + else: + pw_byte = pw_string + pw=ffi.new("char []", pw_byte) salt = ffi.new("char []", salt_byte) kb = ffi.new("char []", 32) r = libgcrypt.gcry_kdf_derive(pw, len(pw_string), GCRY_KDF_ITERSALTED_S2K,