From 839b0156a91874da67e0af9475a862370e1ec01b Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 27 Jun 2012 14:15:51 +0900 Subject: [PATCH] more tests --- ChangeLog | 3 + test/README | 6 +- test/features/030_key_registration.feature | 8 +++ test/features/100_compute_signature.feature | 31 +++++++++ test/features/101_decryption.feature | 16 +++++ test/features/steps.py | 41 ++++++++++++ test/gnuk.py | 36 ++++++++++ test/rsa_keys.py | 74 ++++++++++++++++++++- 8 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 test/features/100_compute_signature.feature create mode 100644 test/features/101_decryption.feature diff --git a/ChangeLog b/ChangeLog index 5747b88..7138295 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2012-06-27 Niibe Yutaka + * 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. diff --git a/test/README b/test/README index 367ad44..6ead706 100644 --- a/test/README +++ b/test/README @@ -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: diff --git a/test/features/030_key_registration.feature b/test/features/030_key_registration.feature index 73f1c10..4381307 100644 --- a/test/features/030_key_registration.feature +++ b/test/features/030_key_registration.feature @@ -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 index 0000000..67fcbcc --- /dev/null +++ b/test/features/100_compute_signature.feature @@ -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 index 0000000..1985dee --- /dev/null +++ b/test/features/101_decryption.feature @@ -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 + diff --git a/test/features/steps.py b/test/features/steps.py index dfe798b..00a5af7 100644 --- a/test/features/steps.py +++ b/test/features/steps.py @@ -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) diff --git a/test/gnuk.py b/test/gnuk.py index 1ea47c2..11a235f 100644 --- a/test/gnuk.py +++ b/test/gnuk.py @@ -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): diff --git a/test/rsa_keys.py b/test/rsa_keys.py index b81f551..3df5a0d 100644 --- a/test/rsa_keys.py +++ b/test/rsa_keys.py @@ -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))