Compare commits
51 Commits
release/1.
...
release/1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed61ed980d | ||
|
|
7ff0baf5df | ||
|
|
46259ce63d | ||
|
|
0aca10f307 | ||
|
|
5f2a8b835c | ||
|
|
5213d9ab82 | ||
|
|
c12f331217 | ||
|
|
62b4369d2c | ||
|
|
9dde59867d | ||
|
|
eae955b15e | ||
|
|
7e8dd12654 | ||
|
|
8c91d2ef2e | ||
|
|
6b6913c676 | ||
|
|
3ad9373163 | ||
|
|
220d5c0307 | ||
|
|
de7f9f6417 | ||
|
|
a302585602 | ||
|
|
ea2191105f | ||
|
|
32094099dd | ||
|
|
9b71d70b73 | ||
|
|
77d06fb301 | ||
|
|
78b642507b | ||
|
|
90a11859dc | ||
|
|
ad9a901e1b | ||
|
|
500b12b60d | ||
|
|
4bfe087583 | ||
|
|
22d0cb689a | ||
|
|
fe6337f988 | ||
|
|
1a2560531d | ||
|
|
40e234b799 | ||
|
|
deccb981ad | ||
|
|
12bd1161a4 | ||
|
|
d72a0b7893 | ||
|
|
ee5b6a2a82 | ||
|
|
f9b43a67ee | ||
|
|
70846e8b81 | ||
|
|
f6df7701f9 | ||
|
|
b9772265cf | ||
|
|
254c521c6f | ||
|
|
d7c6b95ba1 | ||
|
|
2e7d93a556 | ||
|
|
db2d897c3f | ||
|
|
23a9fe3bdc | ||
|
|
e8f773d2f6 | ||
|
|
6b5fc04c0d | ||
|
|
39bee2ee01 | ||
|
|
f39380d3aa | ||
|
|
0d36a58804 | ||
|
|
eb0e913eee | ||
|
|
7575dda42a | ||
|
|
61ec9b7ed7 |
194
ChangeLog
194
ChangeLog
@@ -1,3 +1,197 @@
|
|||||||
|
2013-02-15 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* Version 1.0.2.
|
||||||
|
* src/usb_desc.c (gnukStringSerial): Updated.
|
||||||
|
|
||||||
|
2013-02-14 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* test/features/002_get_data_static.feature: Value of extended
|
||||||
|
capabilities changed.
|
||||||
|
* test/features/402_get_data_static.feature: Ditto.
|
||||||
|
* test/features/802_get_data_static.feature: Ditto.
|
||||||
|
|
||||||
|
* src/openpgp.c (cmd_write_binary): Move erasing page of update
|
||||||
|
keys to...
|
||||||
|
(modify_binary): ...here.
|
||||||
|
|
||||||
|
* src/flash.c (flash_write_binary): Handle removal of update keys.
|
||||||
|
|
||||||
|
2013-02-13 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/openpgp.c (cmd_get_challenge): Handle Le field.
|
||||||
|
|
||||||
|
* src/openpgp-do.c (extended_capabilities): Fix for GET CHALLENGE.
|
||||||
|
|
||||||
|
* src/gnuk.h (CHALLENGE_LEN): Moved here (was: openpgp.c).
|
||||||
|
|
||||||
|
* tool/gnuk_token.py (iso7816_compose): Add Le field.
|
||||||
|
|
||||||
|
2013-01-30 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/openpgp.c (cmd_external_authenticate): Fix off-by-one error.
|
||||||
|
|
||||||
|
* tool/gnuk_token.py (gnuk_token.cmd_external_authenticate): Add
|
||||||
|
KEYNO to the arguments.
|
||||||
|
|
||||||
|
* tool/upgrade_by_passwd.py (main): Explicitly say it's KEYNO.
|
||||||
|
|
||||||
|
2013-01-28 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/openpgp-do.c (gpg_pw_get_retry_counter): New.
|
||||||
|
* src/openpgp.c (cmd_verify): Implement VERIFY with empty data.
|
||||||
|
|
||||||
|
2013-01-22 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/pinpadtest.py (Card.cmd_vega_alpha_disable_empty_verify):
|
||||||
|
New.
|
||||||
|
(main): call cmd_vega_alpha_disable_empty_verify if it's
|
||||||
|
COVADIS_VEGA_ALPHA.
|
||||||
|
|
||||||
|
2013-01-21 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/pageant_proxy_to_gpg.py: New.
|
||||||
|
* tool/sexp.py: New.
|
||||||
|
|
||||||
|
2013-01-20 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/gpg_agent.py: New.
|
||||||
|
|
||||||
|
2013-01-11 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/pinpadtest.py: Add fixed length input.
|
||||||
|
|
||||||
|
2012-12-25 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/rsa.py: New.
|
||||||
|
|
||||||
|
* tool/rsa_example.key: New. Example RSA key information.
|
||||||
|
|
||||||
|
* tool/upgrade_by_passwd.py: New.
|
||||||
|
|
||||||
|
2012-12-19 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/Makefile.in (USE_OPT): -O3 and -Os (was: -O2).
|
||||||
|
|
||||||
|
* tool/gnuk_token.py (gnuk_token.stop_gnuk, gnuk_token.mem_info)
|
||||||
|
(gnuk_token.download, gnuk_token.execute)
|
||||||
|
(gnuk_token.cmd_get_challenge)
|
||||||
|
(gnuk_token.cmd_external_authenticate): New.
|
||||||
|
(gnuk_devices_by_vidpid): New.
|
||||||
|
(regnual): New.
|
||||||
|
|
||||||
|
2012-12-18 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* test/gnuk.py: Remove.
|
||||||
|
|
||||||
|
* test/features/steps.py: Use tool/gnuk_token.py.
|
||||||
|
|
||||||
|
* tool/gnuk_put_binary_libusb.py: Use gnuk_token.py.
|
||||||
|
(main): Follow the API change.
|
||||||
|
|
||||||
|
* tool/gnuk_token.py (list_to_string): New.
|
||||||
|
(gnuk_token.get_string, gnuk_token.increment_seq)
|
||||||
|
(gnuk_token.reset_device, gnuk_token.release_gnuk): New.
|
||||||
|
(gnuk_token.icc_power_on): Set self.atr and it's now string.
|
||||||
|
(gnuk_token.icc_send_cmd): Handle time extension.
|
||||||
|
(gnuk_token.cmd_get_response): Return string (was: list).
|
||||||
|
(gnuk_token.cmd_get_data): Return "" when success.
|
||||||
|
(gnuk_token.cmd_change_reference_data, gnuk_token.cmd_put_data)
|
||||||
|
(gnuk_token.cmd_put_data_odd)
|
||||||
|
(gnuk_token.cmd_reset_retry_counter, gnuk_token.cmd_pso)
|
||||||
|
(gnuk_token.cmd_pso_longdata)
|
||||||
|
(gnuk_token.cmd_internal_authenticate, gnuk_token.cmd_genkey)
|
||||||
|
(gnuk_token.cmd_get_public_key): New.
|
||||||
|
(compare): New.
|
||||||
|
(get_gnuk_device): New.
|
||||||
|
|
||||||
|
2012-12-14 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/openpgp.c (cmd_change_password): Check password length
|
||||||
|
for admin less mode.
|
||||||
|
|
||||||
|
2012-12-13 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/openpgp-do.c (gpg_do_put_data): Add GPG_SUCCESS for
|
||||||
|
completeness (it worked because of lower layer goodness).
|
||||||
|
|
||||||
|
2012-12-12 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/gnuk_token.py: Add module imports.
|
||||||
|
|
||||||
|
* tool/gnuk_remove_keys.py (main): Fix data object number
|
||||||
|
for KGTIME_SIG, KGTIME_DEC and KGTIME_AUT.
|
||||||
|
|
||||||
|
* tool/gnuk_remove_keys_libusb.py (main): Likewise.
|
||||||
|
|
||||||
|
2012-12-05 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/gnuk_remove_keys_libusb.py: New.
|
||||||
|
* tool/gnuk_token.py: New.
|
||||||
|
|
||||||
|
2012-11-07 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/usb-icc.c (icc_send_data_block_internal): New.
|
||||||
|
(icc_send_data_block_time_extension): New.
|
||||||
|
(icc_handle_timeout): Use icc_send_data_block_time_extension.
|
||||||
|
(icc_send_data_block): Only one argument.
|
||||||
|
(USBthread): Follow the change.
|
||||||
|
|
||||||
|
2012-11-01 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/gnuk_upgrade.py (main): New option '-k' to specify keygrip
|
||||||
|
for non-smartcard key.
|
||||||
|
(gpg_sign): Support non-smartcard key.
|
||||||
|
|
||||||
|
2012-10-31 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/get_raw_public_key.py: New.
|
||||||
|
|
||||||
|
2012-10-26 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* GNUK_USB_DEVICE_ID (Product_STRING): It's considered better not
|
||||||
|
to include vendor name. Change the name to "Gnuk Token" (was:
|
||||||
|
FSIJ USB Token).
|
||||||
|
|
||||||
|
2012-10-13 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* boards/STBEE_MINI/board.c [!DFU_SUPPORT] (hwinit1): Don't run
|
||||||
|
when "user switch" is pushed. This is for JTAG/SWD debugger.
|
||||||
|
|
||||||
|
2012-09-25 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/stlinkv2.py (main): Print out option bytes value.
|
||||||
|
Call reset_sys before blank_check.
|
||||||
|
|
||||||
|
2012-09-18 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/stlinkv2.py (stlinkv2.option_bytes_erase)
|
||||||
|
(stlinkv2.flash_erase_all, stlinkv2.flash_erase_page): : Fix
|
||||||
|
OperationFailure (was OperationError).
|
||||||
|
(main): Call option_bytes_erase if it's not 0xff.
|
||||||
|
|
||||||
|
2012-09-12 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* src/sha256.c: Include <stdint.h>.
|
||||||
|
|
||||||
|
* src/sha256.h (SHA256_DIGEST_SIZE, SHA256_BLOCK_SIZE): Move
|
||||||
|
from sha256.c.
|
||||||
|
|
||||||
|
2012-08-29 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* tool/hub_ctrl.py (__main__): Fix to busnum (was: bunum).
|
||||||
|
Thanks to Henry Hu.
|
||||||
|
|
||||||
|
2012-08-03 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* Version 1.0.1.
|
||||||
|
* src/usb_desc.c (gnukStringSerial): Updated.
|
||||||
|
* src/main.c (ID_OFFSET): Fix.
|
||||||
|
|
||||||
|
2012-08-02 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
* test/gnuk.py (gnuk_token.get_string): New.
|
||||||
|
* test/features/991_version_string.feature: New.
|
||||||
|
|
||||||
2012-07-21 Niibe Yutaka <gniibe@fsij.org>
|
2012-07-21 Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
* Version 1.0.
|
* Version 1.0.
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# VID:PID bcdDev Product_STRING Vender_STRING
|
# VID:PID bcdDev Product_STRING Vender_STRING
|
||||||
234b:0000 0200 FSIJ USB Token Free Software Initiative of Japan
|
234b:0000 0200 Gnuk Token Free Software Initiative of Japan
|
||||||
##########<TAB> ##<TAB> ##########<TAB> #################
|
##########<TAB> ##<TAB> ##########<TAB> #################
|
||||||
|
|||||||
46
NEWS
46
NEWS
@@ -1,5 +1,51 @@
|
|||||||
Gnuk NEWS - User visible changes
|
Gnuk NEWS - User visible changes
|
||||||
|
|
||||||
|
* Major changes in Gnuk 1.0.2
|
||||||
|
|
||||||
|
Released 2013-02-15, by NIIBE Yutaka
|
||||||
|
|
||||||
|
** Product string is now "Gnuk Token" (was: "FSIJ USB Token")
|
||||||
|
Since the USB ID Repository suggests not including vendor name
|
||||||
|
in product string, we changed the product string.
|
||||||
|
|
||||||
|
** New tool (experimental): test/upgrade_by_passwd.py
|
||||||
|
This is the tool to install new firmware to Gnuk Token, provided
|
||||||
|
that it's just shipped from factory (and nothing changed). It
|
||||||
|
authenticate as admin by factory setting, register a public key
|
||||||
|
for firmware upgrade, and then, does firmware upgrade.
|
||||||
|
|
||||||
|
** tool/gnuk_upgrade.py supports '-k' option
|
||||||
|
It now supports RSA key on the host PC (not the one on the Token).
|
||||||
|
|
||||||
|
** New tool: tool/get_raw_public_key.py
|
||||||
|
This is a script to dump raw data of RSA public key, which is useful
|
||||||
|
to register to Gnuk Token as a firmware upgrade key.
|
||||||
|
|
||||||
|
** New tool: tool/gnuk_remove_keys_libusb.py
|
||||||
|
This tool is libusb version of gnuk_remove_keys.py. Besides, a bug in
|
||||||
|
gnuk_remove_keys.py was fixed.
|
||||||
|
|
||||||
|
** CCID protocol fix
|
||||||
|
When time extension is requested by Gnuk Token to host PC, argument
|
||||||
|
field was 0, which was wrong (but it works for most PC/SC
|
||||||
|
implementations and GnuPG internal driver). Now it's 1, which means
|
||||||
|
1*BWT.
|
||||||
|
|
||||||
|
** OpenPGP card protocol enhancement
|
||||||
|
Now, VERIFY command accepts empty data and returns remaining trial
|
||||||
|
counts, or 0x9000 (OK) when it's already authenticated. This is
|
||||||
|
useful for application to synchronize card's authentication status.
|
||||||
|
|
||||||
|
|
||||||
|
* Major changes in Gnuk 1.0.1
|
||||||
|
|
||||||
|
Released 2012-08-03, by NIIBE Yutaka
|
||||||
|
|
||||||
|
** USB SerialNumber String
|
||||||
|
In 1.0, it has a bug for USB SerialNumber String. It has been fixed
|
||||||
|
in 1.0.1.
|
||||||
|
|
||||||
|
|
||||||
* Major changes in Gnuk 1.0
|
* Major changes in Gnuk 1.0
|
||||||
|
|
||||||
Released 2012-07-21, by NIIBE Yutaka
|
Released 2012-07-21, by NIIBE Yutaka
|
||||||
|
|||||||
57
README
57
README
@@ -1,7 +1,7 @@
|
|||||||
Gnuk - An Implementation of USB Cryptographic Token for GnuPG
|
Gnuk - An Implementation of USB Cryptographic Token for GnuPG
|
||||||
|
|
||||||
Version 1.0
|
Version 1.0.2
|
||||||
2012-07-21
|
2013-02-15
|
||||||
Niibe Yutaka
|
Niibe Yutaka
|
||||||
Free Software Initiative of Japan
|
Free Software Initiative of Japan
|
||||||
|
|
||||||
@@ -14,9 +14,9 @@ STM32F103 processor.
|
|||||||
|
|
||||||
I wish that Gnuk will be a developer's soother who uses GnuPG. I have
|
I wish that Gnuk will be a developer's soother who uses GnuPG. I have
|
||||||
been nervous of storing secret key(s) on usual secondary storage.
|
been nervous of storing secret key(s) on usual secondary storage.
|
||||||
There is a solution with OpenPGP card, but it is not the choice for me
|
There is a solution with OpenPGP card, but it is not the choice for
|
||||||
to bring a card reader all the time. With Gnuk, this issue will be
|
me, as card reader is not common device. With Gnuk, this issue will
|
||||||
solved by a USB token which is small enough.
|
be solved by a USB token.
|
||||||
|
|
||||||
Please look at the graphics of "gnuk.svg" for the software name. My
|
Please look at the graphics of "gnuk.svg" for the software name. My
|
||||||
son used to be with his NUK(R), always, everywhere. Now, I am with a
|
son used to be with his NUK(R), always, everywhere. Now, I am with a
|
||||||
@@ -35,14 +35,14 @@ A0: Good points of Gnuk are:
|
|||||||
Gnuk Token cheaper (see Q8-A8).
|
Gnuk Token cheaper (see Q8-A8).
|
||||||
* You can study Gnuk to modify and to enhance. For example, you
|
* You can study Gnuk to modify and to enhance. For example, you
|
||||||
can implement your own authentication method with some sensor
|
can implement your own authentication method with some sensor
|
||||||
such as acceleration sensor.
|
such as an acceleration sensor.
|
||||||
* It is "of Free Software"; Gnuk is distributed under GPLv3+,
|
* It is "of Free Software"; Gnuk is distributed under GPLv3+,
|
||||||
"by Free Software"; Gnuk development requires only Free Software
|
"by Free Software"; Gnuk development requires only Free Software
|
||||||
(GNU Toolchain, Python, etc.),
|
(GNU Toolchain, Python, etc.),
|
||||||
"for Free Software"; Gnuk supports GnuPG.
|
"for Free Software"; Gnuk supports GnuPG.
|
||||||
|
|
||||||
Q1: What kind of key algorithm is supported?
|
Q1: What kind of key algorithm is supported?
|
||||||
A1: Gnuk only supports 2048-bit RSA.
|
A1: Gnuk version 1 only supports 2048-bit RSA.
|
||||||
|
|
||||||
Q2: How long does it take for digital signing?
|
Q2: How long does it take for digital signing?
|
||||||
A2: It takes a second and a half or so.
|
A2: It takes a second and a half or so.
|
||||||
@@ -51,8 +51,8 @@ Q3: What's your recommendation for target board?
|
|||||||
A3: Orthodox choice is Olimex STM32-H103.
|
A3: Orthodox choice is Olimex STM32-H103.
|
||||||
If you have skill of electronics and like DIY, STM32 part of STM8S
|
If you have skill of electronics and like DIY, STM32 part of STM8S
|
||||||
Discovery Kit might be the best choice.
|
Discovery Kit might be the best choice.
|
||||||
Currently FST-01 (Flying Stone Tiny 01) is under development,
|
FST-01 (Flying Stone Tiny 01) will be soon available for sale,
|
||||||
it will be the best choice, hopefully.
|
and it will be the best choice, hopefully.
|
||||||
|
|
||||||
Q4: What's version of GnuPG are you using?
|
Q4: What's version of GnuPG are you using?
|
||||||
A4: In Debian GNU/Linux system, I use gnupg 1.4.11-3 and gnupg-agent
|
A4: In Debian GNU/Linux system, I use gnupg 1.4.11-3 and gnupg-agent
|
||||||
@@ -63,12 +63,13 @@ Q5: What's version of pcscd and libccid are you using?
|
|||||||
A5: In Debian GNU/Linux system, I use pcscd 1.5.5-4 and libccid 1.3.11-2,
|
A5: In Debian GNU/Linux system, I use pcscd 1.5.5-4 and libccid 1.3.11-2,
|
||||||
which is in squeeze. Note that you need to edit /etc/libccid_Info.plist
|
which is in squeeze. Note that you need to edit /etc/libccid_Info.plist
|
||||||
when using libccid (< 1.4.1).
|
when using libccid (< 1.4.1).
|
||||||
|
Note that pcscd and libccid are optional, you can use Gnuk without them.
|
||||||
|
|
||||||
Q6: What kinds of hardware is required for development?
|
Q6: What kinds of hardware is required for development?
|
||||||
A6: You need a target board plus a JTAG debugger. If you just want to
|
A6: You need a target board plus a JTAG/SWD debugger. If you just
|
||||||
test Gnuk for target boards with DfuSe, JTAG debugger is not
|
want to test Gnuk for target boards with DfuSe, JTAG debugger is
|
||||||
the requirement. Note that for real use, you need JTAG debugger
|
not the requirement. Note that for real use, you need JTAG/SWD
|
||||||
to enable flash ROM protection.
|
debugger to enable flash ROM protection.
|
||||||
|
|
||||||
Q7: How much does it cost?
|
Q7: How much does it cost?
|
||||||
A7: Olimex STM32-H103 plus ARM-USB-TINY-H cost 70 Euro or so.
|
A7: Olimex STM32-H103 plus ARM-USB-TINY-H cost 70 Euro or so.
|
||||||
@@ -107,17 +108,18 @@ Ab: That's because gnome-keyring-daemon interferes GnuPG. Type:
|
|||||||
"GPG Password Agent" and "SSH Key Agent".
|
"GPG Password Agent" and "SSH Key Agent".
|
||||||
|
|
||||||
Qc: Do you know a good SWD debugger to connect FST-01 or something?
|
Qc: Do you know a good SWD debugger to connect FST-01 or something?
|
||||||
Ac: STLink v2 is cheap one. We have a tool/stlinkv2.py as flash ROM
|
Ac: ST-Link/V2 is cheap one. We have a tool/stlinkv2.py as flash ROM
|
||||||
writer program.
|
writer program.
|
||||||
|
|
||||||
|
|
||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This is version 1.0 release of Gnuk, after a year and eleven months
|
This is a second minor release in version 1.0 series of Gnuk.
|
||||||
development. While it is daily use for a year or so, some newly
|
|
||||||
introduced features (including key generation and firmware upgrade)
|
While it is daily use for a year and a half, some newly introduced
|
||||||
should be considered experimental.
|
features (including key generation and firmware upgrade) should be
|
||||||
|
considered experimental.
|
||||||
|
|
||||||
Tested features are:
|
Tested features are:
|
||||||
|
|
||||||
@@ -259,9 +261,8 @@ please contact Niibe, so that it is listed to the file in the official
|
|||||||
release of the source code.
|
release of the source code.
|
||||||
|
|
||||||
When you are modifing Gnuk and installing the binary to device, you
|
When you are modifing Gnuk and installing the binary to device, you
|
||||||
should replace "FSIJ" in the string gnukStringSerial (usb_desc.c) to
|
should replace the vendor string to yours, so that users can see it's
|
||||||
yours, so that the device will say it's modified version by device
|
not by original vendor, and it is modified version.
|
||||||
serial number.
|
|
||||||
|
|
||||||
FSIJ allows you to use USB device ID of FSIJ (234b:0000) for devices
|
FSIJ allows you to use USB device ID of FSIJ (234b:0000) for devices
|
||||||
with Gnuk under one of following conditions:
|
with Gnuk under one of following conditions:
|
||||||
@@ -292,8 +293,8 @@ respect users' freedom for computing. Please ask FSIJ for the
|
|||||||
license.
|
license.
|
||||||
|
|
||||||
Otherwise, companies which want to distribute Gnuk devices, please use
|
Otherwise, companies which want to distribute Gnuk devices, please use
|
||||||
your own USB vendor ID and product ID. Please replace "FSIJ" in the
|
your own USB vendor ID and product ID. Please replace vendor string
|
||||||
string gnukStringSerial (usb_desc.c) to yours, when you modify Gnuk.
|
and possibly product string to yours, when you modify Gnuk.
|
||||||
|
|
||||||
|
|
||||||
Host Requirements
|
Host Requirements
|
||||||
@@ -659,10 +660,10 @@ Firmware update
|
|||||||
See doc/note/firmware-update.
|
See doc/note/firmware-update.
|
||||||
|
|
||||||
|
|
||||||
Read-only Git Repository
|
Git Repositories
|
||||||
========================
|
================
|
||||||
|
|
||||||
You can browse at http://www.gniibe.org/gitweb?p=gnuk.git;a=summary
|
You can browse at: http://www.gniibe.org/gitweb?p=gnuk.git;a=summary
|
||||||
|
|
||||||
You can get it by:
|
You can get it by:
|
||||||
|
|
||||||
@@ -673,6 +674,8 @@ or
|
|||||||
$ git clone http://www.gniibe.org/git/gnuk.git/
|
$ git clone http://www.gniibe.org/git/gnuk.git/
|
||||||
|
|
||||||
|
|
||||||
|
Copy is available at: http://gitorious.org/gnuk
|
||||||
|
|
||||||
|
|
||||||
Information on the Web
|
Information on the Web
|
||||||
======================
|
======================
|
||||||
|
|||||||
1
THANKS
1
THANKS
@@ -11,6 +11,7 @@ Hironobu SUZUKI hironobu@h2np.net
|
|||||||
Jan Suhr jan@suhr.info
|
Jan Suhr jan@suhr.info
|
||||||
Kaz Kojima kkojima@rr.iij4u.or.jp
|
Kaz Kojima kkojima@rr.iij4u.or.jp
|
||||||
Ludovic Rousseau ludovic.rousseau@free.fr
|
Ludovic Rousseau ludovic.rousseau@free.fr
|
||||||
|
Luis Felipe R. Murillo luisfelipe@ucla.edu
|
||||||
MATSUU Takuto matsuu@gentoo.org
|
MATSUU Takuto matsuu@gentoo.org
|
||||||
NAGAMI Takeshi nagami-takeshi@aist.go.jp
|
NAGAMI Takeshi nagami-takeshi@aist.go.jp
|
||||||
Shane Coughlan scoughlan@openinventionnetwork.com
|
Shane Coughlan scoughlan@openinventionnetwork.com
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ hwinit1 (void)
|
|||||||
{
|
{
|
||||||
hwinit1_common ();
|
hwinit1_common ();
|
||||||
|
|
||||||
|
#if !defined(DFU_SUPPORT)
|
||||||
|
if (palReadPad (IOPORT3, GPIOC_BUTTON) == 0)
|
||||||
|
/*
|
||||||
|
* Since LEDs are connected to JTMS/SWDIO and JTDI pin,
|
||||||
|
* we can't use LED to let know users in this state.
|
||||||
|
*/
|
||||||
|
for (;;); /* Wait for JTAG debugger connection */
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(PINPAD_SUPPORT) && !defined(DFU_SUPPORT)
|
#if defined(PINPAD_SUPPORT) && !defined(DFU_SUPPORT)
|
||||||
palWritePort(IOPORT2, 0x7fff); /* Only clear GPIOB_7SEG_DP */
|
palWritePort(IOPORT2, 0x7fff); /* Only clear GPIOB_7SEG_DP */
|
||||||
while (palReadPad (IOPORT2, GPIOB_BUTTON) != 0)
|
while (palReadPad (IOPORT2, GPIOB_BUTTON) != 0)
|
||||||
|
|||||||
3
doc/__update_web
Normal file
3
doc/__update_web
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
cd _build
|
||||||
|
rsync -rntpv html/ atom.fsij.org:/home/fsij/gnuk-doc-html/
|
||||||
|
rsync -rtpv html/ atom.fsij.org:/home/fsij/gnuk-doc-html/
|
||||||
@@ -41,7 +41,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Gnuk Documentation'
|
project = u'Gnuk Documentation'
|
||||||
copyright = u'2012, Niibe Yutaka'
|
copyright = u'2012, NIIBE Yutaka'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
@@ -184,7 +184,7 @@ latex_elements = {
|
|||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'GnukDocumentation.tex', u'Gnuk Documentation Documentation',
|
('index', 'GnukDocumentation.tex', u'Gnuk Documentation Documentation',
|
||||||
u'Niibe Yutaka', 'manual'),
|
u'NIIBE Yutaka', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
@@ -214,7 +214,7 @@ latex_documents = [
|
|||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', 'gnukdocumentation', u'Gnuk Documentation Documentation',
|
('index', 'gnukdocumentation', u'Gnuk Documentation Documentation',
|
||||||
[u'Niibe Yutaka'], 1)
|
[u'NIIBE Yutaka'], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
@@ -228,7 +228,7 @@ man_pages = [
|
|||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'GnukDocumentation', u'Gnuk Documentation Documentation',
|
('index', 'GnukDocumentation', u'Gnuk Documentation Documentation',
|
||||||
u'Niibe Yutaka', 'GnukDocumentation', 'One line description of project.',
|
u'NIIBE Yutaka', 'GnukDocumentation', 'One line description of project.',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,27 @@ Development Environment
|
|||||||
Hardware
|
Hardware
|
||||||
--------
|
--------
|
||||||
|
|
||||||
JTAG debugger or SWD debugger is required.
|
For development, it is highly recommended to have JTAG/SWD debugger.
|
||||||
|
|
||||||
|
For boards with DFU (Device Firmware Upgrade) feature (such as DfuSe),
|
||||||
|
it is possible to develop with that. But it should be considered
|
||||||
|
*experimental* environment, and it should not be used for usual
|
||||||
|
purpose. That's because it is basically impossible for DfuSe
|
||||||
|
implementations to disable reading-out from flash ROM. It means
|
||||||
|
that your secret will be readily extracted by DfuSe.
|
||||||
|
|
||||||
|
For JTAG debugger, Olimex JTAG-Tiny is good and supported well. For
|
||||||
|
SWD debugger, ST-Link/V2 would be good, and it is supported by
|
||||||
|
tool/stlinkv2.py.
|
||||||
|
|
||||||
|
|
||||||
|
OpenOCD
|
||||||
|
-------
|
||||||
|
|
||||||
|
For JTAG/SWD debugger, we can use OpenOCD.
|
||||||
|
|
||||||
|
Note that ST-Link/V2 is *not* supported by OpenOCD 0.5.0. It is
|
||||||
|
supported by version 0.6 or later.
|
||||||
|
|
||||||
|
|
||||||
GNU Toolchain
|
GNU Toolchain
|
||||||
@@ -13,9 +33,11 @@ GNU Toolchain
|
|||||||
|
|
||||||
You need GNU toolchain and newlib for 'arm-none-eabi' target.
|
You need GNU toolchain and newlib for 'arm-none-eabi' target.
|
||||||
|
|
||||||
See http://github.com/esden/summon-arm-toolchain/ (which includes fix
|
There is "gcc-arm-embedded" project. See:
|
||||||
of binutils-2.21.1) for preparation of GNU Toolchain for
|
https://launchpad.net/gcc-arm-embedded/
|
||||||
'arm-none-eabi' target. This is for GCC 4.5.
|
|
||||||
|
It is based on GCC 4.6. You'd need "-O3 -Os" instead of "-O2" and it
|
||||||
|
will be slightly better.
|
||||||
|
|
||||||
Note that we need to link correct C library (for string functions).
|
Note that we need to link correct C library (for string functions).
|
||||||
For this purpose, our src/Makefile.in contains following line:
|
For this purpose, our src/Makefile.in contains following line:
|
||||||
@@ -23,20 +45,9 @@ For this purpose, our src/Makefile.in contains following line:
|
|||||||
MCFLAGS= -mcpu=$(MCU) -mfix-cortex-m3-ldrd
|
MCFLAGS= -mcpu=$(MCU) -mfix-cortex-m3-ldrd
|
||||||
|
|
||||||
This should not be needed (as -mcpu=cortex-m3 means
|
This should not be needed (as -mcpu=cortex-m3 means
|
||||||
-mfix-cortex-m3-ldrd), but it is needed for the configuration of
|
-mfix-cortex-m3-ldrd), but it was needed for the configuration of
|
||||||
patch-gcc-config-arm-t-arm-elf.diff in summon-arm-toolchain in practice.
|
patch-gcc-config-arm-t-arm-elf.diff in summon-arm-toolchain in practice.
|
||||||
|
|
||||||
In ChibiOS_2.0.8/os/ports/GCC/ARM/rules.mk, it specifies
|
|
||||||
-mno-thumb-interwork option. This means that you should not link C
|
|
||||||
library which contains ARM (not Thumb) code.
|
|
||||||
|
|
||||||
Recently, there is "gcc-arm-embedded" project. See:
|
|
||||||
https://launchpad.net/gcc-arm-embedded/
|
|
||||||
|
|
||||||
It is based on GCC 4.6. For version 4.6-2012-q2-update, you'd
|
|
||||||
need "-O3 -s" instead of "-O2" and it will be slightly better.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Building Gnuk
|
Building Gnuk
|
||||||
-------------
|
-------------
|
||||||
@@ -50,8 +61,8 @@ Then, run ``configure``:
|
|||||||
$ ./configure --vidpid=<VID:PID>
|
$ ./configure --vidpid=<VID:PID>
|
||||||
|
|
||||||
Here, you need to specify USB vendor ID and product ID. For FSIJ's,
|
Here, you need to specify USB vendor ID and product ID. For FSIJ's,
|
||||||
it's: --vidpid=234b:0000 . Please read section 'USB vendor ID and
|
it's: --vidpid=234b:0000 . Please read the section 'USB vendor ID and
|
||||||
product ID' above.
|
product ID' in README.
|
||||||
|
|
||||||
Type:
|
Type:
|
||||||
|
|
||||||
|
|||||||
311
doc/generating-2048-RSA-key.rst
Normal file
311
doc/generating-2048-RSA-key.rst
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
============================
|
||||||
|
Generating 2048-bit RSA keys
|
||||||
|
============================
|
||||||
|
|
||||||
|
In this section, we describe how to generate 2048-bit RSA keys.
|
||||||
|
|
||||||
|
|
||||||
|
Key length of RSA
|
||||||
|
=================
|
||||||
|
|
||||||
|
In 2005, NIST (National Institute of Standards and Technology, USA)
|
||||||
|
has issued the first revision of NIST Special Publication 800-57,
|
||||||
|
"Recommendation for Key Management".
|
||||||
|
|
||||||
|
In 800-57, NIST advises that 1024-bit RSA keys will no longer be
|
||||||
|
viable after 2010 and advises moving to 2048-bit RSA keys. NIST
|
||||||
|
advises that 2048-bit keys should be viable until 2030.
|
||||||
|
|
||||||
|
As of 2010, GnuPG's default for generating RSA key is 2048-bit.
|
||||||
|
|
||||||
|
Some people have preference on RSA 4096-bit keys, considering
|
||||||
|
"longer is better".
|
||||||
|
|
||||||
|
However, "longer is better" is not always true. When it's long, it
|
||||||
|
requires more computational resource, memory and storage, and it
|
||||||
|
consumes more power for nomal usages. These days, many people has
|
||||||
|
enough computational resource, that would be true, but less is better
|
||||||
|
for power consumption.
|
||||||
|
|
||||||
|
For security, the key length is a single factor. We had and will have
|
||||||
|
algorithm issues, too. It is true that it's difficult to update
|
||||||
|
our public keys, but this problem wouldn't be solved by just have
|
||||||
|
longer keys.
|
||||||
|
|
||||||
|
We deliberately support only RSA 2048-bit keys for Gnuk, considering
|
||||||
|
device computation power and host software constraints.
|
||||||
|
|
||||||
|
Thus, the key size is 2048-bit in the examples below.
|
||||||
|
|
||||||
|
Generating keys on host PC
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Here is the example session to generate main key and a subkey for encryption.
|
||||||
|
|
||||||
|
I invoke GnuPG with ``--gen-key`` option. ::
|
||||||
|
|
||||||
|
$ gpg --gen-key
|
||||||
|
gpg (GnuPG) 1.4.11; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
|
||||||
|
and GnuPG asks kind of key. Select ``RSA and RSA``. ::
|
||||||
|
|
||||||
|
Please select what kind of key you want:
|
||||||
|
(1) RSA and RSA (default)
|
||||||
|
(2) DSA and Elgamal
|
||||||
|
(3) DSA (sign only)
|
||||||
|
(4) RSA (sign only)
|
||||||
|
Your selection? 1
|
||||||
|
RSA keys may be between 1024 and 4096 bits long.
|
||||||
|
|
||||||
|
and select 2048-bit (as Gnuk Token only supports this). ::
|
||||||
|
|
||||||
|
What keysize do you want? (2048)
|
||||||
|
Requested keysize is 2048 bits
|
||||||
|
|
||||||
|
and select expiration of the key. ::
|
||||||
|
|
||||||
|
Please specify how long the key should be valid.
|
||||||
|
0 = key does not expire
|
||||||
|
<n> = key expires in n days
|
||||||
|
<n>w = key expires in n weeks
|
||||||
|
<n>m = key expires in n months
|
||||||
|
<n>y = key expires in n years
|
||||||
|
Key is valid for? (0) 0
|
||||||
|
Key does not expire at all
|
||||||
|
|
||||||
|
Confirm key types, bitsize and expiration. ::
|
||||||
|
|
||||||
|
Is this correct? (y/N) y
|
||||||
|
|
||||||
|
Then enter user ID. ::
|
||||||
|
|
||||||
|
You need a user ID to identify your key; the software constructs the user ID
|
||||||
|
from the Real Name, Comment and Email Address in this form:
|
||||||
|
"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
|
||||||
|
|
||||||
|
Real name: Niibe Yutaka
|
||||||
|
Email address: gniibe@fsij.org
|
||||||
|
Comment:
|
||||||
|
You selected this USER-ID:
|
||||||
|
"Niibe Yutaka <gniibe@fsij.org>"
|
||||||
|
|
||||||
|
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
|
||||||
|
|
||||||
|
and enter passphrase for this **key on host PC**.
|
||||||
|
Note that this is a passphrase for the key on host PC.
|
||||||
|
It is different thing to the password of Gnuk Token.
|
||||||
|
|
||||||
|
We enter two same inputs two times
|
||||||
|
(once for passphrase input, and another for confirmation). ::
|
||||||
|
|
||||||
|
You need a Passphrase to protect your secret key.
|
||||||
|
<PASSWORD-KEY-ON-PC>
|
||||||
|
|
||||||
|
Then, GnuPG generate keys. It takes some time. ::
|
||||||
|
|
||||||
|
We need to generate a lot of random bytes. It is a good idea to perform
|
||||||
|
some other action (type on the keyboard, move the mouse, utilize the
|
||||||
|
disks) during the prime generation; this gives the random number
|
||||||
|
generator a better chance to gain enough entropy.
|
||||||
|
...+++++
|
||||||
|
+++++
|
||||||
|
We need to generate a lot of random bytes. It is a good idea to perform
|
||||||
|
some other action (type on the keyboard, move the mouse, utilize the
|
||||||
|
disks) during the prime generation; this gives the random number
|
||||||
|
generator a better chance to gain enough entropy.
|
||||||
|
..+++++
|
||||||
|
|
||||||
|
Not enough random bytes available. Please do some other work to give
|
||||||
|
the OS a chance to collect more entropy! (Need 15 more bytes)
|
||||||
|
...+++++
|
||||||
|
gpg: key 28C0CD7C marked as ultimately trusted
|
||||||
|
public and secret key created and signed.
|
||||||
|
|
||||||
|
gpg: checking the trustdb
|
||||||
|
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
|
||||||
|
pub 2048R/4CA7BABE 2010-10-15
|
||||||
|
Key fingerprint = 1241 24BD 3B48 62AF 7A0A 42F1 00B4 5EBD 4CA7 BABE
|
||||||
|
uid Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
sub 2048R/084239CF 2010-10-15
|
||||||
|
$
|
||||||
|
|
||||||
|
Done.
|
||||||
|
|
||||||
|
Then, we create authentication subkey.
|
||||||
|
Authentication subkey is not that common,
|
||||||
|
but very useful (for SSH authentication).
|
||||||
|
As it is not that common, we need ``--expert`` option for GnuPG. ::
|
||||||
|
|
||||||
|
$ gpg --expert --edit-key 4CA7BABE
|
||||||
|
gpg (GnuPG) 1.4.11; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
|
||||||
|
Secret key is available.
|
||||||
|
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: ultimate validity: ultimate
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
[ultimate] (1). Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
gpg>
|
||||||
|
|
||||||
|
Here, it displays that there are main key and a subkey.
|
||||||
|
It prompts sub-command with ``gpg>`` .
|
||||||
|
|
||||||
|
Here, we enter ``addkey`` sub-command.
|
||||||
|
Then, we enter the passphrase of **key on host PC**.
|
||||||
|
It's the one we entered above as <PASSWORD-KEY-ON-PC>. ::
|
||||||
|
|
||||||
|
gpg> addkey
|
||||||
|
Key is protected.
|
||||||
|
|
||||||
|
You need a passphrase to unlock the secret key for
|
||||||
|
user: "Niibe Yutaka <gniibe@fsij.org>"
|
||||||
|
2048-bit RSA key, ID 4CA7BABE, created 2010-10-15
|
||||||
|
<PASSWORD-KEY-ON-PC>
|
||||||
|
gpg: gpg-agent is not available in this session
|
||||||
|
|
||||||
|
GnuPG asks kind of key. We select ``RSA (set your own capabilities)``. ::
|
||||||
|
|
||||||
|
Please select what kind of key you want:
|
||||||
|
(3) DSA (sign only)
|
||||||
|
(4) RSA (sign only)
|
||||||
|
(5) Elgamal (encrypt only)
|
||||||
|
(6) RSA (encrypt only)
|
||||||
|
(7) DSA (set your own capabilities)
|
||||||
|
(8) RSA (set your own capabilities)
|
||||||
|
Your selection? 8
|
||||||
|
|
||||||
|
And select ``Authenticate`` for the capabilities for this key.
|
||||||
|
Initially, it's ``Sign`` and ``Encrypt``.
|
||||||
|
I need to deselect ``Sign`` and ``Encrypt``, and select ``Authenticate``.
|
||||||
|
To do that, I enter ``s``, ``e``, and ``a``. ::
|
||||||
|
|
||||||
|
Possible actions for a RSA key: Sign Encrypt Authenticate
|
||||||
|
Current allowed actions: Sign Encrypt
|
||||||
|
|
||||||
|
(S) Toggle the sign capability
|
||||||
|
(E) Toggle the encrypt capability
|
||||||
|
(A) Toggle the authenticate capability
|
||||||
|
(Q) Finished
|
||||||
|
|
||||||
|
Your selection? s
|
||||||
|
|
||||||
|
Possible actions for a RSA key: Sign Encrypt Authenticate
|
||||||
|
Current allowed actions: Encrypt
|
||||||
|
|
||||||
|
(S) Toggle the sign capability
|
||||||
|
(E) Toggle the encrypt capability
|
||||||
|
(A) Toggle the authenticate capability
|
||||||
|
(Q) Finished
|
||||||
|
|
||||||
|
Your selection? e
|
||||||
|
|
||||||
|
Possible actions for a RSA key: Sign Encrypt Authenticate
|
||||||
|
Current allowed actions:
|
||||||
|
|
||||||
|
(S) Toggle the sign capability
|
||||||
|
(E) Toggle the encrypt capability
|
||||||
|
(A) Toggle the authenticate capability
|
||||||
|
(Q) Finished
|
||||||
|
|
||||||
|
Your selection? a
|
||||||
|
|
||||||
|
Possible actions for a RSA key: Sign Encrypt Authenticate
|
||||||
|
Current allowed actions: Authenticate
|
||||||
|
|
||||||
|
(S) Toggle the sign capability
|
||||||
|
(E) Toggle the encrypt capability
|
||||||
|
(A) Toggle the authenticate capability
|
||||||
|
(Q) Finished
|
||||||
|
|
||||||
|
OK, we set the capability of ``Authenticate``.
|
||||||
|
We enter ``q`` to finish setting capabilities. ::
|
||||||
|
|
||||||
|
Your selection? q
|
||||||
|
|
||||||
|
GnuPG asks bitsize and expiration, we enter 2048 for bitsize and no expiration.
|
||||||
|
Then, we confirm that we really create the key. ::
|
||||||
|
|
||||||
|
RSA keys may be between 1024 and 4096 bits long.
|
||||||
|
What keysize do you want? (2048)
|
||||||
|
Requested keysize is 2048 bits
|
||||||
|
Please specify how long the key should be valid.
|
||||||
|
0 = key does not expire
|
||||||
|
<n> = key expires in n days
|
||||||
|
<n>w = key expires in n weeks
|
||||||
|
<n>m = key expires in n months
|
||||||
|
<n>y = key expires in n years
|
||||||
|
Key is valid for? (0) 0
|
||||||
|
Key does not expire at all
|
||||||
|
Is this correct? (y/N) y
|
||||||
|
Really create? (y/N) y
|
||||||
|
|
||||||
|
Then, GnuPG generate the key. ::
|
||||||
|
|
||||||
|
We need to generate a lot of random bytes. It is a good idea to perform
|
||||||
|
some other action (type on the keyboard, move the mouse, utilize the
|
||||||
|
disks) during the prime generation; this gives the random number
|
||||||
|
generator a better chance to gain enough entropy.
|
||||||
|
.......+++++
|
||||||
|
+++++
|
||||||
|
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: ultimate validity: ultimate
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
sub 2048R/5BB065DC created: 2010-10-22 expires: never usage: A
|
||||||
|
[ultimate] (1). Niibe Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
gpg>
|
||||||
|
|
||||||
|
We save the key (to the storage of the host PC. ::
|
||||||
|
|
||||||
|
gpg> save
|
||||||
|
$
|
||||||
|
|
||||||
|
Now, we have three keys (one primary key for signature and certification,
|
||||||
|
subkey for encryption, and another subkey for authentication).
|
||||||
|
|
||||||
|
|
||||||
|
Publishing public key
|
||||||
|
=====================
|
||||||
|
|
||||||
|
We make a file for the public key by ``--export`` option of GnuPG. ::
|
||||||
|
|
||||||
|
$ gpg --armor --output <YOUR-KEY>.asc --export <YOUR-KEY-ID>
|
||||||
|
|
||||||
|
We can publish the file by web server. Or we can publish the key
|
||||||
|
to a keyserver, by invoking GnuPG with ``--send-keys`` option. ::
|
||||||
|
|
||||||
|
$ gpg --keyserver pool.sks-keyservers.net --send-keys <YOUR-KEY-ID>
|
||||||
|
|
||||||
|
Here, pool.sks-keyservers.net is a keyserver, which is widely used.
|
||||||
|
|
||||||
|
|
||||||
|
Backup the private key
|
||||||
|
======================
|
||||||
|
|
||||||
|
There are some ways to back up private key, such that backup .gnupg
|
||||||
|
directory entirely, use of paperkey. Here we describe backup by ASCII
|
||||||
|
file. ASCII file is good, because it has less risk on transfer.
|
||||||
|
Binary file has a risk to be modified on transfer.
|
||||||
|
|
||||||
|
Note that the key on host PC is protected by passphrase (which
|
||||||
|
is <PASSWORD-KEY-ON-PC> in the example above). Using the key
|
||||||
|
from the backup needs this passphrase. It is common that
|
||||||
|
people will forget passphrase for backup. Never forget it.
|
||||||
|
You have been warned.
|
||||||
|
|
||||||
|
To make ASCII backup for private key,
|
||||||
|
invokde GnuPG with ``--armor`` option and ``--export-secret-keys``
|
||||||
|
specifying the key identifier. ::
|
||||||
|
|
||||||
|
$ gpg --armor --output <YOUR-SECRET>.asc --export-secret-keys <YOUR-KEY-ID>
|
||||||
|
|
||||||
|
From the backup,
|
||||||
|
we can recover privet key by invoking GnuPG with ``--import`` option. ::
|
||||||
|
|
||||||
|
$ gpg --import <YOUR-SECRET>.asc
|
||||||
38
doc/gnome3-gpg-settings.rst
Normal file
38
doc/gnome3-gpg-settings.rst
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
==========================
|
||||||
|
GnuPG settings for GNOME 3
|
||||||
|
==========================
|
||||||
|
|
||||||
|
In the article `GnuPG settings`_, I wrote how I disable GNOME-keyrings for SSH.
|
||||||
|
|
||||||
|
It was for GNOME 2. The old days was good, we just disabled GNOME-keyrings
|
||||||
|
interference to SSH and customizing our desktop was easy for GNU and UNIX users.
|
||||||
|
|
||||||
|
.. _GnuPG settings: gpg-settings
|
||||||
|
|
||||||
|
|
||||||
|
GNOME keyrings in GNOME 3
|
||||||
|
=========================
|
||||||
|
|
||||||
|
It seems that it is more integrated into the desktop.
|
||||||
|
It is difficult to kill it. It would be possible to kill it simply,
|
||||||
|
but then, I can't use, say, wi-fi access (which needs to access "secrets")
|
||||||
|
any more.
|
||||||
|
|
||||||
|
We can't use GNOME configuration tool to disable interference by
|
||||||
|
GNOME keyrings any more. It seems that desktop should not have
|
||||||
|
customization these days.
|
||||||
|
|
||||||
|
|
||||||
|
GNOME-SESSION-PROPERTIES
|
||||||
|
========================
|
||||||
|
|
||||||
|
After struggling some hours, I figured out it is GNOME-SESSION-PROPERTIES
|
||||||
|
to disable the interference. Invoking::
|
||||||
|
|
||||||
|
$ gnome-session-properties
|
||||||
|
|
||||||
|
and at the tab of "Startup Programs", I removed radio check buttons
|
||||||
|
for "GPG Password Agent" and "SSH Key Agent".
|
||||||
|
|
||||||
|
|
||||||
|
Now, I use gpg-agent for GnuPG Agent and SSH agent with Gnuk Token.
|
||||||
183
doc/gnuk-keytocard-noremoval.rst
Normal file
183
doc/gnuk-keytocard-noremoval.rst
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
=============================================
|
||||||
|
Key import from PC to Gnuk Token (no removal)
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
This document describes how I put my **keys on PC** to the Token
|
||||||
|
without removing keys from PC.
|
||||||
|
|
||||||
|
The difference is just not-to-save changes after key imports.
|
||||||
|
|
||||||
|
After personalization, I put my keys into the Token.
|
||||||
|
|
||||||
|
Here is the log.
|
||||||
|
|
||||||
|
I invoke GnuPG with my key (4ca7babe) and with ``--homedir`` option
|
||||||
|
to specify the directory which contains my secret keys. ::
|
||||||
|
|
||||||
|
$ gpg --homedir=/home/gniibe/tmp/gnuk-testing-dir --edit-key 4ca7babe
|
||||||
|
gpg (GnuPG) 1.4.11; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
|
||||||
|
Secret key is available.
|
||||||
|
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: ultimate validity: ultimate
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
sub 2048R/5BB065DC created: 2010-10-22 expires: never usage: A
|
||||||
|
[ultimate] (1). NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
|
||||||
|
Then, GnuPG enters its own command interaction mode. The prompt is ``gpg>``.
|
||||||
|
To enable ``keytocard`` command, I type ``toggle`` command. ::
|
||||||
|
|
||||||
|
gpg> toggle
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
Firstly, I import my primary key into Gnuk Token.
|
||||||
|
I type ``keytocard`` command, answer ``y`` to confirm keyimport,
|
||||||
|
and type ``1`` to say it's signature key. ::
|
||||||
|
|
||||||
|
gpg> keytocard
|
||||||
|
Really move the primary key? (y/N) y
|
||||||
|
gpg: detected reader `FSIJ Gnuk (0.12-38FF6A06) 00 00'
|
||||||
|
Signature key ....: [none]
|
||||||
|
Encryption key....: [none]
|
||||||
|
Authentication key: [none]
|
||||||
|
|
||||||
|
Please select where to store the key:
|
||||||
|
(1) Signature key
|
||||||
|
(3) Authentication key
|
||||||
|
Your selection? 1
|
||||||
|
|
||||||
|
Then, GnuPG asks two passwords. One is the passphrase of **keys on PC**
|
||||||
|
and another is the password of **Gnuk Token**. Note that the password of
|
||||||
|
the token and the password of the keys on PC are different things,
|
||||||
|
although they can be same.
|
||||||
|
|
||||||
|
I enter these passwords. ::
|
||||||
|
|
||||||
|
You need a passphrase to unlock the secret key for
|
||||||
|
user: "NIIBE Yutaka <gniibe@fsij.org>"
|
||||||
|
2048-bit RSA key, ID 4CA7BABE, created 2010-10-15
|
||||||
|
<PASSWORD-KEY-4CA7BABE>
|
||||||
|
gpg: writing new key
|
||||||
|
gpg: 3 Admin PIN attempts remaining before card is permanently locked
|
||||||
|
|
||||||
|
Please enter the Admin PIN
|
||||||
|
Enter Admin PIN: <PASSWORD-GNUK>
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
The primary key is now on the Token and GnuPG says its card-no (F517 00000001),
|
||||||
|
where F517 is the vendor ID of FSIJ.
|
||||||
|
|
||||||
|
Secondly, I import my subkey of encryption. I select key number '1'. ::
|
||||||
|
|
||||||
|
gpg> key 1
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
You can see that the subkey is marked by '*'.
|
||||||
|
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||||
|
I select ``2`` as it's encryption key. ::
|
||||||
|
|
||||||
|
gpg> keytocard
|
||||||
|
Signature key ....: [none]
|
||||||
|
Encryption key....: [none]
|
||||||
|
Authentication key: [none]
|
||||||
|
|
||||||
|
Please select where to store the key:
|
||||||
|
(2) Encryption key
|
||||||
|
Your selection? 2
|
||||||
|
|
||||||
|
Then, GnuPG asks the passphrase of **keys on PC** again. I enter. ::
|
||||||
|
|
||||||
|
You need a passphrase to unlock the secret key for
|
||||||
|
user: "NIIBE Yutaka <gniibe@fsij.org>"
|
||||||
|
2048-bit RSA key, ID 084239CF, created 2010-10-15
|
||||||
|
<PASSWORD-KEY-4CA7BABE>
|
||||||
|
gpg: writing new key
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
The sub key is now on the Token and GnuPG says its card-no for it.
|
||||||
|
|
||||||
|
I type ``key 1`` to deselect key number '1'. ::
|
||||||
|
|
||||||
|
gpg> key 1
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
Thirdly, I select sub key of authentication which has key number '2'. ::
|
||||||
|
|
||||||
|
gpg> key 2
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
You can see that the subkey number '2' is marked by '*'.
|
||||||
|
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||||
|
I select ``3`` as it's authentication key. ::
|
||||||
|
|
||||||
|
gpg> keytocard
|
||||||
|
Signature key ....: [none]
|
||||||
|
Encryption key....: [none]
|
||||||
|
Authentication key: [none]
|
||||||
|
|
||||||
|
Please select where to store the key:
|
||||||
|
(3) Authentication key
|
||||||
|
Your selection? 3
|
||||||
|
|
||||||
|
Then, GnuPG asks the passphrase of **keys on PC** again. I enter. ::
|
||||||
|
|
||||||
|
You need a passphrase to unlock the secret key for
|
||||||
|
user: "NIIBE Yutaka <gniibe@fsij.org>"
|
||||||
|
2048-bit RSA key, ID 5BB065DC, created 2010-10-22
|
||||||
|
<PASSWORD-KEY-4CA7BABE>
|
||||||
|
gpg: writing new key
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
The sub key is now on the Token and GnuPG says its card-no for it.
|
||||||
|
|
||||||
|
Lastly, I quit GnuPG. Note that I **don't** save changes. ::
|
||||||
|
|
||||||
|
gpg> quit
|
||||||
|
Save changes? (y/N) n
|
||||||
|
Quit without saving? (y/N) y
|
||||||
|
$
|
||||||
|
|
||||||
|
All keys are imported to Gnuk Token now.
|
||||||
193
doc/gnuk-keytocard.rst
Normal file
193
doc/gnuk-keytocard.rst
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
================================
|
||||||
|
Key import from PC to Gnuk Token
|
||||||
|
================================
|
||||||
|
|
||||||
|
This document describes how I put my **keys on PC** to the Token,
|
||||||
|
and remove keys from PC.
|
||||||
|
|
||||||
|
Note that there is **no ways** to export keys from the Token,
|
||||||
|
so please be careful.
|
||||||
|
|
||||||
|
|
||||||
|
If you want to import same keys to multiple Tokens,
|
||||||
|
please copy ``.gnupg`` directory beforehand.
|
||||||
|
|
||||||
|
In my case, I do something like following: ::
|
||||||
|
|
||||||
|
$ cp -a .gnupg tmp/gnuk-testing-dir
|
||||||
|
|
||||||
|
See `another document`_ to import keys to the Token from copied directory.
|
||||||
|
|
||||||
|
.. _another document: gnuk-keytocard-noremoval
|
||||||
|
|
||||||
|
After personalization, I put my keys into the Token.
|
||||||
|
|
||||||
|
Here is the log.
|
||||||
|
|
||||||
|
I invoke GnuPG with my key (4ca7babe). ::
|
||||||
|
|
||||||
|
$ gpg --edit-key 4ca7babe
|
||||||
|
gpg (GnuPG) 1.4.11; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
|
||||||
|
Secret key is available.
|
||||||
|
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: ultimate validity: ultimate
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
sub 2048R/5BB065DC created: 2010-10-22 expires: never usage: A
|
||||||
|
[ultimate] (1). NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
|
||||||
|
Then, GnuPG enters its own command interaction mode. The prompt is ``gpg>``.
|
||||||
|
To enable ``keytocard`` command, I type ``toggle`` command. ::
|
||||||
|
|
||||||
|
gpg> toggle
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
Firstly, I import my primary key into Gnuk Token.
|
||||||
|
I type ``keytocard`` command, answer ``y`` to confirm keyimport,
|
||||||
|
and type ``1`` to say it's signature key. ::
|
||||||
|
|
||||||
|
gpg> keytocard
|
||||||
|
Really move the primary key? (y/N) y
|
||||||
|
gpg: detected reader `FSIJ Gnuk (0.12-38FF6A06) 00 00'
|
||||||
|
Signature key ....: [none]
|
||||||
|
Encryption key....: [none]
|
||||||
|
Authentication key: [none]
|
||||||
|
|
||||||
|
Please select where to store the key:
|
||||||
|
(1) Signature key
|
||||||
|
(3) Authentication key
|
||||||
|
Your selection? 1
|
||||||
|
|
||||||
|
Then, GnuPG asks two passwords. One is the passphrase of **keys on PC**
|
||||||
|
and another is the password of **Gnuk Token**. Note that the password of
|
||||||
|
the token and the password of the keys on PC are different things,
|
||||||
|
although they can be same.
|
||||||
|
|
||||||
|
I enter these passwords. ::
|
||||||
|
|
||||||
|
You need a passphrase to unlock the secret key for
|
||||||
|
user: "NIIBE Yutaka <gniibe@fsij.org>"
|
||||||
|
2048-bit RSA key, ID 4CA7BABE, created 2010-10-15
|
||||||
|
<PASSWORD-KEY-4CA7BABE>
|
||||||
|
gpg: writing new key
|
||||||
|
gpg: 3 Admin PIN attempts remaining before card is permanently locked
|
||||||
|
|
||||||
|
Please enter the Admin PIN
|
||||||
|
Enter Admin PIN: <PASSWORD-GNUK>
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
The primary key is now on the Token and GnuPG says its card-no (F517 00000001) , where F517 is the vendor ID of FSIJ.
|
||||||
|
|
||||||
|
Secondly, I import my subkey of encryption. I select key number '1'. ::
|
||||||
|
|
||||||
|
gpg> key 1
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
You can see that the subkey is marked by '*'.
|
||||||
|
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||||
|
I select ``2`` as it's encryption key. ::
|
||||||
|
|
||||||
|
gpg> keytocard
|
||||||
|
Signature key ....: [none]
|
||||||
|
Encryption key....: [none]
|
||||||
|
Authentication key: [none]
|
||||||
|
|
||||||
|
Please select where to store the key:
|
||||||
|
(2) Encryption key
|
||||||
|
Your selection? 2
|
||||||
|
|
||||||
|
Then, GnuPG asks the passphrase of **keys on PC** again. I enter. ::
|
||||||
|
|
||||||
|
You need a passphrase to unlock the secret key for
|
||||||
|
user: "NIIBE Yutaka <gniibe@fsij.org>"
|
||||||
|
2048-bit RSA key, ID 084239CF, created 2010-10-15
|
||||||
|
<PASSWORD-KEY-4CA7BABE>
|
||||||
|
gpg: writing new key
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
The sub key is now on the Token and GnuPG says its card-no for it.
|
||||||
|
|
||||||
|
I type ``key 1`` to deselect key number '1'. ::
|
||||||
|
|
||||||
|
gpg> key 1
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
Thirdly, I select sub key of authentication which has key number '2'. ::
|
||||||
|
|
||||||
|
gpg> key 2
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
You can see that the subkey number '2' is marked by '*'.
|
||||||
|
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||||
|
I select ``3`` as it's authentication key. ::
|
||||||
|
|
||||||
|
gpg> keytocard
|
||||||
|
Signature key ....: [none]
|
||||||
|
Encryption key....: [none]
|
||||||
|
Authentication key: [none]
|
||||||
|
|
||||||
|
Please select where to store the key:
|
||||||
|
(3) Authentication key
|
||||||
|
Your selection? 3
|
||||||
|
|
||||||
|
Then, GnuPG asks the passphrase of **keys on PC** again. I enter. ::
|
||||||
|
|
||||||
|
You need a passphrase to unlock the secret key for
|
||||||
|
user: "NIIBE Yutaka <gniibe@fsij.org>"
|
||||||
|
2048-bit RSA key, ID 5BB065DC, created 2010-10-22
|
||||||
|
<PASSWORD-KEY-4CA7BABE>
|
||||||
|
gpg: writing new key
|
||||||
|
|
||||||
|
sec 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb* 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
|
The sub key is now on the Token and GnuPG says its card-no for it.
|
||||||
|
|
||||||
|
Lastly, I save changes of **keys on PC** and quit GnuPG. ::
|
||||||
|
|
||||||
|
gpg> save
|
||||||
|
$
|
||||||
|
|
||||||
|
All secret keys are imported to Gnuk Token now.
|
||||||
|
On PC, only references (card-no) to the Token remain.
|
||||||
153
doc/gnuk-personalization.rst
Normal file
153
doc/gnuk-personalization.rst
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
=============================
|
||||||
|
Personalization of Gnuk Token
|
||||||
|
=============================
|
||||||
|
|
||||||
|
|
||||||
|
Personalize your Gnuk Token
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Invoke GnuPG with the option ``--card-edit``. ::
|
||||||
|
|
||||||
|
$ gpg --card-edit
|
||||||
|
gpg: detected reader `FSIJ Gnuk (0.12-34006E06) 00 00'
|
||||||
|
Application ID ...: D276000124010200F517000000010000
|
||||||
|
Version ..........: 2.0
|
||||||
|
Manufacturer .....: FSIJ
|
||||||
|
Serial number ....: 00000001
|
||||||
|
Name of cardholder: [not set]
|
||||||
|
Language prefs ...: [not set]
|
||||||
|
Sex ..............: unspecified
|
||||||
|
URL of public key : [not set]
|
||||||
|
Login data .......: [not set]
|
||||||
|
Signature PIN ....: forced
|
||||||
|
Key attributes ...: 2048R 2048R 2048R
|
||||||
|
Max. PIN lengths .: 127 127 127
|
||||||
|
PIN retry counter : 3 3 3
|
||||||
|
Signature counter : 0
|
||||||
|
Signature key ....: [none]
|
||||||
|
Encryption key....: [none]
|
||||||
|
Authentication key: [none]
|
||||||
|
General key info..: [none]
|
||||||
|
|
||||||
|
It shows the status of the card (as same as the output of ``gpg --card-status``). It shows token's name and its USB serial string (0.12-34006E06) from PC/SC-lite.
|
||||||
|
|
||||||
|
Then, GnuPG enters its own command interaction mode. The prompt is ``gpg/card>``.
|
||||||
|
|
||||||
|
In the OpenPGPcard specification, there are two passwords: one is
|
||||||
|
user-password and another is admin-password. In the specification,
|
||||||
|
user-password is refered as PW1, and admin-password is refered as PW3.
|
||||||
|
|
||||||
|
Note that people sometimes use different words than "password" to
|
||||||
|
refer same thing, in GnuPG and its applications. For example, the
|
||||||
|
output explained above includes the word "PIN" (Personal
|
||||||
|
Identification Number), and the helper program for input is named
|
||||||
|
"pinentry". Note that it is OK (and recommended) to include
|
||||||
|
characters other than digits for the case of OpenPGPcard.
|
||||||
|
|
||||||
|
Besides, some people sometimes prefer the word "passphrase" to
|
||||||
|
"password", as it can encourage to have longer string, but it means
|
||||||
|
same thing and it just refer user-password or admin-password.
|
||||||
|
|
||||||
|
Firstly, I change PIN of card user from factory setting (of "123456").
|
||||||
|
Note that, by only changing user's PIN, it enables "admin less mode" of Gnuk.
|
||||||
|
"Admin less mode" means that admin password will become same one of user's.
|
||||||
|
That is, PW1 = PW3.
|
||||||
|
Note that *the length of PIN should be more than (or equals to) 8* for
|
||||||
|
"admin less mode". ::
|
||||||
|
|
||||||
|
gpg/card> passwd
|
||||||
|
gpg: OpenPGP card no. D276000124010200F517000000010000 detected
|
||||||
|
|
||||||
|
Please enter the PIN
|
||||||
|
Enter PIN: 123456
|
||||||
|
|
||||||
|
New PIN
|
||||||
|
Enter New PIN: <PASSWORD-OF-GNUK>
|
||||||
|
|
||||||
|
New PIN
|
||||||
|
Repeat this PIN: <PASSWORD-OF-GNUK>
|
||||||
|
PIN changed.
|
||||||
|
|
||||||
|
The "admin less mode" is Gnuk only feature, not defined in the
|
||||||
|
OpenPGPcard specification. By using "admin less mode", it will be
|
||||||
|
only a sigle password for user to memorize, and it will be easier if a token
|
||||||
|
is used by an individual.
|
||||||
|
|
||||||
|
(If you want normal way ("admin full mode" in Gnuk's term),
|
||||||
|
that is, user-password *and* admin-password independently,
|
||||||
|
please change admin-password at first.
|
||||||
|
Then, the token works as same as OpenPGPcard specification
|
||||||
|
with regards to PW1 and PW3.)
|
||||||
|
|
||||||
|
Secondly, enabling admin command, I put name of mine.
|
||||||
|
Note that I input user's PIN (which I set above) here,
|
||||||
|
because it is "admin less mode". ::
|
||||||
|
|
||||||
|
gpg/card> admin
|
||||||
|
Admin commands are allowed
|
||||||
|
|
||||||
|
gpg/card> name
|
||||||
|
Cardholder's surname: Niibe
|
||||||
|
Cardholder's given name: Yutaka
|
||||||
|
gpg: 3 Admin PIN attempts remaining before card is permanently locked
|
||||||
|
|
||||||
|
Please enter the Admin PIN
|
||||||
|
Enter Admin PIN: <PASSWORD-OF-GNUK>
|
||||||
|
|
||||||
|
Thirdly, I put some other informations, such as language, sex,
|
||||||
|
login, and URL. URL specifies the place where I put my public keys. ::
|
||||||
|
|
||||||
|
gpg/card> lang
|
||||||
|
Language preferences: ja
|
||||||
|
|
||||||
|
gpg/card> sex
|
||||||
|
Sex ((M)ale, (F)emale or space): m
|
||||||
|
|
||||||
|
gpg/card> url
|
||||||
|
URL to retrieve public key: http://www.gniibe.org/gniibe.asc
|
||||||
|
|
||||||
|
gpg/card> login
|
||||||
|
Login data (account name): gniibe
|
||||||
|
|
||||||
|
Since I don't force PIN input everytime,
|
||||||
|
toggle it to non-force-pin-for-signature. ::
|
||||||
|
|
||||||
|
gpg/card> forcesig
|
||||||
|
|
||||||
|
Lastly, I setup reset code. This is optional. ::
|
||||||
|
|
||||||
|
gpg/card> passwd
|
||||||
|
gpg: OpenPGP card no. D276000124010200F517000000010000 detected
|
||||||
|
|
||||||
|
1 - change PIN
|
||||||
|
2 - unblock PIN
|
||||||
|
3 - change Admin PIN
|
||||||
|
4 - set the Reset Code
|
||||||
|
Q - quit
|
||||||
|
|
||||||
|
Your selection? 4
|
||||||
|
gpg: 3 Admin PIN attempts remaining before card is permanently locked
|
||||||
|
|
||||||
|
Please enter the Admin PIN
|
||||||
|
Enter Admin PIN: <PASSWORD-OF-GNUK>
|
||||||
|
|
||||||
|
New Reset Code
|
||||||
|
Enter New PIN: <RESETCODE-OF-GNUK>
|
||||||
|
|
||||||
|
New Reset Code
|
||||||
|
Repeat this PIN: <RESETCODE-OF-GNUK>
|
||||||
|
Reset Code set.
|
||||||
|
|
||||||
|
1 - change PIN
|
||||||
|
2 - unblock PIN
|
||||||
|
3 - change Admin PIN
|
||||||
|
4 - set the Reset Code
|
||||||
|
Q - quit
|
||||||
|
|
||||||
|
Your selection? q
|
||||||
|
|
||||||
|
Then, I quit. ::
|
||||||
|
|
||||||
|
gpg/card> quit
|
||||||
|
|
||||||
|
That's all.
|
||||||
43
doc/gnuk-token-initial-configuration.rst
Normal file
43
doc/gnuk-token-initial-configuration.rst
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
===================================
|
||||||
|
Initial Configuration of Gnuk Token
|
||||||
|
===================================
|
||||||
|
|
||||||
|
This is optional. You don't need to setup the serial number of Gnuk Token,
|
||||||
|
as it comes with its default serial number based on MCU's chip ID.
|
||||||
|
|
||||||
|
You can setup the serial number of Gnuk Token only once.
|
||||||
|
|
||||||
|
|
||||||
|
Conditions
|
||||||
|
==========
|
||||||
|
|
||||||
|
I assume you are using GNU/Linux.
|
||||||
|
|
||||||
|
|
||||||
|
Preparation
|
||||||
|
===========
|
||||||
|
|
||||||
|
Make sure there is no ``scdaemon`` for configuring Gnuk Token. You can kill ``scdaemon`` by: ::
|
||||||
|
|
||||||
|
$ gpg-connect-agent "SCD KILLSCD" "SCD BYE" /bye
|
||||||
|
|
||||||
|
|
||||||
|
Serial Number (optional)
|
||||||
|
========================
|
||||||
|
|
||||||
|
In the file ``GNUK_SERIAL_NUMBER``, each line has email and 6-byte serial number. The first two bytes are organization number (F5:17 is for FSIJ). Last four bytes are number for tokens.
|
||||||
|
|
||||||
|
The tool ``../tool/gnuk_put_binary_libusb.py`` examines environment variable of ``EMAIL``, and writes corresponding serial number to Gnuk Token. ::
|
||||||
|
|
||||||
|
$ ../tool/gnuk_put_binary_libusb.py -s ../GNUK_SERIAL_NUMBER
|
||||||
|
Writing serial number
|
||||||
|
Device: 006
|
||||||
|
Configuration: 1
|
||||||
|
Interface: 0
|
||||||
|
d2 76 00 01 24 01 02 00 f5 17 00 00 00 01 00 00
|
||||||
|
|
||||||
|
|
||||||
|
The example above is the case of libusb version.
|
||||||
|
|
||||||
|
Use the tool ``../tool/gnuk_put_binary.py`` instead , for PC/SC Lite.
|
||||||
|
You need PyScard for this.
|
||||||
43
doc/gpg-settings.rst
Normal file
43
doc/gpg-settings.rst
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
.. -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
==============
|
||||||
|
GnuPG settings
|
||||||
|
==============
|
||||||
|
|
||||||
|
Here is my GnuPG settings.
|
||||||
|
|
||||||
|
.gnupg/gpg.conf
|
||||||
|
===============
|
||||||
|
|
||||||
|
I create ``.gnupg/gpg.conf`` file with the following content. ::
|
||||||
|
|
||||||
|
use-agent
|
||||||
|
personal-digest-preferences SHA256
|
||||||
|
cert-digest-algo SHA256
|
||||||
|
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
|
||||||
|
|
||||||
|
default-key 0x4ca7babe
|
||||||
|
|
||||||
|
|
||||||
|
Let gpg-agent manage SSH key
|
||||||
|
============================
|
||||||
|
|
||||||
|
I deactivate seahose-agent. Also, for GNOME 2, I deactivate gnome-keyring managing SSH key. ::
|
||||||
|
|
||||||
|
$ gconftool-2 --type bool --set /apps/gnome-keyring/daemon-components/ssh false
|
||||||
|
|
||||||
|
I edit the file /etc/X11/Xsession.options and comment out use-ssh-agent line.
|
||||||
|
|
||||||
|
Then, I create ``.gnupg/gpg-agent.conf`` file with the following content. ::
|
||||||
|
|
||||||
|
enable-ssh-support
|
||||||
|
|
||||||
|
|
||||||
|
References
|
||||||
|
==========
|
||||||
|
|
||||||
|
* `Creating a new GPG key`_
|
||||||
|
* `Use OpenPGP Keys for OpenSSH, how to use gpg with ssh`_
|
||||||
|
|
||||||
|
.. _Creating a new GPG key: http://keyring.debian.org/creating-key.html
|
||||||
|
.. _Use OpenPGP Keys for OpenSSH, how to use gpg with ssh: http://www.programmierecke.net/howto/gpg-ssh.html
|
||||||
BIN
doc/images/gnuk-sticker.png
Normal file
BIN
doc/images/gnuk-sticker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -2,6 +2,9 @@
|
|||||||
sphinx-quickstart on Wed Jul 4 15:29:05 2012.
|
sphinx-quickstart on Wed Jul 4 15:29:05 2012.
|
||||||
You can adapt this file completely to your liking, but it should at least
|
You can adapt this file completely to your liking, but it should at least
|
||||||
contain the root `toctree` directive.
|
contain the root `toctree` directive.
|
||||||
|
Copyright (C) 2012 NIIBE Yutaka
|
||||||
|
Copyright (C) 2012 Free Software Initiative of Japan
|
||||||
|
This document is licensed under a CC-BY-SA 3.0 Unported License
|
||||||
|
|
||||||
Gnuk Documentation
|
Gnuk Documentation
|
||||||
==================
|
==================
|
||||||
@@ -13,6 +16,16 @@ Contents:
|
|||||||
|
|
||||||
intro.rst
|
intro.rst
|
||||||
development.rst
|
development.rst
|
||||||
|
stop-scdaemon.rst
|
||||||
|
udev-rules.rst
|
||||||
|
gnuk-token-initial-configuration.rst
|
||||||
|
gnuk-personalization.rst
|
||||||
|
generating-2048-RSA-key.rst
|
||||||
|
gnuk-keytocard.rst
|
||||||
|
gnuk-keytocard-noremoval.rst
|
||||||
|
using-gnuk-token-with-another-computer.rst
|
||||||
|
gpg-settings.rst
|
||||||
|
gnome3-gpg-settings.rst
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
|
|||||||
@@ -13,17 +13,41 @@ STM32F103 processor.
|
|||||||
Cryptographic token and feature of Gnuk
|
Cryptographic token and feature of Gnuk
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
Cryptographic token is a store of private keys and it computes cryptographic functions on the device.
|
Cryptographic token is a store of private keys and it computes cryptographic
|
||||||
|
functions on the device.
|
||||||
|
|
||||||
|
The idea is to separate important secrets to independent device,
|
||||||
|
from where nobody can extract them.
|
||||||
|
|
||||||
|
|
||||||
Development Environment
|
Development Environment
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
See :doc:`development` for development environment for Gnuk. It builds on Free Software.
|
See :doc:`development` for development environment for Gnuk.
|
||||||
|
Gnuk is developed on the environment where there are only Free Software.
|
||||||
|
|
||||||
|
|
||||||
Prerequisites
|
Target boards for running Gnuk
|
||||||
-------------
|
------------------------------
|
||||||
|
|
||||||
|
Hardware requirement for Gnuk is the micro controller STM32F103.
|
||||||
|
In version 1.0, Gnuk supports following boards.
|
||||||
|
|
||||||
|
* FST-01 (Flying Stone Tiny ZERO-ONE)
|
||||||
|
|
||||||
|
* Olimex STM32-H103
|
||||||
|
|
||||||
|
* CQ STARM
|
||||||
|
|
||||||
|
* STBee
|
||||||
|
|
||||||
|
* STBee Mini
|
||||||
|
|
||||||
|
* STM32 part of STM8S Discovery Kit
|
||||||
|
|
||||||
|
|
||||||
|
Host prerequisites for using Gnuk Token
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
* GNU Privacy Guard (GnuPG)
|
* GNU Privacy Guard (GnuPG)
|
||||||
|
|
||||||
@@ -36,8 +60,8 @@ Prerequisites
|
|||||||
* Web: scute, firefox
|
* Web: scute, firefox
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usages
|
||||||
-----
|
------
|
||||||
|
|
||||||
* Sign with GnuPG
|
* Sign with GnuPG
|
||||||
* Decrypt with GnuPG
|
* Decrypt with GnuPG
|
||||||
|
|||||||
@@ -38,14 +38,12 @@ We can examine key information of gpg-agent by "KEYINFO" command.
|
|||||||
Here is my example::
|
Here is my example::
|
||||||
|
|
||||||
$ gpg-connect-agent "KEYINFO --list" /bye
|
$ gpg-connect-agent "KEYINFO --list" /bye
|
||||||
S KEYINFO 4970A0D537CA2EF7CE6A106E47AD89B0EFB684C8 D - - - - -
|
|
||||||
S KEYINFO 65F67E742101C7FE6D5B33FCEFCF4F65EAF0688C T D276000124010200F517000000010000 OPENPGP.2 - - -
|
S KEYINFO 65F67E742101C7FE6D5B33FCEFCF4F65EAF0688C T D276000124010200F517000000010000 OPENPGP.2 - - -
|
||||||
S KEYINFO 5D6C89682D07CCFC034AF508420BF2276D8018ED T D276000124010200F517000000010000 OPENPGP.3 - - -
|
|
||||||
S KEYINFO 7D180C0C2A991B25204110A92F5F92A5A509845B D - - - - -
|
|
||||||
S KEYINFO 101DE7B639FE29F4636BDEECF442A9273AFA6565 T D276000124010200F517000000010000 OPENPGP.1 - - -
|
S KEYINFO 101DE7B639FE29F4636BDEECF442A9273AFA6565 T D276000124010200F517000000010000 OPENPGP.1 - - -
|
||||||
|
S KEYINFO 5D6C89682D07CCFC034AF508420BF2276D8018ED T D276000124010200F517000000010000 OPENPGP.3 - - -
|
||||||
OK
|
OK
|
||||||
|
|
||||||
I have two local keys (in my PC) and three keys in my token.
|
I have three keys in my token.
|
||||||
|
|
||||||
With the script below, I extract public key of the keygrip
|
With the script below, I extract public key of the keygrip
|
||||||
5D6C89682D07CCFC034AF508420BF2276D8018ED into the file: 5D6C8968.bin::
|
5D6C89682D07CCFC034AF508420BF2276D8018ED into the file: 5D6C8968.bin::
|
||||||
@@ -63,7 +61,7 @@ Here is the script, get_public_key.py::
|
|||||||
result = check_output(["gpg-connect-agent", "READKEY %s" % keygrip, "/bye"])
|
result = check_output(["gpg-connect-agent", "READKEY %s" % keygrip, "/bye"])
|
||||||
key = ""
|
key = ""
|
||||||
while True:
|
while True:
|
||||||
i = result.find('%')
|
i = result.find('%')
|
||||||
if i < 0:
|
if i < 0:
|
||||||
key += result
|
key += result
|
||||||
break
|
break
|
||||||
|
|||||||
37
doc/stop-scdaemon.rst
Normal file
37
doc/stop-scdaemon.rst
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
===========================
|
||||||
|
Stopping/Resetting SCDAEMON
|
||||||
|
===========================
|
||||||
|
|
||||||
|
There is a daemon named ``scdaemon`` behind gpg-agent, which handles
|
||||||
|
communication to smartcard/token.
|
||||||
|
|
||||||
|
Ideally, we don't need to care about ``scdaemon``, and it should
|
||||||
|
handle everything automatically. But, there are some cases (because
|
||||||
|
of bugs), where we need to talk to the daemon directly, in practice.
|
||||||
|
|
||||||
|
|
||||||
|
How to communicate SCDAEMON
|
||||||
|
===========================
|
||||||
|
|
||||||
|
We have a utility to communicate with a running gpg-agent, that's
|
||||||
|
gpg-connect-agent. We can use it to communicate with scdaemon,
|
||||||
|
as it supports sub-command "SCD", exactly for this purpose.
|
||||||
|
|
||||||
|
|
||||||
|
Stopping SCDAEMON
|
||||||
|
=================
|
||||||
|
|
||||||
|
To stop SCDAEMON and let it exit, type::
|
||||||
|
|
||||||
|
$ gpg-connect-agent "SCD KILLSCD" "SCD BYE" /bye
|
||||||
|
|
||||||
|
Then, you can confirm that there is no SCDAEMON any more by ``ps``
|
||||||
|
command.
|
||||||
|
|
||||||
|
|
||||||
|
Let GPG-AGENT/SCDAEMON learn
|
||||||
|
============================
|
||||||
|
|
||||||
|
To let gpg-agent/scdaemon learn, type::
|
||||||
|
|
||||||
|
$ gpg-connect-agent learn /bye
|
||||||
51
doc/udev-rules.rst
Normal file
51
doc/udev-rules.rst
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
===============================================
|
||||||
|
Device Configuration for Gnuk Token with libusb
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
In order to use Gnuk Token with libusb, configuration of device is
|
||||||
|
needed for permissions. Note that this is not needed for the case of
|
||||||
|
PC/SC Lite, as it has its own device configuration.
|
||||||
|
|
||||||
|
|
||||||
|
udev rules for Gnuk Token
|
||||||
|
=========================
|
||||||
|
|
||||||
|
In case of Debian, there is a file /lib/udev/rules.d/60-gnupg.rules,
|
||||||
|
when you install "gnupg" package. This is the place we need to change.
|
||||||
|
We add lines for Gnuk Token to give a desktop user the permission to
|
||||||
|
use the device. We specify USB ID of Gnuk Token (by FSIJ)::
|
||||||
|
|
||||||
|
--- /lib/udev/rules.d/60-gnupg.rules.orig 2012-06-24 21:51:26.000000000 +0900
|
||||||
|
+++ /lib/udev/rules.d/60-gnupg.rules 2012-07-13 17:18:55.149587687 +0900
|
||||||
|
@@ -10,4 +10,7 @@
|
||||||
|
ATTR{idVendor}=="04e6", ATTR{idProduct}=="5115", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
|
||||||
|
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
|
||||||
|
|
||||||
|
+# Gnuk
|
||||||
|
+ATTR{idVendor}=="234b", ATTR{idProduct}=="0000", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
|
||||||
|
+
|
||||||
|
LABEL="gnupg_rules_end"
|
||||||
|
|
||||||
|
When we install "gnupg2" package only (with no "gnupg" package),
|
||||||
|
there will be no udev rules (there is a bug report #543217 for this issue).
|
||||||
|
In this case, we need something like this in /etc/udev/rules.d/60-gnuk.rules::
|
||||||
|
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="234b", ATTRS{idProduct}=="0000", \
|
||||||
|
ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
|
||||||
|
|
||||||
|
Usually, udev daemon automatically handles for the changes of configuration
|
||||||
|
files. If not, please let the daemon reload rules::
|
||||||
|
|
||||||
|
# udevadm control --reload-rules
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
udev rules for ST-Link/V2
|
||||||
|
=========================
|
||||||
|
|
||||||
|
We need to have a udev rule for ST-Link/V2. It's like::
|
||||||
|
|
||||||
|
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3748", GROUP="tape", MODE="664", SYMLINK+="stlink"
|
||||||
|
|
||||||
|
I have this in the file /etc/udev/rules.d/10-stlink.rules.
|
||||||
178
doc/using-gnuk-token-with-another-computer.rst
Normal file
178
doc/using-gnuk-token-with-another-computer.rst
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
======================================
|
||||||
|
Using Gnuk Token with another computer
|
||||||
|
======================================
|
||||||
|
|
||||||
|
This document describes how you can use Gnuk Token
|
||||||
|
on another PC (which is not the one you generate your keys).
|
||||||
|
|
||||||
|
Note that the Token only brings your secret keys,
|
||||||
|
while ``.gnupg`` directory contains keyrings and trustdb, too.
|
||||||
|
|
||||||
|
|
||||||
|
Fetch the public key and connect it to the Token
|
||||||
|
================================================
|
||||||
|
|
||||||
|
Using the Token, we need to put the public key and the secret
|
||||||
|
key reference (to the token) in ``.gnupg``.
|
||||||
|
|
||||||
|
To do that, invoke GnuPG with ``--card-edit`` option. ::
|
||||||
|
|
||||||
|
$ gpg --card-edit
|
||||||
|
gpg: detected reader `FSIJ Gnuk (0.12-37006A06) 00 00'
|
||||||
|
Application ID ...: D276000124010200F517000000010000
|
||||||
|
Version ..........: 2.0
|
||||||
|
Manufacturer .....: FSIJ
|
||||||
|
Serial number ....: 00000001
|
||||||
|
Name of cardholder: Yutaka Niibe
|
||||||
|
Language prefs ...: ja
|
||||||
|
Sex ..............: male
|
||||||
|
URL of public key : http://www.gniibe.org/gniibe.asc
|
||||||
|
Login data .......: gniibe
|
||||||
|
Signature PIN ....: not forced
|
||||||
|
Key attributes ...: 2048R 2048R 2048R
|
||||||
|
Max. PIN lengths .: 127 127 127
|
||||||
|
PIN retry counter : 3 3 3
|
||||||
|
Signature counter : 6
|
||||||
|
Signature key ....: 1241 24BD 3B48 62AF 7A0A 42F1 00B4 5EBD 4CA7 BABE
|
||||||
|
created ....: 2010-10-15 06:46:33
|
||||||
|
Encryption key....: 42E1 E805 4E6F 1F30 26F2 DC79 79A7 9093 0842 39CF
|
||||||
|
created ....: 2010-10-15 06:46:33
|
||||||
|
Authentication key: B4D9 7142 C42D 6802 F5F7 4E70 9C33 B6BA 5BB0 65DC
|
||||||
|
created ....: 2010-10-22 06:06:36
|
||||||
|
General key info..: [none]
|
||||||
|
|
||||||
|
gpg/card>
|
||||||
|
|
||||||
|
It says, there is no key info related to this token on your PC (``[none]``).
|
||||||
|
|
||||||
|
Fetch the public key from URL specified in the Token. ::
|
||||||
|
|
||||||
|
gpg/card> fetch
|
||||||
|
gpg: requesting key 4CA7BABE from http server www.gniibe.org
|
||||||
|
gpg: key 4CA7BABE: public key "NIIBE Yutaka <gniibe@fsij.org>" imported
|
||||||
|
gpg: no ultimately trusted keys found
|
||||||
|
gpg: Total number processed: 1
|
||||||
|
gpg: imported: 1 (RSA: 1)
|
||||||
|
|
||||||
|
gpg/card>
|
||||||
|
|
||||||
|
Good. The public key is now in ``.gnupg``. We can examine by ``gpg --list-keys``.
|
||||||
|
|
||||||
|
However, the secret key reference (to the token) is not in ``.gnupg`` yet.
|
||||||
|
|
||||||
|
It will be generated when I do ``--card-status`` by GnuPG with
|
||||||
|
correspoinding public key in ``.gnupg``, or just type return
|
||||||
|
at the ``gpg/card>`` prompt. ::
|
||||||
|
|
||||||
|
gpg/card>
|
||||||
|
|
||||||
|
Application ID ...: D276000124010200F517000000010000
|
||||||
|
Version ..........: 2.0
|
||||||
|
Manufacturer .....: FSIJ
|
||||||
|
Serial number ....: 00000001
|
||||||
|
Name of cardholder: Yutaka Niibe
|
||||||
|
Language prefs ...: ja
|
||||||
|
Sex ..............: male
|
||||||
|
URL of public key : http://www.gniibe.org/gniibe.asc
|
||||||
|
Login data .......: gniibe
|
||||||
|
Signature PIN ....: not forced
|
||||||
|
Key attributes ...: 2048R 2048R 2048R
|
||||||
|
Max. PIN lengths .: 127 127 127
|
||||||
|
PIN retry counter : 3 3 3
|
||||||
|
Signature counter : 6
|
||||||
|
Signature key ....: 1241 24BD 3B48 62AF 7A0A 42F1 00B4 5EBD 4CA7 BABE
|
||||||
|
created ....: 2010-10-15 06:46:33
|
||||||
|
Encryption key....: 42E1 E805 4E6F 1F30 26F2 DC79 79A7 9093 0842 39CF
|
||||||
|
created ....: 2010-10-15 06:46:33
|
||||||
|
Authentication key: B4D9 7142 C42D 6802 F5F7 4E70 9C33 B6BA 5BB0 65DC
|
||||||
|
created ....: 2010-10-22 06:06:36
|
||||||
|
General key info..:
|
||||||
|
pub 2048R/4CA7BABE 2010-10-15 NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
sec> 2048R/4CA7BABE created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb> 2048R/084239CF created: 2010-10-15 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
ssb> 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||||
|
card-no: F517 00000001
|
||||||
|
|
||||||
|
gpg/card>
|
||||||
|
|
||||||
|
OK, now I can use the Token on this computer.
|
||||||
|
|
||||||
|
|
||||||
|
Update trustdb for the key on Gnuk Token
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Yes, I can use the Token by the public key and the secret
|
||||||
|
key reference to the card. More, I need to update the trustdb.
|
||||||
|
|
||||||
|
To do that I do: ::
|
||||||
|
|
||||||
|
$ gpg --edit-key 4ca7babe
|
||||||
|
gpg (GnuPG) 1.4.11; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
|
||||||
|
Secret key is available.
|
||||||
|
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: unknown validity: unknown
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
sub 2048R/5BB065DC created: 2010-10-22 expires: never usage: A
|
||||||
|
[ unknown] (1). NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
[ unknown] (2) NIIBE Yutaka <gniibe@debian.org>
|
||||||
|
|
||||||
|
gpg>
|
||||||
|
|
||||||
|
See, the key is ``unknown`` state. Add trust for that. ::
|
||||||
|
|
||||||
|
gpg> trust
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: unknown validity: unknown
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
sub 2048R/5BB065DC created: 2010-10-22 expires: never usage: A
|
||||||
|
[ unknown] (1). NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
[ unknown] (2) NIIBE Yutaka <gniibe@debian.org>
|
||||||
|
|
||||||
|
Please decide how far you trust this user to correctly verify other users' keys
|
||||||
|
(by looking at passports, checking fingerprints from different sources, etc.)
|
||||||
|
|
||||||
|
1 = I don't know or won't say
|
||||||
|
2 = I do NOT trust
|
||||||
|
3 = I trust marginally
|
||||||
|
4 = I trust fully
|
||||||
|
5 = I trust ultimately
|
||||||
|
m = back to the main menu
|
||||||
|
|
||||||
|
Your decision? 5
|
||||||
|
Do you really want to set this key to ultimate trust? (y/N) y
|
||||||
|
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: ultimate validity: unknown
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
sub 2048R/5BB065DC created: 2010-10-22 expires: never usage: A
|
||||||
|
[ unknown] (1). NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
[ unknown] (2) NIIBE Yutaka <gniibe@debian.org>
|
||||||
|
Please note that the shown key validity is not necessarily correct
|
||||||
|
unless you restart the program.
|
||||||
|
|
||||||
|
$
|
||||||
|
|
||||||
|
Next time I invoke GnuPG, it will be ``ultimate`` key. Let's see: ::
|
||||||
|
|
||||||
|
$ gpg --edit-key 4ca7babe
|
||||||
|
gpg (GnuPG) 1.4.11; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
|
||||||
|
Secret key is available.
|
||||||
|
|
||||||
|
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||||
|
trust: ultimate validity: ultimate
|
||||||
|
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||||
|
sub 2048R/5BB065DC created: 2010-10-22 expires: never usage: A
|
||||||
|
[ultimate] (1). NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
[ultimate] (2) NIIBE Yutaka <gniibe@debian.org>
|
||||||
|
|
||||||
|
gpg> quit
|
||||||
|
$
|
||||||
@@ -11,7 +11,7 @@ BOARD_DIR=@BOARD_DIR@
|
|||||||
|
|
||||||
# Compiler options here.
|
# Compiler options here.
|
||||||
ifeq ($(USE_OPT),)
|
ifeq ($(USE_OPT),)
|
||||||
USE_OPT = -O2 -ggdb -fomit-frame-pointer -falign-functions=16
|
USE_OPT = -O3 -Os -ggdb -fomit-frame-pointer -falign-functions=16
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# C++ specific options here (added to USE_OPT).
|
# C++ specific options here (added to USE_OPT).
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* flash.c -- Data Objects (DO) and GPG Key handling on Flash ROM
|
* flash.c -- Data Objects (DO) and GPG Key handling on Flash ROM
|
||||||
*
|
*
|
||||||
* Copyright (C) 2010, 2011, 2012 Free Software Initiative of Japan
|
* Copyright (C) 2010, 2011, 2012, 2013
|
||||||
|
* Free Software Initiative of Japan
|
||||||
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||||
*
|
*
|
||||||
* This file is a part of Gnuk, a GnuPG USB Token implementation.
|
* This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||||
@@ -516,6 +517,12 @@ flash_write_binary (uint8_t file_id, const uint8_t *data,
|
|||||||
{
|
{
|
||||||
maxsize = KEY_CONTENT_LEN;
|
maxsize = KEY_CONTENT_LEN;
|
||||||
p = gpg_get_firmware_update_key (file_id - FILEID_UPDATE_KEY_0);
|
p = gpg_get_firmware_update_key (file_id - FILEID_UPDATE_KEY_0);
|
||||||
|
if (len == 0 && offset == 0)
|
||||||
|
{ /* This means removal of update key. */
|
||||||
|
if (flash_program_halfword ((uint32_t)p, 0) != 0)
|
||||||
|
flash_warning ("DO WRITE ERROR");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#if defined(CERTDO_SUPPORT)
|
#if defined(CERTDO_SUPPORT)
|
||||||
else if (file_id == FILEID_CH_CERTIFICATE)
|
else if (file_id == FILEID_CH_CERTIFICATE)
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ extern volatile uint8_t auth_status;
|
|||||||
#define PW_ERR_PW1 0
|
#define PW_ERR_PW1 0
|
||||||
#define PW_ERR_RC 1
|
#define PW_ERR_RC 1
|
||||||
#define PW_ERR_PW3 2
|
#define PW_ERR_PW3 2
|
||||||
|
extern int gpg_pw_get_retry_counter (int who);
|
||||||
extern int gpg_pw_locked (uint8_t which);
|
extern int gpg_pw_locked (uint8_t which);
|
||||||
extern void gpg_pw_reset_err_counter (uint8_t which);
|
extern void gpg_pw_reset_err_counter (uint8_t which);
|
||||||
extern void gpg_pw_increment_err_counter (uint8_t which);
|
extern void gpg_pw_increment_err_counter (uint8_t which);
|
||||||
@@ -115,6 +116,8 @@ extern void ac_fini (void);
|
|||||||
extern void set_res_sw (uint8_t sw1, uint8_t sw2);
|
extern void set_res_sw (uint8_t sw1, uint8_t sw2);
|
||||||
extern uint16_t data_objects_number_of_bytes;
|
extern uint16_t data_objects_number_of_bytes;
|
||||||
|
|
||||||
|
#define CHALLENGE_LEN 32
|
||||||
|
|
||||||
extern void gpg_data_scan (const uint8_t *p);
|
extern void gpg_data_scan (const uint8_t *p);
|
||||||
extern void gpg_data_copy (const uint8_t *p);
|
extern void gpg_data_copy (const uint8_t *p);
|
||||||
extern void gpg_do_get_data (uint16_t tag, int with_tag);
|
extern void gpg_do_get_data (uint16_t tag, int with_tag);
|
||||||
|
|||||||
@@ -178,7 +178,8 @@ extern msg_t USBthread (void *arg);
|
|||||||
#define LED_TIMEOUT_STOP MS2ST(200)
|
#define LED_TIMEOUT_STOP MS2ST(200)
|
||||||
|
|
||||||
|
|
||||||
#define ID_OFFSET 22
|
/* It has two-byte prefix and content is "FSIJ-1.0.1-" (2 + 11*2). */
|
||||||
|
#define ID_OFFSET 24
|
||||||
static void
|
static void
|
||||||
device_initialize_once (void)
|
device_initialize_once (void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* openpgp-do.c -- OpenPGP card Data Objects (DO) handling
|
* openpgp-do.c -- OpenPGP card Data Objects (DO) handling
|
||||||
*
|
*
|
||||||
* Copyright (C) 2010, 2011, 2012 Free Software Initiative of Japan
|
* Copyright (C) 2010, 2011, 2012, 2013
|
||||||
|
* Free Software Initiative of Japan
|
||||||
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||||
*
|
*
|
||||||
* This file is a part of Gnuk, a GnuPG USB Token implementation.
|
* This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||||
@@ -41,6 +42,17 @@ gpg_pw_get_err_counter (uint8_t which)
|
|||||||
return flash_cnt123_get_value (pw_err_counter_p[which]);
|
return flash_cnt123_get_value (pw_err_counter_p[which]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
gpg_pw_get_retry_counter (int who)
|
||||||
|
{
|
||||||
|
if (who == 0x81 || who == 0x82)
|
||||||
|
return PASSWORD_ERRORS_MAX - gpg_pw_get_err_counter (PW_ERR_PW1);
|
||||||
|
else if (who == 0x83)
|
||||||
|
return PASSWORD_ERRORS_MAX - gpg_pw_get_err_counter (PW_ERR_PW3);
|
||||||
|
else
|
||||||
|
return PASSWORD_ERRORS_MAX - gpg_pw_get_err_counter (PW_ERR_RC);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
gpg_pw_locked (uint8_t which)
|
gpg_pw_locked (uint8_t which)
|
||||||
{
|
{
|
||||||
@@ -88,16 +100,16 @@ static const uint8_t historical_bytes[] __attribute__ ((aligned (1))) = {
|
|||||||
/* Extended Capabilities */
|
/* Extended Capabilities */
|
||||||
static const uint8_t extended_capabilities[] __attribute__ ((aligned (1))) = {
|
static const uint8_t extended_capabilities[] __attribute__ ((aligned (1))) = {
|
||||||
10,
|
10,
|
||||||
0x30, /*
|
0x70, /*
|
||||||
* No SM,
|
* No SM,
|
||||||
* No get challenge,
|
* GET CHALLENGE supported,
|
||||||
* Key import supported,
|
* Key import supported,
|
||||||
* PW status byte can be put,
|
* PW status byte can be put,
|
||||||
* No private_use_DO,
|
* No private_use_DO,
|
||||||
* No algo change allowed
|
* No algo change allowed
|
||||||
*/
|
*/
|
||||||
0, /* Secure Messaging Algorithm: N/A (TDES=0, AES=1) */
|
0, /* Secure Messaging Algorithm: N/A (TDES=0, AES=1) */
|
||||||
0x00, 0x00, /* Max get challenge (0: Get challenge not supported) */
|
0x00, CHALLENGE_LEN, /* Max size of GET CHALLENGE */
|
||||||
#ifdef CERTDO_SUPPORT
|
#ifdef CERTDO_SUPPORT
|
||||||
0x08, 0x00, /* max. length of cardholder certificate (2KiB) */
|
0x08, 0x00, /* max. length of cardholder certificate (2KiB) */
|
||||||
#else
|
#else
|
||||||
@@ -1381,8 +1393,11 @@ gpg_do_put_data (uint16_t tag, const uint8_t *data, int len)
|
|||||||
flash_do_release (*do_data_p);
|
flash_do_release (*do_data_p);
|
||||||
|
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
/* make DO empty */
|
{
|
||||||
*do_data_p = NULL;
|
/* make DO empty */
|
||||||
|
*do_data_p = NULL;
|
||||||
|
GPG_SUCCESS ();
|
||||||
|
}
|
||||||
else if (len > 255)
|
else if (len > 255)
|
||||||
GPG_MEMORY_FAILURE ();
|
GPG_MEMORY_FAILURE ();
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* openpgp.c -- OpenPGP card protocol support
|
* openpgp.c -- OpenPGP card protocol support
|
||||||
*
|
*
|
||||||
* Copyright (C) 2010, 2011, 2012 Free Software Initiative of Japan
|
* Copyright (C) 2010, 2011, 2012, 2013
|
||||||
|
* Free Software Initiative of Japan
|
||||||
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||||
*
|
*
|
||||||
* This file is a part of Gnuk, a GnuPG USB Token implementation.
|
* This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||||
@@ -29,6 +30,8 @@
|
|||||||
#include "openpgp.h"
|
#include "openpgp.h"
|
||||||
#include "sha256.h"
|
#include "sha256.h"
|
||||||
|
|
||||||
|
#define ADMIN_PASSWD_MINLEN 8
|
||||||
|
|
||||||
#define CLS(a) a.cmd_apdu_head[0]
|
#define CLS(a) a.cmd_apdu_head[0]
|
||||||
#define INS(a) a.cmd_apdu_head[1]
|
#define INS(a) a.cmd_apdu_head[1]
|
||||||
#define P1(a) a.cmd_apdu_head[2]
|
#define P1(a) a.cmd_apdu_head[2]
|
||||||
@@ -50,7 +53,6 @@
|
|||||||
#define INS_PUT_DATA 0xda
|
#define INS_PUT_DATA 0xda
|
||||||
#define INS_PUT_DATA_ODD 0xdb /* For key import */
|
#define INS_PUT_DATA_ODD 0xdb /* For key import */
|
||||||
|
|
||||||
#define CHALLENGE_LEN 32
|
|
||||||
static const uint8_t *challenge; /* Random bytes */
|
static const uint8_t *challenge; /* Random bytes */
|
||||||
|
|
||||||
static const uint8_t
|
static const uint8_t
|
||||||
@@ -138,6 +140,27 @@ cmd_verify (void)
|
|||||||
len = apdu.cmd_apdu_data_len;
|
len = apdu.cmd_apdu_data_len;
|
||||||
pw = apdu.cmd_apdu_data;
|
pw = apdu.cmd_apdu_data;
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
{ /* This is to examine status. */
|
||||||
|
if (p2 == 0x81)
|
||||||
|
r = ac_check_status (AC_PSO_CDS_AUTHORIZED);
|
||||||
|
else if (p2 == 0x82)
|
||||||
|
r = ac_check_status (AC_OTHER_AUTHORIZED);
|
||||||
|
else
|
||||||
|
r = ac_check_status (AC_ADMIN_AUTHORIZED);
|
||||||
|
|
||||||
|
if (r)
|
||||||
|
GPG_SUCCESS (); /* If authentication done already, return success. */
|
||||||
|
else
|
||||||
|
{ /* If not, return retry counter, encoded. */
|
||||||
|
r = gpg_pw_get_retry_counter (p2);
|
||||||
|
set_res_sw (0x63, 0xc0 | (r&0x0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is real authentication. */
|
||||||
if (p2 == 0x81)
|
if (p2 == 0x81)
|
||||||
r = verify_pso_cds (pw, len);
|
r = verify_pso_cds (pw, len);
|
||||||
else if (p2 == 0x82)
|
else if (p2 == 0x82)
|
||||||
@@ -234,7 +257,7 @@ cmd_change_password (void)
|
|||||||
|
|
||||||
if (p1 != 0)
|
if (p1 != 0)
|
||||||
{
|
{
|
||||||
GPG_FUNCTION_NOT_SUPPORTED();
|
GPG_FUNCTION_NOT_SUPPORTED ();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,8 +282,18 @@ cmd_change_password (void)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
const uint8_t *ks_pw3 = gpg_do_read_simple (NR_DO_KEYSTRING_PW3);
|
||||||
|
|
||||||
newpw = pw + pw_len;
|
newpw = pw + pw_len;
|
||||||
newpw_len = len - pw_len;
|
newpw_len = len - pw_len;
|
||||||
|
|
||||||
|
/* Check length of password for admin-less mode. */
|
||||||
|
if (ks_pw3 == NULL && newpw_len < ADMIN_PASSWD_MINLEN)
|
||||||
|
{
|
||||||
|
DEBUG_INFO ("new password length is too short.");
|
||||||
|
GPG_CONDITION_NOT_SATISFIED ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else /* PW3 (0x83) */
|
else /* PW3 (0x83) */
|
||||||
@@ -914,6 +947,26 @@ modify_binary (uint8_t op, uint8_t p1, uint8_t p2, int len)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (file_id >= FILEID_UPDATE_KEY_0 && file_id <= FILEID_UPDATE_KEY_3
|
||||||
|
&& len == 0 && offset == 0)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
const uint8_t *p;
|
||||||
|
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
p = gpg_get_firmware_update_key (i);
|
||||||
|
if (p[0] != 0x00 || p[1] != 0x00) /* still valid */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 4) /* all update keys are removed */
|
||||||
|
{
|
||||||
|
p = gpg_get_firmware_update_key (0);
|
||||||
|
flash_erase_page ((uint32_t)p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GPG_SUCCESS ();
|
GPG_SUCCESS ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,25 +988,9 @@ static void
|
|||||||
cmd_write_binary (void)
|
cmd_write_binary (void)
|
||||||
{
|
{
|
||||||
int len = apdu.cmd_apdu_data_len;
|
int len = apdu.cmd_apdu_data_len;
|
||||||
int i;
|
|
||||||
const uint8_t *p;
|
|
||||||
|
|
||||||
DEBUG_INFO (" - WRITE BINARY\r\n");
|
DEBUG_INFO (" - WRITE BINARY\r\n");
|
||||||
modify_binary (MBD_OPRATION_WRITE, P1 (apdu), P2 (apdu), len);
|
modify_binary (MBD_OPRATION_WRITE, P1 (apdu), P2 (apdu), len);
|
||||||
|
|
||||||
for (i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
p = gpg_get_firmware_update_key (i);
|
|
||||||
if (p[0] != 0x00 || p[1] != 0x00) /* still valid */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == 4) /* all update keys are removed */
|
|
||||||
{
|
|
||||||
p = gpg_get_firmware_update_key (0);
|
|
||||||
flash_erase_page ((uint32_t)p);
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_INFO ("WRITE BINARY done.\r\n");
|
DEBUG_INFO ("WRITE BINARY done.\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,7 +1006,7 @@ cmd_external_authenticate (void)
|
|||||||
|
|
||||||
DEBUG_INFO (" - EXTERNAL AUTHENTICATE\r\n");
|
DEBUG_INFO (" - EXTERNAL AUTHENTICATE\r\n");
|
||||||
|
|
||||||
if (keyno > 4)
|
if (keyno >= 4)
|
||||||
{
|
{
|
||||||
GPG_CONDITION_NOT_SATISFIED ();
|
GPG_CONDITION_NOT_SATISFIED ();
|
||||||
return;
|
return;
|
||||||
@@ -1002,14 +1039,25 @@ cmd_external_authenticate (void)
|
|||||||
static void
|
static void
|
||||||
cmd_get_challenge (void)
|
cmd_get_challenge (void)
|
||||||
{
|
{
|
||||||
|
int len = apdu.expected_res_size;
|
||||||
|
|
||||||
DEBUG_INFO (" - GET CHALLENGE\r\n");
|
DEBUG_INFO (" - GET CHALLENGE\r\n");
|
||||||
|
|
||||||
|
if (len > CHALLENGE_LEN)
|
||||||
|
{
|
||||||
|
GPG_CONDITION_NOT_SATISFIED ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (len == 0)
|
||||||
|
/* Le is not specified. Return full-sized challenge by GET_RESPONSE. */
|
||||||
|
len = CHALLENGE_LEN;
|
||||||
|
|
||||||
if (challenge)
|
if (challenge)
|
||||||
random_bytes_free (challenge);
|
random_bytes_free (challenge);
|
||||||
|
|
||||||
challenge = random_bytes_get ();
|
challenge = random_bytes_get ();
|
||||||
memcpy (res_APDU, challenge, CHALLENGE_LEN);
|
memcpy (res_APDU, challenge, len);
|
||||||
res_APDU_size = CHALLENGE_LEN;
|
res_APDU_size = len;
|
||||||
GPG_SUCCESS ();
|
GPG_SUCCESS ();
|
||||||
DEBUG_INFO ("GET CHALLENGE done.\r\n");
|
DEBUG_INFO ("GET CHALLENGE done.\r\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "sha256.h"
|
#include "sha256.h"
|
||||||
|
|
||||||
#define SHA256_DIGEST_SIZE 32
|
|
||||||
#define SHA256_BLOCK_SIZE 64
|
|
||||||
#define SHA256_MASK (SHA256_BLOCK_SIZE - 1)
|
#define SHA256_MASK (SHA256_BLOCK_SIZE - 1)
|
||||||
|
|
||||||
static void bswap32_buf (uint32_t *p, int n)
|
static void bswap32_buf (uint32_t *p, int n)
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#define SHA256_DIGEST_SIZE 32
|
||||||
|
#define SHA256_BLOCK_SIZE 64
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint32_t total[2];
|
uint32_t total[2];
|
||||||
|
|||||||
@@ -827,36 +827,7 @@ icc_power_off (struct ccid *c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
icc_send_data_block_0x9000 (struct ccid *c)
|
icc_send_data_block_internal (struct ccid *c, uint8_t status, uint8_t error)
|
||||||
{
|
|
||||||
uint8_t p[ICC_MSG_HEADER_SIZE+2];
|
|
||||||
size_t len = 2;
|
|
||||||
|
|
||||||
p[0] = ICC_DATA_BLOCK_RET;
|
|
||||||
p[1] = len & 0xFF;
|
|
||||||
p[2] = (len >> 8)& 0xFF;
|
|
||||||
p[3] = (len >> 16)& 0xFF;
|
|
||||||
p[4] = (len >> 24)& 0xFF;
|
|
||||||
p[5] = 0x00; /* Slot */
|
|
||||||
p[ICC_MSG_SEQ_OFFSET] = c->a->seq;
|
|
||||||
p[ICC_MSG_STATUS_OFFSET] = 0;
|
|
||||||
p[ICC_MSG_ERROR_OFFSET] = 0;
|
|
||||||
p[ICC_MSG_CHAIN_OFFSET] = 0;
|
|
||||||
p[ICC_MSG_CHAIN_OFFSET+1] = 0x90;
|
|
||||||
p[ICC_MSG_CHAIN_OFFSET+2] = 0x00;
|
|
||||||
|
|
||||||
usb_lld_txcpy (p, c->epi->ep_num, 0, ICC_MSG_HEADER_SIZE + len);
|
|
||||||
c->epi->buf = NULL;
|
|
||||||
c->epi->tx_done = 1;
|
|
||||||
|
|
||||||
usb_lld_tx_enable (c->epi->ep_num, ICC_MSG_HEADER_SIZE + len);
|
|
||||||
#ifdef DEBUG_MORE
|
|
||||||
DEBUG_INFO ("DATA\r\n");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
icc_send_data_block (struct ccid *c, uint8_t status)
|
|
||||||
{
|
{
|
||||||
int tx_size = USB_LL_BUF_SIZE;
|
int tx_size = USB_LL_BUF_SIZE;
|
||||||
uint8_t p[ICC_MSG_HEADER_SIZE];
|
uint8_t p[ICC_MSG_HEADER_SIZE];
|
||||||
@@ -875,7 +846,7 @@ icc_send_data_block (struct ccid *c, uint8_t status)
|
|||||||
p[5] = 0x00; /* Slot */
|
p[5] = 0x00; /* Slot */
|
||||||
p[ICC_MSG_SEQ_OFFSET] = c->a->seq;
|
p[ICC_MSG_SEQ_OFFSET] = c->a->seq;
|
||||||
p[ICC_MSG_STATUS_OFFSET] = status;
|
p[ICC_MSG_STATUS_OFFSET] = status;
|
||||||
p[ICC_MSG_ERROR_OFFSET] = 0;
|
p[ICC_MSG_ERROR_OFFSET] = error;
|
||||||
p[ICC_MSG_CHAIN_OFFSET] = 0;
|
p[ICC_MSG_CHAIN_OFFSET] = 0;
|
||||||
|
|
||||||
usb_lld_txcpy (p, c->epi->ep_num, 0, ICC_MSG_HEADER_SIZE);
|
usb_lld_txcpy (p, c->epi->ep_num, 0, ICC_MSG_HEADER_SIZE);
|
||||||
@@ -933,6 +904,50 @@ icc_send_data_block (struct ccid *c, uint8_t status)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
icc_send_data_block (struct ccid *c)
|
||||||
|
{
|
||||||
|
icc_send_data_block_internal (c, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
icc_send_data_block_time_extension (struct ccid *c)
|
||||||
|
{
|
||||||
|
icc_send_data_block_internal (c, ICC_CMD_STATUS_TIMEEXT, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
icc_send_data_block_0x9000 (struct ccid *c)
|
||||||
|
{
|
||||||
|
uint8_t p[ICC_MSG_HEADER_SIZE+2];
|
||||||
|
size_t len = 2;
|
||||||
|
|
||||||
|
p[0] = ICC_DATA_BLOCK_RET;
|
||||||
|
p[1] = len & 0xFF;
|
||||||
|
p[2] = (len >> 8)& 0xFF;
|
||||||
|
p[3] = (len >> 16)& 0xFF;
|
||||||
|
p[4] = (len >> 24)& 0xFF;
|
||||||
|
p[5] = 0x00; /* Slot */
|
||||||
|
p[ICC_MSG_SEQ_OFFSET] = c->a->seq;
|
||||||
|
p[ICC_MSG_STATUS_OFFSET] = 0;
|
||||||
|
p[ICC_MSG_ERROR_OFFSET] = 0;
|
||||||
|
p[ICC_MSG_CHAIN_OFFSET] = 0;
|
||||||
|
p[ICC_MSG_CHAIN_OFFSET+1] = 0x90;
|
||||||
|
p[ICC_MSG_CHAIN_OFFSET+2] = 0x00;
|
||||||
|
|
||||||
|
usb_lld_txcpy (p, c->epi->ep_num, 0, ICC_MSG_HEADER_SIZE + len);
|
||||||
|
c->epi->buf = NULL;
|
||||||
|
c->epi->tx_done = 1;
|
||||||
|
|
||||||
|
usb_lld_tx_enable (c->epi->ep_num, ICC_MSG_HEADER_SIZE + len);
|
||||||
|
#ifdef DEBUG_MORE
|
||||||
|
DEBUG_INFO ("DATA\r\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reply to the host for "GET RESPONSE".
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
icc_send_data_block_gr (struct ccid *c, size_t chunk_len)
|
icc_send_data_block_gr (struct ccid *c, size_t chunk_len)
|
||||||
{
|
{
|
||||||
@@ -1260,7 +1275,7 @@ icc_handle_timeout (struct ccid *c)
|
|||||||
switch (c->icc_state)
|
switch (c->icc_state)
|
||||||
{
|
{
|
||||||
case ICC_STATE_EXECUTE:
|
case ICC_STATE_EXECUTE:
|
||||||
icc_send_data_block (c, ICC_CMD_STATUS_TIMEEXT);
|
icc_send_data_block_time_extension (c);
|
||||||
led_blink (LED_ONESHOT);
|
led_blink (LED_ONESHOT);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1311,7 +1326,7 @@ USBthread (void *arg)
|
|||||||
c->sw1sw2[0] = 0x90;
|
c->sw1sw2[0] = 0x90;
|
||||||
c->sw1sw2[1] = 0x00;
|
c->sw1sw2[1] = 0x00;
|
||||||
c->state = APDU_STATE_RESULT;
|
c->state = APDU_STATE_RESULT;
|
||||||
icc_send_data_block (c, 0);
|
icc_send_data_block (c);
|
||||||
c->icc_state = ICC_STATE_EXITED;
|
c->icc_state = ICC_STATE_EXITED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1323,7 +1338,7 @@ USBthread (void *arg)
|
|||||||
if (c->a->res_apdu_data_len <= c->a->expected_res_size)
|
if (c->a->res_apdu_data_len <= c->a->expected_res_size)
|
||||||
{
|
{
|
||||||
c->state = APDU_STATE_RESULT;
|
c->state = APDU_STATE_RESULT;
|
||||||
icc_send_data_block (c, 0);
|
icc_send_data_block (c);
|
||||||
c->icc_state = ICC_STATE_WAIT;
|
c->icc_state = ICC_STATE_WAIT;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -258,11 +258,11 @@ static const uint8_t gnukStringLangID[] = {
|
|||||||
#include "usb-strings.c.inc"
|
#include "usb-strings.c.inc"
|
||||||
|
|
||||||
const uint8_t gnukStringSerial[] = {
|
const uint8_t gnukStringSerial[] = {
|
||||||
17*2+2, /* bLength */
|
19*2+2, /* bLength */
|
||||||
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
|
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
|
||||||
/* FSIJ-1.0 */
|
/* FSIJ-1.0.1- */
|
||||||
'F', 0, 'S', 0, 'I', 0, 'J', 0, '-', 0,
|
'F', 0, 'S', 0, 'I', 0, 'J', 0, '-', 0,
|
||||||
'1', 0, '.', 0, '0', 0, /* Version number of Gnuk */
|
'1', 0, '.', 0, '0', 0, '.', 0, '2', 0, /* Version number of Gnuk */
|
||||||
'-', 0,
|
'-', 0,
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Feature: command GET DATA
|
|||||||
|
|
||||||
Scenario: data object extended capabilities
|
Scenario: data object extended capabilities
|
||||||
When requesting extended capabilities: c0
|
When requesting extended capabilities: c0
|
||||||
Then data should match: \x30\x00\x00\x00[\x00\x08]\x00\x00\xff\x01\x00
|
Then data should match: \x70\x00\x00\x20[\x00\x08]\x00\x00\xff\x01\x00
|
||||||
|
|
||||||
Scenario: data object algorithm attributes 1
|
Scenario: data object algorithm attributes 1
|
||||||
When requesting algorithm attributes 1: c1
|
When requesting algorithm attributes 1: c1
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Feature: command GET DATA
|
|||||||
|
|
||||||
Scenario: data object extended capabilities
|
Scenario: data object extended capabilities
|
||||||
When requesting extended capabilities: c0
|
When requesting extended capabilities: c0
|
||||||
Then data should match: \x30\x00\x00\x00[\x00\x08]\x00\x00\xff\x01\x00
|
Then data should match: \x70\x00\x00\x20[\x00\x08]\x00\x00\xff\x01\x00
|
||||||
|
|
||||||
Scenario: data object algorithm attributes 1
|
Scenario: data object algorithm attributes 1
|
||||||
When requesting algorithm attributes 1: c1
|
When requesting algorithm attributes 1: c1
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Feature: command GET DATA
|
|||||||
|
|
||||||
Scenario: data object extended capabilities
|
Scenario: data object extended capabilities
|
||||||
When requesting extended capabilities: c0
|
When requesting extended capabilities: c0
|
||||||
Then data should match: \x30\x00\x00\x00[\x00\x08]\x00\x00\xff\x01\x00
|
Then data should match: \x70\x00\x00\x20[\x00\x08]\x00\x00\xff\x01\x00
|
||||||
|
|
||||||
Scenario: data object algorithm attributes 1
|
Scenario: data object algorithm attributes 1
|
||||||
When requesting algorithm attributes 1: c1
|
When requesting algorithm attributes 1: c1
|
||||||
|
|||||||
8
test/features/991_version_string.feature
Normal file
8
test/features/991_version_string.feature
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
@usb
|
||||||
|
Feature: examine USB version string
|
||||||
|
In order to work as Gnuk Token
|
||||||
|
A token should support version string
|
||||||
|
|
||||||
|
Scenario: USB version string
|
||||||
|
Given USB version string of the token
|
||||||
|
Then data should match: ([a-zA-Z0-9]*)-([.0-9]+)-[0-9A-F]+
|
||||||
@@ -5,7 +5,7 @@ from binascii import hexlify
|
|||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
import gnuk
|
import gnuk_token as gnuk
|
||||||
import rsa_keys
|
import rsa_keys
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -108,6 +108,9 @@ def encrypt_on_host_public_key():
|
|||||||
def decrypt():
|
def decrypt():
|
||||||
scc.result = ftc.token.cmd_pso_longdata(0x80, 0x86, scc.ciphertext)
|
scc.result = ftc.token.cmd_pso_longdata(0x80, 0x86, scc.ciphertext)
|
||||||
|
|
||||||
|
@Given("USB version string of the token")
|
||||||
|
def usb_version_string():
|
||||||
|
scc.result = ftc.token.get_string(3)
|
||||||
|
|
||||||
@When("requesting (.+): ([0-9a-fA-F]+)")
|
@When("requesting (.+): ([0-9a-fA-F]+)")
|
||||||
def get_data(name, tag_str):
|
def get_data(name, tag_str):
|
||||||
|
|||||||
1
test/gnuk_token.py
Symbolic link
1
test/gnuk_token.py
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../tool/gnuk_token.py
|
||||||
32
tool/get_raw_public_key.py
Executable file
32
tool/get_raw_public_key.py
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
import sys, binascii
|
||||||
|
from subprocess import check_output
|
||||||
|
|
||||||
|
def get_gpg_public_key(keygrip):
|
||||||
|
result = check_output(["gpg-connect-agent", "READKEY %s" % keygrip, "/bye"])
|
||||||
|
key = ""
|
||||||
|
while True:
|
||||||
|
i = result.find('%')
|
||||||
|
if i < 0:
|
||||||
|
key += result
|
||||||
|
break
|
||||||
|
hex_str = result[i+1:i+3]
|
||||||
|
key += result[0:i]
|
||||||
|
key += chr(int(hex_str,16))
|
||||||
|
result = result[i+3:]
|
||||||
|
|
||||||
|
pos = key.index("D (10:public-key(3:rsa(1:n257:") + 31 # skip NUL too
|
||||||
|
pos_last = key.index(")(1:e3:")
|
||||||
|
key = key[pos:pos_last]
|
||||||
|
if len(key) != 256:
|
||||||
|
raise ValueError, binascii.hexlify(key)
|
||||||
|
return key
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
keygrip = sys.argv[1]
|
||||||
|
k = get_gpg_public_key(keygrip)
|
||||||
|
shorthand = keygrip[0:8] + ".bin"
|
||||||
|
f = open(shorthand,"w")
|
||||||
|
f.write(k)
|
||||||
|
f.close()
|
||||||
@@ -25,266 +25,46 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
from struct import *
|
from struct import *
|
||||||
import sys, time, os, binascii, string
|
import sys, time, os, binascii, string
|
||||||
|
from gnuk_token import *
|
||||||
|
|
||||||
# INPUT: binary file
|
# INPUT: binary file
|
||||||
|
|
||||||
# Assume only single CCID device is attached to computer, and it's Gnuk Token
|
# 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
|
|
||||||
|
|
||||||
# This class only supports Gnuk (for now)
|
|
||||||
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 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
|
|
||||||
return 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 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, sw
|
|
||||||
|
|
||||||
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"
|
|
||||||
if cmd_data1:
|
|
||||||
sw = self.icc_send_cmd(cmd_data1)
|
|
||||||
if len(sw) != 2:
|
|
||||||
raise ValueError, "cmd_write_binary 1"
|
|
||||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
|
||||||
raise ValueError, "cmd_write_binary 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]))
|
|
||||||
|
|
||||||
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] != 0x61:
|
|
||||||
raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
|
|
||||||
return self.cmd_get_response(sw[1])
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
DEFAULT_PW3 = "12345678"
|
DEFAULT_PW3 = "12345678"
|
||||||
BY_ADMIN = 3
|
BY_ADMIN = 3
|
||||||
|
|
||||||
def main(fileid, is_update, data, passwd):
|
def main(fileid, is_update, data, passwd):
|
||||||
icc = None
|
gnuk = None
|
||||||
for (dev, config, intf) in gnuk_devices():
|
for (dev, config, intf) in gnuk_devices():
|
||||||
try:
|
try:
|
||||||
icc = gnuk_token(dev, config, intf)
|
gnuk = gnuk_token(dev, config, intf)
|
||||||
print "Device: ", dev.filename
|
print "Device: ", dev.filename
|
||||||
print "Configuration: ", config.value
|
print "Configuration: ", config.value
|
||||||
print "Interface: ", intf.interfaceNumber
|
print "Interface: ", intf.interfaceNumber
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if icc.icc_get_status() == 2:
|
if gnuk.icc_get_status() == 2:
|
||||||
raise ValueError, "No ICC present"
|
raise ValueError, "No ICC present"
|
||||||
elif icc.icc_get_status() == 1:
|
elif gnuk.icc_get_status() == 1:
|
||||||
icc.icc_power_on()
|
gnuk.icc_power_on()
|
||||||
icc.cmd_verify(BY_ADMIN, passwd)
|
gnuk.cmd_verify(BY_ADMIN, passwd)
|
||||||
icc.cmd_write_binary(fileid, data, is_update)
|
gnuk.cmd_write_binary(fileid, data, is_update)
|
||||||
icc.cmd_select_openpgp()
|
gnuk.cmd_select_openpgp()
|
||||||
if fileid == 0:
|
if fileid == 0:
|
||||||
data_in_device = icc.cmd_get_data(0x00, 0x4f)
|
data_in_device = gnuk.cmd_get_data(0x00, 0x4f)
|
||||||
for d in data_in_device:
|
for d in data_in_device:
|
||||||
print "%02x" % d,
|
print "%02x" % ord(d),
|
||||||
print
|
print
|
||||||
compare(data, data_in_device[8:])
|
compare(data + '\x00\x00', data_in_device[8:])
|
||||||
elif fileid >= 1 and fileid <= 4:
|
elif fileid >= 1 and fileid <= 4:
|
||||||
data_in_device = icc.cmd_read_binary(fileid)
|
data_in_device = gnuk.cmd_read_binary(fileid)
|
||||||
compare(data, data_in_device)
|
compare(data, data_in_device)
|
||||||
else:
|
else:
|
||||||
data_in_device = icc.cmd_get_data(0x7f, 0x21)
|
data_in_device = gnuk.cmd_get_data(0x7f, 0x21)
|
||||||
compare(data, data_in_device)
|
compare(data, data_in_device)
|
||||||
icc.icc_power_off()
|
gnuk.icc_power_off()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -90,13 +90,13 @@ def main(passwd):
|
|||||||
gnuk.cmd_verify(BY_ADMIN, passwd)
|
gnuk.cmd_verify(BY_ADMIN, passwd)
|
||||||
gnuk.cmd_select_openpgp()
|
gnuk.cmd_select_openpgp()
|
||||||
gnuk.cmd_put_data_remove(0x00, 0xc7) # FP_SIG
|
gnuk.cmd_put_data_remove(0x00, 0xc7) # FP_SIG
|
||||||
gnuk.cmd_put_data_remove(0x00, 0xcd) # KGTIME_SIG
|
gnuk.cmd_put_data_remove(0x00, 0xce) # KGTIME_SIG
|
||||||
gnuk.cmd_put_data_key_import_remove(1)
|
gnuk.cmd_put_data_key_import_remove(1)
|
||||||
gnuk.cmd_put_data_remove(0x00, 0xc8) # FP_DEC
|
gnuk.cmd_put_data_remove(0x00, 0xc8) # FP_DEC
|
||||||
gnuk.cmd_put_data_remove(0x00, 0xce) # KGTIME_DEC
|
gnuk.cmd_put_data_remove(0x00, 0xcf) # KGTIME_DEC
|
||||||
gnuk.cmd_put_data_key_import_remove(2)
|
gnuk.cmd_put_data_key_import_remove(2)
|
||||||
gnuk.cmd_put_data_remove(0x00, 0xc9) # FP_AUT
|
gnuk.cmd_put_data_remove(0x00, 0xc9) # FP_AUT
|
||||||
gnuk.cmd_put_data_remove(0x00, 0xcf) # KGTIME_AUT
|
gnuk.cmd_put_data_remove(0x00, 0xd0) # KGTIME_AUT
|
||||||
gnuk.cmd_put_data_key_import_remove(3)
|
gnuk.cmd_put_data_key_import_remove(3)
|
||||||
|
|
||||||
gnuk.connection.disconnect()
|
gnuk.connection.disconnect()
|
||||||
@@ -105,7 +105,7 @@ def main(passwd):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
passwd = DEFAULT_PW3
|
passwd = DEFAULT_PW3
|
||||||
if sys.argv[1] == '-p':
|
if len(sys.argv) > 1 and sys.argv[1] == '-p':
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
passwd = getpass("Admin password: ")
|
passwd = getpass("Admin password: ")
|
||||||
sys.argv.pop(1)
|
sys.argv.pop(1)
|
||||||
|
|||||||
70
tool/gnuk_remove_keys_libusb.py
Executable file
70
tool/gnuk_remove_keys_libusb.py
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
"""
|
||||||
|
gnuk_remove_keys_libusb.py - a tool to remove keys in Gnuk Token
|
||||||
|
|
||||||
|
Copyright (C) 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/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, string
|
||||||
|
|
||||||
|
from gnuk_token import *
|
||||||
|
|
||||||
|
# Assume only single CCID device is attached to computer and it's Gnuk Token
|
||||||
|
|
||||||
|
DEFAULT_PW3 = "12345678"
|
||||||
|
BY_ADMIN = 3
|
||||||
|
|
||||||
|
def main(passwd):
|
||||||
|
gnuk = None
|
||||||
|
for (dev, config, intf) in gnuk_devices():
|
||||||
|
try:
|
||||||
|
gnuk = gnuk_token(dev, config, intf)
|
||||||
|
print "Device: ", dev.filename
|
||||||
|
print "Configuration: ", config.value
|
||||||
|
print "Interface: ", intf.interfaceNumber
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if gnuk.icc_get_status() == 2:
|
||||||
|
raise ValueError, "No ICC present"
|
||||||
|
elif gnuk.icc_get_status() == 1:
|
||||||
|
gnuk.icc_power_on()
|
||||||
|
gnuk.cmd_verify(BY_ADMIN, passwd)
|
||||||
|
gnuk.cmd_select_openpgp()
|
||||||
|
gnuk.cmd_put_data_remove(0x00, 0xc7) # FP_SIG
|
||||||
|
gnuk.cmd_put_data_remove(0x00, 0xce) # KGTIME_SIG
|
||||||
|
gnuk.cmd_put_data_key_import_remove(1)
|
||||||
|
gnuk.cmd_put_data_remove(0x00, 0xc8) # FP_DEC
|
||||||
|
gnuk.cmd_put_data_remove(0x00, 0xcf) # KGTIME_DEC
|
||||||
|
gnuk.cmd_put_data_key_import_remove(2)
|
||||||
|
gnuk.cmd_put_data_remove(0x00, 0xc9) # FP_AUT
|
||||||
|
gnuk.cmd_put_data_remove(0x00, 0xd0) # KGTIME_AUT
|
||||||
|
gnuk.cmd_put_data_key_import_remove(3)
|
||||||
|
gnuk.icc_power_off()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
passwd = DEFAULT_PW3
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == '-p':
|
||||||
|
from getpass import getpass
|
||||||
|
passwd = getpass("Admin password: ")
|
||||||
|
sys.argv.pop(1)
|
||||||
|
main(passwd)
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
gnuk.py - a library for Gnuk Token
|
gnuk_token.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
|
Copyright (C) 2011, 2012, 2013 Free Software Initiative of Japan
|
||||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||||
@@ -22,11 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from struct import *
|
from struct import *
|
||||||
import string
|
import string, binascii
|
||||||
|
import usb, time
|
||||||
# Assume only single CCID device is attached to computer, and it's Gnuk Token
|
|
||||||
|
|
||||||
import usb
|
|
||||||
|
|
||||||
# USB class, subclass, protocol
|
# USB class, subclass, protocol
|
||||||
CCID_CLASS = 0x0B
|
CCID_CLASS = 0x0B
|
||||||
@@ -36,16 +32,24 @@ CCID_PROTOCOL_0 = 0x00
|
|||||||
def icc_compose(msg_type, data_len, slot, seq, param, data):
|
def icc_compose(msg_type, data_len, slot, seq, param, data):
|
||||||
return pack('<BiBBBH', msg_type, data_len, slot, seq, 0, param) + data
|
return pack('<BiBBBH', msg_type, data_len, slot, seq, 0, param) + data
|
||||||
|
|
||||||
def iso7816_compose(ins, p1, p2, data, cls=0x00):
|
def iso7816_compose(ins, p1, p2, data, cls=0x00, le=None):
|
||||||
data_len = len(data)
|
data_len = len(data)
|
||||||
if data_len == 0:
|
if data_len == 0:
|
||||||
return pack('>BBBB', cls, ins, p1, p2)
|
if not le:
|
||||||
|
return pack('>BBBB', cls, ins, p1, p2)
|
||||||
|
else:
|
||||||
|
return pack('>BBBBB', cls, ins, p1, p2, le)
|
||||||
else:
|
else:
|
||||||
return pack('>BBBBB', cls, ins, p1, p2, data_len) + data
|
if not le:
|
||||||
|
return pack('>BBBBB', cls, ins, p1, p2, data_len) + data
|
||||||
|
else:
|
||||||
|
return pack('>BBBBB', cls, ins, p1, p2, data_len) \
|
||||||
|
+ data + pack('>B', le)
|
||||||
|
|
||||||
def list_to_string(l):
|
def list_to_string(l):
|
||||||
return string.join([chr(c) for c in l], '')
|
return string.join([chr(c) for c in l], '')
|
||||||
|
|
||||||
|
# This class only supports Gnuk (for now)
|
||||||
class gnuk_token(object):
|
class gnuk_token(object):
|
||||||
def __init__(self, device, configuration, interface):
|
def __init__(self, device, configuration, interface):
|
||||||
"""
|
"""
|
||||||
@@ -77,6 +81,9 @@ class gnuk_token(object):
|
|||||||
self.__timeout = 10000
|
self.__timeout = 10000
|
||||||
self.__seq = 0
|
self.__seq = 0
|
||||||
|
|
||||||
|
def get_string(self, num):
|
||||||
|
return self.__devhandle.getString(num, 512)
|
||||||
|
|
||||||
def increment_seq(self):
|
def increment_seq(self):
|
||||||
self.__seq = (self.__seq + 1) & 0xff
|
self.__seq = (self.__seq + 1) & 0xff
|
||||||
|
|
||||||
@@ -89,6 +96,50 @@ class gnuk_token(object):
|
|||||||
def release_gnuk(self):
|
def release_gnuk(self):
|
||||||
self.__devhandle.releaseInterface()
|
self.__devhandle.releaseInterface()
|
||||||
|
|
||||||
|
def stop_gnuk(self):
|
||||||
|
self.__devhandle.releaseInterface()
|
||||||
|
self.__devhandle.setConfiguration(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
def mem_info(self):
|
||||||
|
mem = self.__devhandle.controlMsg(requestType = 0xc0, request = 0,
|
||||||
|
value = 0, index = 0, buffer = 8,
|
||||||
|
timeout = 10)
|
||||||
|
start = ((mem[3]*256 + mem[2])*256 + mem[1])*256 + mem[0]
|
||||||
|
end = ((mem[7]*256 + mem[6])*256 + mem[5])*256 + mem[4]
|
||||||
|
return (start, end)
|
||||||
|
|
||||||
|
def download(self, start, data):
|
||||||
|
addr = start
|
||||||
|
addr_end = (start + len(data)) & 0xffffff00
|
||||||
|
i = (addr - 0x20000000) / 0x100
|
||||||
|
j = 0
|
||||||
|
print "start %08x" % addr
|
||||||
|
print "end %08x" % addr_end
|
||||||
|
while addr < addr_end:
|
||||||
|
print "# %08x: %d : %d" % (addr, i, 256)
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 1,
|
||||||
|
value = i, index = 0,
|
||||||
|
buffer = data[j*256:j*256+256],
|
||||||
|
timeout = 10)
|
||||||
|
i = i+1
|
||||||
|
j = j+1
|
||||||
|
addr = addr + 256
|
||||||
|
residue = len(data) % 256
|
||||||
|
if residue != 0:
|
||||||
|
print "# %08x: %d : %d" % (addr, i, residue)
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 1,
|
||||||
|
value = i, index = 0,
|
||||||
|
buffer = data[j*256:],
|
||||||
|
timeout = 10)
|
||||||
|
|
||||||
|
def execute(self, last_addr):
|
||||||
|
i = (last_addr - 0x20000000) / 0x100
|
||||||
|
o = (last_addr - 0x20000000) % 0x100
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 2,
|
||||||
|
value = i, index = o, buffer = None,
|
||||||
|
timeout = 10)
|
||||||
|
|
||||||
def icc_get_result(self):
|
def icc_get_result(self):
|
||||||
msg = self.__devhandle.bulkRead(self.__bulkin, 1024, self.__timeout)
|
msg = self.__devhandle.bulkRead(self.__bulkin, 1024, self.__timeout)
|
||||||
if len(msg) < 10:
|
if len(msg) < 10:
|
||||||
@@ -120,6 +171,7 @@ class gnuk_token(object):
|
|||||||
status, chain, data = self.icc_get_result()
|
status, chain, data = self.icc_get_result()
|
||||||
# XXX: check status, chain
|
# XXX: check status, chain
|
||||||
self.atr = list_to_string(data) # ATR
|
self.atr = list_to_string(data) # ATR
|
||||||
|
return self.atr
|
||||||
|
|
||||||
def icc_power_off(self):
|
def icc_power_off(self):
|
||||||
msg = icc_compose(0x63, 0, 0, self.__seq, 0, "")
|
msg = icc_compose(0x63, 0, 0, self.__seq, 0, "")
|
||||||
@@ -187,7 +239,7 @@ class gnuk_token(object):
|
|||||||
cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, '')
|
cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, '')
|
||||||
sw = self.icc_send_cmd(cmd_data)
|
sw = self.icc_send_cmd(cmd_data)
|
||||||
if len(sw) != 2:
|
if len(sw) != 2:
|
||||||
raise ValueError, sw
|
raise ValueError(sw)
|
||||||
if sw[0] != 0x61:
|
if sw[0] != 0x61:
|
||||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||||
return self.cmd_get_response(sw[1])
|
return self.cmd_get_response(sw[1])
|
||||||
@@ -222,9 +274,9 @@ class gnuk_token(object):
|
|||||||
if cmd_data1:
|
if cmd_data1:
|
||||||
sw = self.icc_send_cmd(cmd_data1)
|
sw = self.icc_send_cmd(cmd_data1)
|
||||||
if len(sw) != 2:
|
if len(sw) != 2:
|
||||||
raise ValueError("cmd_write_binary", sw)
|
raise ValueError("cmd_write_binary 1", sw)
|
||||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||||
raise ValueError("cmd_write_binary", "%02x%02x" % (sw[0], sw[1]))
|
raise ValueError("cmd_write_binary 1", "%02x%02x" % (sw[0], sw[1]))
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
def cmd_select_openpgp(self):
|
def cmd_select_openpgp(self):
|
||||||
@@ -360,13 +412,158 @@ class gnuk_token(object):
|
|||||||
pk = self.cmd_get_response(sw[1])
|
pk = self.cmd_get_response(sw[1])
|
||||||
return (pk[9:9+256], pk[9+256+2:9+256+2+3])
|
return (pk[9:9+256], pk[9+256+2:9+256+2+3])
|
||||||
|
|
||||||
|
def cmd_put_data_remove(self, tagh, tagl):
|
||||||
|
cmd_data = iso7816_compose(0xda, tagh, tagl, "")
|
||||||
|
sw = self.icc_send_cmd(cmd_data)
|
||||||
|
if sw[0] != 0x90 and sw[1] != 0x00:
|
||||||
|
raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
|
||||||
|
|
||||||
|
def cmd_put_data_key_import_remove(self, keyno):
|
||||||
|
if keyno == 1:
|
||||||
|
keyspec = "\xb6\x00" # SIG
|
||||||
|
elif keyno == 2:
|
||||||
|
keyspec = "\xb8\x00" # DEC
|
||||||
|
else:
|
||||||
|
keyspec = "\xa4\x00" # AUT
|
||||||
|
cmd_data = iso7816_compose(0xdb, 0x3f, 0xff, "\x4d\x02" + keyspec)
|
||||||
|
sw = self.icc_send_cmd(cmd_data)
|
||||||
|
if sw[0] != 0x90 and sw[1] != 0x00:
|
||||||
|
raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
|
||||||
|
|
||||||
|
def cmd_get_challenge(self):
|
||||||
|
cmd_data = iso7816_compose(0x84, 0x00, 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_external_authenticate(self, keyno, signed):
|
||||||
|
cmd_data = iso7816_compose(0x82, 0x00, keyno, signed[0:128], cls=0x10)
|
||||||
|
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]))
|
||||||
|
cmd_data = iso7816_compose(0x82, 0x00, keyno, signed[128:])
|
||||||
|
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]))
|
||||||
|
|
||||||
|
|
||||||
|
class regnual(object):
|
||||||
|
def __init__(self, dev):
|
||||||
|
conf = dev.configurations[0]
|
||||||
|
intf_alt = conf.interfaces[0]
|
||||||
|
intf = intf_alt[0]
|
||||||
|
if intf.interfaceClass != 0xff:
|
||||||
|
raise ValueError("Wrong interface class")
|
||||||
|
self.__devhandle = dev.open()
|
||||||
|
try:
|
||||||
|
self.__devhandle.setConfiguration(conf)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.__devhandle.claimInterface(intf)
|
||||||
|
self.__devhandle.setAltInterface(intf)
|
||||||
|
|
||||||
|
def mem_info(self):
|
||||||
|
mem = self.__devhandle.controlMsg(requestType = 0xc0, request = 0,
|
||||||
|
value = 0, index = 0, buffer = 8,
|
||||||
|
timeout = 10000)
|
||||||
|
start = ((mem[3]*256 + mem[2])*256 + mem[1])*256 + mem[0]
|
||||||
|
end = ((mem[7]*256 + mem[6])*256 + mem[5])*256 + mem[4]
|
||||||
|
return (start, end)
|
||||||
|
|
||||||
|
def download(self, start, data):
|
||||||
|
addr = start
|
||||||
|
addr_end = (start + len(data)) & 0xffffff00
|
||||||
|
i = (addr - 0x08000000) / 0x100
|
||||||
|
j = 0
|
||||||
|
print "start %08x" % addr
|
||||||
|
print "end %08x" % addr_end
|
||||||
|
while addr < addr_end:
|
||||||
|
print "# %08x: %d: %d : %d" % (addr, i, j, 256)
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 1,
|
||||||
|
value = 0, index = 0,
|
||||||
|
buffer = data[j*256:j*256+256],
|
||||||
|
timeout = 10000)
|
||||||
|
crc32code = crc32(data[j*256:j*256+256])
|
||||||
|
res = self.__devhandle.controlMsg(requestType = 0xc0, request = 2,
|
||||||
|
value = 0, index = 0, buffer = 4,
|
||||||
|
timeout = 10000)
|
||||||
|
r_value = ((res[3]*256 + res[2])*256 + res[1])*256 + res[0]
|
||||||
|
if (crc32code ^ r_value) != 0xffffffff:
|
||||||
|
print "failure"
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 3,
|
||||||
|
value = i, index = 0,
|
||||||
|
buffer = None,
|
||||||
|
timeout = 10000)
|
||||||
|
time.sleep(0.010)
|
||||||
|
res = self.__devhandle.controlMsg(requestType = 0xc0, request = 2,
|
||||||
|
value = 0, index = 0, buffer = 4,
|
||||||
|
timeout = 10000)
|
||||||
|
r_value = ((res[3]*256 + res[2])*256 + res[1])*256 + res[0]
|
||||||
|
if r_value == 0:
|
||||||
|
print "failure"
|
||||||
|
i = i+1
|
||||||
|
j = j+1
|
||||||
|
addr = addr + 256
|
||||||
|
residue = len(data) % 256
|
||||||
|
if residue != 0:
|
||||||
|
print "# %08x: %d : %d" % (addr, i, residue)
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 1,
|
||||||
|
value = 0, index = 0,
|
||||||
|
buffer = data[j*256:],
|
||||||
|
timeout = 10000)
|
||||||
|
crc32code = crc32(data[j*256:].ljust(256,chr(255)))
|
||||||
|
res = self.__devhandle.controlMsg(requestType = 0xc0, request = 2,
|
||||||
|
value = 0, index = 0, buffer = 4,
|
||||||
|
timeout = 10000)
|
||||||
|
r_value = ((res[3]*256 + res[2])*256 + res[1])*256 + res[0]
|
||||||
|
if (crc32code ^ r_value) != 0xffffffff:
|
||||||
|
print "failure"
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 3,
|
||||||
|
value = i, index = 0,
|
||||||
|
buffer = None,
|
||||||
|
timeout = 10000)
|
||||||
|
time.sleep(0.010)
|
||||||
|
res = self.__devhandle.controlMsg(requestType = 0xc0, request = 2,
|
||||||
|
value = 0, index = 0, buffer = 4,
|
||||||
|
timeout = 10000)
|
||||||
|
r_value = ((res[3]*256 + res[2])*256 + res[1])*256 + res[0]
|
||||||
|
if r_value == 0:
|
||||||
|
print "failure"
|
||||||
|
|
||||||
|
def protect(self):
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 4,
|
||||||
|
value = 0, index = 0, buffer = None,
|
||||||
|
timeout = 10000)
|
||||||
|
time.sleep(0.100)
|
||||||
|
res = self.__devhandle.controlMsg(requestType = 0xc0, request = 2,
|
||||||
|
value = 0, index = 0, buffer = 4,
|
||||||
|
timeout = 10000)
|
||||||
|
r_value = ((res[3]*256 + res[2])*256 + res[1])*256 + res[0]
|
||||||
|
if r_value == 0:
|
||||||
|
print "protection failure"
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.__devhandle.controlMsg(requestType = 0x40, request = 5,
|
||||||
|
value = 0, index = 0, buffer = None,
|
||||||
|
timeout = 10000)
|
||||||
|
|
||||||
|
def reset_device(self):
|
||||||
|
try:
|
||||||
|
self.__devhandle.reset()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def compare(data_original, data_in_device):
|
def compare(data_original, data_in_device):
|
||||||
i = 0
|
if data_original == data_in_device:
|
||||||
for d in data_original:
|
return True
|
||||||
if ord(d) != data_in_device[i]:
|
raise ValueError("verify failed")
|
||||||
raise ValueError, "verify failed at %08x" % i
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
def gnuk_devices():
|
def gnuk_devices():
|
||||||
busses = usb.busses()
|
busses = usb.busses()
|
||||||
@@ -381,6 +578,20 @@ def gnuk_devices():
|
|||||||
alt.interfaceProtocol == CCID_PROTOCOL_0:
|
alt.interfaceProtocol == CCID_PROTOCOL_0:
|
||||||
yield dev, config, alt
|
yield dev, config, alt
|
||||||
|
|
||||||
|
USB_VENDOR_FSIJ=0x234b
|
||||||
|
USB_PRODUCT_GNUK=0x0000
|
||||||
|
|
||||||
|
def gnuk_devices_by_vidpid():
|
||||||
|
busses = usb.busses()
|
||||||
|
for bus in busses:
|
||||||
|
devices = bus.devices
|
||||||
|
for dev in devices:
|
||||||
|
if dev.idVendor != USB_VENDOR_FSIJ:
|
||||||
|
continue
|
||||||
|
if dev.idProduct != USB_PRODUCT_GNUK:
|
||||||
|
continue
|
||||||
|
yield dev
|
||||||
|
|
||||||
def get_gnuk_device():
|
def get_gnuk_device():
|
||||||
icc = None
|
icc = None
|
||||||
for (dev, config, intf) in gnuk_devices():
|
for (dev, config, intf) in gnuk_devices():
|
||||||
@@ -402,3 +613,12 @@ def get_gnuk_device():
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Unknown ICC status", status)
|
raise ValueError("Unknown ICC status", status)
|
||||||
return icc
|
return icc
|
||||||
|
|
||||||
|
SHA256_OID_PREFIX="3031300d060960864801650304020105000420"
|
||||||
|
|
||||||
|
def UNSIGNED(n):
|
||||||
|
return n & 0xffffffff
|
||||||
|
|
||||||
|
def crc32(bytestr):
|
||||||
|
crc = binascii.crc32(bytestr)
|
||||||
|
return UNSIGNED(crc)
|
||||||
@@ -25,7 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
from struct import *
|
from struct import *
|
||||||
import sys, time, os, binascii, string
|
import sys, time, os, binascii, string
|
||||||
|
|
||||||
# INPUT: binary file
|
# INPUT: binary files (regnual_image, upgrade_firmware_image)
|
||||||
|
|
||||||
# Assume only single CCID device is attached to computer, and it's Gnuk Token
|
# Assume only single CCID device is attached to computer, and it's Gnuk Token
|
||||||
|
|
||||||
@@ -390,11 +390,18 @@ from subprocess import check_output
|
|||||||
|
|
||||||
SHA256_OID_PREFIX="3031300d060960864801650304020105000420"
|
SHA256_OID_PREFIX="3031300d060960864801650304020105000420"
|
||||||
|
|
||||||
def gpg_sign(hash):
|
# When user specify KEYGRIP, use it. Or else, connect to SCD directly.
|
||||||
result = check_output(["gpg-connect-agent",
|
def gpg_sign(keygrip, hash):
|
||||||
"SCD SETDATA " + SHA256_OID_PREFIX + hash,
|
if keygrip:
|
||||||
"SCD PKAUTH OPENPGP.3",
|
result = check_output(["gpg-connect-agent",
|
||||||
"/bye"])
|
"SIGKEY %s" % keygrip,
|
||||||
|
"SETHASH --hash=sha256 %s" % hash,
|
||||||
|
"PKSIGN --hash=sha256", "/bye"])
|
||||||
|
else:
|
||||||
|
result = check_output(["gpg-connect-agent",
|
||||||
|
"SCD SETDATA " + SHA256_OID_PREFIX + hash,
|
||||||
|
"SCD PKAUTH OPENPGP.3",
|
||||||
|
"/bye"])
|
||||||
signed = ""
|
signed = ""
|
||||||
while True:
|
while True:
|
||||||
i = result.find('%')
|
i = result.find('%')
|
||||||
@@ -406,8 +413,12 @@ def gpg_sign(hash):
|
|||||||
signed += chr(int(hex_str,16))
|
signed += chr(int(hex_str,16))
|
||||||
result = result[i+3:]
|
result = result[i+3:]
|
||||||
|
|
||||||
pos = signed.index("D ") + 2
|
if keygrip:
|
||||||
signed = signed[pos:-4] # \nOK\n
|
pos = signed.index("D (7:sig-val(3:rsa(1:s256:") + 26
|
||||||
|
signed = signed[pos:-7]
|
||||||
|
else:
|
||||||
|
pos = signed.index("D ") + 2
|
||||||
|
signed = signed[pos:-4] # \nOK\n
|
||||||
if len(signed) != 256:
|
if len(signed) != 256:
|
||||||
raise ValueError, binascii.hexlify(signed)
|
raise ValueError, binascii.hexlify(signed)
|
||||||
return signed
|
return signed
|
||||||
@@ -419,7 +430,7 @@ def crc32(bytestr):
|
|||||||
crc = binascii.crc32(bytestr)
|
crc = binascii.crc32(bytestr)
|
||||||
return UNSIGNED(crc)
|
return UNSIGNED(crc)
|
||||||
|
|
||||||
def main(data_regnual, data_upgrade):
|
def main(keygrip, data_regnual, data_upgrade):
|
||||||
l = len(data_regnual)
|
l = len(data_regnual)
|
||||||
if (l & 0x03) != 0:
|
if (l & 0x03) != 0:
|
||||||
data_regnual = data_regnual.ljust(l + 4 - (l & 0x03), chr(0))
|
data_regnual = data_regnual.ljust(l + 4 - (l & 0x03), chr(0))
|
||||||
@@ -441,7 +452,7 @@ def main(data_regnual, data_upgrade):
|
|||||||
icc.icc_power_on()
|
icc.icc_power_on()
|
||||||
icc.cmd_select_openpgp()
|
icc.cmd_select_openpgp()
|
||||||
challenge = icc.cmd_get_challenge()
|
challenge = icc.cmd_get_challenge()
|
||||||
signed = gpg_sign(binascii.hexlify(to_string(challenge)))
|
signed = gpg_sign(keygrip, binascii.hexlify(to_string(challenge)))
|
||||||
icc.cmd_external_authenticate(signed)
|
icc.cmd_external_authenticate(signed)
|
||||||
icc.stop_gnuk()
|
icc.stop_gnuk()
|
||||||
mem_info = icc.mem_info()
|
mem_info = icc.mem_info()
|
||||||
@@ -478,6 +489,11 @@ def main(data_regnual, data_upgrade):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
keygrip = None
|
||||||
|
if sys.argv[1] == '-k':
|
||||||
|
sys.argv.pop(1)
|
||||||
|
keygrip = sys.argv[1]
|
||||||
|
sys.argv.pop(1)
|
||||||
filename_regnual = sys.argv[1]
|
filename_regnual = sys.argv[1]
|
||||||
filename_upgrade = sys.argv[2]
|
filename_upgrade = sys.argv[2]
|
||||||
f = open(filename_regnual)
|
f = open(filename_regnual)
|
||||||
@@ -488,4 +504,4 @@ if __name__ == '__main__':
|
|||||||
data_upgrade = f.read()
|
data_upgrade = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
print "%s: %d" % (filename_upgrade, len(data_upgrade))
|
print "%s: %d" % (filename_upgrade, len(data_upgrade))
|
||||||
main(data_regnual, data_upgrade[4096:])
|
main(keygrip, data_regnual, data_upgrade[4096:])
|
||||||
|
|||||||
125
tool/gpg_agent.py
Normal file
125
tool/gpg_agent.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
"""
|
||||||
|
gpg_agent.py - a library to connect gpg-agent
|
||||||
|
|
||||||
|
Copyright (C) 2013 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/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import platform, os, socket
|
||||||
|
IS_WINDOWS=(platform.system() == 'Windows')
|
||||||
|
|
||||||
|
BUFLEN=1024
|
||||||
|
|
||||||
|
class gpg_agent(object):
|
||||||
|
def __init__(self):
|
||||||
|
if IS_WINDOWS:
|
||||||
|
home = os.getenv("HOME")
|
||||||
|
if not home:
|
||||||
|
home = os.getenv("APPDATA")
|
||||||
|
comm_port = os.path.join(home, "gnupg", "S.gpg-agent")
|
||||||
|
#
|
||||||
|
f = open(comm_port, "rb", 0)
|
||||||
|
infostr = f.read()
|
||||||
|
f.close()
|
||||||
|
#
|
||||||
|
info = infostr.split('\n', 1)
|
||||||
|
port = int(info[0])
|
||||||
|
nonce = info[1]
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.connect(("localhost", port))
|
||||||
|
s.send(nonce)
|
||||||
|
else:
|
||||||
|
infostr = os.getenv("GPG_AGENT_INFO")
|
||||||
|
info = infostr.split(':', 2)
|
||||||
|
path = info[0]
|
||||||
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
s.connect(path)
|
||||||
|
self.sock = s
|
||||||
|
self.buf_remained = ""
|
||||||
|
self.response = None
|
||||||
|
|
||||||
|
def read_line(self):
|
||||||
|
line = ""
|
||||||
|
if self.buf_remained != "":
|
||||||
|
chunk = self.buf_remained
|
||||||
|
else:
|
||||||
|
chunk = self.sock.recv(BUFLEN)
|
||||||
|
while True:
|
||||||
|
pos = chunk.find('\n')
|
||||||
|
if pos >= 0:
|
||||||
|
self.buf_remained = chunk[pos+1:]
|
||||||
|
line = line + chunk[0:pos]
|
||||||
|
return line
|
||||||
|
else:
|
||||||
|
line = line + chunk
|
||||||
|
chunk = self.sock.recv(BUFLEN)
|
||||||
|
|
||||||
|
def get_response(self):
|
||||||
|
r = self.response
|
||||||
|
result = ""
|
||||||
|
while True:
|
||||||
|
i = r.find('%')
|
||||||
|
if i < 0:
|
||||||
|
result += r
|
||||||
|
break
|
||||||
|
hex_str = r[i+1:i+3]
|
||||||
|
result += r[0:i]
|
||||||
|
result += chr(int(hex_str,16))
|
||||||
|
r = r[i+3:]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def send_command(self, cmd):
|
||||||
|
self.sock.send(cmd)
|
||||||
|
self.response = ""
|
||||||
|
while True:
|
||||||
|
while True:
|
||||||
|
l = self.read_line()
|
||||||
|
if l[0] != '#':
|
||||||
|
break
|
||||||
|
if l[0] == 'D':
|
||||||
|
self.response += l[2:]
|
||||||
|
elif l[0] == 'O' and l[1] == 'K':
|
||||||
|
return True
|
||||||
|
elif l[0] == 'E' and l[1] == 'R' and l[2] == 'R':
|
||||||
|
return False
|
||||||
|
else: # XXX: S, INQUIRE, END
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.sock.send('BYE\n')
|
||||||
|
bye = self.read_line()
|
||||||
|
self.sock.close()
|
||||||
|
return bye # "OK closing connection"
|
||||||
|
|
||||||
|
# Test
|
||||||
|
if __name__ == '__main__':
|
||||||
|
g = gpg_agent()
|
||||||
|
print g.read_line()
|
||||||
|
print g.send_command("KEYINFO --list --data\n")
|
||||||
|
kl_str = g.get_response()
|
||||||
|
kl_str = kl_str[0:-1]
|
||||||
|
kl = kl_str.split('\n')
|
||||||
|
import re
|
||||||
|
kl_o3 = [kg for kg in kl if re.search("OPENPGP\\.3", kg)]
|
||||||
|
print kl_o3
|
||||||
|
kg = kl_o3[0].split(' ')[0]
|
||||||
|
print g.send_command("READKEY %s\n" % kg)
|
||||||
|
r = g.get_response()
|
||||||
|
import binascii
|
||||||
|
print binascii.hexlify(r)
|
||||||
|
print g.close()
|
||||||
@@ -167,7 +167,7 @@ if __name__ == '__main__':
|
|||||||
option = sys.argv[1]
|
option = sys.argv[1]
|
||||||
sys.argv.pop(1)
|
sys.argv.pop(1)
|
||||||
if option == '-h':
|
if option == '-h':
|
||||||
if bunum != None or devnum != None:
|
if busnum != None or devnum != None:
|
||||||
exit_with_usage(sys.argv[0])
|
exit_with_usage(sys.argv[0])
|
||||||
hub = int(sys.argv[1])
|
hub = int(sys.argv[1])
|
||||||
sys.argv.pop(1)
|
sys.argv.pop(1)
|
||||||
|
|||||||
205
tool/pageant_proxy_to_gpg.py
Normal file
205
tool/pageant_proxy_to_gpg.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
"""
|
||||||
|
pagent_proxy_to_gpg.py - Connect gpg-agent as Pagent
|
||||||
|
|
||||||
|
Copyright (C) 2013 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/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys, re, hashlib, binascii
|
||||||
|
from struct import *
|
||||||
|
from gpg_agent import gpg_agent
|
||||||
|
from sexp import sexp
|
||||||
|
|
||||||
|
# Assume it's only OPENPGP.3 key and it's 2048-bit
|
||||||
|
|
||||||
|
def debug(string):
|
||||||
|
print "DEBUG: %s" % string
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def get_keygrip_list(keyinfo_result):
|
||||||
|
kl_str = keyinfo_result[0:-1] # Chop last newline
|
||||||
|
kl = kl_str.split('\n')
|
||||||
|
# filter by "OPENPGP.3", and only keygrip
|
||||||
|
return [kg.split(' ')[0] for kg in kl if re.search("OPENPGP\\.3", kg)]
|
||||||
|
|
||||||
|
# Connect GPG-Agent, and get list of KEYGRIPs.
|
||||||
|
g = gpg_agent()
|
||||||
|
g.read_line() # Greeting message
|
||||||
|
|
||||||
|
g.send_command('KEYINFO --list --data\n')
|
||||||
|
keyinfo_result = g.get_response()
|
||||||
|
keygrip_list = get_keygrip_list(keyinfo_result)
|
||||||
|
|
||||||
|
debug(keygrip_list)
|
||||||
|
|
||||||
|
keylist = []
|
||||||
|
# For each KEYGRIP, get its PUBLIC-KEY.
|
||||||
|
for kg in keygrip_list:
|
||||||
|
g.send_command('READKEY %s\n' % kg)
|
||||||
|
key = sexp(g.get_response())
|
||||||
|
# [ "public-key" [ "rsa" [ "n" MODULUS ] [ "e" EXPONENT] ] ]
|
||||||
|
n = key[1][1][1]
|
||||||
|
e = key[1][2][1]
|
||||||
|
debug(binascii.hexlify(n))
|
||||||
|
debug(binascii.hexlify(e))
|
||||||
|
keylist.append([n, e, kg])
|
||||||
|
|
||||||
|
# FIXME: should handle all keys, not only a single key
|
||||||
|
# FIXME: should support different key size
|
||||||
|
n = keylist[0][0]
|
||||||
|
e = keylist[0][1]
|
||||||
|
keygrip = keylist[0][2]
|
||||||
|
|
||||||
|
ssh_rsa_public_blob = "\x00\x00\x00\x07ssh-rsa" + \
|
||||||
|
"\x00\x00\x00\x03" + e + "\x00\x00\x01\x01" + n
|
||||||
|
|
||||||
|
ssh_key_comment = "key_on_gpg" # XXX: get login from card for comment?
|
||||||
|
|
||||||
|
import win32con, win32api, win32gui, ctypes, ctypes.wintypes
|
||||||
|
|
||||||
|
|
||||||
|
# For WM_COPYDATA structure
|
||||||
|
class COPYDATA(ctypes.Structure):
|
||||||
|
_fields_ = [ ('dwData', ctypes.wintypes.LPARAM),
|
||||||
|
('cbData', ctypes.wintypes.DWORD),
|
||||||
|
('lpData', ctypes.c_void_p) ]
|
||||||
|
|
||||||
|
P_COPYDATA = ctypes.POINTER(COPYDATA)
|
||||||
|
|
||||||
|
class SSH_MSG_HEAD(ctypes.BigEndianStructure):
|
||||||
|
_pack_ = 1
|
||||||
|
_fields_ = [ ('msg_len', ctypes.c_uint32),
|
||||||
|
('msg_type', ctypes.c_byte) ]
|
||||||
|
|
||||||
|
P_SSH_MSG_HEAD = ctypes.POINTER(SSH_MSG_HEAD)
|
||||||
|
|
||||||
|
class SSH_MSG_ID_ANSWER_HEAD(ctypes.BigEndianStructure):
|
||||||
|
_pack_ = 1
|
||||||
|
_fields_ = [ ('msg_len', ctypes.c_uint32),
|
||||||
|
('msg_type', ctypes.c_byte),
|
||||||
|
('keys', ctypes.c_uint32)]
|
||||||
|
|
||||||
|
P_SSH_MSG_ID_ANSWER = ctypes.POINTER(SSH_MSG_ID_ANSWER_HEAD)
|
||||||
|
|
||||||
|
class SSH_MSG_SIGN_RESPONSE_HEAD(ctypes.BigEndianStructure):
|
||||||
|
_pack_ = 1
|
||||||
|
_fields_ = [ ('msg_len', ctypes.c_uint32),
|
||||||
|
('msg_type', ctypes.c_byte),
|
||||||
|
('sig_len', ctypes.c_uint32)]
|
||||||
|
|
||||||
|
P_SSH_MSG_SIGN_RESPONSE = ctypes.POINTER(SSH_MSG_SIGN_RESPONSE_HEAD)
|
||||||
|
|
||||||
|
|
||||||
|
FILE_MAP_ALL_ACCESS=0x000F001F
|
||||||
|
|
||||||
|
class windows_ipc_listener(object):
|
||||||
|
def __init__(self):
|
||||||
|
message_map = { win32con.WM_COPYDATA: self.OnCopyData }
|
||||||
|
wc = win32gui.WNDCLASS()
|
||||||
|
wc.lpfnWndProc = message_map
|
||||||
|
wc.lpszClassName = 'Pageant'
|
||||||
|
hinst = wc.hInstance = win32api.GetModuleHandle(None)
|
||||||
|
classAtom = win32gui.RegisterClass(wc)
|
||||||
|
self.hwnd = win32gui.CreateWindow (
|
||||||
|
classAtom,
|
||||||
|
"Pageant",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
win32con.CW_USEDEFAULT,
|
||||||
|
win32con.CW_USEDEFAULT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
hinst,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
debug("created: window=%08x" % self.hwnd)
|
||||||
|
|
||||||
|
def OnCopyData(self, hwnd, msg, wparam, lparam):
|
||||||
|
debug("WM_COPYDATA message")
|
||||||
|
debug(" window=%08x" % hwnd)
|
||||||
|
debug(" msg =%08x" % msg)
|
||||||
|
debug(" wparam=%08x" % wparam)
|
||||||
|
pCDS = ctypes.cast(lparam, P_COPYDATA)
|
||||||
|
debug(" dwData=%08x" % (pCDS.contents.dwData & 0xffffffff))
|
||||||
|
debug(" len=%d" % pCDS.contents.cbData)
|
||||||
|
mapname = ctypes.string_at(pCDS.contents.lpData)
|
||||||
|
debug(" mapname='%s'" % ctypes.string_at(pCDS.contents.lpData))
|
||||||
|
hMapObject = ctypes.windll.kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, 0, mapname)
|
||||||
|
if hMapObject == 0:
|
||||||
|
debug("error on OpenFileMapping")
|
||||||
|
return 0
|
||||||
|
pBuf = ctypes.windll.kernel32.MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, 0)
|
||||||
|
if pBuf == 0:
|
||||||
|
ctypes.windll.kernel32.CloseHandle(hMapObject)
|
||||||
|
debug("error on MapViewOfFile")
|
||||||
|
return 0
|
||||||
|
pSshMsg = ctypes.cast(pBuf, P_SSH_MSG_HEAD)
|
||||||
|
debug(" ssh_msg_len: %d" % pSshMsg.contents.msg_len)
|
||||||
|
debug(" ssh_msg_type: %d" % pSshMsg.contents.msg_type)
|
||||||
|
if pSshMsg.contents.msg_type == 11: # SSH2_AGENT_REQUEST_IDENTITIES
|
||||||
|
blob_len = len(ssh_rsa_public_blob)
|
||||||
|
cmnt_len = len(ssh_key_comment)
|
||||||
|
pAns = ctypes.cast(pBuf, P_SSH_MSG_ID_ANSWER)
|
||||||
|
pAns.contents.msg_len = 1+4+4+blob_len+4+cmnt_len
|
||||||
|
pAns.contents.msg_type = 12 # SSH2_AGENT_IDENTITIES_ANSWER
|
||||||
|
pAns.contents.keys = 1
|
||||||
|
ctypes.memmove(pBuf+4+1+4, pack('>I', blob_len), 4)
|
||||||
|
ctypes.memmove(pBuf+4+1+4+4, ssh_rsa_public_blob, blob_len)
|
||||||
|
ctypes.memmove(pBuf+4+1+4+4+blob_len, pack('>I', cmnt_len), 4)
|
||||||
|
ctypes.memmove(pBuf+4+1+4+4+blob_len+4, ssh_key_comment, cmnt_len)
|
||||||
|
|
||||||
|
debug("answer is:")
|
||||||
|
debug(" ssh_msg_len: %d" % pSshMsg.contents.msg_len)
|
||||||
|
debug(" ssh_msg_type: %d" % pSshMsg.contents.msg_type)
|
||||||
|
elif pSshMsg.contents.msg_type == 13: # SSH2_AGENT_SIGN_REQUEST
|
||||||
|
req_blob_len = unpack(">I", ctypes.string_at(pBuf+5, 4))[0]
|
||||||
|
req_blob = ctypes.string_at(pBuf+5+4, req_blob_len)
|
||||||
|
req_data_len = unpack(">I", ctypes.string_at(pBuf+5+4+req_blob_len,4))[0]
|
||||||
|
req_data = ctypes.string_at(pBuf+5+4+req_blob_len+4,req_data_len)
|
||||||
|
debug(" blob_len=%d" % req_blob_len)
|
||||||
|
debug(" data_len=%d" % req_data_len)
|
||||||
|
hash = hashlib.sha1(req_data).hexdigest()
|
||||||
|
debug(" hash=%s" % hash)
|
||||||
|
g.send_command('SIGKEY %s\n' % keygrip)
|
||||||
|
g.send_command('SETHASH --hash=sha1 %s\n' % hash)
|
||||||
|
g.send_command('PKSIGN\n')
|
||||||
|
sig = sexp(g.get_response())
|
||||||
|
# [ "sig-val" [ "rsa" [ "s" "xxx" ] ] ]
|
||||||
|
sig = sig[1][1][1]
|
||||||
|
sig = "\x00\x00\x00\x07" + "ssh-rsa" + "\x00\x00\x01\x00" + sig # FIXME: should support different key size
|
||||||
|
siglen = len(sig)
|
||||||
|
debug("sig_len=%d" % siglen)
|
||||||
|
debug("sig=%s" % binascii.hexlify(sig))
|
||||||
|
pRes = ctypes.cast(pBuf, P_SSH_MSG_SIGN_RESPONSE)
|
||||||
|
pRes.contents.msg_len = 1+4+siglen
|
||||||
|
pRes.contents.msg_type = 14 # SSH2_AGENT_SIGN_RESPONSE
|
||||||
|
pRes.contents.sig_len = siglen
|
||||||
|
ctypes.memmove(pBuf+4+1+4, sig, siglen)
|
||||||
|
debug("answer is:")
|
||||||
|
debug(" ssh_msg_len: %d" % pSshMsg.contents.msg_len)
|
||||||
|
debug(" ssh_msg_type: %d" % pSshMsg.contents.msg_type)
|
||||||
|
else:
|
||||||
|
exit(0)
|
||||||
|
ctypes.windll.kernel32.UnmapViewOfFile(pBuf)
|
||||||
|
ctypes.windll.kernel32.CloseHandle(hMapObject)
|
||||||
|
debug(" ssh_msg: done")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
l = windows_ipc_listener()
|
||||||
|
win32gui.PumpMessages()
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
pinpadtest.py - a tool to test variable length pin entry with pinpad
|
pinpadtest.py - a tool to test variable length pin entry with pinpad
|
||||||
|
|
||||||
Copyright (C) 2011, 2012 Free Software Initiative of Japan
|
Copyright (C) 2011, 2012, 2013 Free Software Initiative of Japan
|
||||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||||
|
|
||||||
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||||
@@ -33,6 +33,7 @@ from smartcard.util import toHexString
|
|||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
|
|
||||||
CM_IOCTL_GET_FEATURE_REQUEST = (0x42000000 + 3400)
|
CM_IOCTL_GET_FEATURE_REQUEST = (0x42000000 + 3400)
|
||||||
|
CM_IOCTL_VENDOR_IFD_EXCHANGE = (0x42000000 + 1)
|
||||||
FEATURE_VERIFY_PIN_DIRECT = 0x06
|
FEATURE_VERIFY_PIN_DIRECT = 0x06
|
||||||
FEATURE_MODIFY_PIN_DIRECT = 0x07
|
FEATURE_MODIFY_PIN_DIRECT = 0x07
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ def confirm_pin_setting(single_step):
|
|||||||
return 0x03 # bConfirmPIN: old PIN and new PIN twice
|
return 0x03 # bConfirmPIN: old PIN and new PIN twice
|
||||||
|
|
||||||
class Card(object):
|
class Card(object):
|
||||||
def __init__(self, add_a_byte, pinmin, pinmax):
|
def __init__(self, add_a_byte, pinmin, pinmax, fixed):
|
||||||
cardtype = AnyCardType()
|
cardtype = AnyCardType()
|
||||||
cardrequest = CardRequest(timeout=10, cardType=cardtype)
|
cardrequest = CardRequest(timeout=10, cardType=cardtype)
|
||||||
cardservice = cardrequest.waitforcard()
|
cardservice = cardrequest.waitforcard()
|
||||||
@@ -61,6 +62,7 @@ class Card(object):
|
|||||||
self.another_byte = add_a_byte
|
self.another_byte = add_a_byte
|
||||||
self.pinmin = pinmin
|
self.pinmin = pinmin
|
||||||
self.pinmax = pinmax
|
self.pinmax = pinmax
|
||||||
|
self.fixed = fixed
|
||||||
|
|
||||||
def get_features(self):
|
def get_features(self):
|
||||||
p = self.connection.control(CM_IOCTL_GET_FEATURE_REQUEST, [])
|
p = self.connection.control(CM_IOCTL_GET_FEATURE_REQUEST, [])
|
||||||
@@ -96,12 +98,21 @@ class Card(object):
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def cmd_vega_alpha_disable_empty_verify(self):
|
||||||
|
apdu = [ 0xB5, # -|
|
||||||
|
0x01, # | Pre-command parameters
|
||||||
|
0x00, # -|
|
||||||
|
0x03, # retry counter value (fixed value)
|
||||||
|
0x00 # enable 3s timeout
|
||||||
|
]
|
||||||
|
data = self.connection.control(CM_IOCTL_VENDOR_IFD_EXCHANGE, apdu)
|
||||||
|
|
||||||
def cmd_verify_pinpad(self, who):
|
def cmd_verify_pinpad(self, who):
|
||||||
apdu = [0x00, 0x20, 0x00, 0x80+who ]
|
apdu = [0x00, 0x20, 0x00, 0x80+who ]
|
||||||
pin_verify = [ 0x00, # bTimeOut
|
pin_verify = [ 0x00, # bTimeOut
|
||||||
0x00, # bTimeOut2
|
0x00, # bTimeOut2
|
||||||
0x82, # bmFormatString: Byte, pos=0, left, ASCII.
|
0x82, # bmFormatString: Byte, pos=0, left, ASCII.
|
||||||
0x00, # bmPINBlockString
|
self.fixed, # bmPINBlockString
|
||||||
0x00, # bmPINLengthFormat
|
0x00, # bmPINLengthFormat
|
||||||
self.pinmax, # wPINMaxExtraDigit Low (PINmax)
|
self.pinmax, # wPINMaxExtraDigit Low (PINmax)
|
||||||
self.pinmin, # wPINMaxExtraDigit High (PINmin)
|
self.pinmin, # wPINMaxExtraDigit High (PINmin)
|
||||||
@@ -112,9 +123,13 @@ class Card(object):
|
|||||||
0x00, # bMsgIndex
|
0x00, # bMsgIndex
|
||||||
0x00, # bTeoPrologue[0]
|
0x00, # bTeoPrologue[0]
|
||||||
0x00, # bTeoPrologue[1]
|
0x00, # bTeoPrologue[1]
|
||||||
0x00 # bTeoPrologue[2]
|
|
||||||
]
|
]
|
||||||
apdu += self.possibly_add_dummy_byte()
|
if self.fixed > 0:
|
||||||
|
apdu += [ self.fixed ]
|
||||||
|
apdu += [ 255 ] * self.fixed
|
||||||
|
else:
|
||||||
|
apdu += self.possibly_add_dummy_byte()
|
||||||
|
pin_verify += [ len(apdu) ] # bTeoPrologue[2]
|
||||||
pin_verify += [ len(apdu), 0, 0, 0 ] + apdu
|
pin_verify += [ len(apdu), 0, 0, 0 ] + apdu
|
||||||
data = self.connection.control(self.verify_ioctl,pin_verify)
|
data = self.connection.control(self.verify_ioctl,pin_verify)
|
||||||
sw1 = data[0]
|
sw1 = data[0]
|
||||||
@@ -128,10 +143,10 @@ class Card(object):
|
|||||||
pin_modify = [ 0x00, # bTimerOut
|
pin_modify = [ 0x00, # bTimerOut
|
||||||
0x00, # bTimerOut2
|
0x00, # bTimerOut2
|
||||||
0x82, # bmFormatString: Byte, pos=0, left, ASCII.
|
0x82, # bmFormatString: Byte, pos=0, left, ASCII.
|
||||||
0x00, # bmPINBlockString
|
self.fixed, # bmPINBlockString
|
||||||
0x00, # bmPINLengthFormat
|
0x00, # bmPINLengthFormat
|
||||||
0x00, # bInsertionOffsetOld
|
0x00, # bInsertionOffsetOld
|
||||||
0x00, # bInsertionOffsetNew
|
self.fixed, # bInsertionOffsetNew
|
||||||
self.pinmax, # wPINMaxExtraDigit Low (PINmax)
|
self.pinmax, # wPINMaxExtraDigit Low (PINmax)
|
||||||
self.pinmin, # wPINMaxExtraDigit High (PINmin)
|
self.pinmin, # wPINMaxExtraDigit High (PINmin)
|
||||||
confirm_pin_setting(single_step),
|
confirm_pin_setting(single_step),
|
||||||
@@ -144,9 +159,13 @@ class Card(object):
|
|||||||
0x02, # bMsgIndex3
|
0x02, # bMsgIndex3
|
||||||
0x00, # bTeoPrologue[0]
|
0x00, # bTeoPrologue[0]
|
||||||
0x00, # bTeoPrologue[1]
|
0x00, # bTeoPrologue[1]
|
||||||
0x00 # bTeoPrologue[2]
|
|
||||||
]
|
]
|
||||||
apdu += self.possibly_add_dummy_byte()
|
if self.fixed > 0:
|
||||||
|
apdu += [ 2*self.fixed ]
|
||||||
|
apdu += [ 255 ] * (2*self.fixed)
|
||||||
|
else:
|
||||||
|
apdu += self.possibly_add_dummy_byte()
|
||||||
|
pin_modify += [ len(apdu) ] # bTeoPrologue[2]
|
||||||
pin_modify += [ len(apdu), 0, 0, 0 ] + apdu
|
pin_modify += [ len(apdu), 0, 0, 0 ] + apdu
|
||||||
data = self.connection.control(self.modify_ioctl,pin_modify)
|
data = self.connection.control(self.modify_ioctl,pin_modify)
|
||||||
sw1 = data[0]
|
sw1 = data[0]
|
||||||
@@ -191,13 +210,27 @@ class Card(object):
|
|||||||
self.send_modify_pinpad(apdu, is_exchange,
|
self.send_modify_pinpad(apdu, is_exchange,
|
||||||
"cmd_change_reference_data_pinpad")
|
"cmd_change_reference_data_pinpad")
|
||||||
|
|
||||||
def main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps):
|
COVADIS_VEGA_ALPHA="COVADIS VEGA-ALPHA (000000F5) 00 00"
|
||||||
card = Card(add_a_byte, pinmin, pinmax)
|
# We need to set ifdDriverOptions in /etc/libccid_Info.plist:
|
||||||
|
#
|
||||||
|
# <key>ifdDriverOptions</key>
|
||||||
|
# <string>0x0001</string>
|
||||||
|
#
|
||||||
|
# 1: DRIVER_OPTION_CCID_EXCHANGE_AUTHORIZED
|
||||||
|
# the CCID Exchange command is allowed. You can use it through
|
||||||
|
# SCardControl(hCard, IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE, ...)
|
||||||
|
|
||||||
|
def main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps, fixed):
|
||||||
|
card = Card(add_a_byte, pinmin, pinmax, fixed)
|
||||||
card.connection.connect()
|
card.connection.connect()
|
||||||
|
|
||||||
print "Reader/Token:", card.connection.getReader()
|
ident = card.connection.getReader()
|
||||||
|
print "Reader/Token:", ident
|
||||||
print "ATR:", toHexString( card.connection.getATR() )
|
print "ATR:", toHexString( card.connection.getATR() )
|
||||||
|
|
||||||
|
if ident == COVADIS_VEGA_ALPHA:
|
||||||
|
card.cmd_vega_alpha_disable_empty_verify()
|
||||||
|
|
||||||
card.get_features()
|
card.get_features()
|
||||||
|
|
||||||
card.cmd_select_openpgp()
|
card.cmd_select_openpgp()
|
||||||
@@ -280,6 +313,7 @@ def print_usage():
|
|||||||
print "\t--put:\t\tsetup resetcode (admin PIN, new PIN twice)"
|
print "\t--put:\t\tsetup resetcode (admin PIN, new PIN twice)"
|
||||||
print "\t--put2::\t\tsetup resetcode (admin PIN:pinpad, new PIN:kbd)"
|
print "\t--put2::\t\tsetup resetcode (admin PIN:pinpad, new PIN:kbd)"
|
||||||
print " options:"
|
print " options:"
|
||||||
|
print "\t--fixed N:\tUse fixed length input"
|
||||||
print "\t--admin:\tby administrator\t\t\t[False]"
|
print "\t--admin:\tby administrator\t\t\t[False]"
|
||||||
print "\t--add:\t\tadd a dummy byte at the end of APDU\t[False]"
|
print "\t--add:\t\tadd a dummy byte at the end of APDU\t[False]"
|
||||||
print "\t--pinmin:\tspecify minimum length of PIN\t\t[6]"
|
print "\t--pinmin:\tspecify minimum length of PIN\t\t[6]"
|
||||||
@@ -302,6 +336,7 @@ if __name__ == '__main__':
|
|||||||
pinmin = PIN_MIN_DEFAULT
|
pinmin = PIN_MIN_DEFAULT
|
||||||
pinmax = PIN_MAX_DEFAULT
|
pinmax = PIN_MAX_DEFAULT
|
||||||
change_by_two_steps = False
|
change_by_two_steps = False
|
||||||
|
fixed=0
|
||||||
while len(sys.argv) >= 2:
|
while len(sys.argv) >= 2:
|
||||||
option = sys.argv[1]
|
option = sys.argv[1]
|
||||||
sys.argv.pop(1)
|
sys.argv.pop(1)
|
||||||
@@ -319,6 +354,9 @@ if __name__ == '__main__':
|
|||||||
change_by_two_steps = True
|
change_by_two_steps = True
|
||||||
elif option == '--add':
|
elif option == '--add':
|
||||||
add_a_byte = True
|
add_a_byte = True
|
||||||
|
elif option == '--fixed':
|
||||||
|
fixed = int(sys.argv[1])
|
||||||
|
sys.argv.pop(1)
|
||||||
elif option == '--pinmin':
|
elif option == '--pinmin':
|
||||||
pinmin = int(sys.argv[1])
|
pinmin = int(sys.argv[1])
|
||||||
sys.argv.pop(1)
|
sys.argv.pop(1)
|
||||||
@@ -337,7 +375,7 @@ if __name__ == '__main__':
|
|||||||
exit(0)
|
exit(0)
|
||||||
else:
|
else:
|
||||||
raise ValueError, option
|
raise ValueError, option
|
||||||
main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps)
|
main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps, fixed)
|
||||||
|
|
||||||
# Failure
|
# Failure
|
||||||
# 67 00: Wrong length; no further indication
|
# 67 00: Wrong length; no further indication
|
||||||
|
|||||||
70
tool/rsa.py
Normal file
70
tool/rsa.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from binascii import hexlify, unhexlify
|
||||||
|
import string
|
||||||
|
from os import urandom
|
||||||
|
|
||||||
|
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), e, p, q, n)
|
||||||
|
|
||||||
|
# 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 compute_signature(key, digestinfo):
|
||||||
|
e = key[4]
|
||||||
|
p = key[5]
|
||||||
|
q = key[6]
|
||||||
|
n = key[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_256(i):
|
||||||
|
s = hex(i)[2:]
|
||||||
|
s = s.rstrip('L')
|
||||||
|
if len(s) & 1:
|
||||||
|
s = '0' + s
|
||||||
|
return string.rjust(unhexlify(s), 256, '\x00')
|
||||||
|
|
||||||
|
def get_raw_pubkey(key):
|
||||||
|
return key[0]
|
||||||
4
tool/rsa_example.key
Normal file
4
tool/rsa_example.key
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
9cf7192b51a574d1ad3ccb08ba09b87f228573893eee355529ff243e90fd4b86f79a82097cc7922c0485bed1616b1656a9b0b19ef78ea8ec34c384019adc5d5bf4db2d2a0a2d9cf14277bdcb7056f48b81214e3f7f7742231e29673966f9b1106862112cc798dba8d4a138bb5abfc6d4c12d53a5d39b2f783da916da20852ee139bbafda61d429caf2a4f30847ce7e7ae32ab4061e27dd9e4d00d60910249db8d8559dd85f7ca59659ef400c8f6318700f4e97f0c6f4165de80641490433c88da8682befe68eb311f54af2b07d97ac74edb5399cf054764211694fbb8d1d333f3269f235abe025067f811ff83a2224826219b309ea3e6c968f42b3e52f245dc9
|
||||||
|
010001
|
||||||
|
b5ab7b159220b18e363258f61ebde08bae83d6ce2dbfe4adc143628c527887acde9de09bf9b49f438019004d71855f30c2d69b6c29bb9882ab641b3387409fe9199464a7faa4b5230c56d9e17cd9ed074bc00180ebed62bae3af28e6ff2ac2654ad968834c5d5c88f8d9d3cc5e167b10453b049d4e454a5761fb0ac717185907
|
||||||
|
dd2fffa9814296156a6926cd17b65564187e424dcadce9b032246ad7e46448bb0f9e0ff3c64f987424b1a40bc694e2e9ac4fb1930d163582d7acf20653a1c44b97846c1c5fd8a7b19bb225fb39c30e25410483deaf8c2538d222b748c4d8103b11cec04f666a5c0dbcbf5d5f625f158f65746c3fafe6418145f7cffa5fadeeaf
|
||||||
86
tool/sexp.py
Normal file
86
tool/sexp.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# SEXP (S-expressions) Basic Transport Support
|
||||||
|
#
|
||||||
|
# See: http://people.csail.mit.edu/rivest/sexp.html
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
sexp.py - a library for SEXP
|
||||||
|
|
||||||
|
Copyright (C) 2013 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/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
WHITESPACE='[ \n\t\v\r\f]+'
|
||||||
|
re_ws = re.compile(WHITESPACE)
|
||||||
|
DIGITS='[0-9]+'
|
||||||
|
re_digit = re.compile(DIGITS)
|
||||||
|
|
||||||
|
def skip_whitespace(string, pos):
|
||||||
|
m = re_ws.match(string, pos)
|
||||||
|
if m:
|
||||||
|
return m.start()
|
||||||
|
else:
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def sexp_match(string, ch, pos):
|
||||||
|
pos = skip_whitespace(string,pos)
|
||||||
|
if string[pos] == ch:
|
||||||
|
return pos+1
|
||||||
|
else:
|
||||||
|
raise ValueError("expect '%s'" % ch)
|
||||||
|
|
||||||
|
def sexp_parse_simple_string(string, pos):
|
||||||
|
pos = skip_whitespace(string,pos)
|
||||||
|
m = re_digit.match(string, pos)
|
||||||
|
if m:
|
||||||
|
length = int(string[m.start():m.end()],10)
|
||||||
|
pos = sexp_match(string, ':', m.end())
|
||||||
|
return (string[pos:pos+length], pos+length)
|
||||||
|
else:
|
||||||
|
raise ValueError('expect digit')
|
||||||
|
|
||||||
|
def sexp_parse_list(string,pos):
|
||||||
|
l = []
|
||||||
|
while True:
|
||||||
|
pos = skip_whitespace(string,pos)
|
||||||
|
if string[pos] == ')':
|
||||||
|
return (l, pos)
|
||||||
|
else:
|
||||||
|
(sexp, pos) = sexp_parse(string,pos)
|
||||||
|
l.append(sexp)
|
||||||
|
|
||||||
|
def sexp_parse(string, pos=0):
|
||||||
|
pos = skip_whitespace(string,pos)
|
||||||
|
if string[pos] == '(':
|
||||||
|
(l, pos) = sexp_parse_list(string,pos+1)
|
||||||
|
pos = sexp_match(string, ')', pos)
|
||||||
|
return (l, pos)
|
||||||
|
elif string[pos] == '[':
|
||||||
|
pos = skip_whitespace(string,pos)
|
||||||
|
(dsp, pos) = sexp_parse_simple_string(string,pos+1)
|
||||||
|
pos = sexp_match(string, ']', pos)
|
||||||
|
pos = skip_whitespace(string,pos)
|
||||||
|
(ss, pos) = sexp_parse_simple_string(string, pos)
|
||||||
|
return ((dsp, ss), pos)
|
||||||
|
else:
|
||||||
|
return sexp_parse_simple_string(string, pos)
|
||||||
|
|
||||||
|
def sexp(string):
|
||||||
|
(sexp, pos) = sexp_parse(string)
|
||||||
|
return sexp
|
||||||
@@ -356,7 +356,7 @@ class stlinkv2(object):
|
|||||||
|
|
||||||
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
||||||
if (status & FLASH_SR_EOP) == 0:
|
if (status & FLASH_SR_EOP) == 0:
|
||||||
raise OperationError("option bytes erase")
|
raise OperationFailure("option bytes erase")
|
||||||
|
|
||||||
def flash_write_internal(self, addr, data, off, size):
|
def flash_write_internal(self, addr, data, off, size):
|
||||||
prog = gen_prog_flash_write(addr,size)
|
prog = gen_prog_flash_write(addr,size)
|
||||||
@@ -415,7 +415,7 @@ class stlinkv2(object):
|
|||||||
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
||||||
|
|
||||||
if (status & FLASH_SR_EOP) == 0:
|
if (status & FLASH_SR_EOP) == 0:
|
||||||
raise OperationError("flash erase all")
|
raise OperationFailure("flash erase all")
|
||||||
|
|
||||||
def flash_erase_page(self, addr):
|
def flash_erase_page(self, addr):
|
||||||
self.write_memory_u32(FLASH_KEYR, FLASH_KEY1)
|
self.write_memory_u32(FLASH_KEYR, FLASH_KEY1)
|
||||||
@@ -439,7 +439,7 @@ class stlinkv2(object):
|
|||||||
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
||||||
|
|
||||||
if (status & FLASH_SR_EOP) == 0:
|
if (status & FLASH_SR_EOP) == 0:
|
||||||
raise OperationError("flash page erase")
|
raise OperationFailure("flash page erase")
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
mode = self.stl_mode()
|
mode = self.stl_mode()
|
||||||
@@ -523,12 +523,13 @@ def main(show_help, erase_only, no_protect, spi_flash_check,
|
|||||||
print "ON"
|
print "ON"
|
||||||
else:
|
else:
|
||||||
print "off"
|
print "off"
|
||||||
option_bytes = stl.option_bytes_read()
|
|
||||||
print "Option bytes: %08x" % option_bytes
|
option_bytes = stl.option_bytes_read()
|
||||||
if (option_bytes & 0xff) == RDP_KEY:
|
print "Option bytes: %08x" % option_bytes
|
||||||
ob_protection_enable = False
|
if (option_bytes & 0xff) == RDP_KEY:
|
||||||
else:
|
ob_protection_enable = False
|
||||||
ob_protection_enable = True
|
else:
|
||||||
|
ob_protection_enable = True
|
||||||
|
|
||||||
stl.enter_debug()
|
stl.enter_debug()
|
||||||
status = stl.get_status()
|
status = stl.get_status()
|
||||||
@@ -543,6 +544,7 @@ def main(show_help, erase_only, no_protect, spi_flash_check,
|
|||||||
raise OperationFailure("Flash ROM is protected")
|
raise OperationFailure("Flash ROM is protected")
|
||||||
else:
|
else:
|
||||||
if not skip_blank_check:
|
if not skip_blank_check:
|
||||||
|
stl.reset_sys()
|
||||||
blank = stl.blank_check()
|
blank = stl.blank_check()
|
||||||
print "Flash ROM blank check: %s" % blank
|
print "Flash ROM blank check: %s" % blank
|
||||||
else:
|
else:
|
||||||
@@ -564,6 +566,9 @@ def main(show_help, erase_only, no_protect, spi_flash_check,
|
|||||||
stl.setup_gpio()
|
stl.setup_gpio()
|
||||||
|
|
||||||
if unlock:
|
if unlock:
|
||||||
|
if option_bytes != 0xff:
|
||||||
|
stl.reset_sys()
|
||||||
|
stl.option_bytes_erase()
|
||||||
stl.reset_sys()
|
stl.reset_sys()
|
||||||
stl.option_bytes_write(OPTION_BYTES_ADDR,RDP_KEY)
|
stl.option_bytes_write(OPTION_BYTES_ADDR,RDP_KEY)
|
||||||
stl.usb_disconnect()
|
stl.usb_disconnect()
|
||||||
|
|||||||
112
tool/upgrade_by_passwd.py
Executable file
112
tool/upgrade_by_passwd.py
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
"""
|
||||||
|
upgrade_by_passwd.py - a tool to install another firmware for Gnuk Token
|
||||||
|
which is just shipped from factory
|
||||||
|
|
||||||
|
Copyright (C) 2012, 2013 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 gnuk_token import *
|
||||||
|
import sys, binascii, time, os
|
||||||
|
import rsa
|
||||||
|
|
||||||
|
DEFAULT_PW3 = "12345678"
|
||||||
|
BY_ADMIN = 3
|
||||||
|
|
||||||
|
KEYNO_FOR_AUTH=2
|
||||||
|
|
||||||
|
def main(passwd, data_regnual, data_upgrade):
|
||||||
|
l = len(data_regnual)
|
||||||
|
if (l & 0x03) != 0:
|
||||||
|
data_regnual = data_regnual.ljust(l + 4 - (l & 0x03), chr(0))
|
||||||
|
crc32code = crc32(data_regnual)
|
||||||
|
print "CRC32: %04x\n" % crc32code
|
||||||
|
data_regnual += pack('<I', crc32code)
|
||||||
|
|
||||||
|
rsa_key = rsa.read_key_from_file('rsa_example.key')
|
||||||
|
rsa_raw_pubkey = rsa.get_raw_pubkey(rsa_key)
|
||||||
|
|
||||||
|
gnuk = get_gnuk_device()
|
||||||
|
gnuk.cmd_verify(BY_ADMIN, passwd)
|
||||||
|
keyno = 0
|
||||||
|
gnuk.cmd_write_binary(1+keyno, rsa_raw_pubkey, False)
|
||||||
|
|
||||||
|
gnuk.cmd_select_openpgp()
|
||||||
|
challenge = gnuk.cmd_get_challenge()
|
||||||
|
digestinfo = binascii.unhexlify(SHA256_OID_PREFIX) + challenge
|
||||||
|
signed = rsa.compute_signature(rsa_key, digestinfo)
|
||||||
|
signed_bytes = rsa.integer_to_bytes_256(signed)
|
||||||
|
gnuk.cmd_external_authenticate(keyno, signed_bytes)
|
||||||
|
gnuk.stop_gnuk()
|
||||||
|
mem_info = gnuk.mem_info()
|
||||||
|
print "%08x:%08x" % mem_info
|
||||||
|
|
||||||
|
print "Downloading flash upgrade program..."
|
||||||
|
gnuk.download(mem_info[0], data_regnual)
|
||||||
|
print "Run flash upgrade program..."
|
||||||
|
gnuk.execute(mem_info[0] + len(data_regnual) - 4)
|
||||||
|
#
|
||||||
|
time.sleep(3)
|
||||||
|
gnuk.reset_device()
|
||||||
|
del gnuk
|
||||||
|
gnuk = None
|
||||||
|
#
|
||||||
|
print "Wait 3 seconds..."
|
||||||
|
time.sleep(3)
|
||||||
|
# Then, send upgrade program...
|
||||||
|
reg = None
|
||||||
|
for dev in gnuk_devices_by_vidpid():
|
||||||
|
try:
|
||||||
|
reg = regnual(dev)
|
||||||
|
print "Device: ", dev.filename
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
mem_info = reg.mem_info()
|
||||||
|
print "%08x:%08x" % mem_info
|
||||||
|
print "Downloading the program"
|
||||||
|
reg.download(mem_info[0], data_upgrade)
|
||||||
|
reg.protect()
|
||||||
|
reg.finish()
|
||||||
|
reg.reset_device()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if os.getcwd() != os.path.dirname(os.path.abspath(__file__)):
|
||||||
|
print "Please change working directory to: %s" % os.path.dirname(os.path.abspath(__file__))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
passwd = DEFAULT_PW3
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == '-p':
|
||||||
|
from getpass import getpass
|
||||||
|
passwd = getpass("Admin password: ")
|
||||||
|
sys.argv.pop(1)
|
||||||
|
filename_regnual = sys.argv[1]
|
||||||
|
filename_upgrade = sys.argv[2]
|
||||||
|
f = open(filename_regnual)
|
||||||
|
data_regnual = f.read()
|
||||||
|
f.close()
|
||||||
|
print "%s: %d" % (filename_regnual, len(data_regnual))
|
||||||
|
f = open(filename_upgrade)
|
||||||
|
data_upgrade = f.read()
|
||||||
|
f.close()
|
||||||
|
print "%s: %d" % (filename_upgrade, len(data_upgrade))
|
||||||
|
# First 4096-byte in data_upgrade is SYS, so, skip it.
|
||||||
|
main(passwd, data_regnual, data_upgrade[4096:])
|
||||||
Reference in New Issue
Block a user