add test
authorNIIBE Yutaka <gniibe@fsij.org>
Tue, 26 Jun 2012 08:59:24 +0000 (17:59 +0900)
committerNIIBE Yutaka <gniibe@fsij.org>
Tue, 26 Jun 2012 08:59:24 +0000 (17:59 +0900)
19 files changed:
ChangeLog
test/README [new file with mode: 0644]
test/features/000_empty_check.feature [new file with mode: 0644]
test/features/001_empty_check_passphrase.feature [new file with mode: 0644]
test/features/002_get_data_static.feature [new file with mode: 0644]
test/features/010_setup_passphrase.feature [new file with mode: 0644]
test/features/020_personalization_write.feature [new file with mode: 0644]
test/features/021_personalization_read.feature [new file with mode: 0644]
test/features/030_key_registration.feature [new file with mode: 0644]
test/features/970_key_removal.feature [new file with mode: 0644]
test/features/980_personalization_reset.feature [new file with mode: 0644]
test/features/990_reset_passphrase.feature [new file with mode: 0644]
test/features/steps.py [new file with mode: 0644]
test/generate_keys.py [new file with mode: 0644]
test/gnuk.py [new file with mode: 0644]
test/rsa-aut.key [new file with mode: 0644]
test/rsa-dec.key [new file with mode: 0644]
test/rsa-sig.key [new file with mode: 0644]
test/rsa_keys.py [new file with mode: 0644]

index c6c81b1..6118c5a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2012-06-26  Niibe Yutaka  <gniibe@fsij.org>
+
+       * test: New.
+
 2012-06-25  Niibe Yutaka  <gniibe@fsij.org>
 
        * tool/usb_strings.py: New.
