more tests
authorNIIBE Yutaka <gniibe@fsij.org>
Wed, 27 Jun 2012 05:15:51 +0000 (14:15 +0900)
committerNIIBE Yutaka <gniibe@fsij.org>
Wed, 27 Jun 2012 05:15:51 +0000 (14:15 +0900)
ChangeLog
test/README
test/features/030_key_registration.feature
test/features/100_compute_signature.feature [new file with mode: 0644]
test/features/101_decryption.feature [new file with mode: 0644]
test/features/steps.py
test/gnuk.py
test/rsa_keys.py

index 5747b88..7138295 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2012-06-27  Niibe Yutaka  <gniibe@fsij.org>
 
+       * test/features/101_decryption.feature: New.
+       * test/features/100_compute_signature.feature: New.
+
        * src/openpgp-do.c (gpg_do_chks_prvkey): Call flash_do_release before
        flash_do_write.
        (gpg_do_write_prvkey): Bug fix when GC occurs.
index 367ad44..6ead706 100644 (file)
@@ -1,7 +1,9 @@
 This is functionality test suite for Gnuk.
 
-You need python-nose, python-freshen, and python-crypto as well as
-python-usb.
+You need python-nose, python-freshen as well as python-usb.
+
+Besides, python-crypto is needed when you use generate_keys.py to
+update *.key.
 
 
 Type:
index 73f1c10..4381307 100644 (file)
@@ -46,3 +46,11 @@ Feature: import keys to token
      Given a timestamp of OPENPGP.3 key
      And put the data to d0
      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/100_compute_signature.feature b/test/features/100_compute_signature.feature
new file mode 100644 (file)
index 0000000..67fcbcc
--- /dev/null
@@ -0,0 +1,31 @@
+Feature: compute digital signature
+  In order to use a token
+  A token should compute digital signature properly
+
+  Scenario: compute digital signature by OPENPGP.1 key (1)
+     Given a message "This is a test message."
+     And let a token compute digital signature
+     And compute digital signature on host with RSA key pair 0
+     Then results should be same
+
+  Scenario: compute digital signature by OPENPGP.1 key (2)
+     Given a message "This is another test message.\nMultiple lines.\n"
+     And let a token compute digital signature
+     And compute digital signature on host with RSA key pair 0
+     Then results should be same
+
+  Scenario: compute digital signature by OPENPGP.3 key (1)
+     Given a message "This is a test message."
+     And let a token authenticate
+     And compute digital signature on host with RSA key pair 2
+     Then results should be same
+
+  Scenario: compute digital signature by OPENPGP.3 key (2)
+     Given a message "This is another test message.\nMultiple lines.\n"
+     And let a token authenticate
+     And compute digital signature on host with RSA key pair 2
+     Then results should be same
+
+  Scenario: data object ds counter
+     When requesting ds counter: 93
+     Then you should get: \x00\x00\x02
diff --git a/test/features/101_decryption.feature b/test/features/101_decryption.feature
new file mode 100644 (file)
index 0000000..1985dee
--- /dev/null
@@ -0,0 +1,16 @@
+Feature: decryption 
+  In order to use a token
+  A token should decrypt encrypted data
+
+  Scenario: decrypt by OPENPGP.2 key (1)
+     Given a plain text "This is a test message."
+     And encrypt it on host with RSA key pair 1
+     And let a token decrypt encrypted data
+     Then decrypted data should be same as a plain text
+
+  Scenario: decrypt by OPENPGP.2 key (2)
+     Given a plain text "RSA decryption is as easy as pie."
+     And encrypt it on host with RSA key pair 1
+     And let a token decrypt encrypted data
+     Then decrypted data should be same as a plain text
+
index dfe798b..00a5af7 100644 (file)
@@ -1,6 +1,7 @@
 from freshen import *
 from freshen.checks import *
 from nose.tools import assert_regexp_matches
+from binascii import hexlify
 
 import ast
 
@@ -63,6 +64,38 @@ def cmd_put_data_with_result(tag_str):
     tagl = tag & 0xff
     scc.result = ftc.token.cmd_put_data(tagh, tagl, scc.result)
 
+@Given("a message (\".*\")")
+def set_msg(content_str_repr):
+    msg = ast.literal_eval(content_str_repr)
+    scc.digestinfo = rsa_keys.compute_digestinfo(msg)
+
+@Given("let a token compute digital signature")
+def compute_signature():
+    scc.sig = int(hexlify(ftc.token.cmd_pso(0x9e, 0x9a, scc.digestinfo)),16)
+
+@Given("let a token authenticate")
+def internal_authenticate():
+    scc.sig = int(hexlify(ftc.token.cmd_internal_authenticate(scc.digestinfo)),16)
+
+@Given("compute digital signature on host with RSA key pair (.*)")
+def compute_signature_on_host(keyno_str):
+    keyno = int(keyno_str)
+    scc.result = rsa_keys.compute_signature(keyno, scc.digestinfo)
+
+@Given("a plain text (\".*\")")
+def set_plaintext(content_str_repr):
+    scc.plaintext = ast.literal_eval(content_str_repr)
+
+@Given("encrypt it on host with RSA key pair (.*)")
+def encrypt_on_host(keyno_str):
+    keyno = int(keyno_str)
+    scc.ciphertext = rsa_keys.encrypt(keyno, scc.plaintext)
+
+@Given("let a token decrypt encrypted data")
+def decrypt():
+    scc.result = ftc.token.cmd_pso_longdata(0x80, 0x86, scc.ciphertext)
+
+
 @When("requesting (.+): ([0-9a-fA-F]+)")
 def get_data(name, tag_str):
     tag = int(tag_str, 16)
