add test
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
2012-06-26 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* test: New.
|
||||||
|
|
||||||
2012-06-25 Niibe Yutaka <gniibe@fsij.org>
|
2012-06-25 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
* tool/usb_strings.py: New.
|
* tool/usb_strings.py: New.
|
||||||
|
|||||||
15
test/README
Normal file
15
test/README
Normal file
@@ -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.
|
||||||
79
test/features/000_empty_check.feature
Normal file
79
test/features/000_empty_check.feature
Normal file
@@ -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
|
||||||
15
test/features/001_empty_check_passphrase.feature
Normal file
15
test/features/001_empty_check_passphrase.feature
Normal file
@@ -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
|
||||||
27
test/features/002_get_data_static.feature
Normal file
27
test/features/002_get_data_static.feature
Normal file
@@ -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
|
||||||
63
test/features/010_setup_passphrase.feature
Normal file
63
test/features/010_setup_passphrase.feature
Normal file
@@ -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
|
||||||
27
test/features/020_personalization_write.feature
Normal file
27
test/features/020_personalization_write.feature
Normal file
@@ -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
|
||||||
27
test/features/021_personalization_read.feature
Normal file
27
test/features/021_personalization_read.feature
Normal file
@@ -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
|
||||||
18
test/features/030_key_registration.feature
Normal file
18
test/features/030_key_registration.feature
Normal file
@@ -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
|
||||||
39
test/features/970_key_removal.feature
Normal file
39
test/features/970_key_removal.feature
Normal file
@@ -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
|
||||||
27
test/features/980_personalization_reset.feature
Normal file
27
test/features/980_personalization_reset.feature
Normal file
@@ -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
|
||||||
7
test/features/990_reset_passphrase.feature
Normal file
7
test/features/990_reset_passphrase.feature
Normal file
@@ -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
|
||||||
76
test/features/steps.py
Normal file
76
test/features/steps.py
Normal file
@@ -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)
|
||||||
25
test/generate_keys.py
Normal file
25
test/generate_keys.py
Normal file
@@ -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)
|
||||||
329
test/gnuk.py
Normal file
329
test/gnuk.py
Normal file
@@ -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
|
||||||
4
test/rsa-aut.key
Normal file
4
test/rsa-aut.key
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
9cf7192b51a574d1ad3ccb08ba09b87f228573893eee355529ff243e90fd4b86f79a82097cc7922c0485bed1616b1656a9b0b19ef78ea8ec34c384019adc5d5bf4db2d2a0a2d9cf14277bdcb7056f48b81214e3f7f7742231e29673966f9b1106862112cc798dba8d4a138bb5abfc6d4c12d53a5d39b2f783da916da20852ee139bbafda61d429caf2a4f30847ce7e7ae32ab4061e27dd9e4d00d60910249db8d8559dd85f7ca59659ef400c8f6318700f4e97f0c6f4165de80641490433c88da8682befe68eb311f54af2b07d97ac74edb5399cf054764211694fbb8d1d333f3269f235abe025067f811ff83a2224826219b309ea3e6c968f42b3e52f245dc9
|
||||||
|
010001
|
||||||
|
b5ab7b159220b18e363258f61ebde08bae83d6ce2dbfe4adc143628c527887acde9de09bf9b49f438019004d71855f30c2d69b6c29bb9882ab641b3387409fe9199464a7faa4b5230c56d9e17cd9ed074bc00180ebed62bae3af28e6ff2ac2654ad968834c5d5c88f8d9d3cc5e167b10453b049d4e454a5761fb0ac717185907
|
||||||
|
dd2fffa9814296156a6926cd17b65564187e424dcadce9b032246ad7e46448bb0f9e0ff3c64f987424b1a40bc694e2e9ac4fb1930d163582d7acf20653a1c44b97846c1c5fd8a7b19bb225fb39c30e25410483deaf8c2538d222b748c4d8103b11cec04f666a5c0dbcbf5d5f625f158f65746c3fafe6418145f7cffa5fadeeaf
|
||||||
4
test/rsa-dec.key
Normal file
4
test/rsa-dec.key
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
d392714c29738aac6372f2c8654a08c25a1299fed7004bd512cd2452b503ebad6301130816ac525ba528dc155be6347a5c70407fb4fbdaed751dfc0a7cd5e3910272ff236c4ed1ce5de6620b191a172e5b247347b8cab73a43d79221708755c959a2f83f486439da30917384554331532aabc8326db48866f8c91198834a86ab94679f6175db737bdf399e3f0b737dcb1f4208279d3e1cc694e78686785e4f363a377dec912b7c2f757b1422d866fb9fa85c96b83adfd6a223989a9a02988bdee81ad17eff6385e7b38cec8611fdf367ba4ac8e90d5f48ac7715c5f47aea06a4a37cdaa3029ce59d29bc66853bf6758ef4a7da5a5953f5e557a5a22f67c368c3
|
||||||
|
010001
|
||||||
|
dae085952c5beee38f25f09bc37a4ca2434c31f78055469d0d5f0bf3337e3a70ba6c91734f195b742e211a5fe283befdf66820008e6ef2c8ca54a91922838fce07d9e33a331ce20dac36803e777d5ee2195ed28d6a4045e28623a6a60b0661e45f7c4f84ae2b1dfad0cf1ec30605158323382a819e730c09a33fad704dd67501
|
||||||
|
f774be43ea198aa2f089274e4fffd7d0092ee7b35a1d2f854cdb166f698caab72fdeb099e690e78438b2e043e452d4d2f19d7f44ba6b286642f0ce5204966ff98ecd9e3b448877324631365dc860797429b9414a21a7e166d504cace156588b9a145657eeb1afb43b8ff65d8d6d93cea2ba4ef8aab047885c4de64ffef0b49c3
|
||||||
4
test/rsa-sig.key
Normal file
4
test/rsa-sig.key
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
c6c877dfd3b441f8fb1b8dc504093a51c2efe4883fe0a6379205acc6e673709905e4d767ddf46143c535cc6d7f10b616f520d8346320ef69ff4a2c4f4a148edc65f7ad24ed7d4fe23bb862a0ae71f4f7904abac0397abf3213df91326b1a25554b3b18cf54584d8bf220169fc92b2aa511e8313983e72b4c9110b3a1aea087aebef95873865608e8faea9ef10e7f7f3a66ca8def2d499c3149c127491e0e4339fd6abe10bfc6c13e43d522004f1485767328eabe35d6ffa8df4c15f0fbcd4eb1c07cc6d85e275139ac69e2962273ae987236926dd6c1144fce3e7ae567fa58ea60620dfafc52f95299fea601739fce27ee71eea978d0074f21e7086f60ba8331
|
||||||
|
010001
|
||||||
|
cc365b5702714bf203e8c49b0b8afa8dad586e929cf5edca38ad07fa45efd5c2d89022d29f40283a57e50ca24c5f28c8e911a74faaf796f112e7e48195956f9a4df7668a5342523b27179cec958f363211ee11d0ec0e0e1b92ca007a61e8c9ac14e00229b9a7624850199e6667afa1a44db8f3c5de0a8eef0e6de050ac0ac633
|
||||||
|
f931a3c12f0e3a5276f712b7706590ba02e14a97ff9b8ce3152af0fc4d9cdc690ea9bc4c82cb16c7d23136cbdab58fbec69880a88bca85c4214df01045082cbe9f4192e3e39c79896533c37dad9eb9e73c2643b9c0a704a4f93d81573537963d6b6e5140a24c702d9f26e06a2095de906daa8824172a6b39f563b7153907050b
|
||||||
52
test/rsa_keys.py
Normal file
52
test/rsa_keys.py
Normal file
@@ -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'
|
||||||
Reference in New Issue
Block a user