diff --git a/test/README b/test/README
new file mode 100644 (file)
index 0000000..367ad44
--- /dev/null
@@ -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 (file)
index 0000000..4f4d35e
--- /dev/null
@@ -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 (file)
index 0000000..f026b08
--- /dev/null
@@ -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 (file)
index 0000000..9de7425
--- /dev/null
@@ -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 (file)
index 0000000..1ddcc23
--- /dev/null
@@ -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 (file)
index 0000000..2fdd387
--- /dev/null
@@ -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 (file)
index 0000000..5f0f972
--- /dev/null
@@ -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 (file)
index 0000000..b71dc90
--- /dev/null
@@ -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 (file)
index 0000000..5f89bc1
--- /dev/null
@@ -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 (file)
index 0000000..51a3430
--- /dev/null
@@ -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 (file)
index 0000000..853c515
--- /dev/null
@@ -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 (file)
index 0000000..d9f27ca
--- /dev/null
@@ -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 (file)
index 0000000..8face41
--- /dev/null
@@ -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 (file)
index 0000000..1ea47c2
--- /dev/null
@@ -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 <gniibe@fsij.org>
+
+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 <http://www.gnu.org/licenses/>.
+"""
+
+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('<BiBBBH', msg_type, data_len, slot, seq, 0, param) + data
+
+def iso7816_compose(ins, p1, p2, data, cls=0x00):
+    data_len = len(data)
+    if data_len == 0:
+        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 (file)
index 0000000..cdf2d5e
--- /dev/null
@@ -0,0 +1,4 @@
+9cf7192b51a574d1ad3ccb08ba09b87f228573893eee355529ff243e90fd4b86f79a82097cc7922c0485bed1616b1656a9b0b19ef78ea8ec34c384019adc5d5bf4db2d2a0a2d9cf14277bdcb7056f48b81214e3f7f7742231e29673966f9b1106862112cc798dba8d4a138bb5abfc6d4c12d53a5d39b2f783da916da20852ee139bbafda61d429caf2a4f30847ce7e7ae32ab4061e27dd9e4d00d60910249db8d8559dd85f7ca59659ef400c8f6318700f4e97f0c6f4165de80641490433c88da8682befe68eb311f54af2b07d97ac74edb5399cf054764211694fbb8d1d333f3269f235abe025067f811ff83a2224826219b309ea3e6c968f42b3e52f245dc9
+010001
+b5ab7b159220b18e363258f61ebde08bae83d6ce2dbfe4adc143628c527887acde9de09bf9b49f438019004d71855f30c2d69b6c29bb9882ab641b3387409fe9199464a7faa4b5230c56d9e17cd9ed074bc00180ebed62bae3af28e6ff2ac2654ad968834c5d5c88f8d9d3cc5e167b10453b049d4e454a5761fb0ac717185907
+dd2fffa9814296156a6926cd17b65564187e424dcadce9b032246ad7e46448bb0f9e0ff3c64f987424b1a40bc694e2e9ac4fb1930d163582d7acf20653a1c44b97846c1c5fd8a7b19bb225fb39c30e25410483deaf8c2538d222b748c4d8103b11cec04f666a5c0dbcbf5d5f625f158f65746c3fafe6418145f7cffa5fadeeaf
diff --git a/test/rsa-dec.key b/test/rsa-dec.key
new file mode 100644 (file)
index 0000000..8c2aa47
--- /dev/null
@@ -0,0 +1,4 @@
+d392714c29738aac6372f2c8654a08c25a1299fed7004bd512cd2452b503ebad6301130816ac525ba528dc155be6347a5c70407fb4fbdaed751dfc0a7cd5e3910272ff236c4ed1ce5de6620b191a172e5b247347b8cab73a43d79221708755c959a2f83f486439da30917384554331532aabc8326db48866f8c91198834a86ab94679f6175db737bdf399e3f0b737dcb1f4208279d3e1cc694e78686785e4f363a377dec912b7c2f757b1422d866fb9fa85c96b83adfd6a223989a9a02988bdee81ad17eff6385e7b38cec8611fdf367ba4ac8e90d5f48ac7715c5f47aea06a4a37cdaa3029ce59d29bc66853bf6758ef4a7da5a5953f5e557a5a22f67c368c3
+010001
+dae085952c5beee38f25f09bc37a4ca2434c31f78055469d0d5f0bf3337e3a70ba6c91734f195b742e211a5fe283befdf66820008e6ef2c8ca54a91922838fce07d9e33a331ce20dac36803e777d5ee2195ed28d6a4045e28623a6a60b0661e45f7c4f84ae2b1dfad0cf1ec30605158323382a819e730c09a33fad704dd67501
+f774be43ea198aa2f089274e4fffd7d0092ee7b35a1d2f854cdb166f698caab72fdeb099e690e78438b2e043e452d4d2f19d7f44ba6b286642f0ce5204966ff98ecd9e3b448877324631365dc860797429b9414a21a7e166d504cace156588b9a145657eeb1afb43b8ff65d8d6d93cea2ba4ef8aab047885c4de64ffef0b49c3
diff --git a/test/rsa-sig.key b/test/rsa-sig.key
new file mode 100644 (file)
index 0000000..c83179a
--- /dev/null
@@ -0,0 +1,4 @@
+c6c877dfd3b441f8fb1b8dc504093a51c2efe4883fe0a6379205acc6e673709905e4d767ddf46143c535cc6d7f10b616f520d8346320ef69ff4a2c4f4a148edc65f7ad24ed7d4fe23bb862a0ae71f4f7904abac0397abf3213df91326b1a25554b3b18cf54584d8bf220169fc92b2aa511e8313983e72b4c9110b3a1aea087aebef95873865608e8faea9ef10e7f7f3a66ca8def2d499c3149c127491e0e4339fd6abe10bfc6c13e43d522004f1485767328eabe35d6ffa8df4c15f0fbcd4eb1c07cc6d85e275139ac69e2962273ae987236926dd6c1144fce3e7ae567fa58ea60620dfafc52f95299fea601739fce27ee71eea978d0074f21e7086f60ba8331
+010001
+cc365b5702714bf203e8c49b0b8afa8dad586e929cf5edca38ad07fa45efd5c2d89022d29f40283a57e50ca24c5f28c8e911a74faaf796f112e7e48195956f9a4df7668a5342523b27179cec958f363211ee11d0ec0e0e1b92ca007a61e8c9ac14e00229b9a7624850199e6667afa1a44db8f3c5de0a8eef0e6de050ac0ac633
+f931a3c12f0e3a5276f712b7706590ba02e14a97ff9b8ce3152af0fc4d9cdc690ea9bc4c82cb16c7d23136cbdab58fbec69880a88bca85c4214df01045082cbe9f4192e3e39c79896533c37dad9eb9e73c2643b9c0a704a4f93d81573537963d6b6e5140a24c702d9f26e06a2095de906daa8824172a6b39f563b7153907050b
diff --git a/test/rsa_keys.py b/test/rsa_keys.py
new file mode 100644 (file)
index 0000000..698fbe3
--- /dev/null
@@ -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'