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.
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:
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
--- /dev/null
+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
--- /dev/null
+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
+
from freshen import *
from freshen.checks import *
from nose.tools import assert_regexp_matches
+from binascii import hexlify
import ast
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)
@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)
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):
-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)
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())
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))