Compare commits
46 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 |
183
ChangeLog
183
ChangeLog
@@ -1,3 +1,186 @@
|
||||
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.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# 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> #################
|
||||
|
||||
37
NEWS
37
NEWS
@@ -1,5 +1,42 @@
|
||||
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
|
||||
|
||||
27
README
27
README
@@ -1,7 +1,7 @@
|
||||
Gnuk - An Implementation of USB Cryptographic Token for GnuPG
|
||||
|
||||
Version 1.0.1
|
||||
2012-08-03
|
||||
Version 1.0.2
|
||||
2013-02-15
|
||||
Niibe Yutaka
|
||||
Free Software Initiative of Japan
|
||||
|
||||
@@ -42,7 +42,7 @@ A0: Good points of Gnuk are:
|
||||
"for Free Software"; Gnuk supports GnuPG.
|
||||
|
||||
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?
|
||||
A2: It takes a second and a half or so.
|
||||
@@ -115,9 +115,9 @@ Ac: ST-Link/V2 is cheap one. We have a tool/stlinkv2.py as flash ROM
|
||||
Release notes
|
||||
=============
|
||||
|
||||
This is a minor release in version 1.0 series of Gnuk.
|
||||
This is a second minor release in version 1.0 series of Gnuk.
|
||||
|
||||
While it is daily use for more than a year, some newly introduced
|
||||
While it is daily use for a year and a half, some newly introduced
|
||||
features (including key generation and firmware upgrade) should be
|
||||
considered experimental.
|
||||
|
||||
@@ -261,9 +261,8 @@ please contact Niibe, so that it is listed to the file in the official
|
||||
release of the source code.
|
||||
|
||||
When you are modifing Gnuk and installing the binary to device, you
|
||||
should replace "FSIJ" in the string gnukStringSerial (usb_desc.c) to
|
||||
yours, so that the device will say it's modified version by device
|
||||
serial number.
|
||||
should replace the vendor string to yours, so that users can see it's
|
||||
not by original vendor, and it is modified version.
|
||||
|
||||
FSIJ allows you to use USB device ID of FSIJ (234b:0000) for devices
|
||||
with Gnuk under one of following conditions:
|
||||
@@ -294,8 +293,8 @@ respect users' freedom for computing. Please ask FSIJ for the
|
||||
license.
|
||||
|
||||
Otherwise, companies which want to distribute Gnuk devices, please use
|
||||
your own USB vendor ID and product ID. Please replace "FSIJ" in the
|
||||
string gnukStringSerial (usb_desc.c) to yours, when you modify Gnuk.
|
||||
your own USB vendor ID and product ID. Please replace vendor string
|
||||
and possibly product string to yours, when you modify Gnuk.
|
||||
|
||||
|
||||
Host Requirements
|
||||
@@ -661,10 +660,10 @@ 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:
|
||||
|
||||
@@ -675,6 +674,8 @@ or
|
||||
$ git clone http://www.gniibe.org/git/gnuk.git/
|
||||
|
||||
|
||||
Copy is available at: http://gitorious.org/gnuk
|
||||
|
||||
|
||||
Information on the Web
|
||||
======================
|
||||
|
||||
1
THANKS
1
THANKS
@@ -11,6 +11,7 @@ Hironobu SUZUKI hironobu@h2np.net
|
||||
Jan Suhr jan@suhr.info
|
||||
Kaz Kojima kkojima@rr.iij4u.or.jp
|
||||
Ludovic Rousseau ludovic.rousseau@free.fr
|
||||
Luis Felipe R. Murillo luisfelipe@ucla.edu
|
||||
MATSUU Takuto matsuu@gentoo.org
|
||||
NAGAMI Takeshi nagami-takeshi@aist.go.jp
|
||||
Shane Coughlan scoughlan@openinventionnetwork.com
|
||||
|
||||
@@ -9,6 +9,15 @@ hwinit1 (void)
|
||||
{
|
||||
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)
|
||||
palWritePort(IOPORT2, 0x7fff); /* Only clear GPIOB_7SEG_DP */
|
||||
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/
|
||||
@@ -5,10 +5,9 @@ Development Environment
|
||||
Hardware
|
||||
--------
|
||||
|
||||
For development, it is highly recommended to have JTAG debugger or SWD
|
||||
debugger.
|
||||
For development, it is highly recommended to have JTAG/SWD debugger.
|
||||
|
||||
For boards with DFU (Device Firmware Upgrade) feature, such as DfuSe,
|
||||
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
|
||||
@@ -17,17 +16,16 @@ 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
|
||||
the tool of tool/stlinkv2.py.
|
||||
tool/stlinkv2.py.
|
||||
|
||||
|
||||
OpenOCD
|
||||
-------
|
||||
|
||||
For JTAG debugger or SWD debugger, we can use OpenOCD.
|
||||
For JTAG/SWD debugger, we can use OpenOCD.
|
||||
|
||||
Note that ST-Link/V2 is *not* supported by OpenOCD 0.5.0. It will be
|
||||
supported by version 0.6 or later, as current development version
|
||||
supports it.
|
||||
Note that ST-Link/V2 is *not* supported by OpenOCD 0.5.0. It is
|
||||
supported by version 0.6 or later.
|
||||
|
||||
|
||||
GNU Toolchain
|
||||
@@ -35,9 +33,11 @@ GNU Toolchain
|
||||
|
||||
You need GNU toolchain and newlib for 'arm-none-eabi' target.
|
||||
|
||||
See http://github.com/esden/summon-arm-toolchain/ (which includes fix
|
||||
of binutils-2.21.1) for preparation of GNU Toolchain for
|
||||
'arm-none-eabi' target. This is for GCC 4.5.
|
||||
There is "gcc-arm-embedded" project. See:
|
||||
https://launchpad.net/gcc-arm-embedded/
|
||||
|
||||
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).
|
||||
For this purpose, our src/Makefile.in contains following line:
|
||||
@@ -45,20 +45,9 @@ For this purpose, our src/Makefile.in contains following line:
|
||||
MCFLAGS= -mcpu=$(MCU) -mfix-cortex-m3-ldrd
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
-------------
|
||||
@@ -72,8 +61,8 @@ Then, run ``configure``:
|
||||
$ ./configure --vidpid=<VID:PID>
|
||||
|
||||
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
|
||||
product ID' above.
|
||||
it's: --vidpid=234b:0000 . Please read the section 'USB vendor ID and
|
||||
product ID' in README.
|
||||
|
||||
Type:
|
||||
|
||||
|
||||
@@ -2,11 +2,45 @@
|
||||
Generating 2048-bit RSA keys
|
||||
============================
|
||||
|
||||
This document describes how I generate 2048-bit RSA keys.
|
||||
In this section, we describe how to generate 2048-bit RSA keys.
|
||||
|
||||
.. BREAK
|
||||
|
||||
Here is the log to generate signature key and encryption subkey.
|
||||
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. ::
|
||||
|
||||
@@ -25,7 +59,7 @@ and GnuPG asks kind of key. Select ``RSA and RSA``. ::
|
||||
Your selection? 1
|
||||
RSA keys may be between 1024 and 4096 bits long.
|
||||
|
||||
and select 2048-bit (as Gnuk Token only suppurt this). ::
|
||||
and select 2048-bit (as Gnuk Token only supports this). ::
|
||||
|
||||
What keysize do you want? (2048)
|
||||
Requested keysize is 2048 bits
|
||||
@@ -59,7 +93,12 @@ Then enter user ID. ::
|
||||
|
||||
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
|
||||
|
||||
and enter passphrase for this **key on PC**. ::
|
||||
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>
|
||||
@@ -86,43 +125,50 @@ Then, GnuPG generate keys. It takes some time. ::
|
||||
|
||||
gpg: checking the trustdb
|
||||
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
|
||||
gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u
|
||||
pub 2048R/28C0CD7C 2011-05-24
|
||||
Key fingerprint = 0B4D C763 D57B ADBB 1870 A978 BDEE 4A35 28C0 CD7C
|
||||
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/F01E19B7 2011-05-24
|
||||
sub 2048R/084239CF 2010-10-15
|
||||
$
|
||||
|
||||
Done.
|
||||
|
||||
Then, I create authentication subkey. Authentication subkey is not that common, but very useful (say, for SSH authentication). As it is not that common, we need ``--expert`` option for GnuPG. ::
|
||||
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 28C0CD7C
|
||||
$ 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/28C0CD7C created: 2011-05-24 expires: never usage: SC
|
||||
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||
trust: ultimate validity: ultimate
|
||||
sub 2048R/F01E19B7 created: 2011-05-24 expires: never usage: E
|
||||
sub 2048R/084239CF created: 2010-10-15 expires: never usage: E
|
||||
[ultimate] (1). Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
gpg>
|
||||
|
||||
Here, I enter ``addkey`` command. Then, I enter the passphrase of **key on PC**, I specified above. ::
|
||||
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 28C0CD7C, created 2011-05-24
|
||||
2048-bit RSA key, ID 4CA7BABE, created 2010-10-15
|
||||
<PASSWORD-KEY-ON-PC>
|
||||
gpg: gpg-agent is not available in this session
|
||||
|
||||
GnuPG askes kind of key. I select ``RSA (set your own capabilities)``. ::
|
||||
GnuPG asks kind of key. We select ``RSA (set your own capabilities)``. ::
|
||||
|
||||
Please select what kind of key you want:
|
||||
(3) DSA (sign only)
|
||||
@@ -133,7 +179,10 @@ GnuPG askes kind of key. I select ``RSA (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 ``Encryp``, and select ``Authenticate``. To do that, I enter ``s``, ``a``, and ``e``. ::
|
||||
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
|
||||
@@ -153,17 +202,17 @@ And select ``Authenticate`` for the capabilities for this key. Initially, it's
|
||||
(A) Toggle the authenticate capability
|
||||
(Q) Finished
|
||||
|
||||
Your selection? a
|
||||
Your selection? e
|
||||
|
||||
Possible actions for a RSA key: Sign Encrypt Authenticate
|
||||
Current allowed actions: Encrypt Authenticate
|
||||
Current allowed actions:
|
||||
|
||||
(S) Toggle the sign capability
|
||||
(E) Toggle the encrypt capability
|
||||
(A) Toggle the authenticate capability
|
||||
(Q) Finished
|
||||
|
||||
Your selection? e
|
||||
Your selection? a
|
||||
|
||||
Possible actions for a RSA key: Sign Encrypt Authenticate
|
||||
Current allowed actions: Authenticate
|
||||
@@ -173,11 +222,13 @@ And select ``Authenticate`` for the capabilities for this key. Initially, it's
|
||||
(A) Toggle the authenticate capability
|
||||
(Q) Finished
|
||||
|
||||
OK, I set the capability of ``Authenticate``. I enter ``q`` to finish setting capabilities. ::
|
||||
OK, we set the capability of ``Authenticate``.
|
||||
We enter ``q`` to finish setting capabilities. ::
|
||||
|
||||
Your selection? q
|
||||
|
||||
GnuPG asks bitsize and expiration, I enter 2048 for bitsize and no expiration. Then, I confirm that I really create the key. ::
|
||||
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)
|
||||
@@ -202,27 +253,59 @@ Then, GnuPG generate the key. ::
|
||||
.......+++++
|
||||
+++++
|
||||
|
||||
pub 2048R/28C0CD7C created: 2011-05-24 expires: never usage: SC
|
||||
pub 2048R/4CA7BABE created: 2010-10-15 expires: never usage: SC
|
||||
trust: ultimate validity: ultimate
|
||||
sub 2048R/F01E19B7 created: 2011-05-24 expires: never usage: E
|
||||
sub 2048R/B8929606 created: 2011-05-24 expires: never usage: A
|
||||
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>
|
||||
|
||||
I save the key. ::
|
||||
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).
|
||||
Now, we have three keys (one primary key for signature and certification,
|
||||
subkey for encryption, and another subkey for authentication).
|
||||
|
||||
|
||||
Publishing public key
|
||||
=====================
|
||||
|
||||
I make a file for my public key by ``--export`` option of GnuPG. ::
|
||||
We make a file for the public key by ``--export`` option of GnuPG. ::
|
||||
|
||||
$ gpg --armor --output gniibe.asc --export 4CA7BABE
|
||||
$ gpg --armor --output <YOUR-KEY>.asc --export <YOUR-KEY-ID>
|
||||
|
||||
and put it at: http://www.gniibe.org/gniibe.asc
|
||||
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
|
||||
|
||||
@@ -4,7 +4,8 @@ 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.
|
||||
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
|
||||
|
||||
@@ -12,19 +13,26 @@ It was for GNOME 2. The old days was good, we just disabled GNOME-keyrings inte
|
||||
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.
|
||||
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.
|
||||
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 ours, I figured out it is GNOME-SESSION-PROPERTIES to disable the interference. Invoking::
|
||||
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".
|
||||
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.
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
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.
|
||||
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.
|
||||
|
||||
.. BREAK
|
||||
|
||||
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. ::
|
||||
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.
|
||||
@@ -54,7 +54,10 @@ and type ``1`` to say it's 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.
|
||||
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. ::
|
||||
|
||||
@@ -74,7 +77,8 @@ I enter these passwords. ::
|
||||
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.
|
||||
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'. ::
|
||||
|
||||
@@ -87,7 +91,8 @@ Secondly, I import my subkey of encryption. I select key number '1'. ::
|
||||
(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. ::
|
||||
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||
I select ``2`` as it's encryption key. ::
|
||||
|
||||
gpg> keytocard
|
||||
Signature key ....: [none]
|
||||
@@ -126,7 +131,7 @@ I type ``key 1`` to deselect key number '1'. ::
|
||||
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
Thirdly, I select sub key of suthentication which has key number '2'. ::
|
||||
Thirdly, I select sub key of authentication which has key number '2'. ::
|
||||
|
||||
gpg> key 2
|
||||
|
||||
@@ -138,7 +143,8 @@ Thirdly, I select sub key of suthentication which has key number '2'. ::
|
||||
(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. ::
|
||||
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||
I select ``3`` as it's authentication key. ::
|
||||
|
||||
gpg> keytocard
|
||||
Signature key ....: [none]
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
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.
|
||||
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.
|
||||
Note that there is **no ways** to export keys from the Token,
|
||||
so please be careful.
|
||||
|
||||
.. BREAK
|
||||
|
||||
If you want to import same keys to multiple Tokens, please copy ``.gnupg`` directory before. In my case, I do something like following: ::
|
||||
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
|
||||
|
||||
@@ -62,7 +66,10 @@ and type ``1`` to say it's 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.
|
||||
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. ::
|
||||
|
||||
@@ -95,7 +102,8 @@ Secondly, I import my subkey of encryption. I select key number '1'. ::
|
||||
(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. ::
|
||||
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||
I select ``2`` as it's encryption key. ::
|
||||
|
||||
gpg> keytocard
|
||||
Signature key ....: [none]
|
||||
@@ -134,7 +142,7 @@ I type ``key 1`` to deselect key number '1'. ::
|
||||
ssb 2048R/5BB065DC created: 2010-10-22 expires: never
|
||||
(1) NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
Thirdly, I select sub key of suthentication which has key number '2'. ::
|
||||
Thirdly, I select sub key of authentication which has key number '2'. ::
|
||||
|
||||
gpg> key 2
|
||||
|
||||
@@ -146,7 +154,8 @@ Thirdly, I select sub key of suthentication which has key number '2'. ::
|
||||
(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. ::
|
||||
I type ``keytocard`` command to import this subkey to Gnuk Token.
|
||||
I select ``3`` as it's authentication key. ::
|
||||
|
||||
gpg> keytocard
|
||||
Signature key ....: [none]
|
||||
@@ -180,4 +189,5 @@ 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.
|
||||
All secret keys are imported to Gnuk Token now.
|
||||
On PC, only references (card-no) to the Token remain.
|
||||
|
||||
@@ -33,7 +33,27 @@ It shows the status of the card (as same as the output of ``gpg --card-status``)
|
||||
|
||||
Then, GnuPG enters its own command interaction mode. The prompt is ``gpg/card>``.
|
||||
|
||||
Firstly, I change PIN of card user from factory setting (of "123456"). Note that, only changing PIN of user enables "admin less mode" of Gnuk. Admin password will become same one of user's. ::
|
||||
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
|
||||
@@ -48,7 +68,20 @@ Firstly, I change PIN of card user from factory setting (of "123456"). Note tha
|
||||
Repeat this PIN: <PASSWORD-OF-GNUK>
|
||||
PIN changed.
|
||||
|
||||
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". ::
|
||||
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
|
||||
@@ -61,7 +94,8 @@ Secondly, enabling admin command, I put name of mine. Note that I input user's
|
||||
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. ::
|
||||
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
|
||||
@@ -75,7 +109,8 @@ Thirdly, I put some other informations, such as language, sex, login, and URL.
|
||||
gpg/card> login
|
||||
Login data (account name): gniibe
|
||||
|
||||
Since I don't force PIN input everytime, toggle it to non-force-pin-for-signature. ::
|
||||
Since I don't force PIN input everytime,
|
||||
toggle it to non-force-pin-for-signature. ::
|
||||
|
||||
gpg/card> forcesig
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
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
|
||||
==========
|
||||
|
||||
@@ -11,7 +17,7 @@ I assume you are using GNU/Linux.
|
||||
Preparation
|
||||
===========
|
||||
|
||||
We need to kill ``scdaemon`` before configuring Gnuk Token. ::
|
||||
Make sure there is no ``scdaemon`` for configuring Gnuk Token. You can kill ``scdaemon`` by: ::
|
||||
|
||||
$ gpg-connect-agent "SCD KILLSCD" "SCD BYE" /bye
|
||||
|
||||
@@ -19,16 +25,19 @@ We need to kill ``scdaemon`` before configuring Gnuk Token. ::
|
||||
Serial Number (optional)
|
||||
========================
|
||||
|
||||
In the file ``GNUK_SERIAL_NUMBER``, each line has email and 6-byte serial number.
|
||||
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.py`` examines environment variable of ``EMAIL``, and writes serial number to Gnuk Token. ::
|
||||
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.py -s ../GNUK_SERIAL_NUMBER
|
||||
$ ../tool/gnuk_put_binary_libusb.py -s ../GNUK_SERIAL_NUMBER
|
||||
Writing serial number
|
||||
Token: FSIJ Gnuk (0.12-38FF6A06) 00 00
|
||||
ATR: 3B DA 11 FF 81 B1 FE 55 1F 03 00 31 84 73 80 01 40 00 90 00 24
|
||||
Device: 006
|
||||
Configuration: 1
|
||||
Interface: 0
|
||||
d2 76 00 01 24 01 02 00 f5 17 00 00 00 01 00 00
|
||||
|
||||
|
||||
The tool ``../tool/gnuk_put_binary.py`` is for PC/SC Lite. Use
|
||||
``../tool/gnuk_put_binary_libusb.py`` instead, if you don't use
|
||||
PC/SC Lite but use libusb directly.
|
||||
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.
|
||||
|
||||
@@ -22,10 +22,12 @@ I create ``.gnupg/gpg.conf`` file with the following content. ::
|
||||
Let gpg-agent manage SSH key
|
||||
============================
|
||||
|
||||
I deactivate seahose-agent. Also, I deactivate gnome-keyring managing 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
|
||||
|
||||
@@ -18,9 +18,9 @@ Contents:
|
||||
development.rst
|
||||
stop-scdaemon.rst
|
||||
udev-rules.rst
|
||||
generating-2048-RSA-key.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
|
||||
|
||||
@@ -38,14 +38,12 @@ We can examine key information of gpg-agent by "KEYINFO" command.
|
||||
Here is my example::
|
||||
|
||||
$ gpg-connect-agent "KEYINFO --list" /bye
|
||||
S KEYINFO 4970A0D537CA2EF7CE6A106E47AD89B0EFB684C8 D - - - - -
|
||||
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 5D6C89682D07CCFC034AF508420BF2276D8018ED T D276000124010200F517000000010000 OPENPGP.3 - - -
|
||||
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
|
||||
5D6C89682D07CCFC034AF508420BF2276D8018ED into the file: 5D6C8968.bin::
|
||||
|
||||
@@ -6,8 +6,8 @@ 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
|
||||
everything automatically. But, there are some cases (because of
|
||||
bugs), where we need to talk to the daemon directly, in practice.
|
||||
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
|
||||
|
||||
@@ -7,11 +7,13 @@ needed for permissions. Note that this is not needed for the case of
|
||||
PC/SC Lite, as it has its own device configuration.
|
||||
|
||||
|
||||
Patching 60-gnupg.rules
|
||||
=======================
|
||||
udev rules for Gnuk Token
|
||||
=========================
|
||||
|
||||
In case of Debian, there is a file /lib/udev/rules.d/60-gnupg.rules.
|
||||
This would be the place we need to change::
|
||||
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
|
||||
@@ -24,25 +26,26 @@ This would be the place we need to change::
|
||||
+
|
||||
LABEL="gnupg_rules_end"
|
||||
|
||||
|
||||
|
||||
Have a another configuration for reGNUal
|
||||
========================================
|
||||
|
||||
For reGNUal (upgrade feature of Gnuk),
|
||||
I also have a file /etc/udev/rules.d/92-gnuk.rules::
|
||||
|
||||
# For updating firmware, permission settings are needed.
|
||||
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_USB_INTERFACES}=="*:ff0000:*", GROUP="pcscd"
|
||||
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
|
||||
|
||||
|
||||
Configuration for ST-Link/V2
|
||||
============================
|
||||
|
||||
This is for development, but I also have a file
|
||||
/etc/udev/rules.d/10-stlink.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.
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
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).
|
||||
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.
|
||||
Note that the Token only brings your secret keys,
|
||||
while ``.gnupg`` directory contains keyrings and trustdb, too.
|
||||
|
||||
.. BREAK
|
||||
|
||||
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``.
|
||||
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. ::
|
||||
|
||||
@@ -58,7 +60,9 @@ 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. ::
|
||||
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>
|
||||
|
||||
@@ -99,7 +103,8 @@ 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.
|
||||
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: ::
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ BOARD_DIR=@BOARD_DIR@
|
||||
|
||||
# Compiler options here.
|
||||
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
|
||||
|
||||
# C++ specific options here (added to USE_OPT).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* 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;
|
||||
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)
|
||||
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_RC 1
|
||||
#define PW_ERR_PW3 2
|
||||
extern int gpg_pw_get_retry_counter (int who);
|
||||
extern int gpg_pw_locked (uint8_t which);
|
||||
extern void gpg_pw_reset_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 uint16_t data_objects_number_of_bytes;
|
||||
|
||||
#define CHALLENGE_LEN 32
|
||||
|
||||
extern void gpg_data_scan (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);
|
||||
|
||||
@@ -178,6 +178,7 @@ extern msg_t USBthread (void *arg);
|
||||
#define LED_TIMEOUT_STOP MS2ST(200)
|
||||
|
||||
|
||||
/* It has two-byte prefix and content is "FSIJ-1.0.1-" (2 + 11*2). */
|
||||
#define ID_OFFSET 24
|
||||
static void
|
||||
device_initialize_once (void)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* 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]);
|
||||
}
|
||||
|
||||
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
|
||||
gpg_pw_locked (uint8_t which)
|
||||
{
|
||||
@@ -88,16 +100,16 @@ static const uint8_t historical_bytes[] __attribute__ ((aligned (1))) = {
|
||||
/* Extended Capabilities */
|
||||
static const uint8_t extended_capabilities[] __attribute__ ((aligned (1))) = {
|
||||
10,
|
||||
0x30, /*
|
||||
0x70, /*
|
||||
* No SM,
|
||||
* No get challenge,
|
||||
* GET CHALLENGE supported,
|
||||
* Key import supported,
|
||||
* PW status byte can be put,
|
||||
* No private_use_DO,
|
||||
* No algo change allowed
|
||||
*/
|
||||
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
|
||||
0x08, 0x00, /* max. length of cardholder certificate (2KiB) */
|
||||
#else
|
||||
@@ -1381,8 +1393,11 @@ gpg_do_put_data (uint16_t tag, const uint8_t *data, int len)
|
||||
flash_do_release (*do_data_p);
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
/* make DO empty */
|
||||
*do_data_p = NULL;
|
||||
GPG_SUCCESS ();
|
||||
}
|
||||
else if (len > 255)
|
||||
GPG_MEMORY_FAILURE ();
|
||||
else
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||
@@ -29,6 +30,8 @@
|
||||
#include "openpgp.h"
|
||||
#include "sha256.h"
|
||||
|
||||
#define ADMIN_PASSWD_MINLEN 8
|
||||
|
||||
#define CLS(a) a.cmd_apdu_head[0]
|
||||
#define INS(a) a.cmd_apdu_head[1]
|
||||
#define P1(a) a.cmd_apdu_head[2]
|
||||
@@ -50,7 +53,6 @@
|
||||
#define INS_PUT_DATA 0xda
|
||||
#define INS_PUT_DATA_ODD 0xdb /* For key import */
|
||||
|
||||
#define CHALLENGE_LEN 32
|
||||
static const uint8_t *challenge; /* Random bytes */
|
||||
|
||||
static const uint8_t
|
||||
@@ -138,6 +140,27 @@ cmd_verify (void)
|
||||
len = apdu.cmd_apdu_data_len;
|
||||
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)
|
||||
r = verify_pso_cds (pw, len);
|
||||
else if (p2 == 0x82)
|
||||
@@ -234,7 +257,7 @@ cmd_change_password (void)
|
||||
|
||||
if (p1 != 0)
|
||||
{
|
||||
GPG_FUNCTION_NOT_SUPPORTED();
|
||||
GPG_FUNCTION_NOT_SUPPORTED ();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -259,8 +282,18 @@ cmd_change_password (void)
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t *ks_pw3 = gpg_do_read_simple (NR_DO_KEYSTRING_PW3);
|
||||
|
||||
newpw = pw + 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) */
|
||||
@@ -914,6 +947,26 @@ modify_binary (uint8_t op, uint8_t p1, uint8_t p2, int len)
|
||||
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 ();
|
||||
}
|
||||
|
||||
@@ -935,25 +988,9 @@ static void
|
||||
cmd_write_binary (void)
|
||||
{
|
||||
int len = apdu.cmd_apdu_data_len;
|
||||
int i;
|
||||
const uint8_t *p;
|
||||
|
||||
DEBUG_INFO (" - WRITE BINARY\r\n");
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -969,7 +1006,7 @@ cmd_external_authenticate (void)
|
||||
|
||||
DEBUG_INFO (" - EXTERNAL AUTHENTICATE\r\n");
|
||||
|
||||
if (keyno > 4)
|
||||
if (keyno >= 4)
|
||||
{
|
||||
GPG_CONDITION_NOT_SATISFIED ();
|
||||
return;
|
||||
@@ -1002,14 +1039,25 @@ cmd_external_authenticate (void)
|
||||
static void
|
||||
cmd_get_challenge (void)
|
||||
{
|
||||
int len = apdu.expected_res_size;
|
||||
|
||||
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)
|
||||
random_bytes_free (challenge);
|
||||
|
||||
challenge = random_bytes_get ();
|
||||
memcpy (res_APDU, challenge, CHALLENGE_LEN);
|
||||
res_APDU_size = CHALLENGE_LEN;
|
||||
memcpy (res_APDU, challenge, len);
|
||||
res_APDU_size = len;
|
||||
GPG_SUCCESS ();
|
||||
DEBUG_INFO ("GET CHALLENGE done.\r\n");
|
||||
}
|
||||
|
||||
@@ -46,11 +46,10 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "sha256.h"
|
||||
|
||||
#define SHA256_DIGEST_SIZE 32
|
||||
#define SHA256_BLOCK_SIZE 64
|
||||
#define SHA256_MASK (SHA256_BLOCK_SIZE - 1)
|
||||
|
||||
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
|
||||
{
|
||||
uint32_t total[2];
|
||||
|
||||
@@ -827,36 +827,7 @@ icc_power_off (struct ccid *c)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
static void
|
||||
icc_send_data_block (struct ccid *c, uint8_t status)
|
||||
icc_send_data_block_internal (struct ccid *c, uint8_t status, uint8_t error)
|
||||
{
|
||||
int tx_size = USB_LL_BUF_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[ICC_MSG_SEQ_OFFSET] = c->a->seq;
|
||||
p[ICC_MSG_STATUS_OFFSET] = status;
|
||||
p[ICC_MSG_ERROR_OFFSET] = 0;
|
||||
p[ICC_MSG_ERROR_OFFSET] = error;
|
||||
p[ICC_MSG_CHAIN_OFFSET] = 0;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
case ICC_STATE_EXECUTE:
|
||||
icc_send_data_block (c, ICC_CMD_STATUS_TIMEEXT);
|
||||
icc_send_data_block_time_extension (c);
|
||||
led_blink (LED_ONESHOT);
|
||||
break;
|
||||
default:
|
||||
@@ -1311,7 +1326,7 @@ USBthread (void *arg)
|
||||
c->sw1sw2[0] = 0x90;
|
||||
c->sw1sw2[1] = 0x00;
|
||||
c->state = APDU_STATE_RESULT;
|
||||
icc_send_data_block (c, 0);
|
||||
icc_send_data_block (c);
|
||||
c->icc_state = ICC_STATE_EXITED;
|
||||
break;
|
||||
}
|
||||
@@ -1323,7 +1338,7 @@ USBthread (void *arg)
|
||||
if (c->a->res_apdu_data_len <= c->a->expected_res_size)
|
||||
{
|
||||
c->state = APDU_STATE_RESULT;
|
||||
icc_send_data_block (c, 0);
|
||||
icc_send_data_block (c);
|
||||
c->icc_state = ICC_STATE_WAIT;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -260,9 +260,9 @@ static const uint8_t gnukStringLangID[] = {
|
||||
const uint8_t gnukStringSerial[] = {
|
||||
19*2+2, /* bLength */
|
||||
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
|
||||
/* FSIJ-1.0 */
|
||||
/* FSIJ-1.0.1- */
|
||||
'F', 0, 'S', 0, 'I', 0, 'J', 0, '-', 0,
|
||||
'1', 0, '.', 0, '0', 0, '.', 0, '1', 0, /* Version number of Gnuk */
|
||||
'1', 0, '.', 0, '0', 0, '.', 0, '2', 0, /* Version number of Gnuk */
|
||||
'-', 0,
|
||||
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
|
||||
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
|
||||
When requesting algorithm attributes 1: c1
|
||||
|
||||
@@ -8,7 +8,7 @@ Feature: command GET DATA
|
||||
|
||||
Scenario: data object extended capabilities
|
||||
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
|
||||
When requesting algorithm attributes 1: c1
|
||||
|
||||
@@ -8,7 +8,7 @@ Feature: command GET DATA
|
||||
|
||||
Scenario: data object extended capabilities
|
||||
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
|
||||
When requesting algorithm attributes 1: c1
|
||||
|
||||
@@ -5,7 +5,7 @@ from binascii import hexlify
|
||||
|
||||
import ast
|
||||
|
||||
import gnuk
|
||||
import gnuk_token as gnuk
|
||||
import rsa_keys
|
||||
|
||||
@Before
|
||||
|
||||
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 *
|
||||
import sys, time, os, binascii, string
|
||||
from gnuk_token import *
|
||||
|
||||
# INPUT: binary file
|
||||
|
||||
# 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"
|
||||
BY_ADMIN = 3
|
||||
|
||||
def main(fileid, is_update, data, passwd):
|
||||
icc = None
|
||||
gnuk = None
|
||||
for (dev, config, intf) in gnuk_devices():
|
||||
try:
|
||||
icc = gnuk_token(dev, config, intf)
|
||||
gnuk = gnuk_token(dev, config, intf)
|
||||
print "Device: ", dev.filename
|
||||
print "Configuration: ", config.value
|
||||
print "Interface: ", intf.interfaceNumber
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if icc.icc_get_status() == 2:
|
||||
if gnuk.icc_get_status() == 2:
|
||||
raise ValueError, "No ICC present"
|
||||
elif icc.icc_get_status() == 1:
|
||||
icc.icc_power_on()
|
||||
icc.cmd_verify(BY_ADMIN, passwd)
|
||||
icc.cmd_write_binary(fileid, data, is_update)
|
||||
icc.cmd_select_openpgp()
|
||||
elif gnuk.icc_get_status() == 1:
|
||||
gnuk.icc_power_on()
|
||||
gnuk.cmd_verify(BY_ADMIN, passwd)
|
||||
gnuk.cmd_write_binary(fileid, data, is_update)
|
||||
gnuk.cmd_select_openpgp()
|
||||
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:
|
||||
print "%02x" % d,
|
||||
print "%02x" % ord(d),
|
||||
print
|
||||
compare(data, data_in_device[8:])
|
||||
compare(data + '\x00\x00', data_in_device[8:])
|
||||
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)
|
||||
else:
|
||||
data_in_device = icc.cmd_get_data(0x7f, 0x21)
|
||||
data_in_device = gnuk.cmd_get_data(0x7f, 0x21)
|
||||
compare(data, data_in_device)
|
||||
icc.icc_power_off()
|
||||
gnuk.icc_power_off()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -90,13 +90,13 @@ def main(passwd):
|
||||
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, 0xcd) # KGTIME_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, 0xce) # KGTIME_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, 0xcf) # KGTIME_AUT
|
||||
gnuk.cmd_put_data_remove(0x00, 0xd0) # KGTIME_AUT
|
||||
gnuk.cmd_put_data_key_import_remove(3)
|
||||
|
||||
gnuk.connection.disconnect()
|
||||
@@ -105,7 +105,7 @@ def main(passwd):
|
||||
|
||||
if __name__ == '__main__':
|
||||
passwd = DEFAULT_PW3
|
||||
if sys.argv[1] == '-p':
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '-p':
|
||||
from getpass import getpass
|
||||
passwd = getpass("Admin password: ")
|
||||
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
|
||||
This tool is for importing certificate, writing serial number, etc.
|
||||
gnuk_token.py - a library for Gnuk Token
|
||||
|
||||
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>
|
||||
|
||||
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 *
|
||||
import string
|
||||
|
||||
# Assume only single CCID device is attached to computer, and it's Gnuk Token
|
||||
|
||||
import usb
|
||||
import string, binascii
|
||||
import usb, time
|
||||
|
||||
# USB class, subclass, protocol
|
||||
CCID_CLASS = 0x0B
|
||||
@@ -36,16 +32,24 @@ 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):
|
||||
def iso7816_compose(ins, p1, p2, data, cls=0x00, le=None):
|
||||
data_len = len(data)
|
||||
if data_len == 0:
|
||||
if not le:
|
||||
return pack('>BBBB', cls, ins, p1, p2)
|
||||
else:
|
||||
return pack('>BBBBB', cls, ins, p1, p2, le)
|
||||
else:
|
||||
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):
|
||||
return string.join([chr(c) for c in l], '')
|
||||
|
||||
# This class only supports Gnuk (for now)
|
||||
class gnuk_token(object):
|
||||
def __init__(self, device, configuration, interface):
|
||||
"""
|
||||
@@ -92,6 +96,50 @@ class gnuk_token(object):
|
||||
def release_gnuk(self):
|
||||
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):
|
||||
msg = self.__devhandle.bulkRead(self.__bulkin, 1024, self.__timeout)
|
||||
if len(msg) < 10:
|
||||
@@ -123,6 +171,7 @@ class gnuk_token(object):
|
||||
status, chain, data = self.icc_get_result()
|
||||
# XXX: check status, chain
|
||||
self.atr = list_to_string(data) # ATR
|
||||
return self.atr
|
||||
|
||||
def icc_power_off(self):
|
||||
msg = icc_compose(0x63, 0, 0, self.__seq, 0, "")
|
||||
@@ -190,7 +239,7 @@ class gnuk_token(object):
|
||||
cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, '')
|
||||
sw = self.icc_send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError, sw
|
||||
raise ValueError(sw)
|
||||
if sw[0] != 0x61:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return self.cmd_get_response(sw[1])
|
||||
@@ -225,9 +274,9 @@ class gnuk_token(object):
|
||||
if cmd_data1:
|
||||
sw = self.icc_send_cmd(cmd_data1)
|
||||
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):
|
||||
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
|
||||
|
||||
def cmd_select_openpgp(self):
|
||||
@@ -363,13 +412,158 @@ class gnuk_token(object):
|
||||
pk = self.cmd_get_response(sw[1])
|
||||
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):
|
||||
i = 0
|
||||
for d in data_original:
|
||||
if ord(d) != data_in_device[i]:
|
||||
raise ValueError, "verify failed at %08x" % i
|
||||
i += 1
|
||||
if data_original == data_in_device:
|
||||
return True
|
||||
raise ValueError("verify failed")
|
||||
|
||||
def gnuk_devices():
|
||||
busses = usb.busses()
|
||||
@@ -384,6 +578,20 @@ def gnuk_devices():
|
||||
alt.interfaceProtocol == CCID_PROTOCOL_0:
|
||||
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():
|
||||
icc = None
|
||||
for (dev, config, intf) in gnuk_devices():
|
||||
@@ -405,3 +613,12 @@ def get_gnuk_device():
|
||||
else:
|
||||
raise ValueError("Unknown ICC status", status)
|
||||
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 *
|
||||
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
|
||||
|
||||
@@ -390,7 +390,14 @@ from subprocess import check_output
|
||||
|
||||
SHA256_OID_PREFIX="3031300d060960864801650304020105000420"
|
||||
|
||||
def gpg_sign(hash):
|
||||
# When user specify KEYGRIP, use it. Or else, connect to SCD directly.
|
||||
def gpg_sign(keygrip, hash):
|
||||
if keygrip:
|
||||
result = check_output(["gpg-connect-agent",
|
||||
"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",
|
||||
@@ -406,6 +413,10 @@ def gpg_sign(hash):
|
||||
signed += chr(int(hex_str,16))
|
||||
result = result[i+3:]
|
||||
|
||||
if keygrip:
|
||||
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:
|
||||
@@ -419,7 +430,7 @@ def crc32(bytestr):
|
||||
crc = binascii.crc32(bytestr)
|
||||
return UNSIGNED(crc)
|
||||
|
||||
def main(data_regnual, data_upgrade):
|
||||
def main(keygrip, data_regnual, data_upgrade):
|
||||
l = len(data_regnual)
|
||||
if (l & 0x03) != 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.cmd_select_openpgp()
|
||||
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.stop_gnuk()
|
||||
mem_info = icc.mem_info()
|
||||
@@ -478,6 +489,11 @@ def main(data_regnual, data_upgrade):
|
||||
|
||||
|
||||
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_upgrade = sys.argv[2]
|
||||
f = open(filename_regnual)
|
||||
@@ -488,4 +504,4 @@ if __name__ == '__main__':
|
||||
data_upgrade = f.read()
|
||||
f.close()
|
||||
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]
|
||||
sys.argv.pop(1)
|
||||
if option == '-h':
|
||||
if bunum != None or devnum != None:
|
||||
if busnum != None or devnum != None:
|
||||
exit_with_usage(sys.argv[0])
|
||||
hub = int(sys.argv[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
|
||||
|
||||
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>
|
||||
|
||||
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
|
||||
|
||||
CM_IOCTL_GET_FEATURE_REQUEST = (0x42000000 + 3400)
|
||||
CM_IOCTL_VENDOR_IFD_EXCHANGE = (0x42000000 + 1)
|
||||
FEATURE_VERIFY_PIN_DIRECT = 0x06
|
||||
FEATURE_MODIFY_PIN_DIRECT = 0x07
|
||||
|
||||
@@ -51,7 +52,7 @@ def confirm_pin_setting(single_step):
|
||||
return 0x03 # bConfirmPIN: old PIN and new PIN twice
|
||||
|
||||
class Card(object):
|
||||
def __init__(self, add_a_byte, pinmin, pinmax):
|
||||
def __init__(self, add_a_byte, pinmin, pinmax, fixed):
|
||||
cardtype = AnyCardType()
|
||||
cardrequest = CardRequest(timeout=10, cardType=cardtype)
|
||||
cardservice = cardrequest.waitforcard()
|
||||
@@ -61,6 +62,7 @@ class Card(object):
|
||||
self.another_byte = add_a_byte
|
||||
self.pinmin = pinmin
|
||||
self.pinmax = pinmax
|
||||
self.fixed = fixed
|
||||
|
||||
def get_features(self):
|
||||
p = self.connection.control(CM_IOCTL_GET_FEATURE_REQUEST, [])
|
||||
@@ -96,12 +98,21 @@ class Card(object):
|
||||
else:
|
||||
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):
|
||||
apdu = [0x00, 0x20, 0x00, 0x80+who ]
|
||||
pin_verify = [ 0x00, # bTimeOut
|
||||
0x00, # bTimeOut2
|
||||
0x82, # bmFormatString: Byte, pos=0, left, ASCII.
|
||||
0x00, # bmPINBlockString
|
||||
self.fixed, # bmPINBlockString
|
||||
0x00, # bmPINLengthFormat
|
||||
self.pinmax, # wPINMaxExtraDigit Low (PINmax)
|
||||
self.pinmin, # wPINMaxExtraDigit High (PINmin)
|
||||
@@ -112,9 +123,13 @@ class Card(object):
|
||||
0x00, # bMsgIndex
|
||||
0x00, # bTeoPrologue[0]
|
||||
0x00, # bTeoPrologue[1]
|
||||
0x00 # bTeoPrologue[2]
|
||||
]
|
||||
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
|
||||
data = self.connection.control(self.verify_ioctl,pin_verify)
|
||||
sw1 = data[0]
|
||||
@@ -128,10 +143,10 @@ class Card(object):
|
||||
pin_modify = [ 0x00, # bTimerOut
|
||||
0x00, # bTimerOut2
|
||||
0x82, # bmFormatString: Byte, pos=0, left, ASCII.
|
||||
0x00, # bmPINBlockString
|
||||
self.fixed, # bmPINBlockString
|
||||
0x00, # bmPINLengthFormat
|
||||
0x00, # bInsertionOffsetOld
|
||||
0x00, # bInsertionOffsetNew
|
||||
self.fixed, # bInsertionOffsetNew
|
||||
self.pinmax, # wPINMaxExtraDigit Low (PINmax)
|
||||
self.pinmin, # wPINMaxExtraDigit High (PINmin)
|
||||
confirm_pin_setting(single_step),
|
||||
@@ -144,9 +159,13 @@ class Card(object):
|
||||
0x02, # bMsgIndex3
|
||||
0x00, # bTeoPrologue[0]
|
||||
0x00, # bTeoPrologue[1]
|
||||
0x00 # bTeoPrologue[2]
|
||||
]
|
||||
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
|
||||
data = self.connection.control(self.modify_ioctl,pin_modify)
|
||||
sw1 = data[0]
|
||||
@@ -191,13 +210,27 @@ class Card(object):
|
||||
self.send_modify_pinpad(apdu, is_exchange,
|
||||
"cmd_change_reference_data_pinpad")
|
||||
|
||||
def main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps):
|
||||
card = Card(add_a_byte, pinmin, pinmax)
|
||||
COVADIS_VEGA_ALPHA="COVADIS VEGA-ALPHA (000000F5) 00 00"
|
||||
# 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()
|
||||
|
||||
print "Reader/Token:", card.connection.getReader()
|
||||
ident = card.connection.getReader()
|
||||
print "Reader/Token:", ident
|
||||
print "ATR:", toHexString( card.connection.getATR() )
|
||||
|
||||
if ident == COVADIS_VEGA_ALPHA:
|
||||
card.cmd_vega_alpha_disable_empty_verify()
|
||||
|
||||
card.get_features()
|
||||
|
||||
card.cmd_select_openpgp()
|
||||
@@ -280,6 +313,7 @@ def print_usage():
|
||||
print "\t--put:\t\tsetup resetcode (admin PIN, new PIN twice)"
|
||||
print "\t--put2::\t\tsetup resetcode (admin PIN:pinpad, new PIN:kbd)"
|
||||
print " options:"
|
||||
print "\t--fixed N:\tUse fixed length input"
|
||||
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--pinmin:\tspecify minimum length of PIN\t\t[6]"
|
||||
@@ -302,6 +336,7 @@ if __name__ == '__main__':
|
||||
pinmin = PIN_MIN_DEFAULT
|
||||
pinmax = PIN_MAX_DEFAULT
|
||||
change_by_two_steps = False
|
||||
fixed=0
|
||||
while len(sys.argv) >= 2:
|
||||
option = sys.argv[1]
|
||||
sys.argv.pop(1)
|
||||
@@ -319,6 +354,9 @@ if __name__ == '__main__':
|
||||
change_by_two_steps = True
|
||||
elif option == '--add':
|
||||
add_a_byte = True
|
||||
elif option == '--fixed':
|
||||
fixed = int(sys.argv[1])
|
||||
sys.argv.pop(1)
|
||||
elif option == '--pinmin':
|
||||
pinmin = int(sys.argv[1])
|
||||
sys.argv.pop(1)
|
||||
@@ -337,7 +375,7 @@ if __name__ == '__main__':
|
||||
exit(0)
|
||||
else:
|
||||
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
|
||||
# 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)
|
||||
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):
|
||||
prog = gen_prog_flash_write(addr,size)
|
||||
@@ -415,7 +415,7 @@ class stlinkv2(object):
|
||||
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
||||
|
||||
if (status & FLASH_SR_EOP) == 0:
|
||||
raise OperationError("flash erase all")
|
||||
raise OperationFailure("flash erase all")
|
||||
|
||||
def flash_erase_page(self, addr):
|
||||
self.write_memory_u32(FLASH_KEYR, FLASH_KEY1)
|
||||
@@ -439,7 +439,7 @@ class stlinkv2(object):
|
||||
self.write_memory_u32(FLASH_CR, FLASH_CR_LOCK)
|
||||
|
||||
if (status & FLASH_SR_EOP) == 0:
|
||||
raise OperationError("flash page erase")
|
||||
raise OperationFailure("flash page erase")
|
||||
|
||||
def start(self):
|
||||
mode = self.stl_mode()
|
||||
@@ -523,6 +523,7 @@ def main(show_help, erase_only, no_protect, spi_flash_check,
|
||||
print "ON"
|
||||
else:
|
||||
print "off"
|
||||
|
||||
option_bytes = stl.option_bytes_read()
|
||||
print "Option bytes: %08x" % option_bytes
|
||||
if (option_bytes & 0xff) == RDP_KEY:
|
||||
@@ -543,6 +544,7 @@ def main(show_help, erase_only, no_protect, spi_flash_check,
|
||||
raise OperationFailure("Flash ROM is protected")
|
||||
else:
|
||||
if not skip_blank_check:
|
||||
stl.reset_sys()
|
||||
blank = stl.blank_check()
|
||||
print "Flash ROM blank check: %s" % blank
|
||||
else:
|
||||
@@ -564,6 +566,9 @@ def main(show_help, erase_only, no_protect, spi_flash_check,
|
||||
stl.setup_gpio()
|
||||
|
||||
if unlock:
|
||||
if option_bytes != 0xff:
|
||||
stl.reset_sys()
|
||||
stl.option_bytes_erase()
|
||||
stl.reset_sys()
|
||||
stl.option_bytes_write(OPTION_BYTES_ADDR,RDP_KEY)
|
||||
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