Compare commits
31 Commits
release/1.
...
release/1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32779b6f96 | ||
|
|
55c1015faa | ||
|
|
0932465f0b | ||
|
|
4417799a51 | ||
|
|
b424cecf1e | ||
|
|
7ef417ae36 | ||
|
|
d4469c24ec | ||
|
|
e4333c6580 | ||
|
|
d2261d53e3 | ||
|
|
27bd37781a | ||
|
|
bed43d4049 | ||
|
|
f7d857b527 | ||
|
|
350528e1f4 | ||
|
|
4de605ed63 | ||
|
|
ffa9bf1f94 | ||
|
|
34d0b34144 | ||
|
|
5795dc9877 | ||
|
|
c8b17a8759 | ||
|
|
38d70e277b | ||
|
|
b00bab8dbf | ||
|
|
3c91dce8b7 | ||
|
|
f1773c146b | ||
|
|
979992c046 | ||
|
|
50700e3887 | ||
|
|
b0ee8b4452 | ||
|
|
a73f8cf4fd | ||
|
|
c1cc75f5b0 | ||
|
|
47150b5c98 | ||
|
|
f46880d2a8 | ||
|
|
23bbc9c755 | ||
|
|
2b784cb3b9 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,3 +12,5 @@ regnual/regnual.bin
|
||||
regnual/regnual.hex
|
||||
regnual/regnual.elf
|
||||
doc/_build
|
||||
tests/.cache
|
||||
tests/__pycache__
|
||||
|
||||
103
ChangeLog
103
ChangeLog
@@ -1,3 +1,106 @@
|
||||
2017-02-02 NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
* VERSION: 1.2.3.
|
||||
|
||||
* src/gnuk.ld.in (__process1_stack_size__): Increase by 0x20.
|
||||
* chopstx: Update to 1.3.
|
||||
* src/configure: Add BLUE_PILL in the help message.
|
||||
|
||||
2017-02-01 NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
* README: Update README. Thanks to Paul Fertser.
|
||||
|
||||
2017-01-02 Szczepan Zalega <szczepan@nitrokey.com>
|
||||
|
||||
* tool/upgrade_by_passwd.py: Add file extention check.
|
||||
|
||||
2017-02-01 NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
* tool/upgrade_by_passwd.py (main): More verbose messages
|
||||
suggested by Szczepan Zalega <szczepan@nitrokey.com>.
|
||||
|
||||
* tool/gnuk_token.py (USB_PRODUCT_LIST): New.
|
||||
(gnuk_devices_by_vidpid): Support searching by USB_PRODUCT_LIST.
|
||||
Thanks to Szczepan Zalega <szczepan@nitrokey.com>.
|
||||
|
||||
* tool/usb_strings.py: Use gnuk_token.py.
|
||||
|
||||
2016-10-21 Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
* src/ecc.c (check_secret): Fix condition.
|
||||
|
||||
2016-10-15 NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
* VERSION: 1.2.2.
|
||||
|
||||
* tool/gnuk_put_binary_libusb.py (main): Likewise.
|
||||
* tool/upgrade_by_passwd.py (main): Add call of cmd_select_openpgp
|
||||
method.
|
||||
|
||||
* src/openpgp.c (gpg_init): flash_init_keys shoule be after
|
||||
gpg_data_scan since flash_init_keys accesses Data Object for
|
||||
key attributes.
|
||||
|
||||
* src/usb-ccid.c (ccid_power_on): Don't waste stack.
|
||||
|
||||
2016-10-14 Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
* src/usb-ccid.c (ccid_power_on) [LIFE_CYCLE_MANAGEMENT_SUPPORT]:
|
||||
Change LCS value in ATR at run time.
|
||||
|
||||
* src/openpgp.c (gpg_init): Handle FILE_CARD_TERMINATED.
|
||||
(cmd_select_file): Don't return AID.
|
||||
(cmd_activate_file, cmd_terminate_df): New.
|
||||
(process_command_apdu): Let return GPG_NO_RECORD() when
|
||||
not selected.
|
||||
|
||||
* src/openpgp-do.c (gpg_do_terminate): New.
|
||||
(gpg_data_scan): Handle p_start is NULL.
|
||||
(do_hist_bytes): Remove.
|
||||
|
||||
* src/flash.c (flash_data): Change the value from 0x0000.
|
||||
(flash_init): Support termination state. Fix handling
|
||||
of the boundary case where gen0 is 0xfffe.
|
||||
(flash_terminate, flash_activate): New.
|
||||
(flash_copying_gc): Skip 0xffff for generation number.
|
||||
|
||||
2016-10-13 Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
* src/status-code.h: Rename from openpgp.h.
|
||||
|
||||
* chopstx: Update to 1.2.
|
||||
|
||||
* tests: New test suite for OpenPGP card with PyTest.
|
||||
|
||||
* src/configure (factory_reset): New.
|
||||
|
||||
* src/usb-ccid.c (ccid_power_on): Use ATR_head and historical
|
||||
bytes.
|
||||
|
||||
* src/openpgp-do.c (rw_algorithm_attr): Clear fingerprint, timestamp,
|
||||
and possibly ds_counter.
|
||||
|
||||
2016-10-12 Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
* test/features/steps.py (cmd_reset_retry_counter): Fix.
|
||||
* tool/gnuk_token.py (gnuk_token.cmd_reset_retry_counter): Fix.
|
||||
(gnuk_token.cmd_select_openpgp): Fix P2.
|
||||
|
||||
2016-09-02 Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
* src/configure (REVISION): Fix the detection of .git.
|
||||
It may be a regular file (if it's created by worktree).
|
||||
|
||||
2016-08-24 Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
* test/features/steps.py (ini): Use GLC (the global context),
|
||||
instead of FTC (the feature context), so that token only is
|
||||
opened once.
|
||||
|
||||
2016-08-03 Niibe Yutaka <gniibe@fsij.org>
|
||||
|
||||
* tool/hub_ctrl.py: Port to Python 3.
|
||||
|
||||
2016-07-11 NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
* VERSION: 1.2.1.
|
||||
|
||||
27
NEWS
27
NEWS
@@ -1,5 +1,32 @@
|
||||
Gnuk NEWS - User visible changes
|
||||
|
||||
* Major changes in Gnuk 1.2.3
|
||||
|
||||
Released 2017-02-02, by NIIBE Yutaka
|
||||
|
||||
** ECC key generation on the device
|
||||
Bug fixed.
|
||||
|
||||
** Upgrade of Chopstx
|
||||
We use Chopstx 1.3.
|
||||
|
||||
|
||||
* Major changes in Gnuk 1.2.2
|
||||
|
||||
Released 2016-10-15, by NIIBE Yutaka
|
||||
|
||||
** Change of SELECT FILE behavior
|
||||
Gnuk used to reply AID upon SELECT FILE command. Now, to be compatible
|
||||
to original OpenPGP card, it returns nothing but status code of 9000.
|
||||
|
||||
** Added feature of Factory Reset as compile time option
|
||||
Original OpenPGP card has the feature, and Gnuk is now configurable to
|
||||
support the feature.
|
||||
|
||||
** Upgrade of Chopstx
|
||||
We use Chopstx 1.2.
|
||||
|
||||
|
||||
* Major changes in Gnuk 1.2.1
|
||||
|
||||
Released 2016-07-11, by NIIBE Yutaka
|
||||
|
||||
83
README
83
README
@@ -1,14 +1,14 @@
|
||||
Gnuk - An Implementation of USB Cryptographic Token for GnuPG
|
||||
|
||||
Version 1.2.1
|
||||
2016-07-11
|
||||
Version 1.2.3
|
||||
2017-02-02
|
||||
Niibe Yutaka
|
||||
Free Software Initiative of Japan
|
||||
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
This is the release of Gnuk, version 1.2.1, which has major
|
||||
This is the release of Gnuk, version 1.2.3, which has major
|
||||
incompatible changes to Gnuk 1.0.x. Specifically, it now supports
|
||||
overriding key import, but importing keys (or generating keys) results
|
||||
password reset. Please update your documentation for Gnuk Token, so
|
||||
@@ -47,7 +47,7 @@ FAQ
|
||||
|
||||
Q0: How Gnuk USB Token is superior than other solutions (OpenPGP
|
||||
card 2.0, YubiKey, etc.) ?
|
||||
http://www.g10code.de/p-card.html
|
||||
https://www.g10code.de/p-card.html
|
||||
https://www.yubico.com/
|
||||
A0: Good points of Gnuk are:
|
||||
* If you have skill of electronics and like DIY, you can build
|
||||
@@ -77,12 +77,12 @@ A3: Orthodox choice is Olimex STM32-H103.
|
||||
choice for experiment.
|
||||
|
||||
Q4: What's version of GnuPG are you using?
|
||||
A4: In Debian GNU/Linux system, I use GnuPG modern 2.1.13 in
|
||||
experimental.
|
||||
A4: In Debian GNU/Linux system, I use GnuPG modern 2.1.18 in
|
||||
unstable.
|
||||
|
||||
Q5: What's version of pcscd and libccid are you using?
|
||||
A5: I don't use them, pcscd and libccid are optional, you can use Gnuk
|
||||
without them.
|
||||
Token without them.
|
||||
I tested pcscd 1.5.5-4 and libccid 1.3.11-2 which were in Debian
|
||||
squeeze.
|
||||
|
||||
@@ -248,7 +248,7 @@ External source code
|
||||
|
||||
Gnuk is distributed with external source code.
|
||||
|
||||
* chopstx/ -- Chopstx 1.1
|
||||
* chopstx/ -- Chopstx 1.3
|
||||
|
||||
We use Chopstx as the kernel for Gnuk.
|
||||
|
||||
@@ -361,10 +361,10 @@ You need GNU toolchain and newlib for 'arm-none-eabi' target.
|
||||
On Debian we can install the packages of gcc-arm-none-eabi,
|
||||
gdb-arm-none-eabi and its friends. I'm using:
|
||||
|
||||
binutils-arm-none-eabi 2.26-4+8
|
||||
gcc-arm-none-eabi 15:4.9.3+svn231177-1
|
||||
gdb-arm-none-eabi 7.10-1+9
|
||||
libnewlib-arm-none-eabi 2.2.0+git20150830.5a3d536-1
|
||||
binutils-arm-none-eabi 2.27-9+9
|
||||
gcc-arm-none-eabi 15:5.4.1+svn241155-1
|
||||
gdb-arm-none-eabi 7.11.1-2+9+b1
|
||||
libnewlib-arm-none-eabi 2.4.0.20160527-2
|
||||
|
||||
Or else, see https://launchpad.net/gcc-arm-embedded for preparation of
|
||||
GNU Toolchain for 'arm-none-eabi' target.
|
||||
@@ -395,18 +395,14 @@ How to install
|
||||
Olimex STM32-H103 board
|
||||
-----------------------
|
||||
|
||||
If you are using Olimex JTAG-Tiny, type following to invoke OpenOCD:
|
||||
If you are using Olimex JTAG-Tiny, type following to invoke OpenOCD
|
||||
and write "gnuk.elf" to Flash ROM:
|
||||
|
||||
$ openocd -f interface/ftdi/olimex-jtag-tiny.cfg -f board/olimex_stm32_h103.cfg
|
||||
$ openocd -f interface/ftdi/olimex-jtag-tiny.cfg \
|
||||
-f board/olimex_stm32_h103.cfg \
|
||||
-c "program build/gnuk.elf verify reset exit"
|
||||
|
||||
Then, with another terminal, type following to write "gnuk.elf" to Flash ROM:
|
||||
|
||||
$ telnet localhost 4444
|
||||
> reset halt
|
||||
> flash write_image erase gnuk.elf
|
||||
> reset
|
||||
> exit
|
||||
$
|
||||
Command invocation is assumed in src/ directory.
|
||||
|
||||
|
||||
Flying Stone Tiny 01
|
||||
@@ -414,9 +410,10 @@ Flying Stone Tiny 01
|
||||
|
||||
If you are using Flying Stone Tiny 01, you need a SWD writer.
|
||||
|
||||
OpenOCD 0.9 now supports ST-Link/V2. We can use it:
|
||||
OpenOCD 0.9.0 now supports ST-Link/V2. We can use it like:
|
||||
|
||||
$ openocd -f interface/stlink-v2.cfg -f target/stm32f1x_stlink.cfg
|
||||
$ openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg \
|
||||
-c "program build/gnuk.elf verify reset exit"
|
||||
|
||||
|
||||
|
||||
@@ -435,20 +432,24 @@ Then, reset the board.
|
||||
How to protect flash ROM
|
||||
========================
|
||||
|
||||
Invoke your OpenOCD and type:
|
||||
To protect, invoke OpenOCD like (for FST-01):
|
||||
|
||||
$ telnet localhost 4444
|
||||
> reset halt
|
||||
> stm32f1x lock 0
|
||||
> reset
|
||||
> shutdown
|
||||
$ openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg \
|
||||
-c init -c "reset halt" -c "stm32f1x lock 0" -c reset -c exit
|
||||
|
||||
After power-off / power-on sequence, the contents of flash ROM cannot
|
||||
be accessible from JTAG debugger.
|
||||
|
||||
Unprotecting is:
|
||||
|
||||
$ openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg \
|
||||
-c init -c "reset halt" -c "stm32f1x unlock 0" -c reset -c exit
|
||||
|
||||
Upon unprotection, flash is erased.
|
||||
|
||||
Note that it would be still possible for some implementation of DfuSe
|
||||
to access the contents. If you want to protect, killing DfuSe and
|
||||
accessing by JTAG debugger is recommended.
|
||||
to access the contents, even if it's protected. If you really want to
|
||||
protect, killing DfuSe and accessing by JTAG debugger is recommended.
|
||||
|
||||
|
||||
(Optional) Configure serial number and X.509 certificate
|
||||
@@ -551,6 +552,10 @@ Inside GDB, we can connect OpenOCD by:
|
||||
|
||||
(gdb) target remote localhost:3333
|
||||
|
||||
or
|
||||
|
||||
(gdb) target extended-remote localhost:3333
|
||||
|
||||
|
||||
You can see the output of PCSCD:
|
||||
|
||||
@@ -582,14 +587,14 @@ You can browse at: http://git.gniibe.org/gitweb?p=gnuk/gnuk.git;a=summary
|
||||
|
||||
I put Chopstx as a submodule of Git. Please do this:
|
||||
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ git submodule update --init
|
||||
|
||||
We have migrated from ChibiOS/RT to Chopstx in Gnuk 1.1. If you have
|
||||
old code of ChibiOS/RT, you need:
|
||||
Gnuk 1.0 uses ChibiOS/RT, and then, we have migrated from to Chopstx
|
||||
in the development phase of Gnuk 1.1. If you have old code of
|
||||
ChibiOS/RT, you need:
|
||||
|
||||
Edit .git/config to remove chibios reference
|
||||
git rm --cached chibios
|
||||
Edit .git/config to remove chibios reference and
|
||||
$ git rm --cached chibios
|
||||
|
||||
|
||||
Information on the Web
|
||||
@@ -599,7 +604,7 @@ Please visit: http://www.fsij.org/gnuk/
|
||||
|
||||
Please see the FST-01 support pages:
|
||||
|
||||
http://www.gniibe.org/category/fst-01.html
|
||||
https://www.gniibe.org/category/fst-01.html
|
||||
|
||||
Please consider to join Gnuk-users mailing list:
|
||||
|
||||
|
||||
2
THANKS
2
THANKS
@@ -29,9 +29,11 @@ NAGAMI Takeshi nagami-takeshi@aist.go.jp
|
||||
Nguyễn Hồng Quân quannguyen@mbm.vn
|
||||
Nico Rikken nico@nicorikken.eu
|
||||
NOKUBI Takatsugu knok@daionet.gr.jp
|
||||
Paul Fertser
|
||||
Paul Bakker polarssl_maintainer@polarssl.org
|
||||
Santiago Ruano Rincón santiago@debian.org
|
||||
Shane Coughlan scoughlan@openinventionnetwork.com
|
||||
Szczepan Zalega szczepan@nitrokey.com
|
||||
Vasily Evseenko
|
||||
Werner Koch wk@gnupg.org
|
||||
Yuji Imai ug@xcast.jp
|
||||
|
||||
2
chopstx
2
chopstx
Submodule chopstx updated: 09f27704f5...89eb54929e
@@ -30,7 +30,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "gnuk.h"
|
||||
#include "openpgp.h"
|
||||
#include "status-code.h"
|
||||
#include "random.h"
|
||||
#include "polarssl/config.h"
|
||||
#include "polarssl/rsa.h"
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
@CERTDO_DEFINE@
|
||||
@HID_CARD_CHANGE_DEFINE@
|
||||
@SERIALNO_STR_LEN_DEFINE@
|
||||
@LIFE_CYCLE_MANAGEMENT_DEFINE@
|
||||
|
||||
32
src/configure
vendored
32
src/configure
vendored
@@ -6,7 +6,7 @@ nl=$'\n'
|
||||
#
|
||||
# This file is *NOT* generated by GNU Autoconf, but written by NIIBE Yutaka
|
||||
#
|
||||
# Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015, 2016
|
||||
# Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
|
||||
# Free Software Initiative of Japan
|
||||
#
|
||||
# This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||
@@ -43,9 +43,10 @@ sys1_compat=yes
|
||||
pinpad=no
|
||||
certdo=no
|
||||
hid_card_change=no
|
||||
factory_reset=no
|
||||
|
||||
# Revision number
|
||||
if test -d ../.git; then
|
||||
if test -e ../.git; then
|
||||
REVISION=`git describe --dirty="-modified"`
|
||||
else
|
||||
REVISION=`cat ../VERSION`
|
||||
@@ -87,6 +88,10 @@ for option; do
|
||||
sys1_compat=yes ;;
|
||||
--disable-sys1-compat)
|
||||
sys1_compat=no ;;
|
||||
--enable-factory-reset)
|
||||
factory_reset=yes ;;
|
||||
--disable-factory-reset)
|
||||
factory_reset=no ;;
|
||||
--with-dfu)
|
||||
with_dfu=yes ;;
|
||||
--without-dfu)
|
||||
@@ -111,15 +116,17 @@ Configuration:
|
||||
--target=TARGET specify target [FST_01]
|
||||
supported targets are:
|
||||
FST_01
|
||||
FST_01G
|
||||
OLIMEX_STM32_H103
|
||||
STM32_PRIMER2
|
||||
STBEE
|
||||
STBEE_MINI
|
||||
MAPLE_MINI
|
||||
ST_DONGLE
|
||||
ST_NUCLEO_F103
|
||||
NITROKEY_START
|
||||
BLUE_PILL
|
||||
CQ_STARM
|
||||
STM32_PRIMER2
|
||||
STBEE
|
||||
STBEE_MINI
|
||||
FST_01_00 (unreleased version with 8MHz XTAL)
|
||||
--enable-debug debug with virtual COM port [no]
|
||||
--enable-pinpad=cir
|
||||
@@ -130,6 +137,8 @@ Configuration:
|
||||
--disable-sys1-compat disable SYS 1.0 compatibility [no]
|
||||
executable is target independent
|
||||
but requires SYS 2.0 or newer
|
||||
--enable-factory-reset
|
||||
support life cycle management [no]
|
||||
--with-dfu build image for DFU [<target specific>]
|
||||
EOF
|
||||
exit 0
|
||||
@@ -189,7 +198,8 @@ fi
|
||||
|
||||
# --with-dfu option
|
||||
if test "$with_dfu" = "yes"; then
|
||||
if test "$target" = "FST_01" -o "$target" = "FST_01_00"; then
|
||||
if test "$target" = "FST_01" -o "$target" = "FST_01G" \
|
||||
-o "$target" = "FST_01_00"; then
|
||||
echo "FST-01 doesn't have DFU loader, you should not use --with-dfu." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -246,6 +256,15 @@ else
|
||||
echo "Card insert/removal by HID device is NOT supported"
|
||||
fi
|
||||
|
||||
# --enable-factory-reset option
|
||||
if test "$factory_reset" = "yes"; then
|
||||
LIFE_CYCLE_MANAGEMENT_DEFINE="#define LIFE_CYCLE_MANAGEMENT_SUPPORT 1"
|
||||
echo "Life cycle management is supported"
|
||||
else
|
||||
LIFE_CYCLE_MANAGEMENT_DEFINE="#undef LIFE_CYCLE_MANAGEMENT_SUPPORT"
|
||||
echo "Life cycle management is NOT supported"
|
||||
fi
|
||||
|
||||
### !!! Replace following string of "FSIJ" to yours !!! ####
|
||||
SERIALNO="FSIJ-`cat ../VERSION | sed -e 's%^[^/]*/%%'`-"
|
||||
|
||||
@@ -373,6 +392,7 @@ sed -e "s/@DEBUG_DEFINE@/$DEBUG_DEFINE/" \
|
||||
-e "s/@PINPAD_MORE_DEFINE@/$PINPAD_MORE_DEFINE/" \
|
||||
-e "s/@CERTDO_DEFINE@/$CERTDO_DEFINE/" \
|
||||
-e "s/@HID_CARD_CHANGE_DEFINE@/$HID_CARD_CHANGE_DEFINE/" \
|
||||
-e "s/@LIFE_CYCLE_MANAGEMENT_DEFINE@/$LIFE_CYCLE_MANAGEMENT_DEFINE/" \
|
||||
-e "s/@SERIALNO_STR_LEN_DEFINE@/$SERIALNO_STR_LEN_DEFINE/" \
|
||||
< config.h.in > config.h
|
||||
exit 0
|
||||
|
||||
@@ -384,7 +384,7 @@ FUNC(check_secret) (const bn256 *d0, bn256 *d1)
|
||||
{
|
||||
ac Q0[1], Q1[1];
|
||||
|
||||
if (bn256_is_zero (d0) || bn256_sub (d1, N, d0) <= 0)
|
||||
if (bn256_is_zero (d0) || bn256_sub (d1, N, d0) != 0)
|
||||
/* == 0 or >= N, it's not valid. */
|
||||
return 0;
|
||||
|
||||
|
||||
45
src/flash.c
45
src/flash.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* flash.c -- Data Objects (DO) and GPG Key handling on Flash ROM
|
||||
*
|
||||
* Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015
|
||||
* Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015, 2016
|
||||
* Free Software Initiative of Japan
|
||||
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
*
|
||||
@@ -75,7 +75,7 @@ static uint8_t *last_p;
|
||||
|
||||
/* The first halfword is generation for the data page (little endian) */
|
||||
const uint8_t const flash_data[4] __attribute__ ((section (".gnuk_data"))) = {
|
||||
0x01, 0x00, 0xff, 0xff
|
||||
0x00, 0x00, 0xff, 0xff
|
||||
};
|
||||
|
||||
/* Linker set this symbol */
|
||||
@@ -114,22 +114,51 @@ flash_init (void)
|
||||
flash_page_size = 2048;
|
||||
|
||||
gen1_p = (uint16_t *)(&_data_pool + flash_page_size);
|
||||
data_pool = &_data_pool;
|
||||
|
||||
/* Check data pool generation and choose the page */
|
||||
gen0 = *gen0_p;
|
||||
gen1 = *gen1_p;
|
||||
|
||||
if (gen0 == 0xffff && gen1 == 0xffff)
|
||||
/* It's terminated. */
|
||||
return NULL;
|
||||
|
||||
if (gen0 == 0xffff)
|
||||
/* Use another page if a page is erased. */
|
||||
data_pool = &_data_pool + flash_page_size;
|
||||
else if (gen1 == 0xffff)
|
||||
/* Or use different page if another page is erased. */
|
||||
data_pool = &_data_pool;
|
||||
else if (gen1 > gen0)
|
||||
else if ((gen0 == 0xfffe && gen1 == 0) || gen1 > gen0)
|
||||
/* When both pages have valid header, use newer page. */
|
||||
data_pool = &_data_pool + flash_page_size;
|
||||
else
|
||||
data_pool = &_data_pool;
|
||||
|
||||
return data_pool + FLASH_DATA_POOL_HEADER_SIZE;
|
||||
}
|
||||
|
||||
static uint8_t *flash_key_getpage (enum kind_of_key kk);
|
||||
|
||||
void
|
||||
flash_terminate (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
flash_erase_page ((uint32_t)flash_key_getpage (i));
|
||||
flash_erase_page ((uint32_t)&_data_pool);
|
||||
flash_erase_page ((uint32_t)(&_data_pool + flash_page_size));
|
||||
data_pool = &_data_pool;
|
||||
last_p = &_data_pool + FLASH_DATA_POOL_HEADER_SIZE;
|
||||
}
|
||||
|
||||
void
|
||||
flash_activate (void)
|
||||
{
|
||||
flash_program_halfword ((uint32_t)&_data_pool, 0);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
flash_init_keys (void)
|
||||
{
|
||||
@@ -209,8 +238,12 @@ flash_copying_gc (void)
|
||||
generation = *(uint16_t *)src;
|
||||
data_pool = dst;
|
||||
gpg_data_copy (data_pool + FLASH_DATA_POOL_HEADER_SIZE);
|
||||
if (generation == 0xfffe)
|
||||
generation = 0;
|
||||
else
|
||||
generation++;
|
||||
flash_program_halfword ((uint32_t)dst, generation);
|
||||
flash_erase_page ((uint32_t)src);
|
||||
flash_program_halfword ((uint32_t)dst, generation+1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,12 +100,15 @@ void ac_fini (void);
|
||||
|
||||
|
||||
void set_res_sw (uint8_t sw1, uint8_t sw2);
|
||||
extern uint8_t file_selection;
|
||||
extern const uint8_t historical_bytes[];
|
||||
extern uint16_t data_objects_number_of_bytes;
|
||||
|
||||
#define CHALLENGE_LEN 32
|
||||
|
||||
void gpg_data_scan (const uint8_t *p);
|
||||
void gpg_data_copy (const uint8_t *p);
|
||||
void gpg_do_terminate (void);
|
||||
void gpg_do_get_data (uint16_t tag, int with_tag);
|
||||
void gpg_do_put_data (uint16_t tag, const uint8_t *data, int len);
|
||||
void gpg_do_public_key (uint8_t kk_byte);
|
||||
@@ -137,6 +140,8 @@ int gpg_get_algo_attr (enum kind_of_key kk);
|
||||
int gpg_get_algo_attr_key_size (enum kind_of_key kk, enum size_of_key s);
|
||||
|
||||
const uint8_t *flash_init (void);
|
||||
void flash_terminate (void);
|
||||
void flash_activate (void);
|
||||
void flash_init_keys (void);
|
||||
void flash_do_release (const uint8_t *);
|
||||
const uint8_t *flash_do_write (uint8_t nr, const uint8_t *data, int len);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
__main_stack_size__ = 0x0080; /* Exception handlers */
|
||||
__process0_stack_size__ = 0x0100; /* main */
|
||||
__process1_stack_size__ = 0x0180; /* ccid */
|
||||
__process1_stack_size__ = 0x01a0; /* ccid */
|
||||
__process2_stack_size__ = 0x0180; /* rng */
|
||||
__process3_stack_size__ = 0x1640; /* gpg */
|
||||
__process4_stack_size__ = 0; /* --- */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* openpgp-do.c -- OpenPGP card Data Objects (DO) handling
|
||||
*
|
||||
* Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015
|
||||
* Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015, 2016
|
||||
* Free Software Initiative of Japan
|
||||
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
*
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#include "sys.h"
|
||||
#include "gnuk.h"
|
||||
#include "openpgp.h"
|
||||
#include "status-code.h"
|
||||
#include "random.h"
|
||||
#include "polarssl/config.h"
|
||||
#include "polarssl/aes.h"
|
||||
@@ -90,11 +90,11 @@ uint16_t data_objects_number_of_bytes;
|
||||
|
||||
/*
|
||||
* Compile time vars:
|
||||
* Historical Bytes (template), Extended Capabilities.
|
||||
* Historical Bytes, Extended Capabilities.
|
||||
*/
|
||||
|
||||
/* Historical Bytes (template) */
|
||||
static const uint8_t historical_bytes[] __attribute__ ((aligned (1))) = {
|
||||
/* Historical Bytes */
|
||||
const uint8_t historical_bytes[] __attribute__ ((aligned (1))) = {
|
||||
10,
|
||||
0x00,
|
||||
0x31, 0x84, /* Full DF name, GET DATA, MF */
|
||||
@@ -102,7 +102,12 @@ static const uint8_t historical_bytes[] __attribute__ ((aligned (1))) = {
|
||||
0x80, 0x01, 0x80, /* Full DF name */
|
||||
/* 1-byte */
|
||||
/* Command chaining, No extended Lc and Le */
|
||||
0x00, 0x90, 0x00 /* Status info (no life cycle management) */
|
||||
#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
|
||||
0x05,
|
||||
#else
|
||||
0x00,
|
||||
#endif
|
||||
0x90, 0x00 /* Status info */
|
||||
};
|
||||
|
||||
/* Extended Capabilities */
|
||||
@@ -485,23 +490,6 @@ copy_tag (uint16_t tag)
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
do_hist_bytes (uint16_t tag, int with_tag)
|
||||
{
|
||||
/*
|
||||
* Currently, we support no life cycle management. In case of Gnuk,
|
||||
* user could flash the MCU with SWD/JTAG, instead. It is also
|
||||
* possible for user to do firmware upgrade through USB.
|
||||
*
|
||||
* Thus, here, it just returns the template as is.
|
||||
*
|
||||
* In future (when Gnuk will be on the real smartcard),
|
||||
* we can support life cycle management by implementing
|
||||
* TERMINATE DF / ACTIVATE FILE and fix code around here.
|
||||
*/
|
||||
copy_do_1 (tag, historical_bytes, with_tag);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define SIZE_FP 20
|
||||
#define SIZE_KGTIME 4
|
||||
@@ -763,6 +751,22 @@ rw_algorithm_attr (uint16_t tag, int with_tag,
|
||||
{
|
||||
gpg_do_delete_prvkey (kk, CLEAN_PAGE_FULL);
|
||||
flash_enum_clear (algo_attr_pp);
|
||||
if (kk == GPG_KEY_FOR_SIGNING)
|
||||
{
|
||||
gpg_reset_digital_signature_counter ();
|
||||
gpg_do_write_simple (NR_DO_FP_SIG, NULL, 0);
|
||||
gpg_do_write_simple (NR_DO_KGTIME_SIG, NULL, 0);
|
||||
}
|
||||
else if (kk == GPG_KEY_FOR_DECRYPTION)
|
||||
{
|
||||
gpg_do_write_simple (NR_DO_FP_DEC, NULL, 0);
|
||||
gpg_do_write_simple (NR_DO_KGTIME_DEC, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpg_do_write_simple (NR_DO_FP_AUT, NULL, 0);
|
||||
gpg_do_write_simple (NR_DO_KGTIME_AUT, NULL, 0);
|
||||
}
|
||||
if (*algo_attr_pp != NULL)
|
||||
return 0;
|
||||
}
|
||||
@@ -770,6 +774,22 @@ rw_algorithm_attr (uint16_t tag, int with_tag,
|
||||
|| (*algo_attr_pp)[1] != algo)
|
||||
{
|
||||
gpg_do_delete_prvkey (kk, CLEAN_PAGE_FULL);
|
||||
if (kk == GPG_KEY_FOR_SIGNING)
|
||||
{
|
||||
gpg_reset_digital_signature_counter ();
|
||||
gpg_do_write_simple (NR_DO_FP_SIG, NULL, 0);
|
||||
gpg_do_write_simple (NR_DO_KGTIME_SIG, NULL, 0);
|
||||
}
|
||||
else if (kk == GPG_KEY_FOR_DECRYPTION)
|
||||
{
|
||||
gpg_do_write_simple (NR_DO_FP_DEC, NULL, 0);
|
||||
gpg_do_write_simple (NR_DO_KGTIME_DEC, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpg_do_write_simple (NR_DO_FP_AUT, NULL, 0);
|
||||
gpg_do_write_simple (NR_DO_KGTIME_AUT, NULL, 0);
|
||||
}
|
||||
*algo_attr_pp = flash_enum_write (kk_to_nr (kk), algo);
|
||||
if (*algo_attr_pp == NULL)
|
||||
return 0;
|
||||
@@ -1042,6 +1062,28 @@ gpg_do_delete_prvkey (enum kind_of_key kk, int clean_page_full)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gpg_do_terminate (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
kd[i].pubkey = NULL;
|
||||
|
||||
for (i = 0; i < NR_DO__LAST__; i++)
|
||||
do_ptr[i] = NULL;
|
||||
|
||||
num_prv_keys = 0;
|
||||
data_objects_number_of_bytes = 0;
|
||||
digital_signature_counter = 0;
|
||||
|
||||
pw1_lifetime_p = NULL;
|
||||
pw_err_counter_p[PW_ERR_PW1] = NULL;
|
||||
pw_err_counter_p[PW_ERR_RC] = NULL;
|
||||
pw_err_counter_p[PW_ERR_PW3] = NULL;
|
||||
algo_attr_sig_p = algo_attr_dec_p = algo_attr_aut_p = NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
gpg_do_write_prvkey (enum kind_of_key kk, const uint8_t *key_data,
|
||||
int prvkey_len, const uint8_t *keystring_admin,
|
||||
@@ -1459,7 +1501,6 @@ gpg_do_table[] = {
|
||||
{ GPG_DO_NAME, DO_VAR, AC_ALWAYS, AC_ADMIN_AUTHORIZED, &do_ptr[12] },
|
||||
{ GPG_DO_LANGUAGE, DO_VAR, AC_ALWAYS, AC_ADMIN_AUTHORIZED, &do_ptr[13] },
|
||||
/* Pseudo DO READ: calculated */
|
||||
{ GPG_DO_HIST_BYTES, DO_PROC_READ, AC_ALWAYS, AC_NEVER, do_hist_bytes },
|
||||
{ GPG_DO_FP_ALL, DO_PROC_READ, AC_ALWAYS, AC_NEVER, do_fp_all },
|
||||
{ GPG_DO_CAFP_ALL, DO_PROC_READ, AC_ALWAYS, AC_NEVER, do_cafp_all },
|
||||
{ GPG_DO_KGTIME_ALL, DO_PROC_READ, AC_ALWAYS, AC_NEVER, do_kgtime_all },
|
||||
@@ -1476,6 +1517,7 @@ gpg_do_table[] = {
|
||||
{ GPG_DO_ALG_AUT, DO_PROC_READWRITE, AC_ALWAYS, AC_ADMIN_AUTHORIZED,
|
||||
rw_algorithm_attr },
|
||||
/* Fixed data */
|
||||
{ GPG_DO_HIST_BYTES, DO_FIXED, AC_ALWAYS, AC_NEVER, historical_bytes },
|
||||
{ GPG_DO_EXTCAP, DO_FIXED, AC_ALWAYS, AC_NEVER, extended_capabilities },
|
||||
/* Compound data: Read access only */
|
||||
{ GPG_DO_CH_DATA, DO_CMP_READ, AC_ALWAYS, AC_NEVER, cmp_ch_data },
|
||||
@@ -1517,7 +1559,7 @@ gpg_data_scan (const uint8_t *p_start)
|
||||
|
||||
/* Traverse DO, counters, etc. in DATA pool */
|
||||
p = p_start;
|
||||
while (*p != NR_EMPTY)
|
||||
while (p && *p != NR_EMPTY)
|
||||
{
|
||||
uint8_t nr = *p++;
|
||||
uint8_t second_byte = *p;
|
||||
|
||||
126
src/openpgp.c
126
src/openpgp.c
@@ -31,7 +31,7 @@
|
||||
|
||||
#include "gnuk.h"
|
||||
#include "sys.h"
|
||||
#include "openpgp.h"
|
||||
#include "status-code.h"
|
||||
#include "sha256.h"
|
||||
#include "random.h"
|
||||
|
||||
@@ -48,6 +48,7 @@ static struct eventflag *openpgp_comm;
|
||||
#define INS_CHANGE_REFERENCE_DATA 0x24
|
||||
#define INS_PSO 0x2a
|
||||
#define INS_RESET_RETRY_COUNTER 0x2c
|
||||
#define INS_ACTIVATE_FILE 0x44
|
||||
#define INS_PGP_GENERATE_ASYMMETRIC_KEY_PAIR 0x47
|
||||
#define INS_EXTERNAL_AUTHENTICATE 0x82
|
||||
#define INS_GET_CHALLENGE 0x84
|
||||
@@ -59,6 +60,7 @@ static struct eventflag *openpgp_comm;
|
||||
#define INS_UPDATE_BINARY 0xd6
|
||||
#define INS_PUT_DATA 0xda
|
||||
#define INS_PUT_DATA_ODD 0xdb /* For key import */
|
||||
#define INS_TERMINATE_DF 0xe6
|
||||
|
||||
static const uint8_t *challenge; /* Random bytes */
|
||||
|
||||
@@ -96,16 +98,23 @@ set_res_sw (uint8_t sw1, uint8_t sw2)
|
||||
#define FILE_EF_UPDATE_KEY_2 7
|
||||
#define FILE_EF_UPDATE_KEY_3 8
|
||||
#define FILE_EF_CH_CERTIFICATE 9
|
||||
#define FILE_CARD_TERMINATED_OPENPGP 254
|
||||
#define FILE_CARD_TERMINATED 255
|
||||
|
||||
static uint8_t file_selection;
|
||||
uint8_t file_selection;
|
||||
|
||||
static void
|
||||
gpg_init (void)
|
||||
{
|
||||
const uint8_t *flash_data_start;
|
||||
|
||||
file_selection = FILE_NONE;
|
||||
flash_data_start = flash_init ();
|
||||
|
||||
if (flash_data_start == NULL)
|
||||
file_selection = FILE_CARD_TERMINATED;
|
||||
else
|
||||
file_selection = FILE_NONE;
|
||||
|
||||
gpg_data_scan (flash_data_start);
|
||||
flash_init_keys ();
|
||||
}
|
||||
@@ -616,9 +625,6 @@ cmd_put_data (void)
|
||||
|
||||
DEBUG_INFO (" - PUT DATA\r\n");
|
||||
|
||||
if (file_selection != FILE_DF_OPENPGP)
|
||||
GPG_NO_RECORD();
|
||||
|
||||
tag = ((P1 (apdu)<<8) | P2 (apdu));
|
||||
len = apdu.cmd_apdu_data_len;
|
||||
data = apdu.cmd_apdu_data;
|
||||
@@ -751,19 +757,17 @@ cmd_select_file (void)
|
||||
return;
|
||||
}
|
||||
|
||||
file_selection = FILE_DF_OPENPGP;
|
||||
if ((P2 (apdu) & 0x0c) == 0x0c) /* No FCI */
|
||||
GPG_SUCCESS ();
|
||||
else
|
||||
if (file_selection == FILE_CARD_TERMINATED)
|
||||
{
|
||||
gpg_do_get_data (0x004f, 1); /* AID */
|
||||
memmove (res_APDU+2, res_APDU, res_APDU_size);
|
||||
res_APDU[0] = 0x6f;
|
||||
res_APDU[1] = 0x12;
|
||||
res_APDU[2] = 0x84; /* overwrite: DF name */
|
||||
res_APDU_size += 2;
|
||||
GPG_SUCCESS ();
|
||||
file_selection = FILE_CARD_TERMINATED_OPENPGP;
|
||||
GPG_APPLICATION_TERMINATED();
|
||||
return;
|
||||
}
|
||||
|
||||
file_selection = FILE_DF_OPENPGP;
|
||||
|
||||
/* Behave just like original OpenPGP card. */
|
||||
GPG_SUCCESS ();
|
||||
}
|
||||
else if (apdu.cmd_apdu_data_len == 2
|
||||
&& apdu.cmd_apdu_data[0] == 0x2f && apdu.cmd_apdu_data[1] == 0x02)
|
||||
@@ -813,9 +817,6 @@ cmd_get_data (void)
|
||||
|
||||
DEBUG_INFO (" - Get Data\r\n");
|
||||
|
||||
if (file_selection != FILE_DF_OPENPGP)
|
||||
GPG_NO_RECORD ();
|
||||
|
||||
gpg_do_get_data (tag, 0);
|
||||
}
|
||||
|
||||
@@ -1317,6 +1318,64 @@ cmd_get_challenge (void)
|
||||
}
|
||||
|
||||
|
||||
#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
|
||||
static void
|
||||
cmd_activate_file (void)
|
||||
{
|
||||
if (file_selection != FILE_CARD_TERMINATED_OPENPGP)
|
||||
{
|
||||
GPG_NO_RECORD();
|
||||
return;
|
||||
}
|
||||
|
||||
flash_activate ();
|
||||
file_selection = FILE_DF_OPENPGP;
|
||||
GPG_SUCCESS ();
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_terminate_df (void)
|
||||
{
|
||||
uint8_t p1 = P1 (apdu);
|
||||
uint8_t p2 = P2 (apdu);
|
||||
|
||||
if (file_selection != FILE_DF_OPENPGP)
|
||||
{
|
||||
GPG_NO_RECORD();
|
||||
return;
|
||||
}
|
||||
|
||||
if (p1 != 0 || p2 != 0)
|
||||
{
|
||||
GPG_BAD_P1_P2();
|
||||
return;
|
||||
}
|
||||
|
||||
if (apdu.cmd_apdu_data_len != 0)
|
||||
{
|
||||
GPG_WRONG_LENGTH();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!ac_check_status (AC_ADMIN_AUTHORIZED) && !gpg_pw_locked (PW_ERR_PW3))
|
||||
{
|
||||
/* Only allow the case admin authorized, or, admin pass is locked. */
|
||||
GPG_SECURITY_FAILURE();
|
||||
return;
|
||||
}
|
||||
|
||||
ac_reset_admin ();
|
||||
ac_reset_pso_cds ();
|
||||
ac_reset_other ();
|
||||
gpg_do_terminate ();
|
||||
flash_terminate ();
|
||||
file_selection = FILE_CARD_TERMINATED;
|
||||
GPG_SUCCESS ();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
struct command
|
||||
{
|
||||
uint8_t command;
|
||||
@@ -1328,13 +1387,16 @@ const struct command cmds[] = {
|
||||
{ INS_CHANGE_REFERENCE_DATA, cmd_change_password },
|
||||
{ INS_PSO, cmd_pso },
|
||||
{ INS_RESET_RETRY_COUNTER, cmd_reset_user_password },
|
||||
#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
|
||||
{ INS_ACTIVATE_FILE, cmd_activate_file },
|
||||
#endif
|
||||
{ INS_PGP_GENERATE_ASYMMETRIC_KEY_PAIR, cmd_pgp_gakp },
|
||||
{ INS_EXTERNAL_AUTHENTICATE, /* Not in OpenPGP card protocol */
|
||||
cmd_external_authenticate },
|
||||
{ INS_GET_CHALLENGE, cmd_get_challenge }, /* Not in OpenPGP card protocol */
|
||||
{ INS_INTERNAL_AUTHENTICATE, cmd_internal_authenticate },
|
||||
{ INS_SELECT_FILE, cmd_select_file },
|
||||
{ INS_READ_BINARY, cmd_read_binary },
|
||||
{ INS_READ_BINARY, cmd_read_binary }, /* Not in OpenPGP card protocol */
|
||||
{ INS_GET_DATA, cmd_get_data },
|
||||
{ INS_WRITE_BINARY, cmd_write_binary}, /* Not in OpenPGP card protocol */
|
||||
#if defined(CERTDO_SUPPORT)
|
||||
@@ -1342,6 +1404,9 @@ const struct command cmds[] = {
|
||||
#endif
|
||||
{ INS_PUT_DATA, cmd_put_data },
|
||||
{ INS_PUT_DATA_ODD, cmd_put_data },
|
||||
#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
|
||||
{ INS_TERMINATE_DF, cmd_terminate_df},
|
||||
#endif
|
||||
};
|
||||
#define NUM_CMDS ((int)(sizeof (cmds) / sizeof (struct command)))
|
||||
|
||||
@@ -1357,9 +1422,22 @@ process_command_apdu (void)
|
||||
|
||||
if (i < NUM_CMDS)
|
||||
{
|
||||
chopstx_setcancelstate (1);
|
||||
cmds[i].cmd_handler ();
|
||||
chopstx_setcancelstate (0);
|
||||
if (file_selection == FILE_CARD_TERMINATED
|
||||
&& cmd != INS_SELECT_FILE && cmd != INS_ACTIVATE_FILE
|
||||
&& cmd != INS_GET_CHALLENGE && cmd != INS_EXTERNAL_AUTHENTICATE)
|
||||
GPG_APPLICATION_TERMINATED();
|
||||
else if (file_selection != FILE_DF_OPENPGP
|
||||
&& cmd != INS_SELECT_FILE && cmd != INS_ACTIVATE_FILE
|
||||
&& cmd != INS_GET_CHALLENGE && cmd != INS_EXTERNAL_AUTHENTICATE
|
||||
&& cmd != INS_WRITE_BINARY && cmd != INS_UPDATE_BINARY
|
||||
&& cmd != INS_READ_BINARY)
|
||||
GPG_NO_RECORD();
|
||||
else
|
||||
{
|
||||
chopstx_setcancelstate (1);
|
||||
cmds[i].cmd_handler ();
|
||||
chopstx_setcancelstate (0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#define GPG_APPLICATION_TERMINATED() set_res_sw (0x62, 0x85)
|
||||
#define GPG_MEMORY_FAILURE() set_res_sw (0x65, 0x81)
|
||||
#define GPG_WRONG_LENGTH() set_res_sw (0x67, 0x00)
|
||||
#define GPG_SECURITY_FAILURE() set_res_sw (0x69, 0x82)
|
||||
#define GPG_SECURITY_AUTH_BLOCKED() set_res_sw (0x69, 0x83)
|
||||
#define GPG_CONDITION_NOT_SATISFIED() set_res_sw (0x69, 0x85)
|
||||
@@ -720,24 +720,12 @@ usb_tx_done (uint8_t ep_num, uint16_t len)
|
||||
* TB3 = 0x55: BWI = 5, CWI = 5 (BWT timeout 3.2 sec)
|
||||
* TD3 = 0x1f: TA4 follows, T=15
|
||||
* TA4 = 0x03: 5V or 3.3V
|
||||
* Historical bytes: to be explained...
|
||||
* XOR check
|
||||
*
|
||||
* Minimum: 0x3b, 0x8a, 0x80, 0x01, + historical bytes, xor check
|
||||
* Minimum: 0x3b, 0x8a, 0x80, 0x01
|
||||
*
|
||||
*/
|
||||
static const uint8_t ATR[] = {
|
||||
static const uint8_t ATR_head[] = {
|
||||
0x3b, 0xda, 0x11, 0xff, 0x81, 0xb1, 0xfe, 0x55, 0x1f, 0x03,
|
||||
0x00,
|
||||
0x31, 0x84, /* full DF name, GET DATA, MF */
|
||||
0x73,
|
||||
0x80, /* DF full name */
|
||||
0x01, /* 1-byte */
|
||||
0x80, /* Command chaining, No extended Lc and extended Le */
|
||||
0x00,
|
||||
0x90, 0x00,
|
||||
(0xda^0x11^0xff^0x81^0xb1^0xfe^0x55^0x1f^0x03
|
||||
^0x00^0x31^0x84^0x73^0x80^0x01^0x80^0x00^0x90^0x00)
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -783,8 +771,11 @@ extern uint8_t __process3_stack_base__[], __process3_stack_size__[];
|
||||
static enum ccid_state
|
||||
ccid_power_on (struct ccid *c)
|
||||
{
|
||||
size_t size_atr = sizeof (ATR);
|
||||
uint8_t p[CCID_MSG_HEADER_SIZE];
|
||||
uint8_t p[CCID_MSG_HEADER_SIZE+1]; /* >= size of historical_bytes -1 */
|
||||
int hist_len = historical_bytes[0];
|
||||
size_t size_atr = sizeof (ATR_head) + hist_len + 1;
|
||||
uint8_t xor_check = 0;
|
||||
int i;
|
||||
|
||||
if (c->application == 0)
|
||||
c->application = chopstx_create (PRIO_GPG, STACK_ADDR_GPG,
|
||||
@@ -803,7 +794,20 @@ ccid_power_on (struct ccid *c)
|
||||
p[CCID_MSG_CHAIN_OFFSET] = 0x00;
|
||||
|
||||
usb_lld_txcpy (p, c->epi->ep_num, 0, CCID_MSG_HEADER_SIZE);
|
||||
usb_lld_txcpy (ATR, c->epi->ep_num, CCID_MSG_HEADER_SIZE, size_atr);
|
||||
usb_lld_txcpy (ATR_head, c->epi->ep_num, CCID_MSG_HEADER_SIZE,
|
||||
sizeof (ATR_head));
|
||||
for (i = 1; i < (int)sizeof (ATR_head); i++)
|
||||
xor_check ^= ATR_head[i];
|
||||
memcpy (p, historical_bytes + 1, hist_len);
|
||||
#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
|
||||
if (file_selection == 255)
|
||||
p[7] = 0x03;
|
||||
#endif
|
||||
for (i = 0; i < hist_len; i++)
|
||||
xor_check ^= p[i];
|
||||
p[i] = xor_check;
|
||||
usb_lld_txcpy (p, c->epi->ep_num, CCID_MSG_HEADER_SIZE + sizeof (ATR_head),
|
||||
hist_len+1);
|
||||
|
||||
/* This is a single packet Bulk-IN transaction */
|
||||
c->epi->buf = NULL;
|
||||
|
||||
@@ -4,7 +4,7 @@ Feature: command GET DATA
|
||||
|
||||
Scenario: data object historical bytes
|
||||
When requesting historical bytes: 5f52
|
||||
Then you should get: \x00\x31\x84\x73\x80\x01\x80\x00\x90\x00
|
||||
Then data should match: \x00\x31\x84\x73\x80\x01\x80[\x00\x05]\x90\x00
|
||||
|
||||
Scenario: data object extended capabilities
|
||||
When requesting extended capabilities: c0
|
||||
|
||||
@@ -4,7 +4,7 @@ Feature: command GET DATA
|
||||
|
||||
Scenario: data object historical bytes
|
||||
When requesting historical bytes: 5f52
|
||||
Then you should get: \x00\x31\x84\x73\x80\x01\x80\x00\x90\x00
|
||||
Then data should match: \x00\x31\x84\x73\x80\x01\x80[\x00\x05]\x90\x00
|
||||
|
||||
Scenario: data object extended capabilities
|
||||
When requesting extended capabilities: c0
|
||||
|
||||
@@ -4,7 +4,7 @@ Feature: command GET DATA
|
||||
|
||||
Scenario: data object historical bytes
|
||||
When requesting historical bytes: 5f52
|
||||
Then you should get: \x00\x31\x84\x73\x80\x01\x80\x00\x90\x00
|
||||
Then data should match: \x00\x31\x84\x73\x80\x01\x80[\x00\x05]\x90\x00
|
||||
|
||||
Scenario: data object extended capabilities
|
||||
When requesting extended capabilities: c0
|
||||
|
||||
@@ -11,19 +11,19 @@ from array import array
|
||||
|
||||
@Before
|
||||
def ini(sc):
|
||||
if not ftc.token:
|
||||
ftc.token = gnuk.get_gnuk_device()
|
||||
ftc.token.cmd_select_openpgp()
|
||||
if not glc.token:
|
||||
glc.token = gnuk.get_gnuk_device()
|
||||
glc.token.cmd_select_openpgp()
|
||||
|
||||
@Given("cmd_verify with (.*) and \"(.*)\"")
|
||||
def cmd_verify(who_str,pass_str):
|
||||
who = int(who_str)
|
||||
scc.result = ftc.token.cmd_verify(who, pass_str)
|
||||
scc.result = glc.token.cmd_verify(who, pass_str)
|
||||
|
||||
@Given("cmd_change_reference_data with (.*) and \"(.*)\"")
|
||||
def cmd_change_reference_data(who_str,pass_str):
|
||||
who = int(who_str)
|
||||
scc.result = ftc.token.cmd_change_reference_data(who, pass_str)
|
||||
scc.result = glc.token.cmd_change_reference_data(who, pass_str)
|
||||
|
||||
@Given("cmd_put_data with (.*) and (\".*\")")
|
||||
def cmd_put_data(tag_str,content_str_repr):
|
||||
@@ -31,12 +31,12 @@ def cmd_put_data(tag_str,content_str_repr):
|
||||
tag = int(tag_str, 16)
|
||||
tagh = tag >> 8
|
||||
tagl = tag & 0xff
|
||||
scc.result = ftc.token.cmd_put_data(tagh, tagl, content_str)
|
||||
scc.result = glc.token.cmd_put_data(tagh, tagl, content_str)
|
||||
|
||||
@Given("cmd_reset_retry_counter with (.*) and \"(.*)\"")
|
||||
def cmd_reset_retry_counter(how_str, data):
|
||||
how = int(how_str)
|
||||
scc.result = ftc.token.cmd_reset_retry_counter(how, data)
|
||||
scc.result = glc.token.cmd_reset_retry_counter(how, 0x81, data)
|
||||
|
||||
@Given("a RSA key pair (.*)")
|
||||
def set_rsa_key(keyno_str):
|
||||
@@ -46,7 +46,7 @@ def set_rsa_key(keyno_str):
|
||||
def import_key(openpgp_keyno_str):
|
||||
openpgp_keyno = int(openpgp_keyno_str)
|
||||
t = rsa_keys.build_privkey_template(openpgp_keyno, scc.keyno)
|
||||
scc.result = ftc.token.cmd_put_data_odd(0x3f, 0xff, t)
|
||||
scc.result = glc.token.cmd_put_data_odd(0x3f, 0xff, t)
|
||||
|
||||
@Given("a fingerprint of OPENPGP.(.*) key")
|
||||
def get_key_fpr(openpgp_keyno_str):
|
||||
@@ -63,7 +63,7 @@ def cmd_put_data_with_result(tag_str):
|
||||
tag = int(tag_str, 16)
|
||||
tagh = tag >> 8
|
||||
tagl = tag & 0xff
|
||||
scc.result = ftc.token.cmd_put_data(tagh, tagl, scc.result)
|
||||
scc.result = glc.token.cmd_put_data(tagh, tagl, scc.result)
|
||||
|
||||
@Given("a message (\".*\")")
|
||||
def set_msg(content_str_repr):
|
||||
@@ -73,7 +73,7 @@ def set_msg(content_str_repr):
|
||||
@Given("a public key from token for OPENPGP.(.*)")
|
||||
def get_public_key(openpgp_keyno_str):
|
||||
openpgp_keyno = int(openpgp_keyno_str)
|
||||
scc.pubkey_info = ftc.token.cmd_get_public_key(openpgp_keyno)
|
||||
scc.pubkey_info = glc.token.cmd_get_public_key(openpgp_keyno)
|
||||
|
||||
@Given("verify signature")
|
||||
def verify_signature():
|
||||
@@ -81,11 +81,11 @@ def verify_signature():
|
||||
|
||||
@Given("let a token compute digital signature")
|
||||
def compute_signature():
|
||||
scc.sig = int(hexlify(ftc.token.cmd_pso(0x9e, 0x9a, scc.digestinfo)),16)
|
||||
scc.sig = int(hexlify(glc.token.cmd_pso(0x9e, 0x9a, scc.digestinfo)),16)
|
||||
|
||||
@Given("let a token authenticate")
|
||||
def internal_authenticate():
|
||||
scc.sig = int(hexlify(ftc.token.cmd_internal_authenticate(scc.digestinfo)),16)
|
||||
scc.sig = int(hexlify(glc.token.cmd_internal_authenticate(scc.digestinfo)),16)
|
||||
|
||||
@Given("compute digital signature on host with RSA key pair (.*)")
|
||||
def compute_signature_on_host(keyno_str):
|
||||
@@ -107,29 +107,29 @@ def encrypt_on_host_public_key():
|
||||
|
||||
@Given("let a token decrypt encrypted data")
|
||||
def decrypt():
|
||||
scc.result = ftc.token.cmd_pso_longdata(0x80, 0x86, scc.ciphertext).tostring()
|
||||
scc.result = glc.token.cmd_pso_longdata(0x80, 0x86, scc.ciphertext).tostring()
|
||||
|
||||
@Given("USB version string of the token")
|
||||
def usb_version_string():
|
||||
scc.result = ftc.token.get_string(3)
|
||||
scc.result = glc.token.get_string(3)
|
||||
|
||||
@When("requesting (.+): ([0-9a-fA-F]+)")
|
||||
def get_data(name, tag_str):
|
||||
tag = int(tag_str, 16)
|
||||
tagh = tag >> 8
|
||||
tagl = tag & 0xff
|
||||
scc.result = ftc.token.cmd_get_data(tagh, tagl)
|
||||
scc.result = glc.token.cmd_get_data(tagh, tagl)
|
||||
|
||||
@When("removing a key OPENPGP.(.*)")
|
||||
def remove_key(openpgp_keyno_str):
|
||||
openpgp_keyno = int(openpgp_keyno_str)
|
||||
t = rsa_keys.build_privkey_template_for_remove(openpgp_keyno)
|
||||
scc.result = ftc.token.cmd_put_data_odd(0x3f, 0xff, t)
|
||||
scc.result = glc.token.cmd_put_data_odd(0x3f, 0xff, t)
|
||||
|
||||
@When("generating a key of OPENPGP.(.*)")
|
||||
def generate_key(openpgp_keyno_str):
|
||||
openpgp_keyno = int(openpgp_keyno_str)
|
||||
pubkey_info = ftc.token.cmd_genkey(openpgp_keyno)
|
||||
pubkey_info = glc.token.cmd_genkey(openpgp_keyno)
|
||||
scc.data = rsa_keys.calc_fpr(pubkey_info[0].tostring(), pubkey_info[1].tostring())
|
||||
|
||||
@When("put the first data to (.*)")
|
||||
@@ -137,14 +137,14 @@ def cmd_put_data_first_with_result(tag_str):
|
||||
tag = int(tag_str, 16)
|
||||
tagh = tag >> 8
|
||||
tagl = tag & 0xff
|
||||
scc.result = ftc.token.cmd_put_data(tagh, tagl, scc.data[0])
|
||||
scc.result = glc.token.cmd_put_data(tagh, tagl, scc.data[0])
|
||||
|
||||
@When("put the second data to (.*)")
|
||||
def cmd_put_data_second_with_result(tag_str):
|
||||
tag = int(tag_str, 16)
|
||||
tagh = tag >> 8
|
||||
tagl = tag & 0xff
|
||||
result = ftc.token.cmd_put_data(tagh, tagl, scc.data[1])
|
||||
result = glc.token.cmd_put_data(tagh, tagl, scc.data[1])
|
||||
scc.result = (scc.result and result)
|
||||
|
||||
@Then("you should get: (.*)")
|
||||
|
||||
@@ -60,7 +60,7 @@ def build_privkey_template(openpgp_keyno, keyno):
|
||||
|
||||
suffix = b'\x5f\x48' + b'\x82\x01\x04'
|
||||
|
||||
t = b'\x4d' + b'\x82\01\16' + exthdr + suffix + e_bytes + p_bytes + q_bytes
|
||||
t = b'\x4d' + b'\x82\x01\x16' + exthdr + suffix + e_bytes + p_bytes + q_bytes
|
||||
return t
|
||||
|
||||
def build_privkey_template_for_remove(openpgp_keyno):
|
||||
|
||||
13
tests/README
Normal file
13
tests/README
Normal file
@@ -0,0 +1,13 @@
|
||||
Here is a test suite for OpenPGP card.
|
||||
|
||||
For now, only TPDU card reader is supported for OpenPGP card.
|
||||
Gnuk Token is supported as well.
|
||||
|
||||
|
||||
You need to install:
|
||||
|
||||
$ sudo apt-get install python3-pytest python3-usb
|
||||
|
||||
Please run test by typing:
|
||||
|
||||
$ py.test-3 -x
|
||||
329
tests/card_reader.py
Normal file
329
tests/card_reader.py
Normal file
@@ -0,0 +1,329 @@
|
||||
"""
|
||||
card_reader.py - a library for smartcard reader
|
||||
|
||||
Copyright (C) 2016 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 usb.core
|
||||
from struct import pack
|
||||
from usb.util import find_descriptor, claim_interface, get_string, \
|
||||
endpoint_type, endpoint_direction, \
|
||||
ENDPOINT_TYPE_BULK, ENDPOINT_OUT, ENDPOINT_IN
|
||||
|
||||
# USB class, subclass, protocol
|
||||
CCID_CLASS = 0x0B
|
||||
CCID_SUBCLASS = 0x00
|
||||
CCID_PROTOCOL_0 = 0x00
|
||||
|
||||
def ccid_compose(msg_type, seq, slot=0, rsv=0, param=0, data=b""):
|
||||
return pack('<BiBBBH', msg_type, len(data), slot, seq, rsv, param) + data
|
||||
|
||||
IFSC=254
|
||||
|
||||
def compute_edc(pcb, info):
|
||||
edc = pcb
|
||||
edc ^= len(info)
|
||||
for i in range(len(info)):
|
||||
edc ^= info[i]
|
||||
return edc
|
||||
|
||||
def compose_i_block(ns, info, more):
|
||||
pcb = 0x00
|
||||
if ns:
|
||||
pcb |= 0x40
|
||||
if more:
|
||||
pcb |= 0x20
|
||||
edc = compute_edc(pcb, info)
|
||||
return bytes([0, pcb, len(info)]) + info + bytes([edc])
|
||||
|
||||
def compose_r_block(nr, edc_error=0):
|
||||
pcb = 0x80
|
||||
if nr:
|
||||
pcb |= 0x10
|
||||
if edc_error:
|
||||
pcb |= 0x01
|
||||
return bytes([0, pcb, 0, pcb])
|
||||
|
||||
def is_r_block_no_error_or_other(blk):
|
||||
return (((blk[1] & 0xC0) == 0x80 and (blk[1] & 0x2f) == 0x00)) or \
|
||||
((blk[1] & 0xC0) != 0x80)
|
||||
|
||||
def is_s_block_time_ext(blk):
|
||||
return (blk[1] == 0xC3)
|
||||
|
||||
def is_i_block_last(blk):
|
||||
return ((blk[1] & 0x80) == 0 and (blk[1] & 0x20) == 0)
|
||||
|
||||
def is_i_block_more(blk):
|
||||
return ((blk[1] & 0x80) == 0 and (blk[1] & 0x20) == 0x20)
|
||||
|
||||
def is_edc_error(blk):
|
||||
# to be implemented
|
||||
return 0
|
||||
|
||||
def i_block_content(blk):
|
||||
return blk[3:-1]
|
||||
|
||||
class CardReader(object):
|
||||
def __init__(self, dev):
|
||||
"""
|
||||
__init__(dev) -> None
|
||||
Initialize the DEV of CCID.
|
||||
device: usb.core.Device object.
|
||||
"""
|
||||
|
||||
cfg = dev.get_active_configuration()
|
||||
intf = find_descriptor(cfg, bInterfaceClass=CCID_CLASS,
|
||||
bInterfaceSubClass=CCID_SUBCLASS,
|
||||
bInterfaceProtocol=CCID_PROTOCOL_0)
|
||||
if intf is None:
|
||||
raise ValueError("Not a CCID device")
|
||||
|
||||
claim_interface(dev, intf)
|
||||
|
||||
for ep in intf:
|
||||
if endpoint_type(ep.bmAttributes) == ENDPOINT_TYPE_BULK and \
|
||||
endpoint_direction(ep.bEndpointAddress) == ENDPOINT_OUT:
|
||||
self.__bulkout = ep.bEndpointAddress
|
||||
if endpoint_type(ep.bmAttributes) == ENDPOINT_TYPE_BULK and \
|
||||
endpoint_direction(ep.bEndpointAddress) == ENDPOINT_IN:
|
||||
self.__bulkin = ep.bEndpointAddress
|
||||
|
||||
assert len(intf.extra_descriptors) == 54
|
||||
assert intf.extra_descriptors[1] == 33
|
||||
|
||||
if (intf.extra_descriptors[42] & 0x02):
|
||||
# Short APDU level exchange
|
||||
self.__use_APDU = True
|
||||
elif (intf.extra_descriptors[42] & 0x04):
|
||||
# Short and extended APDU level exchange
|
||||
self.__use_APDU = True
|
||||
elif (intf.extra_descriptors[42] & 0x01):
|
||||
# TPDU level exchange
|
||||
self.__use_APDU = False
|
||||
else:
|
||||
raise ValueError("Unknown exchange level")
|
||||
|
||||
# Check other bits???
|
||||
# intf.extra_descriptors[40]
|
||||
# intf.extra_descriptors[41]
|
||||
|
||||
self.__dev = dev
|
||||
self.__timeout = 10000
|
||||
self.__seq = 0
|
||||
|
||||
def get_string(self, num):
|
||||
return get_string(self.__dev, num)
|
||||
|
||||
def increment_seq(self):
|
||||
self.__seq = (self.__seq + 1) & 0xff
|
||||
|
||||
def reset_device(self):
|
||||
try:
|
||||
self.__dev.reset()
|
||||
except:
|
||||
pass
|
||||
|
||||
def is_tpdu_reader(self):
|
||||
return not self.__use_APDU
|
||||
|
||||
def ccid_get_result(self):
|
||||
msg = self.__dev.read(self.__bulkin, 1024, self.__timeout)
|
||||
if len(msg) < 10:
|
||||
print(msg)
|
||||
raise ValueError("ccid_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.tobytes())
|
||||
|
||||
def ccid_get_status(self):
|
||||
msg = ccid_compose(0x65, self.__seq)
|
||||
self.__dev.write(self.__bulkout, msg, self.__timeout)
|
||||
self.increment_seq()
|
||||
status, chain, data = self.ccid_get_result()
|
||||
# XXX: check chain, data
|
||||
return status
|
||||
|
||||
def ccid_power_on(self):
|
||||
msg = ccid_compose(0x62, self.__seq, rsv=1) # Vcc=5V
|
||||
self.__dev.write(self.__bulkout, msg, self.__timeout)
|
||||
self.increment_seq()
|
||||
status, chain, data = self.ccid_get_result()
|
||||
# XXX: check status, chain
|
||||
self.atr = data
|
||||
#
|
||||
if self.__use_APDU == False:
|
||||
# TPDU reader configuration
|
||||
self.ns = 0
|
||||
self.nr = 0
|
||||
# Set PPS
|
||||
pps = b"\xFF\x11\x18\xF6"
|
||||
status, chain, ret_pps = self.ccid_send_data_block(pps)
|
||||
# Set parameters
|
||||
param = b"\x18\x10\xFF\x75\x00\xFE\x00"
|
||||
# ^--- This shoud be adapted by ATR string, see update_param_by_atr
|
||||
msg = ccid_compose(0x61, self.__seq, rsv=0x1, data=param)
|
||||
self.__dev.write(self.__bulkout, msg, self.__timeout)
|
||||
self.increment_seq()
|
||||
status, chain, ret_param = self.ccid_get_result()
|
||||
# Send an S-block of changing IFSD=254
|
||||
sblk = b"\x00\xC1\x01\xFE\x3E"
|
||||
status, chain, ret_sblk = self.ccid_send_data_block(sblk)
|
||||
return self.atr
|
||||
|
||||
def ccid_power_off(self):
|
||||
msg = ccid_compose(0x63, self.__seq)
|
||||
self.__dev.write(self.__bulkout, msg, self.__timeout)
|
||||
self.increment_seq()
|
||||
status, chain, data = self.ccid_get_result()
|
||||
# XXX: check chain, data
|
||||
return status
|
||||
|
||||
def ccid_send_data_block(self, data):
|
||||
msg = ccid_compose(0x6f, self.__seq, data=data)
|
||||
self.__dev.write(self.__bulkout, msg, self.__timeout)
|
||||
self.increment_seq()
|
||||
return self.ccid_get_result()
|
||||
|
||||
def ccid_send_cmd(self, data):
|
||||
status, chain, data_rcv = self.ccid_send_data_block(data)
|
||||
if chain == 0:
|
||||
while status == 0x80:
|
||||
status, chain, data_rcv = self.ccid_get_result()
|
||||
return data_rcv
|
||||
elif chain == 1:
|
||||
d = data_rcv
|
||||
while True:
|
||||
msg = ccid_compose(0x6f, self.__seq, param=0x10)
|
||||
self.__dev.write(self.__bulkout, msg, self.__timeout)
|
||||
self.increment_seq()
|
||||
status, chain, data_rcv = self.ccid_get_result()
|
||||
# XXX: check status
|
||||
d += data_rcv
|
||||
if chain == 2:
|
||||
break
|
||||
elif chain == 3:
|
||||
continue
|
||||
else:
|
||||
raise ValueError("ccid_send_cmd chain")
|
||||
return d
|
||||
else:
|
||||
raise ValueError("ccid_send_cmd")
|
||||
|
||||
def send_tpdu(self, info=None, more=0, response_time_ext=0,
|
||||
edc_error=0, no_error=0):
|
||||
if info:
|
||||
data = compose_i_block(self.ns, info, more)
|
||||
elif response_time_ext:
|
||||
# compose S-block
|
||||
data = b"\x00\xE3\x00\xE3"
|
||||
elif edc_error:
|
||||
data = compose_r_block(self.nr, edc_error=1)
|
||||
elif no_error:
|
||||
data = compose_r_block(self.nr)
|
||||
msg = ccid_compose(0x6f, self.__seq, data=data)
|
||||
self.__dev.write(self.__bulkout, msg, self.__timeout)
|
||||
self.increment_seq()
|
||||
|
||||
def recv_tpdu(self):
|
||||
status, chain, data = self.ccid_get_result()
|
||||
return data
|
||||
|
||||
def send_cmd(self, cmd):
|
||||
# Simple APDU case
|
||||
if self.__use_APDU:
|
||||
return self.ccid_send_cmd(cmd)
|
||||
# TPDU case
|
||||
while len(cmd) > 254:
|
||||
blk = cmd[0:254]
|
||||
cmd = cmd[254:]
|
||||
while True:
|
||||
self.send_tpdu(info=blk,more=1)
|
||||
rblk = self.recv_tpdu()
|
||||
if is_r_block_no_error_or_other(rblk):
|
||||
break
|
||||
self.ns = self.ns ^ 1
|
||||
while True:
|
||||
self.send_tpdu(info=cmd)
|
||||
blk = self.recv_tpdu()
|
||||
if is_r_block_no_error_or_other(blk):
|
||||
break
|
||||
self.ns = self.ns ^ 1
|
||||
res = b""
|
||||
while True:
|
||||
if is_s_block_time_ext(blk):
|
||||
self.send_tpdu(response_time_ext=1)
|
||||
elif is_i_block_last(blk):
|
||||
self.nr = self.nr ^ 1
|
||||
if is_edc_error(blk):
|
||||
self.send_tpdu(edc_error=1)
|
||||
else:
|
||||
res += i_block_content(blk)
|
||||
break
|
||||
elif is_i_block_more(blk):
|
||||
self.nr = self.nr ^ 1
|
||||
if is_edc_error(blk):
|
||||
self.send_tpdu(edc_error=1)
|
||||
else:
|
||||
res += i_block_content(blk)
|
||||
self.send_tpdu(no_error=1)
|
||||
blk = self.recv_tpdu()
|
||||
return res
|
||||
|
||||
|
||||
class find_class(object):
|
||||
def __init__(self, usb_class):
|
||||
self.__class = usb_class
|
||||
def __call__(self, device):
|
||||
if device.bDeviceClass == self.__class:
|
||||
return True
|
||||
for cfg in device:
|
||||
intf = find_descriptor(cfg, bInterfaceClass=self.__class)
|
||||
if intf is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_ccid_device():
|
||||
ccid = None
|
||||
dev_list = usb.core.find(find_all=True, custom_match=find_class(CCID_CLASS))
|
||||
for dev in dev_list:
|
||||
try:
|
||||
ccid = CardReader(dev)
|
||||
print("CCID device: Bus %03d Device %03d" % (dev.bus, dev.address))
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if not ccid:
|
||||
raise ValueError("No CCID device present")
|
||||
status = ccid.ccid_get_status()
|
||||
if status == 0:
|
||||
# It's ON already
|
||||
atr = ccid.ccid_power_on()
|
||||
elif status == 1:
|
||||
atr = ccid.ccid_power_on()
|
||||
else:
|
||||
raise ValueError("Unknown CCID status", status)
|
||||
return ccid
|
||||
18
tests/conftest.py
Normal file
18
tests/conftest.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import pytest
|
||||
from card_reader import get_ccid_device
|
||||
from openpgp_card import OpenPGP_Card
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--reader", dest="reader", type=str, action="store",
|
||||
default="gnuk", help="specify reader: gnuk or gemalto")
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def card():
|
||||
print()
|
||||
print("Test start!")
|
||||
reader = get_ccid_device()
|
||||
print("Reader:", reader.get_string(1), reader.get_string(2))
|
||||
card = OpenPGP_Card(reader)
|
||||
card.cmd_select_openpgp()
|
||||
yield card
|
||||
del card
|
||||
338
tests/openpgp_card.py
Normal file
338
tests/openpgp_card.py
Normal file
@@ -0,0 +1,338 @@
|
||||
"""
|
||||
openpgp_card.py - a library for OpenPGP card
|
||||
|
||||
Copyright (C) 2011, 2012, 2013, 2015, 2016
|
||||
Free Software Initiative of Japan
|
||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||
|
||||
Gnuk is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gnuk is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from struct import pack
|
||||
|
||||
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:
|
||||
if data_len <= 255:
|
||||
return pack('>BBBBB', cls, ins, p1, p2, data_len) + data
|
||||
else:
|
||||
return pack('>BBBBBH', cls, ins, p1, p2, 0, data_len) \
|
||||
+ data
|
||||
else:
|
||||
if data_len <= 255 and le < 256:
|
||||
return pack('>BBBBB', cls, ins, p1, p2, data_len) \
|
||||
+ data + pack('>B', le)
|
||||
else:
|
||||
return pack('>BBBBBH', cls, ins, p1, p2, 0, data_len) \
|
||||
+ data + pack('>H', le)
|
||||
|
||||
class OpenPGP_Card(object):
|
||||
def __init__(self, reader):
|
||||
"""
|
||||
__init__(reader) -> None
|
||||
Initialize a OpenPGP card with a CardReader.
|
||||
reader: CardReader object.
|
||||
"""
|
||||
|
||||
self.__reader = reader
|
||||
|
||||
def cmd_get_response(self, expected_len):
|
||||
result = b""
|
||||
while True:
|
||||
cmd_data = iso7816_compose(0xc0, 0x00, 0x00, b'') + pack('>B', expected_len)
|
||||
response = self.__reader.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.__reader.send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_read_binary(self, fileid):
|
||||
cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, b'')
|
||||
sw = self.__reader.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.__reader.send_cmd(cmd_data0)
|
||||
if len(sw) != 2:
|
||||
raise ValueError("cmd_write_binary 0")
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("cmd_write_binary 0", "%02x%02x" % (sw[0], sw[1]))
|
||||
if cmd_data1:
|
||||
sw = self.__reader.send_cmd(cmd_data1)
|
||||
if len(sw) != 2:
|
||||
raise ValueError("cmd_write_binary 1", sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("cmd_write_binary 1", "%02x%02x" % (sw[0], sw[1]))
|
||||
count += 1
|
||||
|
||||
def cmd_select_openpgp(self):
|
||||
cmd_data = iso7816_compose(0xa4, 0x04, 0x00, b"\xD2\x76\x00\x01\x24\x01")
|
||||
r = self.__reader.send_cmd(cmd_data)
|
||||
if len(r) < 2:
|
||||
raise ValueError(r)
|
||||
sw = r[-2:]
|
||||
r = r[0:-2]
|
||||
if sw[0] == 0x61:
|
||||
self.cmd_get_response(sw[1])
|
||||
return True
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_get_data(self, tagh, tagl):
|
||||
cmd_data = iso7816_compose(0xca, tagh, tagl, b"", le=254)
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
if len(sw) < 2:
|
||||
raise ValueError(sw)
|
||||
if sw[0] == 0x61:
|
||||
return self.cmd_get_response(sw[1])
|
||||
elif sw[-2] == 0x90 and sw[-1] == 0x00:
|
||||
return sw[0:-2]
|
||||
if sw[0] == 0x6a and sw[1] == 0x88:
|
||||
return None
|
||||
else:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
|
||||
def cmd_change_reference_data(self, who, data):
|
||||
cmd_data = iso7816_compose(0x24, 0x00, 0x80+who, data)
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_put_data(self, tagh, tagl, content):
|
||||
cmd_data = iso7816_compose(0xda, tagh, tagl, content)
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_put_data_odd(self, tagh, tagl, content):
|
||||
if self.__reader.is_tpdu_reader():
|
||||
cmd_data = iso7816_compose(0xdb, tagh, tagl, content)
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
else:
|
||||
cmd_data0 = iso7816_compose(0xdb, tagh, tagl, content[:128], 0x10)
|
||||
cmd_data1 = iso7816_compose(0xdb, tagh, tagl, content[128:])
|
||||
sw = self.__reader.send_cmd(cmd_data0)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
sw = self.__reader.send_cmd(cmd_data1)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_reset_retry_counter(self, how, who, data):
|
||||
cmd_data = iso7816_compose(0x2c, how, who, data)
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_pso(self, p1, p2, data):
|
||||
if self.__reader.is_tpdu_reader():
|
||||
cmd_data = iso7816_compose(0x2a, p1, p2, data, le=256)
|
||||
r = self.__reader.send_cmd(cmd_data)
|
||||
if len(r) < 2:
|
||||
raise ValueError(r)
|
||||
sw = r[-2:]
|
||||
r = r[0:-2]
|
||||
if sw[0] == 0x61:
|
||||
return self.cmd_get_response(sw[1])
|
||||
elif sw[0] == 0x90 and sw[1] == 0x00:
|
||||
return r
|
||||
else:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
else:
|
||||
if len(data) > 128:
|
||||
cmd_data0 = iso7816_compose(0x2a, p1, p2, data[:128], 0x10)
|
||||
cmd_data1 = iso7816_compose(0x2a, p1, p2, data[128:])
|
||||
sw = self.__reader.send_cmd(cmd_data0)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
sw = self.__reader.send_cmd(cmd_data1)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
elif sw[0] != 0x61:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return self.cmd_get_response(sw[1])
|
||||
else:
|
||||
cmd_data = iso7816_compose(0x2a, p1, p2, data)
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if sw[0] == 0x90 and sw[1] == 0x00:
|
||||
return b""
|
||||
elif sw[0] != 0x61:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return self.cmd_get_response(sw[1])
|
||||
|
||||
def cmd_internal_authenticate(self, data):
|
||||
if self.__reader.is_tpdu_reader():
|
||||
cmd_data = iso7816_compose(0x88, 0, 0, data, le=256)
|
||||
else:
|
||||
cmd_data = iso7816_compose(0x88, 0, 0, data)
|
||||
r = self.__reader.send_cmd(cmd_data)
|
||||
if len(r) < 2:
|
||||
raise ValueError(r)
|
||||
sw = r[-2:]
|
||||
r = r[0:-2]
|
||||
if sw[0] == 0x61:
|
||||
return self.cmd_get_response(sw[1])
|
||||
elif sw[0] == 0x90 and sw[1] == 0x00:
|
||||
return r
|
||||
else:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
|
||||
def cmd_genkey(self, keyno):
|
||||
if keyno == 1:
|
||||
data = b'\xb6\x00'
|
||||
elif keyno == 2:
|
||||
data = b'\xb8\x00'
|
||||
else:
|
||||
data = b'\xa4\x00'
|
||||
cmd_data = iso7816_compose(0x47, 0x80, 0, data)
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if sw[0] == 0x90 and sw[1] == 0x00:
|
||||
return b""
|
||||
elif sw[0] != 0x61:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
pk = self.cmd_get_response(sw[1])
|
||||
return (pk[9:9+256], pk[9+256+2:9+256+2+3])
|
||||
|
||||
def cmd_get_public_key(self, keyno):
|
||||
if keyno == 1:
|
||||
data = b'\xb6\x00'
|
||||
elif keyno == 2:
|
||||
data = b'\xb8\x00'
|
||||
else:
|
||||
data = b'\xa4\x00'
|
||||
if self.__reader.is_tpdu_reader():
|
||||
cmd_data = iso7816_compose(0x47, 0x81, 0, data, le=512)
|
||||
r = self.__reader.send_cmd(cmd_data)
|
||||
else:
|
||||
cmd_data = iso7816_compose(0x47, 0x81, 0, data)
|
||||
r = self.__reader.send_cmd(cmd_data)
|
||||
if len(r) < 2:
|
||||
raise ValueError(r)
|
||||
sw = r[-2:]
|
||||
r = r[0:-2]
|
||||
if sw[0] == 0x61:
|
||||
pk = self.cmd_get_response(sw[1])
|
||||
elif sw[0] == 0x90 and sw[1] == 0x00:
|
||||
pk = r
|
||||
else:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return pk
|
||||
|
||||
def cmd_put_data_remove(self, tagh, tagl):
|
||||
cmd_data = iso7816_compose(0xda, tagh, tagl, b"")
|
||||
sw = self.__reader.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 = b"\xb6\x00" # SIG
|
||||
elif keyno == 2:
|
||||
keyspec = b"\xb8\x00" # DEC
|
||||
else:
|
||||
keyspec = b"\xa4\x00" # AUT
|
||||
cmd_data = iso7816_compose(0xdb, 0x3f, 0xff, b"\x4d\x02" + keyspec)
|
||||
sw = self.__reader.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.__reader.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.__reader.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.__reader.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]))
|
||||
4
tests/rsa-aut.key
Normal file
4
tests/rsa-aut.key
Normal file
@@ -0,0 +1,4 @@
|
||||
9cf7192b51a574d1ad3ccb08ba09b87f228573893eee355529ff243e90fd4b86f79a82097cc7922c0485bed1616b1656a9b0b19ef78ea8ec34c384019adc5d5bf4db2d2a0a2d9cf14277bdcb7056f48b81214e3f7f7742231e29673966f9b1106862112cc798dba8d4a138bb5abfc6d4c12d53a5d39b2f783da916da20852ee139bbafda61d429caf2a4f30847ce7e7ae32ab4061e27dd9e4d00d60910249db8d8559dd85f7ca59659ef400c8f6318700f4e97f0c6f4165de80641490433c88da8682befe68eb311f54af2b07d97ac74edb5399cf054764211694fbb8d1d333f3269f235abe025067f811ff83a2224826219b309ea3e6c968f42b3e52f245dc9
|
||||
010001
|
||||
b5ab7b159220b18e363258f61ebde08bae83d6ce2dbfe4adc143628c527887acde9de09bf9b49f438019004d71855f30c2d69b6c29bb9882ab641b3387409fe9199464a7faa4b5230c56d9e17cd9ed074bc00180ebed62bae3af28e6ff2ac2654ad968834c5d5c88f8d9d3cc5e167b10453b049d4e454a5761fb0ac717185907
|
||||
dd2fffa9814296156a6926cd17b65564187e424dcadce9b032246ad7e46448bb0f9e0ff3c64f987424b1a40bc694e2e9ac4fb1930d163582d7acf20653a1c44b97846c1c5fd8a7b19bb225fb39c30e25410483deaf8c2538d222b748c4d8103b11cec04f666a5c0dbcbf5d5f625f158f65746c3fafe6418145f7cffa5fadeeaf
|
||||
4
tests/rsa-dec.key
Normal file
4
tests/rsa-dec.key
Normal file
@@ -0,0 +1,4 @@
|
||||
d392714c29738aac6372f2c8654a08c25a1299fed7004bd512cd2452b503ebad6301130816ac525ba528dc155be6347a5c70407fb4fbdaed751dfc0a7cd5e3910272ff236c4ed1ce5de6620b191a172e5b247347b8cab73a43d79221708755c959a2f83f486439da30917384554331532aabc8326db48866f8c91198834a86ab94679f6175db737bdf399e3f0b737dcb1f4208279d3e1cc694e78686785e4f363a377dec912b7c2f757b1422d866fb9fa85c96b83adfd6a223989a9a02988bdee81ad17eff6385e7b38cec8611fdf367ba4ac8e90d5f48ac7715c5f47aea06a4a37cdaa3029ce59d29bc66853bf6758ef4a7da5a5953f5e557a5a22f67c368c3
|
||||
010001
|
||||
dae085952c5beee38f25f09bc37a4ca2434c31f78055469d0d5f0bf3337e3a70ba6c91734f195b742e211a5fe283befdf66820008e6ef2c8ca54a91922838fce07d9e33a331ce20dac36803e777d5ee2195ed28d6a4045e28623a6a60b0661e45f7c4f84ae2b1dfad0cf1ec30605158323382a819e730c09a33fad704dd67501
|
||||
f774be43ea198aa2f089274e4fffd7d0092ee7b35a1d2f854cdb166f698caab72fdeb099e690e78438b2e043e452d4d2f19d7f44ba6b286642f0ce5204966ff98ecd9e3b448877324631365dc860797429b9414a21a7e166d504cace156588b9a145657eeb1afb43b8ff65d8d6d93cea2ba4ef8aab047885c4de64ffef0b49c3
|
||||
4
tests/rsa-sig.key
Normal file
4
tests/rsa-sig.key
Normal file
@@ -0,0 +1,4 @@
|
||||
c6c877dfd3b441f8fb1b8dc504093a51c2efe4883fe0a6379205acc6e673709905e4d767ddf46143c535cc6d7f10b616f520d8346320ef69ff4a2c4f4a148edc65f7ad24ed7d4fe23bb862a0ae71f4f7904abac0397abf3213df91326b1a25554b3b18cf54584d8bf220169fc92b2aa511e8313983e72b4c9110b3a1aea087aebef95873865608e8faea9ef10e7f7f3a66ca8def2d499c3149c127491e0e4339fd6abe10bfc6c13e43d522004f1485767328eabe35d6ffa8df4c15f0fbcd4eb1c07cc6d85e275139ac69e2962273ae987236926dd6c1144fce3e7ae567fa58ea60620dfafc52f95299fea601739fce27ee71eea978d0074f21e7086f60ba8331
|
||||
010001
|
||||
cc365b5702714bf203e8c49b0b8afa8dad586e929cf5edca38ad07fa45efd5c2d89022d29f40283a57e50ca24c5f28c8e911a74faaf796f112e7e48195956f9a4df7668a5342523b27179cec958f363211ee11d0ec0e0e1b92ca007a61e8c9ac14e00229b9a7624850199e6667afa1a44db8f3c5de0a8eef0e6de050ac0ac633
|
||||
f931a3c12f0e3a5276f712b7706590ba02e14a97ff9b8ce3152af0fc4d9cdc690ea9bc4c82cb16c7d23136cbdab58fbec69880a88bca85c4214df01045082cbe9f4192e3e39c79896533c37dad9eb9e73c2643b9c0a704a4f93d81573537963d6b6e5140a24c702d9f26e06a2095de906daa8824172a6b39f563b7153907050b
|
||||
148
tests/rsa_keys.py
Normal file
148
tests/rsa_keys.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from binascii import hexlify, unhexlify
|
||||
from time import time
|
||||
from struct import pack
|
||||
from hashlib import sha1, sha256
|
||||
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)
|
||||
|
||||
def calc_fpr(n,e):
|
||||
timestamp = int(time())
|
||||
timestamp_data = pack('>I', timestamp)
|
||||
m_len = 6 + 2 + 256 + 2 + 4
|
||||
m = b'\x99' + pack('>H', m_len) + b'\x04' + timestamp_data + b'\x01' + \
|
||||
pack('>H', 2048) + n + pack('>H', 17) + e
|
||||
fpr = sha1(m).digest()
|
||||
return (fpr, timestamp_data)
|
||||
|
||||
key = [ None, None, None ]
|
||||
fpr = [ None, None, None ]
|
||||
timestamp = [ None, None, None ]
|
||||
|
||||
key[0] = read_key_from_file('rsa-sig.key')
|
||||
key[1] = read_key_from_file('rsa-dec.key')
|
||||
key[2] = read_key_from_file('rsa-aut.key')
|
||||
|
||||
(fpr[0], timestamp[0]) = calc_fpr(key[0][0], key[0][1])
|
||||
(fpr[1], timestamp[1]) = calc_fpr(key[1][0], key[1][1])
|
||||
(fpr[2], timestamp[2]) = calc_fpr(key[2][0], key[2][1])
|
||||
|
||||
def build_privkey_template(openpgp_keyno, keyno):
|
||||
n_bytes = key[keyno][0]
|
||||
e_bytes = b'\x00' + key[keyno][1]
|
||||
p_bytes = key[keyno][2]
|
||||
q_bytes = key[keyno][3]
|
||||
|
||||
if openpgp_keyno == 1:
|
||||
keyspec = b'\xb6'
|
||||
elif openpgp_keyno == 2:
|
||||
keyspec = b'\xb8'
|
||||
else:
|
||||
keyspec = b'\xa4'
|
||||
|
||||
key_template = b'\x91\x04'+ b'\x92\x81\x80' + b'\x93\x81\x80'
|
||||
|
||||
exthdr = keyspec + b'\x00' + b'\x7f\x48' + b'\x08' + key_template
|
||||
|
||||
suffix = b'\x5f\x48' + b'\x82\x01\x04'
|
||||
|
||||
t = b'\x4d' + b'\x82\x01\x16' + exthdr + suffix + e_bytes + p_bytes + q_bytes
|
||||
return t
|
||||
|
||||
def build_privkey_template_for_remove(openpgp_keyno):
|
||||
if openpgp_keyno == 1:
|
||||
keyspec = b'\xb6'
|
||||
elif openpgp_keyno == 2:
|
||||
keyspec = b'\xb8'
|
||||
else:
|
||||
keyspec = b'\xa4'
|
||||
return b'\x4d\02' + keyspec + b'\0x00'
|
||||
|
||||
def compute_digestinfo(msg):
|
||||
digest = sha256(msg).digest()
|
||||
prefix = b'\x30\31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'
|
||||
return prefix + digest
|
||||
|
||||
# egcd and modinv are from wikibooks
|
||||
# https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Extended_Euclidean_algorithm
|
||||
|
||||
def egcd(a, b):
|
||||
if a == 0:
|
||||
return (b, 0, 1)
|
||||
else:
|
||||
g, y, x = egcd(b % a, a)
|
||||
return (g, x - (b // a) * y, y)
|
||||
|
||||
def modinv(a, m):
|
||||
g, x, y = egcd(a, m)
|
||||
if g != 1:
|
||||
raise Exception('modular inverse does not exist')
|
||||
else:
|
||||
return x % m
|
||||
|
||||
def pkcs1_pad_for_sign(digestinfo):
|
||||
byte_repr = b'\x00' + b'\x01' + bytes.ljust(b'', 256 - 19 - 32 - 3, b'\xff') \
|
||||
+ b'\x00' + digestinfo
|
||||
return int(hexlify(byte_repr), 16)
|
||||
|
||||
def pkcs1_pad_for_crypt(msg):
|
||||
padlen = 256 - 3 - len(msg)
|
||||
byte_repr = b'\x00' + b'\x02' \
|
||||
+ bytes.replace(urandom(padlen), b'\x00', b'\x01') + b'\x00' + msg
|
||||
return int(hexlify(byte_repr), 16)
|
||||
|
||||
def compute_signature(keyno, digestinfo):
|
||||
e = key[keyno][4]
|
||||
p = key[keyno][5]
|
||||
q = key[keyno][6]
|
||||
n = key[keyno][7]
|
||||
p1 = p - 1
|
||||
q1 = q - 1
|
||||
h = p1 * q1
|
||||
d = modinv(e, h)
|
||||
dp = d % p1
|
||||
dq = d % q1
|
||||
qp = modinv(q, p)
|
||||
|
||||
input = pkcs1_pad_for_sign(digestinfo)
|
||||
t1 = pow(input, dp, p)
|
||||
t2 = pow(input, dq, q)
|
||||
t = ((t1 - t2) * qp) % p
|
||||
sig = t2 + t * q
|
||||
return sig
|
||||
|
||||
def integer_to_bytes_256(i):
|
||||
return i.to_bytes(256, byteorder='big')
|
||||
|
||||
def encrypt(keyno, plaintext):
|
||||
e = key[keyno][4]
|
||||
n = key[keyno][7]
|
||||
m = pkcs1_pad_for_crypt(plaintext)
|
||||
return b'\x00' + integer_to_bytes_256(pow(m, e, n))
|
||||
|
||||
def encrypt_with_pubkey(pubkey_info, plaintext):
|
||||
n = int(hexlify(pubkey_info[0]), 16)
|
||||
e = int(hexlify(pubkey_info[1]), 16)
|
||||
m = pkcs1_pad_for_crypt(plaintext)
|
||||
return b'\x00' + integer_to_bytes_256(pow(m, e, n))
|
||||
|
||||
def verify_signature(pubkey_info, digestinfo, sig):
|
||||
n = int(hexlify(pubkey_info[0]), 16)
|
||||
e = int(hexlify(pubkey_info[1]), 16)
|
||||
di_pkcs1 = pow(sig,e,n)
|
||||
m = pkcs1_pad_for_sign(digestinfo)
|
||||
return di_pkcs1 == m
|
||||
184
tests/test_empty_card.py
Normal file
184
tests/test_empty_card.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
test_empty_card.py - test empty card
|
||||
|
||||
Copyright (C) 2016 g10 Code GmbH
|
||||
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 binascii import hexlify
|
||||
from re import match, DOTALL
|
||||
from struct import pack
|
||||
from util import *
|
||||
import pytest
|
||||
|
||||
EMPTY_60=bytes(60)
|
||||
|
||||
FACTORY_PASSPHRASE_PW1=b"123456"
|
||||
FACTORY_PASSPHRASE_PW3=b"12345678"
|
||||
|
||||
def test_login(card):
|
||||
login = get_data_object(card, 0x5e)
|
||||
assert check_null(login)
|
||||
|
||||
"""
|
||||
def test_name(card):
|
||||
name = get_data_object(card, 0x5b)
|
||||
assert check_null(name)
|
||||
|
||||
def test_lang(card):
|
||||
lang = get_data_object(card, 0x5f2d)
|
||||
assert check_null(lang)
|
||||
|
||||
def test_sex(card):
|
||||
sex = get_data_object(card, 0x5f35)
|
||||
assert check_null(sex)
|
||||
"""
|
||||
|
||||
def test_name_lang_sex(card):
|
||||
name = b""
|
||||
lang = b""
|
||||
sex = b"9"
|
||||
expected = b'\x5b' + pack('B', len(name)) + name \
|
||||
+ b'\x5f\x2d' + pack('B', len(lang)) + lang \
|
||||
+ b'\x5f\x35' + pack('B', len(sex)) + sex
|
||||
name_lang_sex = get_data_object(card, 0x65)
|
||||
assert name_lang_sex == b'' or name_lang_sex == expected
|
||||
|
||||
def test_app_data(card):
|
||||
app_data = get_data_object(card, 0x6e)
|
||||
hist_len = app_data[20]
|
||||
# FIXME: parse and check DO of C0, C1, C2, C3, C4, and C6
|
||||
assert app_data[0:8] == b"\x4f\x10\xd2\x76\x00\x01\x24\x01" and \
|
||||
app_data[18:18+2] == b"\x5f\x52"
|
||||
|
||||
def test_url(card):
|
||||
url = get_data_object(card, 0x5f50)
|
||||
assert check_null(url)
|
||||
|
||||
def test_ds_counter(card):
|
||||
c = get_data_object(card, 0x7a)
|
||||
assert c == b'\x93\x03\x00\x00\x00'
|
||||
|
||||
def test_pw1_status(card):
|
||||
s = get_data_object(card, 0xc4)
|
||||
assert match(b'\x00...\x03[\x00\x03]\x03', s, DOTALL)
|
||||
|
||||
def test_fingerprint_0(card):
|
||||
fprlist = get_data_object(card, 0xC5)
|
||||
assert fprlist == None or fprlist == EMPTY_60
|
||||
|
||||
def test_fingerprint_1(card):
|
||||
fpr = get_data_object(card, 0xC7)
|
||||
assert check_null(fpr)
|
||||
|
||||
def test_fingerprint_2(card):
|
||||
fpr = get_data_object(card, 0xC8)
|
||||
assert check_null(fpr)
|
||||
|
||||
def test_fingerprint_3(card):
|
||||
fpr = get_data_object(card, 0xC9)
|
||||
assert check_null(fpr)
|
||||
|
||||
def test_ca_fingerprint_0(card):
|
||||
cafprlist = get_data_object(card, 0xC6)
|
||||
assert cafprlist == None or cafprlist == EMPTY_60
|
||||
|
||||
def test_ca_fingerprint_1(card):
|
||||
cafp = get_data_object(card, 0xCA)
|
||||
assert check_null(cafp)
|
||||
|
||||
def test_ca_fingerprint_2(card):
|
||||
cafp = get_data_object(card, 0xCB)
|
||||
assert check_null(cafp)
|
||||
|
||||
def test_ca_fingerprint_3(card):
|
||||
cafp = get_data_object(card, 0xCC)
|
||||
assert check_null(cafp)
|
||||
|
||||
def test_timestamp_0(card):
|
||||
t = get_data_object(card, 0xCD)
|
||||
assert t == None or t == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
|
||||
def test_timestamp_1(card):
|
||||
t = get_data_object(card, 0xCE)
|
||||
assert check_null(t)
|
||||
|
||||
def test_timestamp_2(card):
|
||||
t = get_data_object(card, 0xCF)
|
||||
assert check_null(t)
|
||||
|
||||
def test_timestamp_3(card):
|
||||
t = get_data_object(card, 0xD0)
|
||||
assert check_null(t)
|
||||
|
||||
def test_verify_pw1_1(card):
|
||||
v = card.cmd_verify(1, FACTORY_PASSPHRASE_PW1)
|
||||
assert v
|
||||
|
||||
def test_verify_pw1_2(card):
|
||||
v = card.cmd_verify(2, FACTORY_PASSPHRASE_PW1)
|
||||
assert v
|
||||
|
||||
def test_verify_pw3(card):
|
||||
v = card.cmd_verify(3, FACTORY_PASSPHRASE_PW3)
|
||||
assert v
|
||||
|
||||
def test_historical_bytes(card):
|
||||
h = get_data_object(card, 0x5f52)
|
||||
assert h == b'\x001\xc5s\xc0\x01@\x05\x90\x00' or \
|
||||
h == b'\x00\x31\x84\x73\x80\x01\x80\x00\x90\x00' or \
|
||||
h == b'\x00\x31\x84\x73\x80\x01\x80\x05\x90\x00'
|
||||
|
||||
def test_extended_capabilities(card):
|
||||
a = get_data_object(card, 0xc0)
|
||||
assert a == None or match(b'[\x70\x74]\x00\x00\x20[\x00\x08]\x00\x00\xff\x01\x00', a)
|
||||
|
||||
def test_algorithm_attributes_1(card):
|
||||
a = get_data_object(card, 0xc1)
|
||||
assert a == None or a == b'\x01\x08\x00\x00\x20\x00'
|
||||
|
||||
def test_algorithm_attributes_2(card):
|
||||
a = get_data_object(card, 0xc2)
|
||||
assert a == None or a == b'\x01\x08\x00\x00\x20\x00'
|
||||
|
||||
def test_algorithm_attributes_3(card):
|
||||
a = get_data_object(card, 0xc3)
|
||||
assert a == None or a == b'\x01\x08\x00\x00\x20\x00'
|
||||
|
||||
def test_public_key_1(card):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
pk = card.cmd_get_public_key(1)
|
||||
assert excinfo.value.args[0] == "6a88"
|
||||
|
||||
def test_public_key_2(card):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
pk = card.cmd_get_public_key(2)
|
||||
assert excinfo.value.args[0] == "6a88"
|
||||
|
||||
def test_public_key_3(card):
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
pk = card.cmd_get_public_key(3)
|
||||
assert excinfo.value.args[0] == "6a88"
|
||||
|
||||
def test_AID(card):
|
||||
a = get_data_object(card, 0x4f)
|
||||
print()
|
||||
print("OpenPGP card version: %d.%d" % (a[6], a[7]))
|
||||
print("Card Manufacturer: ", hexlify(a[8:10]).decode("UTF-8"))
|
||||
print("Card serial: ", hexlify(a[10:14]).decode("UTF-8"))
|
||||
assert match(b'\xd2\x76\x00\x01\\$\x01........\x00\x00', a, DOTALL)
|
||||
290
tests/test_personalize_card.py
Normal file
290
tests/test_personalize_card.py
Normal file
@@ -0,0 +1,290 @@
|
||||
"""
|
||||
test_personalize_card.py - test personalizing card
|
||||
|
||||
Copyright (C) 2016 g10 Code GmbH
|
||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||
|
||||
Gnuk is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gnuk is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from struct import pack
|
||||
from re import match, DOTALL
|
||||
from util import *
|
||||
import rsa_keys
|
||||
|
||||
FACTORY_PASSPHRASE_PW1=b"123456"
|
||||
FACTORY_PASSPHRASE_PW3=b"12345678"
|
||||
PW1_TEST0=b"another user pass phrase"
|
||||
PW1_TEST1=b"PASSPHRASE SHOULD BE LONG"
|
||||
PW1_TEST2=b"new user pass phrase"
|
||||
PW1_TEST3=b"next user pass phrase"
|
||||
PW1_TEST4=b"another user pass phrase"
|
||||
PW3_TEST0=b"admin pass phrase"
|
||||
PW3_TEST1=b"another admin pass phrase"
|
||||
|
||||
RESETCODE_TEST=b"example reset code 000"
|
||||
|
||||
def test_setup_pw3_0(card):
|
||||
r = card.cmd_change_reference_data(3, FACTORY_PASSPHRASE_PW3 + PW3_TEST0)
|
||||
assert r
|
||||
|
||||
def test_verify_pw3_0(card):
|
||||
v = card.cmd_verify(3, PW3_TEST0)
|
||||
assert v
|
||||
|
||||
def test_login_put(card):
|
||||
r = card.cmd_put_data(0x00, 0x5e, b"gpg_user")
|
||||
assert r
|
||||
|
||||
def test_name_put(card):
|
||||
r = card.cmd_put_data(0x00, 0x5b, b"GnuPG User")
|
||||
assert r
|
||||
|
||||
def test_lang_put(card):
|
||||
r = card.cmd_put_data(0x5f, 0x2d, b"ja")
|
||||
assert r
|
||||
|
||||
def test_sex_put(card):
|
||||
r = card.cmd_put_data(0x5f, 0x35, b"1")
|
||||
assert r
|
||||
|
||||
def test_url_put(card):
|
||||
r = card.cmd_put_data(0x5f, 0x50, b"https://www.fsij.org/gnuk/")
|
||||
assert r
|
||||
|
||||
def test_pw1_status_put(card):
|
||||
r = card.cmd_put_data(0x00, 0xc4, b"\x01")
|
||||
assert r
|
||||
|
||||
def test_login(card):
|
||||
login = get_data_object(card, 0x5e)
|
||||
assert login == b"gpg_user"
|
||||
|
||||
def test_name_lang_sex(card):
|
||||
name = b"GnuPG User"
|
||||
lang = b"ja"
|
||||
sex = b"1"
|
||||
expected = b'\x5b' + pack('B', len(name)) + name \
|
||||
+ b'\x5f\x2d' + pack('B', len(lang)) + lang \
|
||||
+ b'\x5f\x35' + pack('B', len(sex)) + sex
|
||||
name_lang_sex = get_data_object(card, 0x65)
|
||||
assert name_lang_sex == expected
|
||||
|
||||
def test_url(card):
|
||||
url = get_data_object(card, 0x5f50)
|
||||
assert url == b"https://www.fsij.org/gnuk/"
|
||||
|
||||
def test_pw1_status(card):
|
||||
s = get_data_object(card, 0xc4)
|
||||
assert match(b'\x01...\x03[\x00\x03]\x03', s, DOTALL)
|
||||
|
||||
def test_rsa_import_key_1(card):
|
||||
t = rsa_keys.build_privkey_template(1, 0)
|
||||
r = card.cmd_put_data_odd(0x3f, 0xff, t)
|
||||
assert r
|
||||
|
||||
def test_rsa_import_key_2(card):
|
||||
t = rsa_keys.build_privkey_template(2, 1)
|
||||
r = card.cmd_put_data_odd(0x3f, 0xff, t)
|
||||
assert r
|
||||
|
||||
def test_rsa_import_key_3(card):
|
||||
t = rsa_keys.build_privkey_template(3, 2)
|
||||
r = card.cmd_put_data_odd(0x3f, 0xff, t)
|
||||
assert r
|
||||
|
||||
def test_fingerprint_1_put(card):
|
||||
fpr1 = rsa_keys.fpr[0]
|
||||
r = card.cmd_put_data(0x00, 0xc7, fpr1)
|
||||
assert r
|
||||
|
||||
def test_fingerprint_2_put(card):
|
||||
fpr2 = rsa_keys.fpr[1]
|
||||
r = card.cmd_put_data(0x00, 0xc8, fpr2)
|
||||
assert r
|
||||
|
||||
def test_fingerprint_3_put(card):
|
||||
fpr3 = rsa_keys.fpr[2]
|
||||
r = card.cmd_put_data(0x00, 0xc9, fpr3)
|
||||
assert r
|
||||
|
||||
def test_timestamp_1_put(card):
|
||||
timestamp1 = rsa_keys.timestamp[0]
|
||||
r = card.cmd_put_data(0x00, 0xce, timestamp1)
|
||||
assert r
|
||||
|
||||
def test_timestamp_2_put(card):
|
||||
timestamp2 = rsa_keys.timestamp[1]
|
||||
r = card.cmd_put_data(0x00, 0xcf, timestamp2)
|
||||
assert r
|
||||
|
||||
def test_timestamp_3_put(card):
|
||||
timestamp3 = rsa_keys.timestamp[2]
|
||||
r = card.cmd_put_data(0x00, 0xd0, timestamp3)
|
||||
assert r
|
||||
|
||||
def test_ds_counter_0(card):
|
||||
c = get_data_object(card, 0x7a)
|
||||
assert c == b'\x93\x03\x00\x00\x00'
|
||||
|
||||
def test_pw1_status(card):
|
||||
s = get_data_object(card, 0xc4)
|
||||
assert match(b'\x01...\x03[\x00\x03]\x03', s, DOTALL)
|
||||
|
||||
def test_app_data(card):
|
||||
app_data = get_data_object(card, 0x6e)
|
||||
hist_len = app_data[20]
|
||||
# FIXME: parse and check DO of C0, C1, C2, C3, C4, and C6
|
||||
assert app_data[0:8] == b"\x4f\x10\xd2\x76\x00\x01\x24\x01" and \
|
||||
app_data[18:18+2] == b"\x5f\x52"
|
||||
|
||||
def test_public_key_1(card):
|
||||
pk = card.cmd_get_public_key(1)
|
||||
assert rsa_keys.key[0][0] == pk[9:9+256]
|
||||
|
||||
def test_public_key_2(card):
|
||||
pk = card.cmd_get_public_key(2)
|
||||
assert rsa_keys.key[1][0] == pk[9:9+256]
|
||||
|
||||
def test_public_key_3(card):
|
||||
pk = card.cmd_get_public_key(3)
|
||||
assert rsa_keys.key[2][0] == pk[9:9+256]
|
||||
|
||||
def test_setup_pw1_0(card):
|
||||
r = card.cmd_change_reference_data(1, FACTORY_PASSPHRASE_PW1 + PW1_TEST0)
|
||||
assert r
|
||||
|
||||
def test_verify_pw1_0(card):
|
||||
v = card.cmd_verify(1, PW1_TEST0)
|
||||
assert v
|
||||
|
||||
def test_verify_pw1_0_2(card):
|
||||
v = card.cmd_verify(2, PW1_TEST0)
|
||||
assert v
|
||||
|
||||
def test_setup_pw1_1(card):
|
||||
r = card.cmd_change_reference_data(1, PW1_TEST0 + PW1_TEST1)
|
||||
assert r
|
||||
|
||||
def test_verify_pw1_1(card):
|
||||
v = card.cmd_verify(1, PW1_TEST1)
|
||||
assert v
|
||||
|
||||
def test_verify_pw1_1_2(card):
|
||||
v = card.cmd_verify(2, PW1_TEST1)
|
||||
assert v
|
||||
|
||||
def test_setup_reset_code(card):
|
||||
r = card.cmd_put_data(0x00, 0xd3, RESETCODE_TEST)
|
||||
assert r
|
||||
|
||||
def test_reset_code(card):
|
||||
r = card.cmd_reset_retry_counter(0, 0x81, RESETCODE_TEST + PW1_TEST2)
|
||||
assert r
|
||||
|
||||
def test_verify_pw1_2(card):
|
||||
v = card.cmd_verify(1, PW1_TEST2)
|
||||
assert v
|
||||
|
||||
def test_verify_pw1_2_2(card):
|
||||
v = card.cmd_verify(2, PW1_TEST2)
|
||||
assert v
|
||||
|
||||
def test_setup_pw3_1(card):
|
||||
r = card.cmd_change_reference_data(3, PW3_TEST0 + PW3_TEST1)
|
||||
assert r
|
||||
|
||||
def test_verify_pw3_1(card):
|
||||
v = card.cmd_verify(3, PW3_TEST1)
|
||||
assert v
|
||||
|
||||
def test_reset_userpass_admin(card):
|
||||
r = card.cmd_reset_retry_counter(2, 0x81, PW1_TEST3)
|
||||
assert r
|
||||
|
||||
def test_verify_pw1_3(card):
|
||||
v = card.cmd_verify(1, PW1_TEST3)
|
||||
assert v
|
||||
|
||||
def test_verify_pw1_3_2(card):
|
||||
v = card.cmd_verify(2, PW1_TEST3)
|
||||
assert v
|
||||
|
||||
def test_setup_pw1_4(card):
|
||||
r = card.cmd_change_reference_data(1, PW1_TEST3 + PW1_TEST4)
|
||||
assert r
|
||||
|
||||
def test_verify_pw1_4(card):
|
||||
v = card.cmd_verify(1, PW1_TEST4)
|
||||
assert v
|
||||
|
||||
def test_verify_pw1_4_2(card):
|
||||
v = card.cmd_verify(2, PW1_TEST4)
|
||||
assert v
|
||||
|
||||
def test_setup_pw3_2(card):
|
||||
r = card.cmd_change_reference_data(3, PW3_TEST1 + PW3_TEST0)
|
||||
assert r
|
||||
|
||||
def test_verify_pw3_2(card):
|
||||
v = card.cmd_verify(3, PW3_TEST0)
|
||||
assert v
|
||||
|
||||
PLAIN_TEXT0=b"This is a test message."
|
||||
PLAIN_TEXT1=b"RSA decryption is as easy as pie."
|
||||
PLAIN_TEXT2=b"This is another test message.\nMultiple lines.\n"
|
||||
|
||||
def test_sign_0(card):
|
||||
digestinfo = rsa_keys.compute_digestinfo(PLAIN_TEXT0)
|
||||
r = card.cmd_pso(0x9e, 0x9a, digestinfo)
|
||||
sig = rsa_keys.compute_signature(0, digestinfo)
|
||||
sig_bytes = sig.to_bytes(int((sig.bit_length()+7)/8), byteorder='big')
|
||||
assert r == sig_bytes
|
||||
|
||||
def test_sign_1(card):
|
||||
digestinfo = rsa_keys.compute_digestinfo(PLAIN_TEXT1)
|
||||
r = card.cmd_pso(0x9e, 0x9a, digestinfo)
|
||||
sig = rsa_keys.compute_signature(0, digestinfo)
|
||||
sig_bytes = sig.to_bytes(int((sig.bit_length()+7)/8), byteorder='big')
|
||||
assert r == sig_bytes
|
||||
|
||||
def test_ds_counter_1(card):
|
||||
c = get_data_object(card, 0x7a)
|
||||
assert c == b'\x93\x03\x00\x00\x02'
|
||||
|
||||
def test_sign_auth_0(card):
|
||||
digestinfo = rsa_keys.compute_digestinfo(PLAIN_TEXT0)
|
||||
r = card.cmd_internal_authenticate(digestinfo)
|
||||
sig = rsa_keys.compute_signature(2, digestinfo)
|
||||
sig_bytes = sig.to_bytes(int((sig.bit_length()+7)/8), byteorder='big')
|
||||
assert r == sig_bytes
|
||||
|
||||
def test_sign_auth_1(card):
|
||||
digestinfo = rsa_keys.compute_digestinfo(PLAIN_TEXT1)
|
||||
r = card.cmd_internal_authenticate(digestinfo)
|
||||
sig = rsa_keys.compute_signature(2, digestinfo)
|
||||
sig_bytes = sig.to_bytes(int((sig.bit_length()+7)/8), byteorder='big')
|
||||
assert r == sig_bytes
|
||||
|
||||
def test_decrypt_0(card):
|
||||
ciphertext = rsa_keys.encrypt(1, PLAIN_TEXT0)
|
||||
r = card.cmd_pso(0x80, 0x86, ciphertext)
|
||||
assert r == PLAIN_TEXT0
|
||||
|
||||
def test_decrypt_1(card):
|
||||
ciphertext = rsa_keys.encrypt(1, PLAIN_TEXT1)
|
||||
r = card.cmd_pso(0x80, 0x86, ciphertext)
|
||||
assert r == PLAIN_TEXT1
|
||||
91
tests/test_personalize_reset_card.py
Normal file
91
tests/test_personalize_reset_card.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
test_personalize_reset_card.py - test resetting personalization of card
|
||||
|
||||
Copyright (C) 2016 g10 Code GmbH
|
||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||
|
||||
Gnuk is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gnuk is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from struct import pack
|
||||
from re import match, DOTALL
|
||||
from util import *
|
||||
import rsa_keys
|
||||
|
||||
FACTORY_PASSPHRASE_PW1=b"123456"
|
||||
FACTORY_PASSPHRASE_PW3=b"12345678"
|
||||
PW1_TEST0=b"another user pass phrase"
|
||||
PW1_TEST1=b"PASSPHRASE SHOULD BE LONG"
|
||||
PW1_TEST2=b"new user pass phrase"
|
||||
PW1_TEST3=b"next user pass phrase"
|
||||
PW1_TEST4=b"another user pass phrase"
|
||||
PW3_TEST0=b"admin pass phrase"
|
||||
PW3_TEST1=b"another admin pass phrase"
|
||||
|
||||
RESETCODE_TEST=b"example reset code 000"
|
||||
|
||||
def test_login_put(card):
|
||||
r = card.cmd_put_data(0x00, 0x5e, b"")
|
||||
assert r
|
||||
|
||||
def test_name_put(card):
|
||||
r = card.cmd_put_data(0x00, 0x5b, b"")
|
||||
assert r
|
||||
|
||||
def test_lang_put(card):
|
||||
r = card.cmd_put_data(0x5f, 0x2d, b"")
|
||||
assert r
|
||||
|
||||
def test_sex_put(card):
|
||||
try:
|
||||
# Gnuk
|
||||
r = card.cmd_put_data(0x5f, 0x35, b"")
|
||||
except ValueError:
|
||||
# OpenPGP card which doesn't allow b""
|
||||
r = card.cmd_put_data(0x5f, 0x35, b"9")
|
||||
assert r
|
||||
|
||||
def test_url_put(card):
|
||||
r = card.cmd_put_data(0x5f, 0x50, b"")
|
||||
assert r
|
||||
|
||||
def test_pw1_status_put(card):
|
||||
r = card.cmd_put_data(0x00, 0xc4, b"\x00")
|
||||
assert r
|
||||
|
||||
def test_setup_pw3_0(card):
|
||||
r = card.cmd_change_reference_data(3, PW3_TEST0 + FACTORY_PASSPHRASE_PW3)
|
||||
assert r
|
||||
|
||||
def test_verify_pw3_0(card):
|
||||
v = card.cmd_verify(3, FACTORY_PASSPHRASE_PW3)
|
||||
assert v
|
||||
|
||||
def test_setup_pw1_0(card):
|
||||
r = card.cmd_change_reference_data(1, PW1_TEST4 + FACTORY_PASSPHRASE_PW1)
|
||||
assert r
|
||||
|
||||
def test_verify_pw1_0(card):
|
||||
v = card.cmd_verify(1, FACTORY_PASSPHRASE_PW1)
|
||||
assert v
|
||||
|
||||
def test_verify_pw1_0_2(card):
|
||||
v = card.cmd_verify(2, FACTORY_PASSPHRASE_PW1)
|
||||
assert v
|
||||
|
||||
def test_setup_reset_code(card):
|
||||
r = card.cmd_put_data(0x00, 0xd3, b"")
|
||||
assert r
|
||||
44
tests/test_remove_keys_card.py
Normal file
44
tests/test_remove_keys_card.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
test_remove_keys_card.py - test removing keys on card
|
||||
|
||||
Copyright (C) 2016 g10 Code GmbH
|
||||
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/>.
|
||||
"""
|
||||
|
||||
# Remove a key material on card by changing algorithm attributes of the key
|
||||
|
||||
KEY_ATTRIBUTES_RSA4K=b"\x01\x10\x00\x00\x20\x00"
|
||||
KEY_ATTRIBUTES_RSA2K=b"\x01\x08\x00\x00\x20\x00"
|
||||
|
||||
def test_rsa_import_key_1(card):
|
||||
r = card.cmd_put_data(0x00, 0xc1, KEY_ATTRIBUTES_RSA4K)
|
||||
if r:
|
||||
r = card.cmd_put_data(0x00, 0xc1, KEY_ATTRIBUTES_RSA2K)
|
||||
assert r
|
||||
|
||||
def test_rsa_import_key_2(card):
|
||||
r = card.cmd_put_data(0x00, 0xc2, KEY_ATTRIBUTES_RSA4K)
|
||||
if r:
|
||||
r = card.cmd_put_data(0x00, 0xc2, KEY_ATTRIBUTES_RSA2K)
|
||||
assert r
|
||||
|
||||
def test_rsa_import_key_3(card):
|
||||
r = card.cmd_put_data(0x00, 0xc3, KEY_ATTRIBUTES_RSA4K)
|
||||
if r:
|
||||
r = card.cmd_put_data(0x00, 0xc3, KEY_ATTRIBUTES_RSA2K)
|
||||
assert r
|
||||
7
tests/util.py
Normal file
7
tests/util.py
Normal file
@@ -0,0 +1,7 @@
|
||||
def get_data_object(card, tag):
|
||||
tagh = tag >> 8
|
||||
tagl = tag & 0xff
|
||||
return card.cmd_get_data(tagh, tagl)
|
||||
|
||||
def check_null(data_object):
|
||||
return data_object == None or len(data_object) == 0
|
||||
@@ -49,6 +49,7 @@ def main(fileid, is_update, data, passwd):
|
||||
raise ValueError("No ICC present")
|
||||
elif gnuk.icc_get_status() == 1:
|
||||
gnuk.icc_power_on()
|
||||
gnuk.cmd_select_openpgp()
|
||||
gnuk.cmd_verify(BY_ADMIN, passwd.encode('UTF-8'))
|
||||
gnuk.cmd_write_binary(fileid, data, is_update)
|
||||
gnuk.cmd_select_openpgp()
|
||||
|
||||
@@ -46,6 +46,7 @@ def main(passwd):
|
||||
raise ValueError("No ICC present")
|
||||
elif gnuk.icc_get_status() == 1:
|
||||
gnuk.icc_power_on()
|
||||
gnuk.cmd_select_openpgp()
|
||||
gnuk.cmd_verify(BY_ADMIN, passwd.encode('UTF-8'))
|
||||
gnuk.cmd_select_openpgp()
|
||||
gnuk.cmd_put_data_remove(0x00, 0xc7) # FP_SIG
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
gnuk_token.py - a library for Gnuk Token
|
||||
|
||||
Copyright (C) 2011, 2012, 2013, 2015
|
||||
Copyright (C) 2011, 2012, 2013, 2015, 2017
|
||||
Free Software Initiative of Japan
|
||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
@@ -26,6 +26,12 @@ import binascii
|
||||
import usb, time
|
||||
from array import array
|
||||
|
||||
# Possible Gnuk Token products
|
||||
USB_PRODUCT_LIST=[
|
||||
{ 'vendor' : 0x234b, 'product' : 0x0000 }, # FSIJ Gnuk Token
|
||||
{ 'vendor' : 0x20a0, 'product' : 0x4211 }, # Nitrokey Start
|
||||
]
|
||||
|
||||
# USB class, subclass, protocol
|
||||
CCID_CLASS = 0x0B
|
||||
CCID_SUBCLASS = 0x00
|
||||
@@ -289,13 +295,19 @@ class gnuk_token(object):
|
||||
count += 1
|
||||
|
||||
def cmd_select_openpgp(self):
|
||||
cmd_data = iso7816_compose(0xa4, 0x04, 0x0c, b"\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):
|
||||
cmd_data = iso7816_compose(0xa4, 0x04, 0x00, b"\xD2\x76\x00\x01\x24\x01")
|
||||
r = self.icc_send_cmd(cmd_data)
|
||||
if len(r) < 2:
|
||||
raise ValueError(r)
|
||||
sw = r[-2:]
|
||||
r = r[0:-2]
|
||||
if sw[0] == 0x61:
|
||||
self.cmd_get_response(sw[1])
|
||||
return True
|
||||
elif sw[0] == 0x90 and sw[1] == 0x00:
|
||||
return True
|
||||
else:
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_get_data(self, tagh, tagl):
|
||||
cmd_data = iso7816_compose(0xca, tagh, tagl, b"")
|
||||
@@ -341,8 +353,8 @@ class gnuk_token(object):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
def cmd_reset_retry_counter(self, how, data):
|
||||
cmd_data = iso7816_compose(0x2c, how, 0x00, data)
|
||||
def cmd_reset_retry_counter(self, how, who, data):
|
||||
cmd_data = iso7816_compose(0x2c, how, who, data)
|
||||
sw = self.icc_send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
@@ -580,19 +592,18 @@ 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
|
||||
for cand in USB_PRODUCT_LIST:
|
||||
if dev.idVendor != cand['vendor']:
|
||||
continue
|
||||
if dev.idProduct != cand['product']:
|
||||
continue
|
||||
yield dev
|
||||
break
|
||||
|
||||
def get_gnuk_device():
|
||||
icc = None
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"""
|
||||
hub_ctrl.py - a tool to control port power/led of USB hub
|
||||
|
||||
Copyright (C) 2006, 2011 Free Software Initiative of Japan
|
||||
Copyright (C) 2006, 2011, 2016 Free Software Initiative of Japan
|
||||
|
||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
@@ -23,6 +23,7 @@ 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 __future__ import print_function
|
||||
import usb
|
||||
|
||||
USB_RT_HUB = (usb.TYPE_CLASS | usb.RECIP_DEVICE)
|
||||
@@ -43,7 +44,7 @@ def find_hubs(listing, verbose, busnum=None, devnum=None, hub=None):
|
||||
hubs = []
|
||||
busses = usb.busses()
|
||||
if not busses:
|
||||
raise ValueError, "can't access USB"
|
||||
raise ValueError("can't access USB")
|
||||
|
||||
for bus in busses:
|
||||
devices = bus.devices
|
||||
@@ -79,17 +80,17 @@ def find_hubs(listing, verbose, busnum=None, devnum=None, hub=None):
|
||||
continue
|
||||
|
||||
if printout_enable:
|
||||
print "Hub #%d at %s:%03d" % (number_of_hubs_with_feature,
|
||||
bus.dirname, dev.devnum)
|
||||
print("Hub #%d at %s:%03d" % (number_of_hubs_with_feature,
|
||||
bus.dirname, dev.devnum))
|
||||
if (desc[3] & 0x03) == 0:
|
||||
print " INFO: ganged power switching."
|
||||
print(" INFO: ganged power switching.")
|
||||
elif (desc[3] & 0x03) == 1:
|
||||
print " INFO: individual power switching."
|
||||
print(" INFO: individual power switching.")
|
||||
elif (desc[3] & 0x03) == 2 or (desc[3] & 0x03) == 3:
|
||||
print " WARN: no power switching."
|
||||
print(" WARN: no power switching.")
|
||||
|
||||
if (desc[3] & 0x80) == 0:
|
||||
print " WARN: Port indicators are NOT supported."
|
||||
print(" WARN: Port indicators are NOT supported.")
|
||||
|
||||
hubs.append({ 'busnum' : bus.dirname, 'devnum' : dev.devnum,
|
||||
'indicator_support' : (desc[3] & 0x80) == 0x80,
|
||||
@@ -99,7 +100,7 @@ def find_hubs(listing, verbose, busnum=None, devnum=None, hub=None):
|
||||
return hubs
|
||||
|
||||
def hub_port_status(handle, num_ports):
|
||||
print " Hub Port Status:"
|
||||
print(" Hub Port Status:")
|
||||
for i in range(num_ports):
|
||||
port = i + 1
|
||||
status = handle.controlMsg(requestType = USB_RT_PORT | usb.ENDPOINT_IN,
|
||||
@@ -108,31 +109,31 @@ def hub_port_status(handle, num_ports):
|
||||
index = port, buffer = 4,
|
||||
timeout = 1000)
|
||||
|
||||
print " Port %d: %02x%02x.%02x%02x" % (port, status[3], status[2],
|
||||
status[1], status[0]),
|
||||
print(" Port %d: %02x%02x.%02x%02x" % (port, status[3], status[2],
|
||||
status[1], status[0]),)
|
||||
if status[1] & 0x10:
|
||||
print " indicator",
|
||||
print(" indicator", end='')
|
||||
if status[1] & 0x08:
|
||||
print " test" ,
|
||||
print(" test" , end='')
|
||||
if status[1] & 0x04:
|
||||
print " highspeed",
|
||||
print(" highspeed", end='')
|
||||
if status[1] & 0x02:
|
||||
print " lowspeed",
|
||||
print(" lowspeed", end='')
|
||||
if status[1] & 0x01:
|
||||
print " power",
|
||||
print(" power", end='')
|
||||
|
||||
if status[0] & 0x10:
|
||||
print " RESET",
|
||||
print(" RESET", end='')
|
||||
if status[0] & 0x08:
|
||||
print " oc",
|
||||
print(" oc", end='')
|
||||
if status[0] & 0x04:
|
||||
print " suspend",
|
||||
print(" suspend", end='')
|
||||
if status[0] & 0x02:
|
||||
print " enable",
|
||||
print(" enable", end='')
|
||||
if status[0] & 0x01:
|
||||
print " connect",
|
||||
print(" connect", end='')
|
||||
|
||||
print
|
||||
print()
|
||||
|
||||
import sys
|
||||
|
||||
@@ -142,9 +143,9 @@ COMMAND_SET_POWER = 2
|
||||
HUB_LED_GREEN = 2
|
||||
|
||||
def usage(progname):
|
||||
print >> sys.stderr, """Usage: %s [{-h HUBNUM | -b BUSNUM -d DEVNUM}]
|
||||
print("""Usage: %s [{-h HUBNUM | -b BUSNUM -d DEVNUM}]
|
||||
[-P PORT] [{-p [VALUE]|-l [VALUE]}]
|
||||
""" % progname
|
||||
""" % progname, file=sys.stderr)
|
||||
|
||||
def exit_with_usage(progname):
|
||||
usage(progname)
|
||||
@@ -219,7 +220,7 @@ if __name__ == '__main__':
|
||||
|
||||
hubs = find_hubs(listing, verbose, busnum, devnum, hub)
|
||||
if len(hubs) == 0:
|
||||
print >> sys.stderr, "No hubs found."
|
||||
print("No hubs found.", file=sys.stderr)
|
||||
exit(1)
|
||||
if listing:
|
||||
exit(0)
|
||||
@@ -246,11 +247,11 @@ if __name__ == '__main__':
|
||||
feature = USB_PORT_FEAT_INDICATOR
|
||||
index = (value << 8) | port
|
||||
if verbose:
|
||||
print "Send control message (REQUEST=%d, FEATURE=%d, INDEX=%d) " % (request, feature, index)
|
||||
print("Send control message (REQUEST=%d, FEATURE=%d, INDEX=%d) " % (request, feature, index))
|
||||
|
||||
uh.controlMsg(requestType = USB_RT_PORT, request = request, value = feature,
|
||||
index = index, buffer = None, timeout = 1000)
|
||||
if verbose:
|
||||
hub_port_status(uh,nports)
|
||||
hub_port_status(uh,nports)
|
||||
|
||||
del uh
|
||||
|
||||
@@ -44,6 +44,7 @@ def main(wait_e, keyno, passwd, data_regnual, data_upgrade):
|
||||
rsa_raw_pubkey = rsa.get_raw_pubkey(rsa_key)
|
||||
|
||||
gnuk = get_gnuk_device()
|
||||
gnuk.cmd_select_openpgp()
|
||||
gnuk.cmd_verify(BY_ADMIN, passwd.encode('UTF-8'))
|
||||
gnuk.cmd_write_binary(1+keyno, rsa_raw_pubkey, False)
|
||||
|
||||
@@ -68,8 +69,9 @@ def main(wait_e, keyno, passwd, data_regnual, data_upgrade):
|
||||
gnuk = None
|
||||
#
|
||||
reg = None
|
||||
print("Waiting for device to appear:")
|
||||
while reg == None:
|
||||
print("Wait %d seconds..." % wait_e)
|
||||
print(" Wait %d seconds..." % wait_e)
|
||||
time.sleep(wait_e)
|
||||
for dev in gnuk_devices_by_vidpid():
|
||||
try:
|
||||
@@ -83,9 +85,13 @@ def main(wait_e, keyno, passwd, data_regnual, data_upgrade):
|
||||
print("%08x:%08x" % mem_info)
|
||||
print("Downloading the program")
|
||||
reg.download(mem_info[0], data_upgrade)
|
||||
print("Protecting device")
|
||||
reg.protect()
|
||||
print("Finish flashing")
|
||||
reg.finish()
|
||||
print("Resetting device")
|
||||
reg.reset_device()
|
||||
print("Update procedure finished")
|
||||
return 0
|
||||
|
||||
from getpass import getpass
|
||||
@@ -118,6 +124,9 @@ if __name__ == '__main__':
|
||||
passwd = getpass("Admin password: ")
|
||||
filename_regnual = sys.argv[1]
|
||||
filename_upgrade = sys.argv[2]
|
||||
if not filename_regnual.endswith('bin') or not filename_upgrade.endswith('bin'):
|
||||
print("Both input files must be in binary format (*.bin)!")
|
||||
exit(1)
|
||||
f = open(filename_regnual,"rb")
|
||||
data_regnual = f.read()
|
||||
f.close()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"""
|
||||
usb_strings.py - a tool to dump USB string
|
||||
|
||||
Copyright (C) 2012, 2015 Free Software Initiative of Japan
|
||||
Copyright (C) 2012, 2015, 2017 Free Software Initiative of Japan
|
||||
Author: NIIBE Yutaka <gniibe@fsij.org>
|
||||
|
||||
This file is a part of Gnuk, a GnuPG USB Token implementation.
|
||||
@@ -22,28 +22,14 @@ 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 usb, sys
|
||||
|
||||
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
|
||||
|
||||
field = ['', 'Vendor', 'Product', 'Serial', 'Revision', 'Config', 'Sys', 'Board']
|
||||
|
||||
def main(n):
|
||||
for dev in gnuk_devices_by_vidpid():
|
||||
handle = dev.open()
|
||||
print("Device: %s" % dev.filename)
|
||||
try:
|
||||
for i in range(1,n):
|
||||
s = handle.getString(i, 512)
|
||||
|
||||
Reference in New Issue
Block a user