diff --git a/ChangeLog b/ChangeLog index c6c81b1..6118c5a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2012-06-26 Niibe Yutaka + + * test: New. + 2012-06-25 Niibe Yutaka * tool/usb_strings.py: New. diff --git a/test/README b/test/README new file mode 100644 index 0000000..367ad44 --- /dev/null +++ b/test/README @@ -0,0 +1,15 @@ +This is functionality test suite for Gnuk. + +You need python-nose, python-freshen, and python-crypto as well as +python-usb. + + +Type: + + $ nosetests --with-freshen . + +or + + $ nosetests -v --with-freshen . + +to run the test suite. diff --git a/test/features/000_empty_check.feature b/test/features/000_empty_check.feature new file mode 100644 index 0000000..4f4d35e --- /dev/null +++ b/test/features/000_empty_check.feature @@ -0,0 +1,79 @@ +Feature: confirm empty token + In order to start tests + A token should be empty (no data, no keys) + + Scenario: data object Login + When requesting login data: 5e + Then you should get NULL + + Scenario: data object Name + When requesting name: 5b + Then you should get NULL + + Scenario: data object Language preference + When requesting anguage preference: 5f2d + Then you should get NULL + + Scenario: data object Sex + When requesting sex: 5f35 + Then you should get NULL + + Scenario: data object URL + When requesting URL: 5f50 + Then you should get NULL + + Scenario: data object ds counter + When requesting ds counter: 93 + Then you should get: \x00\x00\x00 + + Scenario: data object pw1 status bytes + When requesting pw1 status bytes: c4 + Then you should get: \x00\x7f\x7f\x7f\x03\x03\x03 + + Scenario: data object finger print 0 + When requesting finger print: c5 + Then you should get: \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 + + Scenario: data object finger print 1 + When requesting finger print: c7 + Then you should get NULL + + Scenario: data object finger print 2 + When requesting finger print: c8 + Then you should get NULL + + Scenario: data object finger print 3 + When requesting finger print: c9 + Then you should get NULL + + Scenario: data object CA finger print 0 + When requesting finger print: c6 + Then you should get: \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 + + Scenario: data object CA finger print 1 + When requesting finger print: ca + Then you should get NULL + + Scenario: data object CA finger print 2 + When requesting finger print: cb + Then you should get NULL + + Scenario: data object CA finger print 3 + When requesting finger print: cc + Then you should get NULL + + Scenario: data object date/time of key pair 0 + When requesting date/time of key pair: cd + Then you should get: \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 + + Scenario: data object date/time of key pair 1 + When requesting date/time of key pair: ce + Then you should get NULL + + Scenario: data object date/time of key pair 2 + When requesting date/time of key pair: cf + Then you should get NULL + + Scenario: data object date/time of key pair 3 + When requesting date/time of key pair: d0 + Then you should get NULL diff --git a/test/features/001_empty_check_passphrase.feature b/test/features/001_empty_check_passphrase.feature new file mode 100644 index 0000000..f026b08 --- /dev/null +++ b/test/features/001_empty_check_passphrase.feature @@ -0,0 +1,15 @@ +Feature: confirm empty token + In order to start tests + A token should be empty (no pass phrase) + + Scenario: verify PW1 factory setting (1) + Given cmd_verify with 1 and "123456" + Then it should get success + + Scenario: verify PW1 factory setting (2) + Given cmd_verify with 2 and "123456" + Then it should get success + + Scenario: verify PW3 factory setting + Given cmd_verify with 3 and "12345678" + Then it should get success diff --git a/test/features/002_get_data_static.feature b/test/features/002_get_data_static.feature new file mode 100644 index 0000000..9de7425 --- /dev/null +++ b/test/features/002_get_data_static.feature @@ -0,0 +1,27 @@ +Feature: command GET DATA + In order to conform OpenPGP card 2.0 specification + A token should support all mandatory features of the specification + + Scenario: data object historical bytes + When requesting historical bytes: 5f52 + Then you should get: \x00\x31\x84\x73\x80\x01\x80\x00\x90\x00 + + Scenario: data object extended capabilities + When requesting extended capabilities: c0 + Then you should get: \x30\x00\x00\x00\x00\x00\x00\xff\x01\x00 + + Scenario: data object algorithm attributes 1 + When requesting algorithm attributes 1: c1 + Then you should get: \x01\x08\x00\x00\x20\x00 + + Scenario: data object algorithm attributes 2 + When requesting algorithm attributes 2: c2 + Then you should get: \x01\x08\x00\x00\x20\x00 + + Scenario: data object algorithm attributes 3 + When requesting algorighm attributes 3: c3 + Then you should get: \x01\x08\x00\x00\x20\x00 + + Scenario: data object AID + When requesting AID: 4f + Then data should match: \xd2\x76\x00\x01\x24\x01\x02\x00......\x00\x00 diff --git a/test/features/010_setup_passphrase.feature b/test/features/010_setup_passphrase.feature new file mode 100644 index 0000000..1ddcc23 --- /dev/null +++ b/test/features/010_setup_passphrase.feature @@ -0,0 +1,63 @@ +Feature: setup pass phrase + In order to conform OpenPGP card 2.0 specification + A token should support pass phrase: PW1, PW3 and reset code + + Scenario: setup PW1 (admin-less mode) + Given cmd_change_reference_data with 1 and "123456user pass phrase" + Then it should get success + + Scenario: verify PW1 (1) + Given cmd_verify with 1 and "user pass phrase" + Then it should get success + + Scenario: verify PW1 (2) + Given cmd_verify with 2 and "user pass phrase" + Then it should get success + + Scenario: verify PW3 (admin-less mode) + Given cmd_verify with 3 and "user pass phrase" + Then it should get success + + Scenario: setup reset code (in admin-less mode) + Given cmd_put_data with d3 and "example reset code 000" + Then it should get success + + Scenario: reset pass phrase by reset code (in admin-less mode) + Given cmd_reset_retry_counter with 0 and "example reset code 000new user pass phrase" + Then it should get success + + Scenario: verify PW1 (1) again + Given cmd_verify with 1 and "new user pass phrase" + Then it should get success + + Scenario: verify PW1 (2) again + Given cmd_verify with 2 and "new user pass phrase" + Then it should get success + + Scenario: verify PW3 (admin-less mode) again + Given cmd_verify with 3 and "new user pass phrase" + Then it should get success + + Scenario: setup PW3 (admin-full mode) + Given cmd_change_reference_data with 3 and "new user pass phraseadmin pass phrase" + Then it should get success + + Scenario: verify PW3 (admin-full mode) + Given cmd_verify with 3 and "admin pass phrase" + Then it should get success + + Scenario: setup reset code (in admin-full mode) + Given cmd_put_data with d3 and "another reset code 000" + Then it should get success + + Scenario: reset pass phrase by reset code (in admin-full mode) + Given cmd_reset_retry_counter with 0 and "another reset code 000another user pass phrase" + Then it should get success + + Scenario: verify PW1 (1) again + Given cmd_verify with 1 and "another user pass phrase" + Then it should get success + + Scenario: verify PW1 (2) again + Given cmd_verify with 2 and "another user pass phrase" + Then it should get success diff --git a/test/features/020_personalization_write.feature b/test/features/020_personalization_write.feature new file mode 100644 index 0000000..2fdd387 --- /dev/null +++ b/test/features/020_personalization_write.feature @@ -0,0 +1,27 @@ +Feature: personalize token write + In order to use a token + A token should be personalized with name, sex, url, etc. + + Scenario: data object Login + Given cmd_put_data with 5e and "gpg_user" + Then it should get success + + Scenario: data object Name + Given cmd_put_data with 5b and "GnuPG User" + Then it should get success + + Scenario: data object Language preference + Given cmd_put_data with 5f2d and "ja" + Then it should get success + + Scenario: data object Sex + Given cmd_put_data with 5f35 and "1" + Then it should get success + + Scenario: data object URL + Given cmd_put_data with 5f50 and "http://www.fsij.org/gnuk/" + Then it should get success + + Scenario: data object pw1 status bytes + Given cmd_put_data with c4 and "\x01" + Then it should get success diff --git a/test/features/021_personalization_read.feature b/test/features/021_personalization_read.feature new file mode 100644 index 0000000..5f0f972 --- /dev/null +++ b/test/features/021_personalization_read.feature @@ -0,0 +1,27 @@ +Feature: personalize token read + In order to use a token + A token should be personalized with name, sex, url, etc. + + Scenario: data object Login + When requesting login data: 5e + Then you should get: gpg_user + + Scenario: data object Name + When requesting name: 5b + Then you should get: GnuPG User + + Scenario: data object Language preference + When requesting anguage preference: 5f2d + Then you should get: ja + + Scenario: data object Sex + When requesting sex: 5f35 + Then you should get: 1 + + Scenario: data object URL + When requesting URL: 5f50 + Then you should get: http://www.fsij.org/gnuk/ + + Scenario: data object pw1 status bytes + When requesting pw1 status bytes: c4 + Then you should get: \x01\x7f\x7f\x7f\x03\x03\x03 diff --git a/test/features/030_key_registration.feature b/test/features/030_key_registration.feature new file mode 100644 index 0000000..b71dc90 --- /dev/null +++ b/test/features/030_key_registration.feature @@ -0,0 +1,18 @@ +Feature: import keys to token + In order to use a token + A token should have keys + + Scenario: importing OPENPGP.1 key (sign) + Given a RSA key pair 0 + And importing it to the token as OPENPGP.1 + Then it should get success + + Scenario: importing OPENPGP.2 key (decrypt) + Given a RSA key pair 1 + And importing it to the token as OPENPGP.2 + Then it should get success + + Scenario: importing OPENPGP.3 key (authentication) + Given a RSA key pair 2 + And importing it to the token as OPENPGP.3 + Then it should get success diff --git a/test/features/970_key_removal.feature b/test/features/970_key_removal.feature new file mode 100644 index 0000000..5f89bc1 --- /dev/null +++ b/test/features/970_key_removal.feature @@ -0,0 +1,39 @@ +Feature: key removal + In order to use a token + A token should have keys + + Scenario: remove OPENPGP.1 key (sign) + When removing a key OPENPGP.1 + Then it should get success + + Scenario: remove OPENPGP.2 key (decrypt) + When removing a key OPENPGP.2 + Then it should get success + + Scenario: remove OPENPGP.3 key (authentication) + When removing a key OPENPGP.3 + Then it should get success + + Scenario: remove data object Finger print sig + Given cmd_put_data with c7 and "" + Then it should get success + + Scenario: remove data object Finger print dec + Given cmd_put_data with c8 and "" + Then it should get success + + Scenario: remove data object Finger print aut + Given cmd_put_data with c9 and "" + Then it should get success + + Scenario: remove data object keygeneration data/time sig + Given cmd_put_data with ce and "" + Then it should get success + + Scenario: remove data object keygeneration data/time dec + Given cmd_put_data with cf and "" + Then it should get success + + Scenario: remove data object keygeneration data/time aut + Given cmd_put_data with d0 and "" + Then it should get success diff --git a/test/features/980_personalization_reset.feature b/test/features/980_personalization_reset.feature new file mode 100644 index 0000000..51a3430 --- /dev/null +++ b/test/features/980_personalization_reset.feature @@ -0,0 +1,27 @@ +Feature: removal of data objects + In order to use a token + A token should have personalized data + + Scenario: remove data object Login + Given cmd_put_data with 5e and "" + Then it should get success + + Scenario: remove data object Name + Given cmd_put_data with 5b and "" + Then it should get success + + Scenario: remove data object Language preference + Given cmd_put_data with 5f2d and "" + Then it should get success + + Scenario: remove data object Sex + Given cmd_put_data with 5f35 and "" + Then it should get success + + Scenario: remove data object URL + Given cmd_put_data with 5f50 and "" + Then it should get success + + Scenario: remove data object pw1 status bytes + Given cmd_put_data with c4 and "\x00" + Then it should get success diff --git a/test/features/990_reset_passphrase.feature b/test/features/990_reset_passphrase.feature new file mode 100644 index 0000000..853c515 --- /dev/null +++ b/test/features/990_reset_passphrase.feature @@ -0,0 +1,7 @@ +Feature: reset pass phrase + In order to conform OpenPGP card 2.0 specification + A token should support pass phrase: PW1, PW3 and reset code + + Scenario: setup PW3 (admin-full mode) + Given cmd_change_reference_data with 3 and "admin pass phrase" + Then it should get success diff --git a/test/features/steps.py b/test/features/steps.py new file mode 100644 index 0000000..d9f27ca --- /dev/null +++ b/test/features/steps.py @@ -0,0 +1,76 @@ +from freshen import * +from freshen.checks import * +from nose.tools import assert_regexp_matches + +import ast + +import gnuk +import rsa_keys + +@Before +def ini(sc): + if not ftc.token: + ftc.token = gnuk.get_gnuk_device() + ftc.token.cmd_select_openpgp() + +@Given("cmd_verify with (.*) and \"(.*)\"") +def cmd_verify(who_str,pass_str): + who = int(who_str) + scc.result = ftc.token.cmd_verify(who, pass_str) + +@Given("cmd_change_reference_data with (.*) and \"(.*)\"") +def cmd_change_reference_data(who_str,pass_str): + who = int(who_str) + scc.result = ftc.token.cmd_change_reference_data(who, pass_str) + +@Given("cmd_put_data with (.*) and \"(.*)\"") +def cmd_put_data(tag_str,content_str): + tag = int(tag_str, 16) + tagh = tag >> 8 + tagl = tag & 0xff + scc.result = ftc.token.cmd_put_data(tagh, tagl, content_str) + +@Given("cmd_reset_retry_counter with (.*) and \"(.*)\"") +def cmd_reset_retry_counter(how_str, data): + how = int(how_str) + scc.result = ftc.token.cmd_reset_retry_counter(how, data) + +@Given("a RSA key pair (.*)") +def set_rsa_key(keyno_str): + scc.keyno = int(keyno_str) + +@Given("importing it to the token as OPENPGP.(.*)") +def import_key(openpgp_keyno_str): + openpgp_keyno = int(openpgp_keyno_str) + t = rsa_keys.build_privkey_template(openpgp_keyno, scc.keyno) + scc.result = ftc.token.cmd_put_data_odd(0x3f, 0xff, t) + +@When("requesting (.+): ([0-9a-fA-F]+)") +def get_data(name, tag_str): + tag = int(tag_str, 16) + tagh = tag >> 8 + tagl = tag & 0xff + scc.result = ftc.token.cmd_get_data(tagh, tagl) + +@When("removing a key OPENPGP.(.*)") +def remove_key(openpgp_keyno_str): + openpgp_keyno = int(openpgp_keyno_str) + t = rsa_keys.build_privkey_template_for_remove(openpgp_keyno) + scc.result = ftc.token.cmd_put_data_odd(0x3f, 0xff, t) + +@Then("you should get: (.*)") +def check_result(v): + value = ast.literal_eval("'" + v + "'") + assert_equal(scc.result, value) + +@Then("it should get success") +def check_success(): + assert_equal(scc.result, True) + +@Then("you should get NULL") +def check_null(): + assert_equal(scc.result, "") + +@Then("data should match: (.*)") +def check_regexp(re): + assert_regexp_matches(scc.result, re) diff --git a/test/generate_keys.py b/test/generate_keys.py new file mode 100644 index 0000000..8face41 --- /dev/null +++ b/test/generate_keys.py @@ -0,0 +1,25 @@ +from Crypto import Random +from Crypto.PublicKey import RSA +from binascii import hexlify + +def print_key_in_hex(k): + prv = k.exportKey(format='DER', pkcs=8) + n = prv[38:38+256] + e = prv[38+256+2:38+256+2+3] + p = prv[38+256+2+3+4+257+4:38+256+2+3+4+257+4+128] + q = prv[38+256+2+3+4+257+4+128+4:38+256+2+3+4+257+4+128+4+128] + n_str = hexlify(n) + e_str = hexlify(e) + p_str = hexlify(p) + q_str = hexlify(q) + if int(p_str, 16)*int(q_str, 16) != int(n_str, 16): + raise ValueError("wrong key", k) + print n_str + print e_str + print p_str + print q_str + +rng = Random.new().read +key = RSA.generate(2048, rng) + +print_key_in_hex(key) diff --git a/test/gnuk.py b/test/gnuk.py new file mode 100644 index 0000000..1ea47c2 --- /dev/null +++ b/test/gnuk.py @@ -0,0 +1,329 @@ +""" +gnuk.py - a library for Gnuk Token +This tool is for importing certificate, writing serial number, etc. + +Copyright (C) 2011, 2012 Free Software Initiative of Japan +Author: NIIBE Yutaka + +This file is a part of Gnuk, a GnuPG USB Token implementation. + +Gnuk is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gnuk 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 General Public +License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from struct import * +import string + +# Assume only single CCID device is attached to computer, and it's Gnuk Token + +import usb + +# USB class, subclass, protocol +CCID_CLASS = 0x0B +CCID_SUBCLASS = 0x00 +CCID_PROTOCOL_0 = 0x00 + +def icc_compose(msg_type, data_len, slot, seq, param, data): + return pack('BBBB', cls, ins, p1, p2) + else: + return pack('>BBBBB', cls, ins, p1, p2, data_len) + data + +def list_to_string(l): + return string.join([chr(c) for c in l], '') + +class gnuk_token(object): + def __init__(self, device, configuration, interface): + """ + __init__(device, configuration, interface) -> None + Initialize the device. + device: usb.Device object. + configuration: configuration number. + interface: usb.Interface object representing the interface and altenate setting. + """ + if interface.interfaceClass != CCID_CLASS: + raise ValueError("Wrong interface class") + if interface.interfaceSubClass != CCID_SUBCLASS: + raise ValueError("Wrong interface sub class") + self.__devhandle = device.open() + try: + self.__devhandle.setConfiguration(configuration) + except: + pass + self.__devhandle.claimInterface(interface) + self.__devhandle.setAltInterface(interface) + + self.__intf = interface.interfaceNumber + self.__alt = interface.alternateSetting + self.__conf = configuration + + self.__bulkout = 1 + self.__bulkin = 0x81 + + self.__timeout = 10000 + self.__seq = 0 + + def reset_device(self): + try: + self.__devhandle.reset() + except: + pass + + def release_gnuk(self): + self.__devhandle.releaseInterface() + + def icc_get_result(self): + msg = self.__devhandle.bulkRead(self.__bulkin, 1024, self.__timeout) + if len(msg) < 10: + print msg + raise ValueError("icc_get_result") + msg_type = msg[0] + data_len = msg[1] + (msg[2]<<8) + (msg[3]<<16) + (msg[4]<<24) + slot = msg[5] + seq = msg[6] + status = msg[7] + error = msg[8] + chain = msg[9] + data = msg[10:] + # XXX: check msg_type, data_len, slot, seq, error + return (status, chain, data) + + def icc_get_status(self): + msg = icc_compose(0x65, 0, 0, self.__seq, 0, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data = self.icc_get_result() + # XXX: check chain, data + return status + + def icc_power_on(self): + msg = icc_compose(0x62, 0, 0, self.__seq, 0, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data = self.icc_get_result() + # XXX: check status, chain + self.atr = list_to_string(data) # ATR + + def icc_power_off(self): + msg = icc_compose(0x63, 0, 0, self.__seq, 0, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data = self.icc_get_result() + # XXX: check chain, data + return status + + def icc_send_data_block(self, data): + msg = icc_compose(0x6f, len(data), 0, self.__seq, 0, data) + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + return self.icc_get_result() + + def icc_send_cmd(self, data): + status, chain, data_rcv = self.icc_send_data_block(data) + if chain == 0: + return data_rcv + elif chain == 1: + d = data_rcv + while True: + msg = icc_compose(0x6f, 0, 0, self.__seq, 0x10, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data_rcv = self.icc_get_result() + # XXX: check status + d += data_rcv + if chain == 2: + break + elif chain == 3: + continue + else: + raise ValueError("icc_send_cmd chain") + return d + else: + raise ValueError("icc_send_cmd") + + def cmd_get_response(self, expected_len): + result = [] + while True: + cmd_data = iso7816_compose(0xc0, 0x00, 0x00, '') + pack('>B', expected_len) + response = self.icc_send_cmd(cmd_data) + result += response[:-2] + sw = response[-2:] + if sw[0] == 0x90 and sw[1] == 0x00: + return list_to_string(result) + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + else: + expected_len = sw[1] + + def cmd_verify(self, who, passwd): + cmd_data = iso7816_compose(0x20, 0x00, 0x80+who, passwd) + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_read_binary(self, fileid): + cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, '') + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError, sw + if sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_write_binary(self, fileid, data, is_update): + count = 0 + data_len = len(data) + if is_update: + ins = 0xd6 + else: + ins = 0xd0 + while count*256 < data_len: + if count == 0: + if len(data) < 128: + cmd_data0 = iso7816_compose(ins, 0x80+fileid, 0x00, data[:128]) + cmd_data1 = None + else: + cmd_data0 = iso7816_compose(ins, 0x80+fileid, 0x00, data[:128], 0x10) + cmd_data1 = iso7816_compose(ins, 0x80+fileid, 0x00, data[128:256]) + else: + if len(data[256*count:256*count+128]) < 128: + cmd_data0 = iso7816_compose(ins, count, 0x00, data[256*count:256*count+128]) + cmd_data1 = None + else: + cmd_data0 = iso7816_compose(ins, count, 0x00, data[256*count:256*count+128], 0x10) + cmd_data1 = iso7816_compose(ins, count, 0x00, data[256*count+128:256*(count+1)]) + sw = self.icc_send_cmd(cmd_data0) + if len(sw) != 2: + raise ValueError("cmd_write_binary 0") + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("cmd_write_binary 0", "%02x%02x" % (sw[0], sw[1])) + if cmd_data1: + sw = self.icc_send_cmd(cmd_data1) + if len(sw) != 2: + raise ValueError("cmd_write_binary", sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("cmd_write_binary", "%02x%02x" % (sw[0], sw[1])) + count += 1 + + def cmd_select_openpgp(self): + cmd_data = iso7816_compose(0xa4, 0x04, 0x0c, "\xD2\x76\x00\x01\x24\x01") + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError, sw + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_get_data(self, tagh, tagl): + cmd_data = iso7816_compose(0xca, tagh, tagl, "") + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError, sw + if sw[0] == 0x90 and sw[1] == 0x00: + return "" + elif sw[0] != 0x61: + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return self.cmd_get_response(sw[1]) + + def cmd_change_reference_data(self, who, data): + cmd_data = iso7816_compose(0x24, 0x00, 0x80+who, data) + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_put_data(self, tagh, tagl, content): + cmd_data = iso7816_compose(0xda, tagh, tagl, content) + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_put_data_odd(self, tagh, tagl, content): + cmd_data0 = iso7816_compose(0xdb, tagh, tagl, content[:128], 0x10) + cmd_data1 = iso7816_compose(0xdb, tagh, tagl, content[128:]) + sw = self.icc_send_cmd(cmd_data0) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + sw = self.icc_send_cmd(cmd_data1) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + def cmd_reset_retry_counter(self, how, data): + cmd_data = iso7816_compose(0x2c, how, 0x00, data) + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError(sw) + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError("%02x%02x" % (sw[0], sw[1])) + return True + + + +def compare(data_original, data_in_device): + i = 0 + for d in data_original: + if ord(d) != data_in_device[i]: + raise ValueError, "verify failed at %08x" % i + i += 1 + +def gnuk_devices(): + busses = usb.busses() + for bus in busses: + devices = bus.devices + for dev in devices: + for config in dev.configurations: + for intf in config.interfaces: + for alt in intf: + if alt.interfaceClass == CCID_CLASS and \ + alt.interfaceSubClass == CCID_SUBCLASS and \ + alt.interfaceProtocol == CCID_PROTOCOL_0: + yield dev, config, alt + +def get_gnuk_device(): + icc = None + for (dev, config, intf) in gnuk_devices(): + try: + icc = gnuk_token(dev, config, intf) + print "Device: ", dev.filename + print "Configuration: ", config.value + print "Interface: ", intf.interfaceNumber + break + except: + pass + if not icc: + raise ValueError("No ICC present") + status = icc.icc_get_status() + if status == 0: + pass # It's ON already + elif status == 1: + icc.icc_power_on() + else: + raise ValueError("Unknown ICC status", status) + return icc diff --git a/test/rsa-aut.key b/test/rsa-aut.key new file mode 100644 index 0000000..cdf2d5e --- /dev/null +++ b/test/rsa-aut.key @@ -0,0 +1,4 @@ +9cf7192b51a574d1ad3ccb08ba09b87f228573893eee355529ff243e90fd4b86f79a82097cc7922c0485bed1616b1656a9b0b19ef78ea8ec34c384019adc5d5bf4db2d2a0a2d9cf14277bdcb7056f48b81214e3f7f7742231e29673966f9b1106862112cc798dba8d4a138bb5abfc6d4c12d53a5d39b2f783da916da20852ee139bbafda61d429caf2a4f30847ce7e7ae32ab4061e27dd9e4d00d60910249db8d8559dd85f7ca59659ef400c8f6318700f4e97f0c6f4165de80641490433c88da8682befe68eb311f54af2b07d97ac74edb5399cf054764211694fbb8d1d333f3269f235abe025067f811ff83a2224826219b309ea3e6c968f42b3e52f245dc9 +010001 +b5ab7b159220b18e363258f61ebde08bae83d6ce2dbfe4adc143628c527887acde9de09bf9b49f438019004d71855f30c2d69b6c29bb9882ab641b3387409fe9199464a7faa4b5230c56d9e17cd9ed074bc00180ebed62bae3af28e6ff2ac2654ad968834c5d5c88f8d9d3cc5e167b10453b049d4e454a5761fb0ac717185907 +dd2fffa9814296156a6926cd17b65564187e424dcadce9b032246ad7e46448bb0f9e0ff3c64f987424b1a40bc694e2e9ac4fb1930d163582d7acf20653a1c44b97846c1c5fd8a7b19bb225fb39c30e25410483deaf8c2538d222b748c4d8103b11cec04f666a5c0dbcbf5d5f625f158f65746c3fafe6418145f7cffa5fadeeaf diff --git a/test/rsa-dec.key b/test/rsa-dec.key new file mode 100644 index 0000000..8c2aa47 --- /dev/null +++ b/test/rsa-dec.key @@ -0,0 +1,4 @@ +d392714c29738aac6372f2c8654a08c25a1299fed7004bd512cd2452b503ebad6301130816ac525ba528dc155be6347a5c70407fb4fbdaed751dfc0a7cd5e3910272ff236c4ed1ce5de6620b191a172e5b247347b8cab73a43d79221708755c959a2f83f486439da30917384554331532aabc8326db48866f8c91198834a86ab94679f6175db737bdf399e3f0b737dcb1f4208279d3e1cc694e78686785e4f363a377dec912b7c2f757b1422d866fb9fa85c96b83adfd6a223989a9a02988bdee81ad17eff6385e7b38cec8611fdf367ba4ac8e90d5f48ac7715c5f47aea06a4a37cdaa3029ce59d29bc66853bf6758ef4a7da5a5953f5e557a5a22f67c368c3 +010001 +dae085952c5beee38f25f09bc37a4ca2434c31f78055469d0d5f0bf3337e3a70ba6c91734f195b742e211a5fe283befdf66820008e6ef2c8ca54a91922838fce07d9e33a331ce20dac36803e777d5ee2195ed28d6a4045e28623a6a60b0661e45f7c4f84ae2b1dfad0cf1ec30605158323382a819e730c09a33fad704dd67501 +f774be43ea198aa2f089274e4fffd7d0092ee7b35a1d2f854cdb166f698caab72fdeb099e690e78438b2e043e452d4d2f19d7f44ba6b286642f0ce5204966ff98ecd9e3b448877324631365dc860797429b9414a21a7e166d504cace156588b9a145657eeb1afb43b8ff65d8d6d93cea2ba4ef8aab047885c4de64ffef0b49c3 diff --git a/test/rsa-sig.key b/test/rsa-sig.key new file mode 100644 index 0000000..c83179a --- /dev/null +++ b/test/rsa-sig.key @@ -0,0 +1,4 @@ +c6c877dfd3b441f8fb1b8dc504093a51c2efe4883fe0a6379205acc6e673709905e4d767ddf46143c535cc6d7f10b616f520d8346320ef69ff4a2c4f4a148edc65f7ad24ed7d4fe23bb862a0ae71f4f7904abac0397abf3213df91326b1a25554b3b18cf54584d8bf220169fc92b2aa511e8313983e72b4c9110b3a1aea087aebef95873865608e8faea9ef10e7f7f3a66ca8def2d499c3149c127491e0e4339fd6abe10bfc6c13e43d522004f1485767328eabe35d6ffa8df4c15f0fbcd4eb1c07cc6d85e275139ac69e2962273ae987236926dd6c1144fce3e7ae567fa58ea60620dfafc52f95299fea601739fce27ee71eea978d0074f21e7086f60ba8331 +010001 +cc365b5702714bf203e8c49b0b8afa8dad586e929cf5edca38ad07fa45efd5c2d89022d29f40283a57e50ca24c5f28c8e911a74faaf796f112e7e48195956f9a4df7668a5342523b27179cec958f363211ee11d0ec0e0e1b92ca007a61e8c9ac14e00229b9a7624850199e6667afa1a44db8f3c5de0a8eef0e6de050ac0ac633 +f931a3c12f0e3a5276f712b7706590ba02e14a97ff9b8ce3152af0fc4d9cdc690ea9bc4c82cb16c7d23136cbdab58fbec69880a88bca85c4214df01045082cbe9f4192e3e39c79896533c37dad9eb9e73c2643b9c0a704a4f93d81573537963d6b6e5140a24c702d9f26e06a2095de906daa8824172a6b39f563b7153907050b diff --git a/test/rsa_keys.py b/test/rsa_keys.py new file mode 100644 index 0000000..698fbe3 --- /dev/null +++ b/test/rsa_keys.py @@ -0,0 +1,52 @@ +from binascii import unhexlify + +def read_key_from_file(file): + f = open(file) + n_str = f.readline()[:-1] + e_str = f.readline()[:-1] + p_str = f.readline()[:-1] + q_str = f.readline()[:-1] + f.close() + e = int(e_str, 16) + p = int(p_str, 16) + q = int(q_str, 16) + n = int(n_str, 16) + if n != p * q: + raise ValueError("wrong key", p, q, n) + return (unhexlify(n_str), unhexlify(e_str), unhexlify(p_str), unhexlify(q_str)) + +key = [ None, None, None ] +key[0] = read_key_from_file('rsa-sig.key') +key[1] = read_key_from_file('rsa-dec.key') +key[2] = read_key_from_file('rsa-aut.key') + +def build_privkey_template(openpgp_keyno, keyno): + n_str = key[keyno][0] + e_str = '\x00' + key[keyno][1] + p_str = key[keyno][2] + q_str = key[keyno][3] + + if openpgp_keyno == 1: + keyspec = '\xb6' + elif openpgp_keyno == 2: + keyspec = '\xb8' + else: + keyspec = '\xa4' + + key_template = '\x91\x04'+ '\x92\x81\x80' + '\x93\x81\x80' + + exthdr = keyspec + '\x00' + '\x7f\x48' + '\x08' + key_template + + suffix = '\x5f\x48' + '\x82\x01\x04' + + t = '\x4d' + '\x82\01\16' + exthdr + suffix + e_str + p_str + q_str + return t + +def build_privkey_template_for_remove(openpgp_keyno): + if openpgp_keyno == 1: + keyspec = '\xb6' + elif openpgp_keyno == 2: + keyspec = '\xb8' + else: + keyspec = '\xa4' + return '\x4d\02' + keyspec + '\0x00'