@@ -92,3 +125,11 @@ def check_null():
 @Then("data should match: (.*)")
 def check_regexp(re):
     assert_regexp_matches(scc.result, re)
+
+@Then("results should be same")
+def check_signature():
+    assert_equal(scc.sig, scc.result)
+
+@Then("decrypted data should be same as a plain text")
+def check_decrypt():
+    assert_equal(scc.plaintext, scc.result)
index 1ea47c2..11a235f 100644 (file)
@@ -284,6 +284,42 @@ class gnuk_token(object):
             raise ValueError("%02x%02x" % (sw[0], sw[1]))
         return True
 
+    def cmd_pso(self, p1, p2, data):
+        cmd_data = iso7816_compose(0x2a, p1, p2, data)
+        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_pso_longdata(self, p1, p2, data):
+        cmd_data0 = iso7816_compose(0x2a, p1, p2, data[:128], 0x10)
+        cmd_data1 = iso7816_compose(0x2a, p1, p2, data[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)
+        elif sw[0] != 0x61:
+            raise ValueError("%02x%02x" % (sw[0], sw[1]))
+        return self.cmd_get_response(sw[1])
+
+    def cmd_internal_authenticate(self, data):
+        cmd_data = iso7816_compose(0x88, 0, 0, data)
+        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 compare(data_original, data_in_device):
index b81f551..3df5a0d 100644 (file)
@@ -1,7 +1,9 @@
-from binascii import unhexlify
+from binascii import hexlify, unhexlify
 from time import time
 from struct import pack
-from hashlib import sha1
+from hashlib import sha1, sha256
+import string
+from os import urandom
 
 def read_key_from_file(file):
     f = open(file)
@@ -16,7 +18,7 @@ def read_key_from_file(file):
     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))
+    return (unhexlify(n_str), unhexlify(e_str), unhexlify(p_str), unhexlify(q_str), e, p, q, n)
 
 def calc_fpr(n,e):
     timestamp = int(time())
@@ -69,3 +71,69 @@ def build_privkey_template_for_remove(openpgp_keyno):
     else:
         keyspec = '\xa4'
     return '\x4d\02' + keyspec + '\0x00'
+
+def compute_digestinfo(msg):
+    digest = sha256(msg).digest()
+    prefix = '\x30\31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'
+    return prefix + digest
+
+# egcd and modinv are from wikibooks
+# https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Extended_Euclidean_algorithm
+
+def egcd(a, b):
+    if a == 0:
+        return (b, 0, 1)
+    else:
+        g, y, x = egcd(b % a, a)
+        return (g, x - (b // a) * y, y)
+
+def modinv(a, m):
+    g, x, y = egcd(a, m)
+    if g != 1:
+        raise Exception('modular inverse does not exist')
+    else:
+        return x % m
+
+def pkcs1_pad_for_sign(digestinfo):
+    byte_repr = '\x00' + '\x01' + string.ljust('', 256 - 19 - 32 - 3, '\xff') \
+        + '\x00' + digestinfo
+    return int(hexlify(byte_repr), 16)
+
+def pkcs1_pad(msg):
+    padlen = 256 - 3 - len(msg)
+    byte_repr = '\x00' + '\x02' \
+        + string.replace(urandom(padlen),'\x00','\x01') + '\x00' + msg
+    return int(hexlify(byte_repr), 16)
+
+def compute_signature(keyno, digestinfo):
+    e = key[keyno][4]
+    p = key[keyno][5]
+    q = key[keyno][6]
+    n = key[keyno][7]
+    p1 = p - 1
+    q1 = q - 1
+    h = p1 * q1
+    d = modinv(e, h)
+    dp = d % p1
+    dq = d % q1
+    qp = modinv(q, p)
+
+    input = pkcs1_pad_for_sign(digestinfo)
+    t1 = pow(input, dp, p)
+    t2 = pow(input, dq, q)
+    t = ((t1 - t2) * qp) % p
+    sig = t2 + t * q
+    return sig
+
+def integer_to_bytes(i):
+    s = hex(i)[2:]
+    s = s.rstrip('L')
+    if len(s) & 1:
+        s = '0' + s
+    return unhexlify(s)
+
+def encrypt(keyno, plaintext):
+    e = key[keyno][4]
+    n = key[keyno][7]
+    m = pkcs1_pad(plaintext)
+    return '\x00' + integer_to_bytes(pow(m, e, n))