├── .gitignore
├── .gitmodules
├── .idea
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── .travis.yml
├── LICENSE.md
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jc101-1c
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── fr
│ │ └── bmartel
│ │ └── helloworld
│ │ └── HelloWorld.java
│ └── test
│ └── java
│ └── fr
│ └── bmartel
│ └── helloworld
│ ├── HelloWorldTest.java
│ ├── JavaCardTest.java
│ └── TestSuite.java
├── jc101-2c
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── fr
│ │ └── bmartel
│ │ └── helloworld
│ │ └── Counter.java
│ └── test
│ └── java
│ └── fr
│ └── bmartel
│ └── helloworld
│ ├── CounterTest.java
│ ├── JavaCardTest.java
│ ├── TestSuite.java
│ └── util
│ └── TestUtils.java
├── jc101-password-pin
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── fr
│ │ └── bmartel
│ │ └── passwords
│ │ ├── PasswordPinEntry.java
│ │ └── PasswordPinManager.java
│ └── test
│ └── java
│ ├── fr
│ └── bmartel
│ │ └── passwords
│ │ ├── JavaCardTest.java
│ │ ├── Password.java
│ │ ├── PasswordEntryTest.java
│ │ ├── PasswordManagerPinTest.java
│ │ ├── TestSuite.java
│ │ └── util
│ │ └── TestUtils.java
│ └── org
│ └── globalplatform
│ └── GPSystem.java
├── jc101-password
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── fr
│ │ └── bmartel
│ │ └── passwords
│ │ ├── PasswordEntry.java
│ │ └── PasswordManager.java
│ └── test
│ └── java
│ └── fr
│ └── bmartel
│ └── passwords
│ ├── JavaCardTest.java
│ ├── Password.java
│ ├── PasswordEntryTest.java
│ ├── PasswordManagerTest.java
│ ├── TestSuite.java
│ └── util
│ └── TestUtils.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | local.properties
3 | .idea/workspace.xml
4 | .idea/libraries
5 | .DS_Store
6 | /build
7 | app/src/main/obj
8 | app/src/main/libs
9 | *.iml
10 | app/build
11 | **/build/
12 | **/out/
13 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "oracle_javacard_sdks"]
2 | path = oracle_javacard_sdks
3 | url = git://github.com/martinpaljak/oracle_javacard_sdks.git
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | script:
3 | - git submodule update --init --recursive
4 | - ./gradlew build jacocoTestReport
5 | jdk: oraclejdk8
6 | after_success:
7 | - ./gradlew jacocoRootReport coveralls
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Bertrand Martel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaCard Tutorial
2 |
3 | [](https://travis-ci.org/bertrandmartel/javacard-tutorial)
4 | [](https://coveralls.io/github/bertrandmartel/javacard-tutorial?branch=master)
5 |
6 | Examples from Eric Vétillard's [tutorial](http://javacard.vetilles.com/tutorial/) re-arranged in a Gradle project using [JavaCard gradle plugin](https://github.com/bertrandmartel/javacard-gradle-plugin) with additional tests
7 |
8 | Each example is referenced as a Gradle module with :
9 |
10 | * source under `main` sourceSet (sdk version can be selected from build.gradle)
11 | * simulation tests using [JCardSim](https://jcardsim.org/)
12 | * unit test using JUnit
13 | * tests on smartcard device using [jnasmartcardio](https://github.com/jnasmartcardio/jnasmartcardio) with [apdu4j logger](https://github.com/martinpaljak/apdu4j)
14 | * quick testing scripts defining some gradle task to send defined apdu sequence
15 |
16 | It's also possible :
17 | * to use `GPTool` and `GlobalPlatform` class under `test` which provide the same power as [GlobalPlatformPro](https://github.com/martinpaljak/GlobalPlatformPro) tool
18 | * to use [`GPExec` gradle task](https://github.com/bertrandmartel/javacard-gradle-plugin#custom-global-platform-pro-task) to call [GlobalPlatformPro](https://github.com/martinpaljak/GlobalPlatformPro) from gradle to create custom tasks
19 |
20 | ## Setup
21 |
22 | ```bash
23 | git clone git@github.com:bertrandmartel/javacard-tutorial.git
24 | cd javacard-tutorial
25 | git submodule update --init
26 | ```
27 |
28 | ## Build all examples
29 |
30 | ```bash
31 | ./gradlew build
32 | ```
33 |
34 | ## jc101-1c : basic applet
35 |
36 | http://javacard.vetilles.com/2006/09/17/hello-world-smart-card/
37 |
38 | #### install
39 |
40 | The following will build, delete applet if existing, install the applet :
41 | ```bash
42 | ./gradlew :jc101-1c:build :jc101-1c:installJavaCard
43 | ```
44 | #### run simulation tests
45 |
46 | ```bash
47 | ./gradlew :jc101-1c:test
48 | ```
49 |
50 | #### run tests on smartcard
51 |
52 | ```bash
53 | ./gradlew :jc101-1c:test -DtestMode=smartcard
54 | ```
55 |
56 | #### send apdu
57 |
58 | From [gradle script task](https://github.com/bertrandmartel/javacard-tutorial/blob/master/jc101-1c/build.gradle#L20-L33) :
59 |
60 | ```bash
61 | ./gradlew :jc101-1c:sendHello
62 | ```
63 |
64 | ## jc101-2c : a simple counter
65 |
66 | http://javacard.vetilles.com/2006/10/30/jc101-2c-a-simple-counter-for-smart-card-developers/
67 |
68 | #### install
69 |
70 | The following will build, delete applet if existing, install the applet :
71 | ```bash
72 | ./gradlew :jc101-2c:build :jc101-2c:installJavaCard
73 | ```
74 |
75 | #### run simulation tests
76 |
77 | ```bash
78 | ./gradlew :jc101-2c:test
79 | ```
80 |
81 | #### run tests on smartcard
82 |
83 | ```bash
84 | ./gradlew :jc101-2c:test -DtestMode=smartcard
85 | ```
86 |
87 | #### send apdu
88 |
89 | From [gradle script task](https://github.com/bertrandmartel/javacard-tutorial/blob/master/jc101-2c/build.gradle#L19-L48) :
90 |
91 | * balance
92 |
93 | ```bash
94 | ./gradlew :jc101-2c:balance
95 | ```
96 |
97 | * credit 5
98 |
99 | ```bash
100 | ./gradlew :jc101-2c:credit
101 | ```
102 |
103 | * debit 5
104 |
105 | ```bash
106 | ./gradlew :jc101-2c:debit
107 | ```
108 |
109 | ## jc101-password : password application
110 |
111 | * http://javacard.vetilles.com/2006/11/07/jc101-3c-a-real-application/
112 | * http://javacard.vetilles.com/2007/02/06/jc101-4c-a-basic-password-manager/
113 | * http://javacard.vetilles.com/2008/01/07/jc101-5c-data-management-and-transactions/
114 | * http://javacard.vetilles.com/2008/01/15/jc101-6c-specifying-the-apdus/
115 | * http://javacard.vetilles.com/2008/04/05/jc101-7c-processing-apdus-12/
116 | * http://javacard.vetilles.com/2008/04/09/jc101-8c-processing-apdu%E2%80%99s-22/
117 |
118 | #### install
119 |
120 | The following will build, delete applet if existing, install the applet :
121 | ```bash
122 | ./gradlew :jc101-password:build :jc101-password:installJavaCard
123 | ```
124 |
125 | #### run simulation tests
126 |
127 | ```bash
128 | ./gradlew :jc101-password:test
129 | ```
130 |
131 | #### run tests on smartcard
132 |
133 | ```bash
134 | ./gradlew :jc101-password:test -DtestMode=smartcard
135 | ```
136 |
137 | #### send apdu
138 |
139 | From [gradle script task](https://github.com/bertrandmartel/javacard-tutorial/blob/master/jc101-password/build.gradle#L19-L56) :
140 |
141 | * add password entry
142 |
143 | ```bash
144 | ./gradlew :jc101-password:addPassword
145 | ```
146 |
147 | * delete password entry
148 |
149 | ```bash
150 | ./gradlew :jc101-password:removePassword
151 | ```
152 |
153 | * list identifiers
154 |
155 | ```bash
156 | ./gradlew :jc101-password:listPassword
157 | ```
158 |
159 | ## jc101-password-pin : password application with PIN security
160 |
161 | * http://javacard.vetilles.com/2008/04/16/jc101-9c-authentication-and-lifecycle/
162 | * http://javacard.vetilles.com/2008/04/21/jc101-10c-adding-a-password-and-state-management/
163 |
164 | #### install
165 |
166 | The following will build, delete applet if existing, install the applet :
167 | ```bash
168 | ./gradlew :jc101-password-pin:build :jc101-password-pin:installJavaCard
169 | ```
170 |
171 | #### run simulation tests
172 |
173 | ```bash
174 | ./gradlew :jc101-password-pin:test
175 | ```
176 |
177 | #### run tests on smartcard
178 |
179 | ```bash
180 | ./gradlew :jc101-password-pin:test -DtestMode=smartcard
181 | ```
182 |
183 | #### send pin code apdu
184 |
185 | * set pin code
186 |
187 | ```bash
188 | ./gradlew :jc101-password-pin:setPinCode
189 | ```
190 |
191 | #### send apdu
192 |
193 | From [gradle script task](https://github.com/bertrandmartel/javacard-tutorial/blob/master/jc101-password-pin/build.gradle#L24-L81) :
194 |
195 | * add password entry
196 |
197 | ```bash
198 | ./gradlew :jc101-password-pin:addPassword
199 | ```
200 |
201 | * delete password entry
202 |
203 | ```bash
204 | ./gradlew :jc101-password-pin:removePassword
205 | ```
206 |
207 | * list identifiers
208 |
209 | ```bash
210 | ./gradlew :jc101-password-pin:listPassword
211 | ```
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'fr.bmartel:gradle-javacard:1.5.6'
7 | classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1'
8 | }
9 | }
10 |
11 | allprojects {
12 | apply plugin: 'base'
13 | apply plugin: 'jacoco'
14 | apply plugin: 'com.github.kt3k.coveralls'
15 |
16 | repositories {
17 | jcenter()
18 | }
19 |
20 | jacoco {
21 | toolVersion = '0.7.9'
22 | }
23 | }
24 |
25 | subprojects {
26 | apply plugin: 'java'
27 |
28 | test {
29 | systemProperty 'testMode', System.getProperty("testMode") ?: 'simulator'
30 | include '**/TestSuite.class'
31 | outputs.upToDateWhen { false }
32 | testLogging {
33 | //showStandardStreams = true
34 | exceptionFormat = 'full'
35 | }
36 | }
37 | }
38 |
39 | def publishedProjects = subprojects
40 |
41 | task jacocoMerge(type: JacocoMerge) {
42 | publishedProjects.each { subproject ->
43 | executionData subproject.tasks.withType(Test)
44 | }
45 | doFirst {
46 | executionData = files(executionData.findAll { it.exists() })
47 | }
48 | }
49 |
50 | task jacocoRootReport(type: JacocoReport, group: 'Coverage reports') {
51 | description = 'Generates an aggregate report from all subprojects'
52 | dependsOn publishedProjects.test, jacocoMerge
53 |
54 | additionalSourceDirs = files(publishedProjects.sourceSets.main.allSource.srcDirs)
55 | sourceDirectories = files(publishedProjects.sourceSets.main.allSource.srcDirs)
56 | classDirectories = files(publishedProjects.sourceSets.main.output)
57 | executionData jacocoMerge.destinationFile
58 |
59 | reports {
60 | html.enabled = true // human readable
61 | xml.enabled = true // required by coveralls
62 | }
63 | }
64 |
65 | coveralls {
66 | sourceDirs = publishedProjects.sourceSets.main.allSource.srcDirs.flatten()
67 | jacocoReportPath = "${buildDir}/reports/jacoco/jacocoRootReport/jacocoRootReport.xml"
68 | }
69 |
70 | tasks.coveralls {
71 | group = 'Coverage reports'
72 | description = 'Uploads the aggregated coverage report to Coveralls'
73 |
74 | dependsOn jacocoRootReport
75 | }
76 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/javacard-tutorial/82ebc12cf640eb4b1e646aeaddc716fa243b5800/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Aug 11 02:05:15 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/jc101-1c/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'javacard'
2 |
3 | dependencies {
4 | testCompile 'com.github.martinpaljak:apdu4j:18.10.04'
5 | compile 'com.github.martinpaljak:globalplatformpro:18.09.14'
6 | }
7 |
8 | javacard {
9 |
10 | config {
11 | jckit '../oracle_javacard_sdks/jc221_kit'
12 |
13 | cap {
14 | packageName 'fr.bmartel.helloworld'
15 | version '0.1'
16 | aid '01:02:03:04:05:06:07:08:09'
17 | output 'applet.cap'
18 | applet {
19 | className 'fr.bmartel.helloworld.HelloWorld'
20 | aid '01:02:03:04:05:06:07:08:09:01'
21 | }
22 | }
23 | }
24 |
25 | scripts {
26 | script {
27 | name 'select'
28 | apdu '00:A4:04:00:0A:01:02:03:04:05:06:07:08:09:01'
29 | }
30 | script {
31 | name 'hello'
32 | apdu '00:40:00:00:00'
33 | }
34 | task {
35 | name 'sendHello'
36 | scripts 'select', 'hello'
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/jc101-1c/src/main/java/fr/bmartel/helloworld/HelloWorld.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import javacard.framework.*;
4 |
5 | public class HelloWorld extends Applet {
6 |
7 | private final static byte[] hello = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
8 |
9 | public static void install(byte[] buffer, short offset, byte length) {
10 | (new HelloWorld()).register();
11 | }
12 |
13 | public void process(APDU apdu) {
14 | byte[] buf = apdu.getBuffer();
15 |
16 | switch (buf[ISO7816.OFFSET_INS]) {
17 | case 0x40:
18 | Util.arrayCopy(hello, (byte) 0, buf, ISO7816.OFFSET_CDATA, (byte) 5);
19 | apdu.setOutgoingAndSend(
20 | ISO7816.OFFSET_CDATA, (byte) 5);
21 | break;
22 | case ISO7816.INS_SELECT:
23 | apdu.setOutgoingAndSend(
24 | ISO7816.OFFSET_CDATA, (byte) 5);
25 | break;
26 | default:
27 | ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/jc101-1c/src/test/java/fr/bmartel/helloworld/HelloWorldTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import org.junit.BeforeClass;
4 | import org.junit.Test;
5 |
6 | import javax.smartcardio.CardException;
7 | import javax.smartcardio.CommandAPDU;
8 | import javax.smartcardio.ResponseAPDU;
9 |
10 | import static org.junit.Assert.assertArrayEquals;
11 | import static org.junit.Assert.assertEquals;
12 |
13 | public class HelloWorldTest extends JavaCardTest {
14 |
15 | private final static byte[] hello = {(byte) 0x48, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, (byte) 0x6f};
16 |
17 | @BeforeClass
18 | public static void setup() throws CardException {
19 | TestSuite.setup();
20 | }
21 |
22 | @Test
23 | public void helloTest() throws CardException {
24 | CommandAPDU c = new CommandAPDU(0x00, 0x40, 0x00, 0x00);
25 | ResponseAPDU response = transmitCommand(c);
26 | assertEquals(0x9000, response.getSW());
27 | assertArrayEquals(hello, response.getData());
28 | }
29 | }
--------------------------------------------------------------------------------
/jc101-1c/src/test/java/fr/bmartel/helloworld/JavaCardTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import javax.smartcardio.CardException;
4 | import javax.smartcardio.CommandAPDU;
5 | import javax.smartcardio.ResponseAPDU;
6 |
7 | public class JavaCardTest {
8 |
9 | public ResponseAPDU transmitCommand(CommandAPDU data) throws CardException {
10 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
11 | return TestSuite.getCard().getBasicChannel().transmit(data);
12 | } else {
13 | return TestSuite.getSimulator().transmitCommand(data);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/jc101-1c/src/test/java/fr/bmartel/helloworld/TestSuite.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import apdu4j.LoggingCardTerminal;
4 | import apdu4j.TerminalManager;
5 | import com.licel.jcardsim.smartcardio.CardSimulator;
6 | import com.licel.jcardsim.utils.AIDUtil;
7 | import javacard.framework.AID;
8 | import org.junit.AfterClass;
9 | import org.junit.BeforeClass;
10 | import org.junit.runner.RunWith;
11 | import org.junit.runners.Suite;
12 | import org.junit.runners.Suite.SuiteClasses;
13 |
14 | import javax.smartcardio.*;
15 | import javax.smartcardio.CardTerminals.State;
16 | import java.io.OutputStream;
17 | import java.security.NoSuchAlgorithmException;
18 | import java.util.List;
19 |
20 | import static org.junit.Assert.fail;
21 | import static org.junit.Assert.assertEquals;
22 |
23 | @RunWith(Suite.class)
24 | @SuiteClasses({HelloWorldTest.class})
25 | public class TestSuite {
26 |
27 | private final static String APPLET_AID = "01020304050607080901";
28 |
29 | private static CardSimulator mSimulator;
30 |
31 | private static boolean mInitialized;
32 |
33 | private static Card mCard;
34 |
35 | private static Card initGp() {
36 |
37 | try {
38 | TerminalFactory tf = TerminalManager.getTerminalFactory(null);
39 |
40 | CardTerminals terminals = tf.terminals();
41 |
42 | System.out.println("# Detected readers from " + tf.getProvider().getName());
43 | for (CardTerminal term : terminals.list()) {
44 | System.out.println((term.isCardPresent() ? "[*] " : "[ ] ") + term.getName());
45 | }
46 |
47 | // Select terminal(s) to work on
48 | List do_readers;
49 | do_readers = terminals.list(State.CARD_PRESENT);
50 |
51 | if (do_readers.size() == 0) {
52 | fail("No smart card readers with a card found");
53 | }
54 | // Work all readers
55 | for (CardTerminal reader : do_readers) {
56 | if (do_readers.size() > 1) {
57 | System.out.println("# " + reader.getName());
58 | }
59 |
60 | OutputStream o = null;
61 | reader = LoggingCardTerminal.getInstance(reader, o);
62 |
63 | Card card = null;
64 | // Establish connection
65 | try {
66 | card = reader.connect("*");
67 | // Use use apdu4j which by default uses jnasmartcardio
68 | // which uses real SCardBeginTransaction
69 | card.beginExclusive();
70 |
71 | return card;
72 |
73 | } catch (CardException e) {
74 | System.err.println("Could not connect to " + reader.getName() + ": " + TerminalManager.getExceptionMessage(e));
75 | continue;
76 | }
77 | }
78 | } catch (CardException e) {
79 | // Sensible wrapper for the different PC/SC exceptions
80 | if (TerminalManager.getExceptionMessage(e) != null) {
81 | System.out.println("PC/SC failure: " + TerminalManager.getExceptionMessage(e));
82 | } else {
83 | e.printStackTrace(); // TODO: remove
84 | fail("CardException, terminating");
85 | }
86 | } catch (NoSuchAlgorithmException e) {
87 | e.printStackTrace();
88 | }
89 | return null;
90 | }
91 |
92 | @BeforeClass
93 | public static void setup() throws CardException {
94 | if (mInitialized) {
95 | return;
96 | }
97 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
98 | mCard = initGp();
99 | CommandAPDU c = new CommandAPDU(AIDUtil.select(APPLET_AID));
100 | ResponseAPDU response = mCard.getBasicChannel().transmit(c);
101 | assertEquals(0x9000, response.getSW());
102 | } else {
103 | mSimulator = new CardSimulator();
104 | AID appletAID = AIDUtil.create(APPLET_AID);
105 | mSimulator.installApplet(appletAID, HelloWorld.class);
106 | mSimulator.selectApplet(appletAID);
107 | }
108 | mInitialized = true;
109 | }
110 |
111 | public static Card getCard() {
112 | return mCard;
113 | }
114 |
115 | public static CardSimulator getSimulator() {
116 | return mSimulator;
117 | }
118 |
119 | @AfterClass
120 | public static void close() throws CardException {
121 | if (mCard != null) {
122 | mCard.endExclusive();
123 | mCard.disconnect(true);
124 | mCard = null;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/jc101-2c/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'javacard'
2 |
3 | dependencies {
4 | testCompile 'com.github.martinpaljak:apdu4j:18.10.04'
5 | compile 'com.github.martinpaljak:globalplatformpro:18.09.14'
6 | }
7 |
8 | javacard {
9 |
10 | config {
11 | jckit '../oracle_javacard_sdks/jc221_kit'
12 | cap {
13 | packageName 'fr.bmartel.helloworld'
14 | version '0.1'
15 | aid '01:02:03:04:05:06:07:08:09'
16 | output 'applet.cap'
17 | applet {
18 | className 'fr.bmartel.helloworld.Counter'
19 | aid '01:02:03:04:05:06:07:08:09:01'
20 | }
21 | }
22 | }
23 |
24 | scripts {
25 | script {
26 | name 'select'
27 | apdu '00:A4:04:00:0A:01:02:03:04:05:06:07:08:09:01:00'
28 | }
29 | script {
30 | name 'balance'
31 | apdu '00:02:00:00:00'
32 | }
33 | script {
34 | name 'credit+5'
35 | apdu '00:04:00:00:02:00:05'
36 | }
37 | script {
38 | name 'debit-5'
39 | apdu '00:06:00:00:02:00:05'
40 | }
41 | task {
42 | name 'balance'
43 | scripts 'select', 'balance'
44 | }
45 | task {
46 | name 'credit'
47 | scripts 'select', 'balance', 'credit+5', 'balance'
48 | }
49 | task {
50 | name 'debit'
51 | scripts 'select', 'balance', 'debit-5', 'balance'
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/jc101-2c/src/main/java/fr/bmartel/helloworld/Counter.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import javacard.framework.*;
4 |
5 | public class Counter extends Applet {
6 |
7 | public static final byte INS_GET_BALANCE = 0x02;
8 | public static final byte INS_CREDIT = 0x04;
9 | public static final byte INS_DEBIT = 0x06;
10 |
11 | public static final short MAX_BALANCE = 10000;
12 | public static final short MAX_CREDIT = 5000;
13 | public static final short MAX_DEBIT = 1000;
14 |
15 | private short balance;
16 |
17 | private Counter() {
18 | balance = 0;
19 | }
20 |
21 | public static void install(byte[] buffer, short offset, byte length) {
22 | (new Counter()).register();
23 | }
24 |
25 | public void process(APDU apdu) {
26 | byte[] buffer = apdu.getBuffer();
27 | if (selectingApplet()) return;
28 |
29 | switch (buffer[ISO7816.OFFSET_INS]) {
30 | case INS_GET_BALANCE:
31 | getBalance(apdu, buffer);
32 | return;
33 | case INS_CREDIT:
34 | credit(apdu, buffer);
35 | return;
36 | case INS_DEBIT:
37 | debit(apdu, buffer);
38 | return;
39 | default:
40 | ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
41 | }
42 | }
43 |
44 | private void getBalance(APDU apdu, byte[] buffer) {
45 | Util.setShort(buffer, (byte) 0, balance);
46 | apdu.setOutgoingAndSend((byte) 0, (byte) 2);
47 | }
48 |
49 | private void debit(APDU apdu, byte[] buffer) {
50 | short debit;
51 |
52 | if (apdu.setIncomingAndReceive() != 2)
53 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
54 |
55 | debit = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
56 |
57 | if ((debit > balance) || (debit <= 0) ||
58 | (debit > MAX_DEBIT))
59 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
60 |
61 | balance -= debit;
62 |
63 | getBalance(apdu, buffer);
64 | }
65 |
66 | private void credit(APDU apdu, byte[] buffer) {
67 | short credit;
68 | if (apdu.setIncomingAndReceive() != 2)
69 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
70 | credit = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
71 |
72 | if (((short) (credit + balance) > MAX_BALANCE) ||
73 | (credit <= 0) ||
74 | (credit > MAX_CREDIT))
75 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
76 |
77 | balance += credit;
78 |
79 | getBalance(apdu, buffer);
80 | }
81 | }
--------------------------------------------------------------------------------
/jc101-2c/src/test/java/fr/bmartel/helloworld/CounterTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import fr.bmartel.helloworld.util.TestUtils;
4 | import javacard.framework.ISO7816;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import javax.smartcardio.CardException;
9 | import javax.smartcardio.CommandAPDU;
10 | import javax.smartcardio.ResponseAPDU;
11 |
12 | import java.nio.ByteBuffer;
13 |
14 | import static org.junit.Assert.assertArrayEquals;
15 | import static org.junit.Assert.assertEquals;
16 |
17 | public class CounterTest extends JavaCardTest {
18 |
19 | private final static byte[] CMD_BALANCE = new byte[]{0x00, 0x02, 0x00, 0x00};
20 | private final static byte[] CMD_CREDIT = new byte[]{0x00, 0x04, 0x00, 0x00};
21 | private final static byte[] CMD_DEBIT = new byte[]{0x00, 0x06, 0x00, 0x00};
22 |
23 | private final static byte[] MAX_BALANCE = new byte[]{0x27, 0x10};
24 | private final static byte[] MAX_DEBIT = new byte[]{0x03, (byte) 0xE8};
25 |
26 | private final static byte[] NEGATIVE_CREDIT = new byte[]{(byte) 0xFF, (byte) 0xFF};
27 | private final static byte[] MAX_CREDIT = new byte[]{0x13, (byte) 0x88};
28 | private final static byte[] OVERFLOW_CREDIT = new byte[]{0x13, (byte) 0x89};
29 |
30 | private final static byte[] BAD_CMD = new byte[]{0x00, 0x07, 0x00, 0x00};
31 |
32 | private void resetBalance() throws CardException {
33 | CommandAPDU commandAPDU = new CommandAPDU(CMD_BALANCE);
34 | ResponseAPDU response = transmitCommand(commandAPDU);
35 | assertEquals(0x9000, response.getSW());
36 | assertEquals(2, response.getData().length);
37 | int currentCredit = ((response.getData()[0] & 0xFF) << 8) + (response.getData()[1] & 0xFF);
38 |
39 | if (currentCredit != 0) {
40 | int it = currentCredit / (((MAX_DEBIT[0] & 0xFF) << 8) + (MAX_DEBIT[1] & 0xFF));
41 |
42 | for (int i = 0; i < it; i++) {
43 | commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_DEBIT, MAX_DEBIT));
44 | response = transmitCommand(commandAPDU);
45 | assertEquals(0x9000, response.getSW());
46 | }
47 |
48 | int remain = currentCredit % 1000;
49 |
50 | if (remain > 0) {
51 | byte[] data = ByteBuffer.allocate(4).putInt(remain).array();
52 | commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_DEBIT, new byte[]{data[2], data[3]}));
53 | response = transmitCommand(commandAPDU);
54 | assertEquals(0x9000, response.getSW());
55 | }
56 | assertArrayEquals(new byte[]{0x00, 0x00}, response.getData());
57 | }
58 | }
59 |
60 | @Before
61 | public void initTest() throws CardException {
62 | TestSuite.setup();
63 | resetBalance();
64 | }
65 |
66 | private ResponseAPDU getBalance() throws CardException {
67 | CommandAPDU commandAPDU = new CommandAPDU(CMD_BALANCE);
68 | ResponseAPDU response = transmitCommand(commandAPDU);
69 | assertEquals(0x9000, response.getSW());
70 | return response;
71 | }
72 |
73 | private void sendCmdBatch(byte[] cmd, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
74 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(cmd, data));
75 | ResponseAPDU response = transmitCommand(commandAPDU);
76 | assertEquals(expectedSw, response.getSW());
77 | assertArrayEquals(expectedResponse, response.getData());
78 | }
79 |
80 | private void sendCredit(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
81 | sendCmdBatch(CMD_CREDIT, data, expectedSw, expectedResponse);
82 | }
83 |
84 | private void sendDebit(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
85 | sendCmdBatch(CMD_DEBIT, data, expectedSw, expectedResponse);
86 | }
87 |
88 | @Test
89 | public void balanceTest() throws CardException {
90 | assertArrayEquals(new byte[]{0x00, 0x00}, getBalance().getData());
91 | }
92 |
93 | @Test
94 | public void creditTest() throws CardException {
95 | sendCredit(new byte[]{0x00, 0x05}, 0x9000, new byte[]{0x00, 0x05});
96 | assertArrayEquals(new byte[]{0x00, 0x05}, getBalance().getData());
97 | }
98 |
99 | @Test
100 | public void creditNegativeTest() throws CardException {
101 | sendCredit(NEGATIVE_CREDIT, ISO7816.SW_WRONG_DATA, new byte[]{});
102 | }
103 |
104 | @Test
105 | public void creditOverflowTest() throws CardException {
106 | sendCredit(OVERFLOW_CREDIT, ISO7816.SW_WRONG_DATA, new byte[]{});
107 | sendCredit(MAX_CREDIT, 0x9000, MAX_CREDIT);
108 | assertArrayEquals(MAX_CREDIT, getBalance().getData());
109 | }
110 |
111 | @Test
112 | public void balanceOverflowTest() throws CardException {
113 | sendCredit(MAX_CREDIT, 0x9000, MAX_CREDIT);
114 | sendCredit(MAX_CREDIT, 0x9000, TestUtils.getByte(TestUtils.getInt(MAX_CREDIT) * 2));
115 | sendCredit(new byte[]{0x00, 0x01}, ISO7816.SW_WRONG_DATA, new byte[]{});
116 | assertArrayEquals(MAX_BALANCE, getBalance().getData());
117 | }
118 |
119 | @Test
120 | public void debitTest() throws CardException {
121 | sendCredit(new byte[]{0x00, 0x05}, 0x9000, new byte[]{0x00, 0x05});
122 | assertArrayEquals(new byte[]{0x00, 0x05}, getBalance().getData());
123 | sendDebit(new byte[]{0x00, 0x05}, 0x9000, new byte[]{0x00, 0x00});
124 | assertArrayEquals(new byte[]{0x00, 0x00}, getBalance().getData());
125 | }
126 |
127 | @Test
128 | public void debitNegativeTest() throws CardException {
129 | sendCredit(new byte[]{0x00, 0x05}, 0x9000, new byte[]{0x00, 0x05});
130 | assertArrayEquals(new byte[]{0x00, 0x05}, getBalance().getData());
131 | sendDebit(new byte[]{0x00, 0x06}, ISO7816.SW_WRONG_DATA, new byte[]{});
132 | assertArrayEquals(new byte[]{0x00, 0x05}, getBalance().getData());
133 | }
134 |
135 | @Test
136 | public void debitOverflowTest() throws CardException {
137 | sendDebit(new byte[]{0x00, 0x01}, ISO7816.SW_WRONG_DATA, new byte[]{});
138 | assertArrayEquals(new byte[]{0x00, 0x00}, getBalance().getData());
139 | }
140 |
141 | @Test
142 | public void badCmdTest() throws CardException {
143 | sendCmdBatch(BAD_CMD, new byte[]{}, ISO7816.SW_INS_NOT_SUPPORTED, new byte[]{});
144 | }
145 | }
--------------------------------------------------------------------------------
/jc101-2c/src/test/java/fr/bmartel/helloworld/JavaCardTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import javax.smartcardio.CardException;
4 | import javax.smartcardio.CommandAPDU;
5 | import javax.smartcardio.ResponseAPDU;
6 |
7 | public class JavaCardTest {
8 |
9 | public ResponseAPDU transmitCommand(CommandAPDU data) throws CardException {
10 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
11 | return TestSuite.getCard().getBasicChannel().transmit(data);
12 | } else {
13 | return TestSuite.getSimulator().transmitCommand(data);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/jc101-2c/src/test/java/fr/bmartel/helloworld/TestSuite.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld;
2 |
3 | import apdu4j.LoggingCardTerminal;
4 | import apdu4j.TerminalManager;
5 | import com.licel.jcardsim.smartcardio.CardSimulator;
6 | import com.licel.jcardsim.utils.AIDUtil;
7 | import javacard.framework.AID;
8 | import org.junit.AfterClass;
9 | import org.junit.BeforeClass;
10 | import org.junit.runner.RunWith;
11 | import org.junit.runners.Suite;
12 | import org.junit.runners.Suite.SuiteClasses;
13 |
14 | import javax.smartcardio.*;
15 | import javax.smartcardio.CardTerminals.State;
16 | import java.io.OutputStream;
17 | import java.security.NoSuchAlgorithmException;
18 | import java.util.List;
19 |
20 | import static org.junit.Assert.fail;
21 | import static org.junit.Assert.assertEquals;
22 |
23 | @RunWith(Suite.class)
24 | @SuiteClasses({CounterTest.class})
25 | public class TestSuite {
26 |
27 | private final static String APPLET_AID = "01020304050607080901";
28 |
29 | private static CardSimulator mSimulator;
30 |
31 | private static boolean mInitialized;
32 |
33 | private static Card mCard;
34 |
35 | private static Card initGp() {
36 |
37 | try {
38 | TerminalFactory tf = TerminalManager.getTerminalFactory(null);
39 |
40 | CardTerminals terminals = tf.terminals();
41 |
42 | System.out.println("# Detected readers from " + tf.getProvider().getName());
43 | for (CardTerminal term : terminals.list()) {
44 | System.out.println((term.isCardPresent() ? "[*] " : "[ ] ") + term.getName());
45 | }
46 |
47 | // Select terminal(s) to work on
48 | List do_readers;
49 | do_readers = terminals.list(State.CARD_PRESENT);
50 |
51 | if (do_readers.size() == 0) {
52 | fail("No smart card readers with a card found");
53 | }
54 | // Work all readers
55 | for (CardTerminal reader : do_readers) {
56 | if (do_readers.size() > 1) {
57 | System.out.println("# " + reader.getName());
58 | }
59 |
60 | OutputStream o = null;
61 | reader = LoggingCardTerminal.getInstance(reader, o);
62 |
63 | Card card = null;
64 | // Establish connection
65 | try {
66 | card = reader.connect("*");
67 | // Use use apdu4j which by default uses jnasmartcardio
68 | // which uses real SCardBeginTransaction
69 | card.beginExclusive();
70 |
71 | return card;
72 |
73 | } catch (CardException e) {
74 | System.err.println("Could not connect to " + reader.getName() + ": " + TerminalManager.getExceptionMessage(e));
75 | continue;
76 | }
77 | }
78 | } catch (CardException e) {
79 | // Sensible wrapper for the different PC/SC exceptions
80 | if (TerminalManager.getExceptionMessage(e) != null) {
81 | System.out.println("PC/SC failure: " + TerminalManager.getExceptionMessage(e));
82 | } else {
83 | e.printStackTrace(); // TODO: remove
84 | fail("CardException, terminating");
85 | }
86 | } catch (NoSuchAlgorithmException e) {
87 | e.printStackTrace();
88 | }
89 | return null;
90 | }
91 |
92 | @BeforeClass
93 | public static void setup() throws CardException {
94 | if (mInitialized) {
95 | return;
96 | }
97 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
98 | mCard = initGp();
99 | CommandAPDU c = new CommandAPDU(AIDUtil.select(APPLET_AID));
100 | ResponseAPDU response = mCard.getBasicChannel().transmit(c);
101 | assertEquals(0x9000, response.getSW());
102 | } else {
103 | mSimulator = new CardSimulator();
104 | AID appletAID = AIDUtil.create(APPLET_AID);
105 | mSimulator.installApplet(appletAID, Counter.class);
106 | mSimulator.selectApplet(appletAID);
107 | }
108 | mInitialized = true;
109 | }
110 |
111 | public static Card getCard() {
112 | return mCard;
113 | }
114 |
115 | public static CardSimulator getSimulator() {
116 | return mSimulator;
117 | }
118 |
119 | @AfterClass
120 | public static void close() throws CardException {
121 | if (mCard != null) {
122 | mCard.endExclusive();
123 | mCard.disconnect(true);
124 | mCard = null;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/jc101-2c/src/test/java/fr/bmartel/helloworld/util/TestUtils.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld.util;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.nio.ByteOrder;
5 | import java.util.Arrays;
6 |
7 | public class TestUtils {
8 |
9 | public static byte[] buildApdu(byte[] command, byte[] data) {
10 | byte[] apdu = new byte[command.length + data.length + 1];
11 | System.arraycopy(command, 0, apdu, 0, command.length);
12 | apdu[command.length] = (byte) data.length;
13 | System.arraycopy(data, 0, apdu, command.length + 1, data.length);
14 | return apdu;
15 | }
16 |
17 | public static void logData(byte[] data) {
18 | System.out.println(Arrays.toString(data));
19 | }
20 |
21 | public static int getInt(byte[] data) {
22 | return (((data[0] & 0xff) << 8) | (data[1] & 0xff));
23 | }
24 |
25 | public static byte[] getByte(int data) {
26 | byte[] out = ByteBuffer.allocate(4).putInt(data).array();
27 | return new byte[]{out[2], out[3]};
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/jc101-password-pin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'javacard'
2 |
3 | dependencies {
4 | testCompile 'com.github.martinpaljak:apdu4j:18.10.04'
5 | compile 'com.github.martinpaljak:globalplatformpro:18.09.14'
6 | }
7 |
8 | javacard {
9 |
10 | config {
11 | jckit '../oracle_javacard_sdks/jc221_kit'
12 | cap {
13 | packageName 'fr.bmartel.passwords'
14 | version '0.1'
15 | aid '01:02:03:04:05:06:07:08:09'
16 | output 'applet.cap'
17 |
18 | applet {
19 | className 'fr.bmartel.passwords.PasswordPinManager'
20 | aid '01:02:03:04:05:06:07:08:09:01'
21 | }
22 |
23 | dependencies {
24 | remote 'fr.bmartel:gplatform:2.1.1'
25 | }
26 | }
27 | }
28 |
29 | scripts {
30 | //select AID
31 | script {
32 | name 'select'
33 | apdu '00:A4:04:00:0A:01:02:03:04:05:06:07:08:09:01:00'
34 | }
35 |
36 | //pin code apdu
37 | script {
38 | name 'update pin'
39 | apdu '00:24:00:80:08:07:61:42:63:31:32:45:34'
40 | }
41 | script {
42 | name 'verify pin'
43 | apdu '00:20:00:80:07:61:42:63:31:32:45:34'
44 | }
45 |
46 | //password manager apdu
47 | script {
48 | name 'add password entry'
49 | apdu '00:30:00:00:11:F1:04:48:6F:6D:65:F2:03:62:6F:62:F3:04:70:61:73:73'
50 | }
51 | script {
52 | name 'get password entry'
53 | apdu '00:32:00:00:06:F1:04:48:6F:6D:65'
54 | }
55 | script {
56 | name 'delete password entry'
57 | apdu '00:34:00:00:06:F1:04:48:6F:6D:65'
58 | }
59 | script {
60 | name 'list identifiers'
61 | apdu '00:36:00:00'
62 | }
63 |
64 | //set pin code task
65 | task {
66 | name 'setPinCode'
67 | scripts 'select', 'update pin'
68 | }
69 | //all other task have 'verify pin' before issuing the specified command
70 | task {
71 | name 'listPassword'
72 | scripts 'select', 'verify pin', 'list identifiers'
73 | }
74 | task {
75 | name 'addPassword'
76 | scripts 'select', 'verify pin', 'add password entry'
77 | }
78 | task {
79 | name 'removePassword'
80 | scripts 'select', 'verify pin', 'delete password entry'
81 | }
82 | task {
83 | name 'addRemovePassword'
84 | scripts 'select', 'verify pin', 'add password entry', 'delete password entry'
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/jc101-password-pin/src/main/java/fr/bmartel/passwords/PasswordPinEntry.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import javacard.framework.JCSystem;
4 | import javacard.framework.Util;
5 |
6 | public class PasswordPinEntry {
7 |
8 | public static short SIZE_ID = 16;
9 | public static short SIZE_USERNAME = 24;
10 | public static short SIZE_PASSWORD = 16;
11 |
12 | private PasswordPinEntry next;
13 | private static PasswordPinEntry first;
14 | private static PasswordPinEntry deleted;
15 |
16 | private byte[] id;
17 | private byte[] username;
18 | private byte[] password;
19 |
20 | private byte idLength;
21 | private byte userNameLength;
22 | private byte passwordLength;
23 |
24 | private PasswordPinEntry() {
25 | // Allocates all fields
26 | id = new byte[SIZE_ID];
27 | username = new byte[SIZE_USERNAME];
28 | password = new byte[SIZE_PASSWORD];
29 | // The new element is inserted in front of the list
30 | next = first;
31 | first = this;
32 | }
33 |
34 | static PasswordPinEntry getInstance() {
35 | if (deleted == null) {
36 | // There is no element to recycle
37 | return new PasswordPinEntry();
38 | } else {
39 | // Recycle the first available element
40 | PasswordPinEntry instance = deleted;
41 | deleted = instance.next;
42 | instance.next = first;
43 | first = instance;
44 | return instance;
45 | }
46 | }
47 |
48 | static PasswordPinEntry search(byte[] buf, short ofs, byte len) {
49 | for (PasswordPinEntry pe = first; pe != null; pe = pe.next) {
50 | if (pe.idLength != len) continue;
51 | if (Util.arrayCompare(pe.id, (short) 0, buf, ofs, len) == 0)
52 | return pe;
53 | }
54 | return null;
55 | }
56 |
57 | public static PasswordPinEntry getFirst() {
58 | return first;
59 | }
60 |
61 | private void remove() {
62 | if (first == this) {
63 | first = next;
64 | } else {
65 | for (PasswordPinEntry pe = first; pe != null; pe = pe.next)
66 | if (pe.next == this)
67 | pe.next = next;
68 | }
69 | }
70 |
71 | private void recycle() {
72 | next = deleted;
73 | idLength = 0;
74 | userNameLength = 0;
75 | passwordLength = 0;
76 | deleted = this;
77 | }
78 |
79 | static void delete(byte[] buf, short ofs, byte len) {
80 | PasswordPinEntry pe = search(buf, ofs, len);
81 | if (pe != null) {
82 | JCSystem.beginTransaction();
83 | pe.remove();
84 | pe.recycle();
85 | JCSystem.commitTransaction();
86 | }
87 | }
88 |
89 | byte getId(byte[] buf, short ofs) {
90 | Util.arrayCopy(id, (short) 0, buf, ofs, idLength);
91 | return idLength;
92 | }
93 |
94 | byte getUserName(byte[] buf, short ofs) {
95 | Util.arrayCopy(username, (short) 0, buf, ofs, userNameLength);
96 | return userNameLength;
97 | }
98 |
99 | byte getPassword(byte[] buf, short ofs) {
100 | Util.arrayCopy(password, (short) 0, buf, ofs, passwordLength);
101 | return passwordLength;
102 | }
103 |
104 | public byte getIdLength() {
105 | return idLength;
106 | }
107 |
108 | public PasswordPinEntry getNext() {
109 | return next;
110 | }
111 |
112 | public void setId(byte[] buf, short ofs, byte len) {
113 | Util.arrayCopy(buf, ofs, id, (short) 0, len);
114 | idLength = len;
115 | }
116 |
117 | public void setUserName(byte[] buf, short ofs, byte len) {
118 | Util.arrayCopy(buf, ofs, username, (short) 0, len);
119 | userNameLength = len;
120 | }
121 |
122 | public void setPassword(byte[] buf, short ofs, byte len) {
123 | Util.arrayCopy(buf, ofs, password, (short) 0, len);
124 | passwordLength = len;
125 | }
126 | }
--------------------------------------------------------------------------------
/jc101-password-pin/src/main/java/fr/bmartel/passwords/PasswordPinManager.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import javacard.framework.*;
4 | import org.globalplatform.GPSystem;
5 |
6 | public class PasswordPinManager extends Applet {
7 |
8 | private OwnerPIN pin;
9 |
10 | private final static short SW_WRONG_PIN = (short) 0x63c0;
11 |
12 | /**
13 | * INS byte for the command that verifies the PIN
14 | * (from ISO7816-4)
15 | */
16 | public final static byte INS_VERIFY = (byte) 0x20;
17 |
18 | /**
19 | * PIN try limit
20 | */
21 | public final static byte PIN_TRY_LIMIT = (byte) 3;
22 |
23 | /**
24 | * PIN Maximum size
25 | */
26 | public final static byte PIN_MAX_SIZE = (byte) 16;
27 |
28 | /**
29 | * INS byte for the command that changes the PIN
30 | * (from ISO7816-4)
31 | */
32 | public final static byte INS_CHANGE_REFERENCE_DATA = (byte) 0x24;
33 |
34 | /**
35 | * INS for command that adds a password entry
36 | */
37 | public final static byte INS_ADD_PASSWORD_ENTRY = (byte) 0x30;
38 |
39 | /**
40 | * INS for command that retrieves a password entry
41 | */
42 | public final static byte INS_RETRIEVE_PASSWORD_ENTRY = (byte) 0x32;
43 |
44 | /**
45 | * INS for command that deletes a password entry
46 | */
47 | public final static byte INS_DELETE_PASSWORD_ENTRY = (byte) 0x34;
48 |
49 | /**
50 | * INS for command that lists all defined identifiers
51 | */
52 | public final static byte INS_LIST_IDENTIFIERS = (byte) 0x36;
53 |
54 | /**
55 | * Status word for a duplicate identifier
56 | */
57 | public final static short SW_DUPLICATE_IDENTIFIER = (short) 0x6A8A;
58 |
59 | /**
60 | * Status word for a failed allocation
61 | */
62 | public final static short SW_NOT_ENOUGH_MEMORY = (short) 0x6A84;
63 |
64 | public static final short SW_IDENTIFIER_NOT_FOUND = (short) 0x6A82;
65 |
66 | /**
67 | * Tag byte for identifiers
68 | */
69 | public final static byte TAG_IDENTIFIER = (byte) 0xF1;
70 |
71 | /**
72 | * Tag byte for user name records
73 | */
74 | public final static byte TAG_USERNAME = (byte) 0xF2;
75 |
76 | /**
77 | * Tag byte for password records
78 | */
79 | public final static byte TAG_PASSWORD = (byte) 0xF3;
80 |
81 | private PasswordPinEntry current;
82 |
83 | private PasswordPinManager() {
84 | pin = new OwnerPIN(PIN_TRY_LIMIT, PIN_MAX_SIZE);
85 | }
86 |
87 | public static void install
88 | (byte[] bArray, short bOffset, byte bLength) {
89 | new PasswordPinManager().register();
90 | }
91 |
92 | public void process(APDU apdu) {
93 | // Nothing particular to do on SELECT
94 | if (selectingApplet()) {
95 | return;
96 | }
97 |
98 | byte[] buf = apdu.getBuffer();
99 |
100 | // First, switches on the application's state,
101 | // as managed by GlobalPlatform
102 | switch (GPSystem.getCardContentState()) {
103 |
104 | // In the SELECTABLE state,
105 | // the application is not personalized yet
106 | case GPSystem.APPLICATION_SELECTABLE:
107 | switch (buf[ISO7816.OFFSET_INS]) {
108 | case INS_CHANGE_REFERENCE_DATA:
109 | processChangeReferenceData();
110 | break;
111 | default:
112 | // If you don't know the INStruction, say so:
113 | ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
114 | }
115 | break;
116 | case GPSystem.CARD_SECURED:
117 | //Resets the identifier list's current reference if required
118 | if (buf[ISO7816.OFFSET_INS] != INS_LIST_IDENTIFIERS)
119 | current = null;
120 |
121 | switch (buf[ISO7816.OFFSET_INS]) {
122 | case INS_ADD_PASSWORD_ENTRY:
123 | checkAuthentication();
124 | processAddPasswordEntry();
125 | break;
126 | case INS_RETRIEVE_PASSWORD_ENTRY:
127 | checkAuthentication();
128 | processRetrievePasswordEntry();
129 | break;
130 | case INS_DELETE_PASSWORD_ENTRY:
131 | checkAuthentication();
132 | processDeletePasswordEntry();
133 | break;
134 | case INS_LIST_IDENTIFIERS:
135 | checkAuthentication();
136 | processListIdentifiers();
137 | break;
138 | case INS_VERIFY:
139 | processVerify();
140 | break;
141 | case INS_CHANGE_REFERENCE_DATA:
142 | processChangeReferenceData();
143 | break;
144 | default:
145 | // good practice: If you don't know the INStruction, say so:
146 | ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
147 | }
148 | break;
149 | default:
150 | ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
151 | }
152 | }
153 |
154 | short checkTLV(byte[] buffer, short inOfs,
155 | byte tag, short maxLen) {
156 | if (buffer[inOfs++] != tag)
157 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
158 | short len = buffer[inOfs++];
159 | if (len > maxLen)
160 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
161 | return (short) (inOfs + len);
162 | }
163 |
164 | void processAddPasswordEntry() {
165 | // Retrieves references to the APDU and its buffer
166 | APDU apdu = APDU.getCurrentAPDU();
167 | byte[] buf = APDU.getCurrentAPDUBuffer();
168 |
169 | // Checks the value of P1&P2
170 | if (Util.getShort(buf, ISO7816.OFFSET_P1) != 0)
171 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
172 | // Checks the minimum length
173 | if ((short) (buf[ISO7816.OFFSET_LC] & 0xFF) < 3)
174 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
175 | // Receives data and checks its length
176 | if (apdu.setIncomingAndReceive() !=
177 | (short) (buf[ISO7816.OFFSET_LC] & 0xFF))
178 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
179 |
180 | // Checks the identifier
181 | short ofsId = ISO7816.OFFSET_CDATA;
182 | short ofsUserName = checkTLV(buf, ofsId, TAG_IDENTIFIER, PasswordPinEntry.SIZE_ID);
183 |
184 | if (buf[ISO7816.OFFSET_LC] < (short) (ofsUserName - 3))
185 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
186 |
187 | // Checks the user name
188 | short ofsPassword = checkTLV(buf, ofsUserName, TAG_USERNAME, PasswordPinEntry.SIZE_USERNAME);
189 | if (buf[ISO7816.OFFSET_LC] < (short) (ofsPassword - 3))
190 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
191 |
192 | // Checks the password
193 | if (checkTLV(buf, ofsPassword, TAG_PASSWORD, PasswordPinEntry.SIZE_PASSWORD) !=
194 | (short) (ISO7816.OFFSET_CDATA + (short) (buf[ISO7816.OFFSET_LC] & 0xFF)))
195 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
196 |
197 | // Search the identifier in the current base
198 | if (PasswordPinEntry.search(buf, (short) (ofsId + 2), buf[(short) (ofsId + 1)]) != null)
199 | ISOException.throwIt(SW_DUPLICATE_IDENTIFIER);
200 |
201 | // Allocates and initializes a password entry
202 | JCSystem.beginTransaction();
203 | PasswordPinEntry pe = PasswordPinEntry.getInstance();
204 | pe.setId(buf, (short) (ofsId + 2), buf[(short) (ofsId + 1)]);
205 | pe.setUserName(buf, (short) (ofsUserName + 2), buf[(short) (ofsUserName + 1)]);
206 | pe.setPassword(buf, (short) (ofsPassword + 2), buf[(short) (ofsPassword + 1)]);
207 | JCSystem.commitTransaction();
208 | }
209 |
210 | void processDeletePasswordEntry() {
211 | // Retrieves references to the APDU and its buffer
212 | APDU apdu = APDU.getCurrentAPDU();
213 | byte[] buf = APDU.getCurrentAPDUBuffer();
214 |
215 | // Checks the value of P1&P2
216 | if (Util.getShort(buf, ISO7816.OFFSET_P1) != 0)
217 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
218 | // Checks the minimum length
219 | if ((short) (buf[ISO7816.OFFSET_LC] & 0xFF) < 3)
220 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
221 | // Receives data and checks its length
222 | if (apdu.setIncomingAndReceive() != (short) (buf[ISO7816.OFFSET_LC] & 0xFF))
223 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
224 |
225 | // Checks the identifier
226 | short ofsId = ISO7816.OFFSET_CDATA;
227 | if (checkTLV(buf, ISO7816.OFFSET_CDATA, TAG_IDENTIFIER, PasswordPinEntry.SIZE_ID) !=
228 | (short) (ISO7816.OFFSET_CDATA + (short) (buf[ISO7816.OFFSET_LC] & 0xFF)))
229 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
230 |
231 | // Search the identifier in the current base
232 | PasswordPinEntry pe = PasswordPinEntry.search(buf, (short) (ISO7816.OFFSET_CDATA + 2), buf[ISO7816.OFFSET_CDATA + 1]);
233 | if (pe == null)
234 | ISOException.throwIt(SW_IDENTIFIER_NOT_FOUND);
235 |
236 | PasswordPinEntry.delete(buf, (short) (ofsId + 2), buf[(short) (ofsId + 1)]);
237 | }
238 |
239 | void processRetrievePasswordEntry() {
240 | // Retrieves references to the APDU and its buffer
241 | APDU apdu = APDU.getCurrentAPDU();
242 | byte[] buf = APDU.getCurrentAPDUBuffer();
243 |
244 | // INITIAL CHECKS
245 |
246 | // Checks the value of P1&P2
247 | if (Util.getShort(buf, ISO7816.OFFSET_P1) != 0)
248 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
249 | // Checks the minimum length
250 | if ((short) (buf[ISO7816.OFFSET_LC] & 0xFF) < 3)
251 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
252 | // Receives data and checks its length
253 | if (apdu.setIncomingAndReceive() != (short) (buf[ISO7816.OFFSET_LC] & 0xFF))
254 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
255 |
256 |
257 | // INTERPRETS AND CHECKS THE DATA
258 |
259 | // Checks the identifier
260 | if (checkTLV(buf, ISO7816.OFFSET_CDATA, TAG_IDENTIFIER, PasswordPinEntry.SIZE_ID) !=
261 | (short) (ISO7816.OFFSET_CDATA + (short) (buf[ISO7816.OFFSET_LC] & 0xFF)))
262 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
263 |
264 | // Search the identifier in the current base
265 | PasswordPinEntry pe = PasswordPinEntry.search(buf, (short) (ISO7816.OFFSET_CDATA + 2), buf[ISO7816.OFFSET_CDATA + 1]);
266 | if (pe == null)
267 | ISOException.throwIt(SW_IDENTIFIER_NOT_FOUND);
268 |
269 | // Builds the result, starting with the user name
270 | short outOfs = 0;
271 | buf[outOfs++] = TAG_USERNAME;
272 | byte len = pe.getUserName(buf, (short) (outOfs + 1));
273 | buf[outOfs++] = len;
274 | outOfs += len;
275 |
276 | // Builds the result, continuing with the password
277 | buf[outOfs++] = TAG_PASSWORD;
278 | len = pe.getPassword(buf, (short) (outOfs + 1));
279 | buf[outOfs++] = len;
280 | outOfs += len;
281 |
282 | // Sends the result
283 | apdu.setOutgoingAndSend((short) 0, outOfs);
284 | }
285 |
286 | void processListIdentifiers() {
287 | // Retrieves references to the APDU and its buffer
288 | APDU apdu = APDU.getCurrentAPDU();
289 | byte[] buf = APDU.getCurrentAPDUBuffer();
290 |
291 | // Checks P1 and initializes the "current" value
292 | if (buf[ISO7816.OFFSET_P1] == 0)
293 | current = PasswordPinEntry.getFirst();
294 | else if ((buf[ISO7816.OFFSET_P1] != 1) || (current == null))
295 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
296 |
297 |
298 | // Builds the response
299 | short offset = 0;
300 | while (current != null) {
301 | // Checks that the identifier record fits in the APDU
302 | // WARNING: assumes a 256-byte APDU buffer
303 | byte len = current.getIdLength();
304 | if ((short) ((short) (offset + len) + 2) > 255)
305 | break;
306 |
307 | // Copies the identifier in the buffer
308 | buf[offset++] = TAG_IDENTIFIER;
309 | buf[offset++] = len;
310 | current.getId(buf, offset);
311 |
312 | // Gest to the next record
313 | offset += len;
314 | current = current.getNext();
315 | }
316 | apdu.setOutgoingAndSend((short) 0, offset);
317 | }
318 |
319 | void processVerify() {
320 | // Retrieves references to the APDU and its buffer
321 | APDU apdu = APDU.getCurrentAPDU();
322 | byte[] buf = APDU.getCurrentAPDUBuffer();
323 |
324 | // INITIAL CHECKS
325 | // Expect P1=00 and P2=80
326 | if (buf[ISO7816.OFFSET_P2] != (byte) 0x80)
327 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
328 |
329 | // First verifies that the PIN is not blocked
330 | if (pin.getTriesRemaining() == 0)
331 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
332 |
333 | // If no input data, simply return the number of remaining tries
334 | if (buf[ISO7816.OFFSET_LC] == 0) {
335 | if (pin.isValidated())
336 | return;
337 | else
338 | ISOException.throwIt((short) (SW_WRONG_PIN + pin.getTriesRemaining()));
339 | }
340 |
341 | // Receives data and checks it
342 | short len = apdu.setIncomingAndReceive();
343 | verifyPIN(buf, ISO7816.OFFSET_CDATA, (byte) len);
344 | }
345 |
346 | void verifyPIN(byte[] buffer, short index, byte len) {
347 | if (len > PIN_MAX_SIZE)
348 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
349 | if (!pin.check(buffer, index, len)) {
350 | if (pin.getTriesRemaining() == 0)
351 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
352 | else
353 | ISOException.throwIt((short) (SW_WRONG_PIN + pin.getTriesRemaining()));
354 | }
355 | }
356 |
357 | void processChangeReferenceData() {
358 | // Retrieves references to the APDU and its buffer
359 | APDU apdu = APDU.getCurrentAPDU();
360 | byte[] buf = APDU.getCurrentAPDUBuffer();
361 |
362 | // INITIAL CHECKS
363 |
364 | // Checks the value of P2
365 | if (buf[ISO7816.OFFSET_P2] != (byte) 0x80)
366 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
367 |
368 | byte p1 = buf[ISO7816.OFFSET_P1];
369 | switch (p1) {
370 | case 0:
371 | if (GPSystem.getCardContentState() != GPSystem.APPLICATION_SELECTABLE)
372 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
373 | break;
374 | case 1:
375 | if (GPSystem.getCardContentState() != GPSystem.CARD_SECURED)
376 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
377 | break;
378 | default:
379 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
380 | }
381 |
382 | // Receives data and checks it
383 | short len = apdu.setIncomingAndReceive();
384 | if (len < 2)
385 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
386 |
387 | short index = ISO7816.OFFSET_CDATA;
388 |
389 | // If there is a PIN presentation, checks it
390 | if (p1 == 1) {
391 | byte oldPinLen = buf[index++];
392 | if (len < (short) (oldPinLen + 3))
393 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
394 | verifyPIN(buf, index, oldPinLen);
395 | index += oldPinLen;
396 | }
397 |
398 | // In all cases, checks the new PIN value
399 | byte newPinLen = buf[index++];
400 | if (len != (short) (index + (short) (newPinLen - ISO7816.OFFSET_CDATA)))
401 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
402 | if (newPinLen > PIN_MAX_SIZE)
403 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
404 |
405 | // If everything is OK, update the PIN value
406 | pin.update(buf, index, newPinLen);
407 |
408 | // If everything went fine, update the state
409 | if (p1 == 0) {
410 | GPSystem.setCardContentState(GPSystem.CARD_SECURED);
411 | }
412 | }
413 |
414 | void checkAuthentication() {
415 | if (!pin.isValidated())
416 | ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
417 | }
418 |
419 | /**
420 | * Resets the PIN's validated flag upon deselection of the applet.
421 | */
422 | public void deselect() {
423 | pin.reset();
424 | }
425 | }
--------------------------------------------------------------------------------
/jc101-password-pin/src/test/java/fr/bmartel/passwords/JavaCardTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import javax.smartcardio.CardException;
4 | import javax.smartcardio.CommandAPDU;
5 | import javax.smartcardio.ResponseAPDU;
6 |
7 | public class JavaCardTest {
8 |
9 | public ResponseAPDU transmitCommand(CommandAPDU data) throws CardException {
10 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
11 | return TestSuite.getCard().getBasicChannel().transmit(data);
12 | } else {
13 | return TestSuite.getSimulator().transmitCommand(data);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/jc101-password-pin/src/test/java/fr/bmartel/passwords/Password.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import fr.bmartel.helloworld.util.TestUtils;
4 |
5 | public class Password {
6 |
7 | private byte[] id;
8 | private byte[] username;
9 | private byte[] password;
10 |
11 | public Password(byte[] id, byte[] username, byte[] password) {
12 | this.id = id;
13 | this.username = username;
14 | this.password = password;
15 | }
16 |
17 | public byte[] getFullApdu() {
18 | return TestUtils.concatByteArray(id, username, password);
19 | }
20 |
21 | public byte[] getId() {
22 | return id;
23 | }
24 |
25 | public byte[] getData() {
26 | return TestUtils.concatByteArray(username, password);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/jc101-password-pin/src/test/java/fr/bmartel/passwords/PasswordEntryTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import fr.bmartel.helloworld.util.TestUtils;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import java.lang.reflect.Field;
8 |
9 | import static org.junit.Assert.*;
10 |
11 | public class PasswordEntryTest {
12 |
13 | private PasswordPinEntry entry;
14 |
15 | private final static byte[] ID_BASIC = new byte[]{0x01, 0x02};
16 | private final static byte[] USERNAME_BASIC = new byte[]{0x03, 0x04, 0x05};
17 | private final static byte[] PASSWORD_BASIC = new byte[]{0x05, 0x06, 0x07, 0x08};
18 |
19 | private final static byte[] ID_BASIC1 = new byte[]{0x11, 0x12};
20 | private final static byte[] USERNAME_BASIC1 = new byte[]{0x13, 0x14, 0x15};
21 | private final static byte[] PASSWORD_BASIC1 = new byte[]{0x15, 0x16, 0x17, 0x18};
22 |
23 | private final static byte[] ID_BASIC2 = new byte[]{0x21, 0x22};
24 | private final static byte[] USERNAME_BASIC2 = new byte[]{0x23, 0x24, 0x25};
25 | private final static byte[] PASSWORD_BASIC2 = new byte[]{0x25, 0x26, 0x27, 0x28};
26 |
27 | private final static byte[] ID_BASIC3 = new byte[]{0x31, 0x32};
28 | private final static byte[] USERNAME_BASIC3 = new byte[]{0x33, 0x34, 0x35};
29 | private final static byte[] PASSWORD_BASIC3 = new byte[]{0x35, 0x36, 0x37, 0x38};
30 |
31 | private final static byte[] ID_BASIC4 = new byte[]{0x41, 0x42};
32 |
33 | /**
34 | * Get static field "first" by reflection
35 | *
36 | * @return first PasswordPinEntry
37 | * @throws NoSuchFieldException
38 | * @throws IllegalAccessException
39 | */
40 | private PasswordPinEntry getFirst() throws NoSuchFieldException, IllegalAccessException {
41 | Field f = TestUtils.getField(PasswordPinEntry.class, "first");
42 | if (f == null)
43 | throw new NoSuchFieldException();
44 | return (PasswordPinEntry) f.get(null);
45 | }
46 |
47 | /**
48 | * Get static field "deleted" by reflection
49 | *
50 | * @return item to recycle
51 | * @throws NoSuchFieldException
52 | * @throws IllegalAccessException
53 | */
54 | private PasswordPinEntry getDeleted() throws NoSuchFieldException, IllegalAccessException {
55 | Field f = TestUtils.getField(PasswordPinEntry.class, "deleted");
56 | if (f == null)
57 | throw new NoSuchFieldException();
58 | return (PasswordPinEntry) f.get(null);
59 | }
60 |
61 | /**
62 | * Get the next password entry for a specific instance
63 | *
64 | * @param entry password entry instance
65 | * @return
66 | * @throws NoSuchFieldException
67 | * @throws IllegalAccessException
68 | */
69 | private PasswordPinEntry getNext(PasswordPinEntry entry) throws NoSuchFieldException, IllegalAccessException {
70 | Field f = TestUtils.getField(entry.getClass(), "next");
71 | if (f == null)
72 | throw new NoSuchFieldException();
73 | return (PasswordPinEntry) f.get(entry);
74 | }
75 |
76 | /**
77 | * Get id length property by reflection.
78 | *
79 | * @param entry Password Entry instance
80 | * @return id length
81 | * @throws NoSuchFieldException
82 | * @throws IllegalAccessException
83 | */
84 | private byte getIdLength(PasswordPinEntry entry) throws NoSuchFieldException, IllegalAccessException {
85 | Field f = TestUtils.getField(entry.getClass(), "idLength");
86 | if (f == null)
87 | throw new NoSuchFieldException();
88 | return f.getByte(entry);
89 | }
90 |
91 | /**
92 | * Get username length property by reflection.
93 | *
94 | * @param entry Password entry instance
95 | * @return username length
96 | * @throws NoSuchFieldException
97 | * @throws IllegalAccessException
98 | */
99 | private byte getUsernameLength(PasswordPinEntry entry) throws NoSuchFieldException, IllegalAccessException {
100 | Field f = TestUtils.getField(entry.getClass(), "userNameLength");
101 | if (f == null)
102 | throw new NoSuchFieldException();
103 | return f.getByte(entry);
104 | }
105 |
106 | /**
107 | * Get password length property by reflection.
108 | *
109 | * @param entry Password entry instance
110 | * @return password length
111 | * @throws NoSuchFieldException
112 | * @throws IllegalAccessException
113 | */
114 | private byte getPasswordLength(PasswordPinEntry entry) throws NoSuchFieldException, IllegalAccessException {
115 | Field f = TestUtils.getField(entry.getClass(), "passwordLength");
116 | if (f == null)
117 | throw new NoSuchFieldException();
118 | return f.getByte(entry);
119 | }
120 |
121 | /**
122 | * Get byte array property
123 | *
124 | * @param object Object instance
125 | * @param name field name
126 | * @return byte array object matching the specified field name
127 | * @throws IllegalAccessException
128 | * @throws NoSuchFieldException
129 | */
130 | private byte[] getByteArray(Object object, String name) throws IllegalAccessException, NoSuchFieldException {
131 | Field f = TestUtils.getField(object.getClass(), name);
132 | if (f == null)
133 | throw new NoSuchFieldException();
134 | return (byte[]) f.get(object);
135 | }
136 |
137 | /**
138 | * Check size of byte array properties
139 | *
140 | * @param entry Password entry instance
141 | * @throws IllegalAccessException
142 | * @throws NoSuchFieldException
143 | */
144 | private void checkDataSize(PasswordPinEntry entry) throws IllegalAccessException, NoSuchFieldException {
145 | assertNotNull("id array not null", getByteArray(entry, "id"));
146 | assertEquals("id array size check", getByteArray(entry, "id").length, PasswordPinEntry.SIZE_ID);
147 | assertNotNull("username array not null", getByteArray(entry, "username"));
148 | assertEquals("username array size check", getByteArray(entry, "username").length, PasswordPinEntry.SIZE_USERNAME);
149 | assertNotNull("password array not null", getByteArray(entry, "password"));
150 | assertEquals("password array size check", getByteArray(entry, "password").length, PasswordPinEntry.SIZE_PASSWORD);
151 | }
152 |
153 | private void addItem(byte[] id, byte[] username, byte[] password) throws NoSuchFieldException, IllegalAccessException {
154 | entry = PasswordPinEntry.getInstance();
155 |
156 | assertNotNull("created instance exist", entry);
157 |
158 | PasswordPinEntry firstElement = getFirst();
159 |
160 | assertNotNull("first element exists", firstElement);
161 | assertEquals("first element is the created element", firstElement, entry);
162 |
163 | assertNull("no item to recycle", getDeleted());
164 |
165 | assertEquals("next getter valid", getNext(entry), entry.getNext());
166 | assertEquals("first getter valid", getFirst(), PasswordPinEntry.getFirst());
167 |
168 | assertEquals("check id length getter", 0, entry.getIdLength());
169 |
170 | assertEquals("check empty password length", 0, entry.getPassword(new byte[PasswordPinEntry.SIZE_PASSWORD], (short) 0));
171 | assertEquals("check empty username length", 0, entry.getUserName(new byte[PasswordPinEntry.SIZE_USERNAME], (short) 0));
172 | assertEquals("check empty id length", 0, entry.getId(new byte[PasswordPinEntry.SIZE_ID], (short) 0));
173 |
174 | assertEquals("check password length via reflection", 0, getPasswordLength(entry));
175 | assertEquals("check username length via reflection", 0, getUsernameLength(entry));
176 | assertEquals("check id length via reflection", 0, getIdLength(entry));
177 |
178 | entry.setId(id, (short) 0, (byte) id.length);
179 | entry.setUserName(username, (short) 0, (byte) username.length);
180 | entry.setPassword(password, (short) 0, (byte) password.length);
181 |
182 | checkDataSize(entry);
183 | }
184 |
185 | private void deleteItem(byte[] id) {
186 | PasswordPinEntry.delete(id, (short) 0, (byte) id.length);
187 | }
188 |
189 | private void checkData(PasswordPinEntry entry, byte[] expectedId, byte[] expectedUsername, byte[] expectedPassword) {
190 | byte[] passwordOut = new byte[expectedPassword.length];
191 | byte[] usernameOut = new byte[expectedUsername.length];
192 | byte[] idOut = new byte[expectedId.length];
193 |
194 | assertEquals("check password length", expectedPassword.length, entry.getPassword(passwordOut, (short) 0));
195 | assertEquals("check username length", expectedUsername.length, entry.getUserName(usernameOut, (short) 0));
196 | assertEquals("check id length", expectedId.length, entry.getId(idOut, (short) 0));
197 |
198 | assertArrayEquals("check password data", expectedPassword, passwordOut);
199 | assertArrayEquals("check username data", expectedUsername, usernameOut);
200 | assertArrayEquals("check id data", expectedId, idOut);
201 | }
202 |
203 | private int getLength() {
204 | int length = 0;
205 | PasswordPinEntry current = PasswordPinEntry.getFirst();
206 | while (current != null) {
207 | length++;
208 | current = current.getNext();
209 | }
210 | return length;
211 | }
212 |
213 | @Before
214 | public void initTest() throws NoSuchFieldException, IllegalAccessException {
215 | Field f = TestUtils.getField(PasswordPinEntry.class, "first");
216 | if (f == null)
217 | throw new NoSuchFieldException();
218 | f.set(null, null);
219 | f = TestUtils.getField(PasswordPinEntry.class, "deleted");
220 | if (f == null)
221 | throw new NoSuchFieldException();
222 | f.set(null, null);
223 | assertNull("no first element", getFirst());
224 | assertNull("no item to recycle", getDeleted());
225 | }
226 |
227 | @Test
228 | public void setPropertiesTest() throws NoSuchFieldException, IllegalAccessException {
229 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
230 | assertNull("no next element", entry.getNext());
231 |
232 | entry.setId(ID_BASIC, (short) 0, (byte) ID_BASIC.length);
233 | entry.setUserName(USERNAME_BASIC, (short) 0, (byte) USERNAME_BASIC.length);
234 | entry.setPassword(PASSWORD_BASIC, (short) 0, (byte) PASSWORD_BASIC.length);
235 |
236 | assertEquals("check id length getter", ID_BASIC.length, entry.getIdLength());
237 |
238 | checkData(entry, ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
239 | }
240 |
241 | @Test
242 | public void setPropertiesOffsetTest() throws NoSuchFieldException, IllegalAccessException {
243 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
244 | assertNull("no next element", entry.getNext());
245 |
246 | short offset = 2;
247 |
248 | entry.setId(TestUtils.addOffset(offset, ID_BASIC), offset, (byte) ID_BASIC.length);
249 | entry.setUserName(TestUtils.addOffset(offset, USERNAME_BASIC), offset, (byte) USERNAME_BASIC.length);
250 | entry.setPassword(TestUtils.addOffset(offset, PASSWORD_BASIC), offset, (byte) PASSWORD_BASIC.length);
251 |
252 | assertEquals("check id length getter", ID_BASIC.length, entry.getIdLength());
253 |
254 | checkData(entry, ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
255 | }
256 |
257 | @Test
258 | public void addMultipleEntries() throws NoSuchFieldException, IllegalAccessException {
259 | assertEquals(0, getLength());
260 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
261 | assertEquals(1, getLength());
262 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
263 | assertEquals(2, getLength());
264 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
265 | assertEquals(3, getLength());
266 | }
267 |
268 | private PasswordPinEntry checkSearchedItem(byte[] id, byte[] username, byte[] password) {
269 | PasswordPinEntry searchEntry = PasswordPinEntry.search(id, (short) 0, (byte) id.length);
270 | assertNotNull("search result not null", searchEntry);
271 | checkData(searchEntry, id, username, password);
272 | return searchEntry;
273 | }
274 |
275 | @Test
276 | public void searchIdValues() throws NoSuchFieldException, IllegalAccessException {
277 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
278 | addItem(ID_BASIC1, USERNAME_BASIC1, PASSWORD_BASIC1);
279 | addItem(ID_BASIC2, USERNAME_BASIC2, PASSWORD_BASIC2);
280 | addItem(ID_BASIC3, USERNAME_BASIC3, PASSWORD_BASIC3);
281 |
282 | assertEquals("init length", 4, getLength());
283 |
284 | assertNull("search value check next null (eg first added item)", checkSearchedItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC).getNext());
285 | assertNotNull("search value next not null", checkSearchedItem(ID_BASIC1, USERNAME_BASIC1, PASSWORD_BASIC1).getNext());
286 | assertNotNull("search value next not null", checkSearchedItem(ID_BASIC2, USERNAME_BASIC2, PASSWORD_BASIC2).getNext());
287 | assertNotNull("search value next not null", checkSearchedItem(ID_BASIC3, USERNAME_BASIC3, PASSWORD_BASIC3).getNext());
288 |
289 | PasswordPinEntry searchEntry = PasswordPinEntry.search(ID_BASIC4, (short) 0, (byte) ID_BASIC4.length);
290 | assertNull("search value invalid", searchEntry);
291 | }
292 |
293 | @Test
294 | public void deleteTest() throws NoSuchFieldException, IllegalAccessException {
295 | assertEquals("init length", 0, getLength());
296 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
297 | assertEquals("length after addition", 1, getLength());
298 | deleteItem(ID_BASIC);
299 | assertEquals("length after deletion", 0, getLength());
300 | }
301 |
302 | @Test
303 | public void deleteMultipleTest() throws NoSuchFieldException, IllegalAccessException {
304 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
305 | addItem(ID_BASIC1, USERNAME_BASIC1, PASSWORD_BASIC1);
306 | addItem(ID_BASIC2, USERNAME_BASIC2, PASSWORD_BASIC2);
307 | addItem(ID_BASIC3, USERNAME_BASIC3, PASSWORD_BASIC3);
308 | assertEquals("init length", 4, getLength());
309 | deleteItem(ID_BASIC);
310 | deleteItem(ID_BASIC1);
311 | deleteItem(ID_BASIC2);
312 | deleteItem(ID_BASIC3);
313 | assertEquals("length after deletion", 0, getLength());
314 | }
315 |
316 | @Test
317 | public void checkRecycledItem() throws NoSuchFieldException, IllegalAccessException {
318 | assertNull("no item to recycle", getDeleted());
319 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
320 | deleteItem(ID_BASIC);
321 | assertNotNull("1 item to recycle", getDeleted());
322 | }
323 |
324 | @Test
325 | public void interTwinedAddDelete() throws NoSuchFieldException, IllegalAccessException {
326 | for (int i = 0; i < 10; i++) {
327 | assertEquals("init length, iteration n°" + i, 0, getLength());
328 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
329 | assertEquals("length after addition, iteration n°" + i, 1, getLength());
330 | deleteItem(ID_BASIC);
331 | assertEquals("length after deletion, iteration n°" + i, 0, getLength());
332 | }
333 | }
334 | }
--------------------------------------------------------------------------------
/jc101-password-pin/src/test/java/fr/bmartel/passwords/PasswordManagerPinTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import fr.bmartel.helloworld.util.TestUtils;
4 | import javacard.framework.ISO7816;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import javax.smartcardio.CardException;
9 | import javax.smartcardio.CommandAPDU;
10 | import javax.smartcardio.ResponseAPDU;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import static org.junit.Assert.assertEquals;
15 |
16 | public class PasswordManagerPinTest extends JavaCardTest {
17 |
18 | private final static byte[] CMD_ADD_PASSWORD = new byte[]{0x00, 0x30, 0x00, 0x00};
19 | private final static byte[] CMD_GET_PASSWORD = new byte[]{0x00, 0x32, 0x00, 0x00};
20 | private final static byte[] CMD_DELETE_PASSWORD = new byte[]{0x00, 0x34, 0x00, 0x00};
21 | private final static byte[] CMD_LIST_ID = new byte[]{0x00, 0x36, 0x00, 0x00};
22 |
23 | private final static byte[] CMD_VERIFY_PIN_CODE = new byte[]{0x00, 0x20, 0x00, (byte) 0x80};
24 |
25 | private final static Password DATA_ENTRY_VALID = new Password(
26 | new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65},
27 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
28 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
29 |
30 | private final static Password DATA_ENTRY_VALID1 = new Password(
31 | new byte[]{(byte) 0xF1, 0x04, 0x49, 0x6F, 0x6D, 0x65},
32 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
33 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
34 |
35 | private final static Password DATA_ENTRY_VALID2 = new Password(
36 | new byte[]{(byte) 0xF1, 0x04, 0x50, 0x6F, 0x6D, 0x65},
37 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
38 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
39 |
40 | private final static Password DATA_ENTRY_VALID3 = new Password(
41 | new byte[]{(byte) 0xF1, 0x04, 0x51, 0x6F, 0x6D, 0x65},
42 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
43 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
44 |
45 | //invalid data for addition
46 | private final static byte[] DATA_ENTRY_INVALID_TAG1 = new byte[]{0x01, 0x02, 0x03, 0x04};
47 | private final static byte[] DATA_ENTRY_INVALID_TAG2 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65};
48 | private final static byte[] DATA_ENTRY_INVALID_TAG3 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62};
49 | private final static byte[] DATA_ENTRY_INVLID_LENGTH1 = new byte[]{(byte) 0xF1, 0x03, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
50 | private final static byte[] DATA_ENTRY_INVLID_LENGTH2 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x02, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
51 | private final static byte[] DATA_ENTRY_INVLID_LENGTH3 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x02, 0x70, 0x61, 0x73, 0x73};
52 | private final static byte[] DATA_ENTRY_ID_OVERFLOW = new byte[]{(byte) 0xF1, 0x11, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
53 | private final static byte[] DATA_ENTRY_USERNAME_OVERFLOW = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
54 | private final static byte[] DATA_ENTRY_PASSWORD_OVERFLOW = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x11, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12};
55 |
56 | private void sendAddPassword(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
57 | TestUtils.sendCmdBatch(this, CMD_ADD_PASSWORD, data, expectedSw, expectedResponse);
58 | }
59 |
60 | private void sendGetPassword(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
61 | TestUtils.sendCmdBatch(this, CMD_GET_PASSWORD, data, expectedSw, expectedResponse);
62 | }
63 |
64 | private void sendDeletePassword(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
65 | TestUtils.sendCmdBatch(this, CMD_DELETE_PASSWORD, data, expectedSw, expectedResponse);
66 | }
67 |
68 | private void sendListId(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
69 | TestUtils.sendCmdBatch(this, CMD_LIST_ID, data, expectedSw, expectedResponse);
70 | }
71 |
72 | private void verifyPinCode(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
73 | TestUtils.sendCmdBatch(this, CMD_VERIFY_PIN_CODE, data, expectedSw, expectedResponse);
74 | }
75 |
76 | private void deleteAllPassword() throws CardException {
77 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_LIST_ID, new byte[]{}));
78 | ResponseAPDU response = transmitCommand(commandAPDU);
79 | assertEquals(0x9000, response.getSW());
80 | if (response.getData().length > 0) {
81 | List ids = new ArrayList<>();
82 | int state = 0;
83 | int length = 0;
84 | int index = 0;
85 | byte[] current = new byte[]{};
86 |
87 | for (int i = 0; i < response.getData().length; i++) {
88 | switch (state) {
89 | case 0:
90 | if ((response.getData()[i] & 0xFF) == 0xF1) {
91 | state = 1;
92 | }
93 | break;
94 | case 1:
95 | //set length
96 | length = response.getData()[i] & 0xFF;
97 | current = new byte[length + 2];
98 | current[0] = (byte) 0xF1;
99 | current[1] = response.getData()[i];
100 | index = 2;
101 | state = 2;
102 | break;
103 | case 2:
104 | //set data
105 | current[index++] = response.getData()[i];
106 | length--;
107 | if (length == 0) {
108 | ids.add(current);
109 | state = 0;
110 | }
111 | }
112 |
113 | }
114 |
115 | for (int i = 0; i < ids.size(); i++) {
116 | commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_DELETE_PASSWORD, ids.get(i)));
117 | response = transmitCommand(commandAPDU);
118 | assertEquals(0x9000, response.getSW());
119 | }
120 |
121 | commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_LIST_ID, new byte[]{}));
122 | response = transmitCommand(commandAPDU);
123 | assertEquals(0x9000, response.getSW());
124 | assertEquals(0, response.getData().length);
125 | }
126 | }
127 |
128 | @Before
129 | public void initTest() throws NoSuchFieldException, IllegalAccessException, CardException {
130 | TestSuite.setup();
131 | verifyPinCode(TestUtils.TEST_PIN_CODE, 0x9000, new byte[]{});
132 | deleteAllPassword();
133 | }
134 |
135 | @Test
136 | public void ListEmptyTest() throws CardException {
137 | sendListId(new byte[]{}, 0x9000, new byte[]{});
138 | }
139 |
140 | @Test
141 | public void addPasswordTest() throws CardException {
142 | sendListId(new byte[]{}, 0x9000, new byte[]{});
143 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
144 | sendListId(new byte[]{}, 0x9000, DATA_ENTRY_VALID.getId());
145 | }
146 |
147 | @Test
148 | public void multipleAddPasswordTest() throws CardException {
149 | sendListId(new byte[]{}, 0x9000, new byte[]{});
150 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
151 | sendAddPassword(DATA_ENTRY_VALID1.getFullApdu(), 0x9000, new byte[]{});
152 | sendAddPassword(DATA_ENTRY_VALID2.getFullApdu(), 0x9000, new byte[]{});
153 | sendAddPassword(DATA_ENTRY_VALID3.getFullApdu(), 0x9000, new byte[]{});
154 | sendListId(new byte[]{}, 0x9000, TestUtils.concatByteArray(DATA_ENTRY_VALID3.getId(),
155 | DATA_ENTRY_VALID2.getId(),
156 | DATA_ENTRY_VALID1.getId(),
157 | DATA_ENTRY_VALID.getId()));
158 | }
159 |
160 | @Test
161 | public void invalidAddTagTest() throws CardException {
162 | sendListId(new byte[]{}, 0x9000, new byte[]{});
163 | sendAddPassword(DATA_ENTRY_INVALID_TAG1, ISO7816.SW_DATA_INVALID, new byte[]{});
164 | sendAddPassword(DATA_ENTRY_INVALID_TAG2, ISO7816.SW_DATA_INVALID, new byte[]{});
165 | sendAddPassword(DATA_ENTRY_INVALID_TAG3, ISO7816.SW_DATA_INVALID, new byte[]{});
166 | sendListId(new byte[]{}, 0x9000, new byte[]{});
167 | }
168 |
169 | @Test
170 | public void invalidAddDataLengthTest() throws CardException {
171 | sendListId(new byte[]{}, 0x9000, new byte[]{});
172 | sendAddPassword(DATA_ENTRY_INVLID_LENGTH1, ISO7816.SW_DATA_INVALID, new byte[]{});
173 | sendAddPassword(DATA_ENTRY_INVLID_LENGTH2, ISO7816.SW_DATA_INVALID, new byte[]{});
174 | sendAddPassword(DATA_ENTRY_INVLID_LENGTH3, ISO7816.SW_DATA_INVALID, new byte[]{});
175 | sendListId(new byte[]{}, 0x9000, new byte[]{});
176 | }
177 |
178 | @Test
179 | public void invalidAddDataOverflowTest() throws CardException {
180 | sendListId(new byte[]{}, 0x9000, new byte[]{});
181 | sendAddPassword(DATA_ENTRY_ID_OVERFLOW, ISO7816.SW_DATA_INVALID, new byte[]{});
182 | sendAddPassword(DATA_ENTRY_USERNAME_OVERFLOW, ISO7816.SW_DATA_INVALID, new byte[]{});
183 | sendAddPassword(DATA_ENTRY_PASSWORD_OVERFLOW, ISO7816.SW_DATA_INVALID, new byte[]{});
184 | sendListId(new byte[]{}, 0x9000, new byte[]{});
185 | }
186 |
187 | @Test
188 | public void duplicateAddTest() throws CardException {
189 | sendListId(new byte[]{}, 0x9000, new byte[]{});
190 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
191 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), PasswordPinManager.SW_DUPLICATE_IDENTIFIER, new byte[]{});
192 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), PasswordPinManager.SW_DUPLICATE_IDENTIFIER, new byte[]{});
193 | sendListId(new byte[]{}, 0x9000, DATA_ENTRY_VALID.getId());
194 | }
195 |
196 | @Test
197 | public void getPasswordTest() throws CardException {
198 | sendListId(new byte[]{}, 0x9000, new byte[]{});
199 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
200 | sendGetPassword(DATA_ENTRY_VALID.getId(), 0x9000, DATA_ENTRY_VALID.getData());
201 | sendAddPassword(DATA_ENTRY_VALID1.getFullApdu(), 0x9000, new byte[]{});
202 | sendGetPassword(DATA_ENTRY_VALID1.getId(), 0x9000, DATA_ENTRY_VALID1.getData());
203 | }
204 |
205 | @Test
206 | public void getInvalidIdTest() throws CardException {
207 | sendListId(new byte[]{}, 0x9000, new byte[]{});
208 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
209 | sendGetPassword(DATA_ENTRY_VALID1.getId(), PasswordPinManager.SW_IDENTIFIER_NOT_FOUND, new byte[]{});
210 | }
211 |
212 | @Test
213 | public void deleteIdTest() throws CardException {
214 | sendListId(new byte[]{}, 0x9000, new byte[]{});
215 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
216 | sendDeletePassword(DATA_ENTRY_VALID.getId(), 0x9000, new byte[]{});
217 | }
218 |
219 | @Test
220 | public void deleteIdInvalidTest() throws CardException {
221 | sendListId(new byte[]{}, 0x9000, new byte[]{});
222 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
223 | sendDeletePassword(DATA_ENTRY_VALID1.getId(), PasswordPinManager.SW_IDENTIFIER_NOT_FOUND, new byte[]{});
224 | }
225 |
226 | @Test
227 | public void intertwinedAddDeleteTest() throws CardException {
228 | for (int i = 0; i < 10; i++) {
229 | sendListId(new byte[]{}, 0x9000, new byte[]{});
230 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
231 | sendListId(new byte[]{}, 0x9000, DATA_ENTRY_VALID.getId());
232 | sendDeletePassword(DATA_ENTRY_VALID.getId(), 0x9000, new byte[]{});
233 | }
234 | }
235 | }
--------------------------------------------------------------------------------
/jc101-password-pin/src/test/java/fr/bmartel/passwords/TestSuite.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import apdu4j.LoggingCardTerminal;
4 | import apdu4j.TerminalManager;
5 | import com.licel.jcardsim.smartcardio.CardSimulator;
6 | import com.licel.jcardsim.utils.AIDUtil;
7 | import fr.bmartel.helloworld.util.TestUtils;
8 | import javacard.framework.AID;
9 | import org.junit.AfterClass;
10 | import org.junit.BeforeClass;
11 | import org.junit.runner.RunWith;
12 | import org.junit.runners.Suite;
13 | import org.junit.runners.Suite.SuiteClasses;
14 |
15 | import javax.smartcardio.*;
16 | import javax.smartcardio.CardTerminals.State;
17 | import java.io.OutputStream;
18 | import java.security.NoSuchAlgorithmException;
19 | import java.util.List;
20 |
21 | import static org.junit.Assert.fail;
22 | import static org.junit.Assert.assertEquals;
23 |
24 | @RunWith(Suite.class)
25 | @SuiteClasses({PasswordEntryTest.class, PasswordManagerPinTest.class})
26 | public class TestSuite {
27 |
28 | private final static String APPLET_AID = "01020304050607080901";
29 |
30 | private static CardSimulator mSimulator;
31 |
32 | private static boolean mInitialized;
33 |
34 | private static Card mCard;
35 |
36 | private static Card initGp() {
37 |
38 | try {
39 | TerminalFactory tf = TerminalManager.getTerminalFactory(null);
40 |
41 | CardTerminals terminals = tf.terminals();
42 |
43 | System.out.println("# Detected readers from " + tf.getProvider().getName());
44 | for (CardTerminal term : terminals.list()) {
45 | System.out.println((term.isCardPresent() ? "[*] " : "[ ] ") + term.getName());
46 | }
47 |
48 | // Select terminal(s) to work on
49 | List do_readers;
50 | do_readers = terminals.list(State.CARD_PRESENT);
51 |
52 | if (do_readers.size() == 0) {
53 | fail("No smart card readers with a card found");
54 | }
55 | // Work all readers
56 | for (CardTerminal reader : do_readers) {
57 | if (do_readers.size() > 1) {
58 | System.out.println("# " + reader.getName());
59 | }
60 |
61 | OutputStream o = null;
62 | reader = LoggingCardTerminal.getInstance(reader, o);
63 |
64 | Card card = null;
65 | // Establish connection
66 | try {
67 | card = reader.connect("*");
68 | // Use use apdu4j which by default uses jnasmartcardio
69 | // which uses real SCardBeginTransaction
70 | card.beginExclusive();
71 |
72 | return card;
73 |
74 | } catch (CardException e) {
75 | System.err.println("Could not connect to " + reader.getName() + ": " + TerminalManager.getExceptionMessage(e));
76 | continue;
77 | }
78 | }
79 | } catch (CardException e) {
80 | // Sensible wrapper for the different PC/SC exceptions
81 | if (TerminalManager.getExceptionMessage(e) != null) {
82 | System.out.println("PC/SC failure: " + TerminalManager.getExceptionMessage(e));
83 | } else {
84 | e.printStackTrace(); // TODO: remove
85 | fail("CardException, terminating");
86 | }
87 | } catch (NoSuchAlgorithmException e) {
88 | e.printStackTrace();
89 | }
90 | return null;
91 | }
92 |
93 | @BeforeClass
94 | public static void setup() throws CardException {
95 | if (mInitialized) {
96 | return;
97 | }
98 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
99 | mCard = initGp();
100 | CommandAPDU c = new CommandAPDU(AIDUtil.select(APPLET_AID));
101 | ResponseAPDU response = mCard.getBasicChannel().transmit(c);
102 | assertEquals(0x9000, response.getSW());
103 |
104 | try {
105 | TestUtils.setPinCode(mCard, TestUtils.concatByteArray(new byte[]{0x07}, TestUtils.TEST_PIN_CODE), 0x9000, new byte[]{});
106 | } catch (CardException e) {
107 | System.out.println("set pin code failed");
108 | }
109 | } else {
110 | mSimulator = new CardSimulator();
111 | AID appletAID = AIDUtil.create(APPLET_AID);
112 | mSimulator.installApplet(appletAID, PasswordPinManager.class);
113 | mSimulator.selectApplet(appletAID);
114 | TestUtils.setPinCode(mSimulator, TestUtils.concatByteArray(new byte[]{0x07}, TestUtils.TEST_PIN_CODE), 0x9000, new byte[]{});
115 | }
116 | mInitialized = true;
117 | }
118 |
119 | public static Card getCard() {
120 | return mCard;
121 | }
122 |
123 | public static CardSimulator getSimulator() {
124 | return mSimulator;
125 | }
126 |
127 | @AfterClass
128 | public static void close() throws CardException {
129 | if (mCard != null) {
130 | mCard.endExclusive();
131 | mCard.disconnect(true);
132 | mCard = null;
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/jc101-password-pin/src/test/java/fr/bmartel/passwords/util/TestUtils.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld.util;
2 |
3 | import com.licel.jcardsim.smartcardio.CardSimulator;
4 | import fr.bmartel.passwords.JavaCardTest;
5 |
6 | import javax.smartcardio.Card;
7 | import javax.smartcardio.CardException;
8 | import javax.smartcardio.CommandAPDU;
9 | import javax.smartcardio.ResponseAPDU;
10 | import java.lang.reflect.Field;
11 | import java.nio.ByteBuffer;
12 | import java.util.Arrays;
13 |
14 | import static org.junit.Assert.assertArrayEquals;
15 | import static org.junit.Assert.assertEquals;
16 |
17 | public class TestUtils {
18 |
19 | private final static byte[] CMD_SET_PIN_CODE = new byte[]{0x00, 0x24, 0x00, (byte) 0x80};
20 |
21 | public final static byte[] TEST_PIN_CODE = new byte[]{0x61, 0x42, 0x63, 0x31, 0x32, 0x45, 0x34};
22 |
23 | public static byte[] buildApdu(byte[] command, byte[] data) {
24 | byte[] apdu = new byte[command.length + data.length + 1];
25 | System.arraycopy(command, 0, apdu, 0, command.length);
26 | apdu[command.length] = (byte) data.length;
27 | System.arraycopy(data, 0, apdu, command.length + 1, data.length);
28 | return apdu;
29 | }
30 |
31 | public static void logData(byte[] data) {
32 | System.out.println(Arrays.toString(data));
33 | }
34 |
35 | public static int getInt(byte[] data) {
36 | return (((data[0] & 0xff) << 8) | (data[1] & 0xff));
37 | }
38 |
39 | public static byte[] getByte(int data) {
40 | byte[] out = ByteBuffer.allocate(4).putInt(data).array();
41 | return new byte[]{out[2], out[3]};
42 | }
43 |
44 | /**
45 | * Add n x 0x00 offset to a byte array (at the beginning)
46 | *
47 | * @param offset number of byte to set to 0x00 before the data
48 | * @param data the data
49 | * @return byte array with offset before data
50 | */
51 | public static byte[] addOffset(short offset, byte[] data) {
52 | byte[] resp = new byte[data.length + offset];
53 | for (int i = 0; i < offset; i++) {
54 | resp[i] = 0x00;
55 | }
56 | System.arraycopy(resp, offset, data, 0, data.length);
57 | return resp;
58 | }
59 |
60 | public static byte[] concatByteArray(byte[]... data) {
61 | int length = 0;
62 | for (byte[] item : data) {
63 | length += item.length;
64 | }
65 | byte[] resp = new byte[length];
66 | int offset = 0;
67 | for (byte[] item : data) {
68 | System.arraycopy(item, 0, resp, offset, item.length);
69 | offset += item.length;
70 | }
71 | return resp;
72 | }
73 |
74 | /**
75 | * Get class field by reflection.
76 | *
77 | * @param object class
78 | * @param name field name
79 | * @return field
80 | */
81 | public static Field getField(Class object, String name) {
82 | for (Field f : object.getDeclaredFields()) {
83 | f.setAccessible(true);
84 | if (f != null && f.getName().equals(name)) {
85 | return f;
86 | }
87 | }
88 | return null;
89 | }
90 |
91 | public static void sendCmdBatch(JavaCardTest card, byte[] cmd, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
92 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(cmd, data));
93 | ResponseAPDU response = card.transmitCommand(commandAPDU);
94 | assertEquals(expectedSw, response.getSW());
95 | assertArrayEquals(expectedResponse, response.getData());
96 | }
97 |
98 | public static void setPinCode(CardSimulator card, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
99 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_SET_PIN_CODE, data));
100 | ResponseAPDU response = card.transmitCommand(commandAPDU);
101 | assertEquals(expectedSw, response.getSW());
102 | assertArrayEquals(expectedResponse, response.getData());
103 | }
104 |
105 | public static void setPinCode(Card card, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
106 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_SET_PIN_CODE, data));
107 | ResponseAPDU response = card.getBasicChannel().transmit(commandAPDU);
108 | //assertEquals(expectedSw, response.getSW());
109 | //assertArrayEquals(expectedResponse, response.getData());
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/jc101-password-pin/src/test/java/org/globalplatform/GPSystem.java:
--------------------------------------------------------------------------------
1 | package org.globalplatform;
2 |
3 | public class GPSystem {
4 |
5 | public static final byte APPLICATION_INSTALLED = 3;
6 | public static final byte APPLICATION_SELECTABLE = 7;
7 | public static final byte SECURITY_DOMAIN_PERSONALIZED = 15;
8 | public static final byte CARD_OP_READY = 1;
9 | public static final byte CARD_INITIALIZED = 7;
10 | public static final byte CARD_SECURED = 15;
11 | public static final byte CARD_LOCKED = 127;
12 | public static final byte CARD_TERMINATED = -1;
13 | public static final byte CVM_GLOBAL_PIN = 17;
14 |
15 | private static byte state = APPLICATION_SELECTABLE;
16 |
17 | public GPSystem() {
18 | }
19 |
20 | public static byte getCardContentState() {
21 | return state;
22 | }
23 |
24 | public static byte getCardState() {
25 | return 0;
26 | }
27 |
28 | public static CVM getCVM(byte var0) {
29 | return null;
30 | }
31 |
32 | public static SecureChannel getSecureChannel() {
33 | return null;
34 | }
35 |
36 | public static boolean lockCard() {
37 | return false;
38 | }
39 |
40 | public static boolean terminateCard() {
41 | return false;
42 | }
43 |
44 | public static boolean setATRHistBytes(byte[] var0, short var1, byte var2) {
45 | return false;
46 | }
47 |
48 | public static boolean setCardContentState(byte state) {
49 | GPSystem.state = state;
50 | return true;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/jc101-password/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'javacard'
2 |
3 | dependencies {
4 | testCompile 'com.github.martinpaljak:apdu4j:18.10.04'
5 | compile 'com.github.martinpaljak:globalplatformpro:18.09.14'
6 | }
7 |
8 | javacard {
9 |
10 | config {
11 | jckit '../oracle_javacard_sdks/jc221_kit'
12 | cap {
13 | packageName 'fr.bmartel.passwords'
14 | version '0.1'
15 | aid '01:02:03:04:05:06:07:08:09'
16 | output 'applet.cap'
17 | applet {
18 | className 'fr.bmartel.passwords.PasswordManager'
19 | aid '01:02:03:04:05:06:07:08:09:01'
20 | }
21 | }
22 | }
23 |
24 | scripts {
25 | script {
26 | name 'select'
27 | apdu '00:A4:04:00:0A:01:02:03:04:05:06:07:08:09:01:00'
28 | }
29 | script {
30 | name 'add password entry'
31 | apdu '00:30:00:00:11:F1:04:48:6F:6D:65:F2:03:62:6F:62:F3:04:70:61:73:73'
32 | }
33 | script {
34 | name 'get password entry'
35 | apdu '00:32:00:00:06:F1:04:48:6F:6D:65'
36 | }
37 | script {
38 | name 'delete password entry'
39 | apdu '00:34:00:00:06:F1:04:48:6F:6D:65'
40 | }
41 | script {
42 | name 'list identifiers'
43 | apdu '00:36:00:00'
44 | }
45 | task {
46 | name 'listPassword'
47 | scripts 'select', 'list identifiers'
48 | }
49 | task {
50 | name 'addPassword'
51 | scripts 'select', 'add password entry'
52 | }
53 | task {
54 | name 'removePassword'
55 | scripts 'select', 'delete password entry'
56 | }
57 | task {
58 | name 'addRemovePassword'
59 | scripts 'select', 'add password entry', 'delete password entry'
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/jc101-password/src/main/java/fr/bmartel/passwords/PasswordEntry.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import javacard.framework.JCSystem;
4 | import javacard.framework.Util;
5 |
6 | public class PasswordEntry {
7 |
8 | public static short SIZE_ID = 16;
9 | public static short SIZE_USERNAME = 24;
10 | public static short SIZE_PASSWORD = 16;
11 |
12 | private PasswordEntry next;
13 | private static PasswordEntry first;
14 | private static PasswordEntry deleted;
15 |
16 | private byte[] id;
17 | private byte[] username;
18 | private byte[] password;
19 |
20 | private byte idLength;
21 | private byte userNameLength;
22 | private byte passwordLength;
23 |
24 | private PasswordEntry() {
25 | // Allocates all fields
26 | id = new byte[SIZE_ID];
27 | username = new byte[SIZE_USERNAME];
28 | password = new byte[SIZE_PASSWORD];
29 | // The new element is inserted in front of the list
30 | next = first;
31 | first = this;
32 | }
33 |
34 | static PasswordEntry getInstance() {
35 | if (deleted == null) {
36 | // There is no element to recycle
37 | return new PasswordEntry();
38 | } else {
39 | // Recycle the first available element
40 | PasswordEntry instance = deleted;
41 | deleted = instance.next;
42 | instance.next = first;
43 | first = instance;
44 | return instance;
45 | }
46 | }
47 |
48 | static PasswordEntry search(byte[] buf, short ofs, byte len) {
49 | for (PasswordEntry pe = first; pe != null; pe = pe.next) {
50 | if (pe.idLength != len) continue;
51 | if (Util.arrayCompare(pe.id, (short) 0, buf, ofs, len) == 0)
52 | return pe;
53 | }
54 | return null;
55 | }
56 |
57 | public static PasswordEntry getFirst() {
58 | return first;
59 | }
60 |
61 | private void remove() {
62 | if (first == this) {
63 | first = next;
64 | } else {
65 | for (PasswordEntry pe = first; pe != null; pe = pe.next)
66 | if (pe.next == this)
67 | pe.next = next;
68 | }
69 | }
70 |
71 | private void recycle() {
72 | next = deleted;
73 | idLength = 0;
74 | userNameLength = 0;
75 | passwordLength = 0;
76 | deleted = this;
77 | }
78 |
79 | static void delete(byte[] buf, short ofs, byte len) {
80 | PasswordEntry pe = search(buf, ofs, len);
81 | if (pe != null) {
82 | JCSystem.beginTransaction();
83 | pe.remove();
84 | pe.recycle();
85 | JCSystem.commitTransaction();
86 | }
87 | }
88 |
89 | byte getId(byte[] buf, short ofs) {
90 | Util.arrayCopy(id, (short) 0, buf, ofs, idLength);
91 | return idLength;
92 | }
93 |
94 | byte getUserName(byte[] buf, short ofs) {
95 | Util.arrayCopy(username, (short) 0, buf, ofs, userNameLength);
96 | return userNameLength;
97 | }
98 |
99 | byte getPassword(byte[] buf, short ofs) {
100 | Util.arrayCopy(password, (short) 0, buf, ofs, passwordLength);
101 | return passwordLength;
102 | }
103 |
104 | public byte getIdLength() {
105 | return idLength;
106 | }
107 |
108 | public PasswordEntry getNext() {
109 | return next;
110 | }
111 |
112 | public void setId(byte[] buf, short ofs, byte len) {
113 | Util.arrayCopy(buf, ofs, id, (short) 0, len);
114 | idLength = len;
115 | }
116 |
117 | public void setUserName(byte[] buf, short ofs, byte len) {
118 | Util.arrayCopy(buf, ofs, username, (short) 0, len);
119 | userNameLength = len;
120 | }
121 |
122 | public void setPassword(byte[] buf, short ofs, byte len) {
123 | Util.arrayCopy(buf, ofs, password, (short) 0, len);
124 | passwordLength = len;
125 | }
126 | }
--------------------------------------------------------------------------------
/jc101-password/src/main/java/fr/bmartel/passwords/PasswordManager.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import javacard.framework.*;
4 |
5 | public class PasswordManager extends Applet {
6 |
7 | /**
8 | * INS for command that adds a password entry
9 | */
10 | public final static byte INS_ADD_PASSWORD_ENTRY = (byte) 0x30;
11 |
12 | /**
13 | * INS for command that retrieves a password entry
14 | */
15 | public final static byte INS_RETRIEVE_PASSWORD_ENTRY = (byte) 0x32;
16 |
17 | /**
18 | * INS for command that deletes a password entry
19 | */
20 | public final static byte INS_DELETE_PASSWORD_ENTRY = (byte) 0x34;
21 |
22 | /**
23 | * INS for command that lists all defined identifiers
24 | */
25 | public final static byte INS_LIST_IDENTIFIERS = (byte) 0x36;
26 |
27 | /**
28 | * Status word for a duplicate identifier
29 | */
30 | public final static short SW_DUPLICATE_IDENTIFIER = (short) 0x6A8A;
31 |
32 | /**
33 | * Status word for a failed allocation
34 | */
35 | public final static short SW_NOT_ENOUGH_MEMORY = (short) 0x6A84;
36 |
37 | public final static short SW_IDENTIFIER_NOT_FOUND = (short) 0x6A82;
38 |
39 | /**
40 | * Tag byte for identifiers
41 | */
42 | public final static byte TAG_IDENTIFIER = (byte) 0xF1;
43 |
44 | /**
45 | * Tag byte for user name records
46 | */
47 | public final static byte TAG_USERNAME = (byte) 0xF2;
48 |
49 | /**
50 | * Tag byte for password records
51 | */
52 | public final static byte TAG_PASSWORD = (byte) 0xF3;
53 |
54 | private PasswordEntry current;
55 |
56 | private PasswordManager() {
57 | }
58 |
59 | public static void install(byte[] bArray, short bOffset, byte bLength) {
60 | new PasswordManager().register();
61 | }
62 |
63 | public void process(APDU apdu) {
64 | // Nothing particular to do on SELECT
65 | if (selectingApplet()) {
66 | return;
67 | }
68 |
69 | byte[] buf = apdu.getBuffer();
70 | switch (buf[ISO7816.OFFSET_INS]) {
71 | case INS_ADD_PASSWORD_ENTRY:
72 | processAddPasswordEntry();
73 | break;
74 | case INS_RETRIEVE_PASSWORD_ENTRY:
75 | processRetrievePasswordEntry();
76 | break;
77 | case INS_DELETE_PASSWORD_ENTRY:
78 | processDeletePasswordEntry();
79 | break;
80 | case INS_LIST_IDENTIFIERS:
81 | processListIdentifiers();
82 | break;
83 | default:
84 | // good practice: If you don't know the INS, say so:
85 | ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
86 | }
87 | }
88 |
89 | short checkTLV(byte[] buffer, short inOfs,
90 | byte tag, short maxLen) {
91 | if (buffer[inOfs++] != tag)
92 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
93 | short len = buffer[inOfs++];
94 | if (len > maxLen)
95 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
96 | return (short) (inOfs + len);
97 | }
98 |
99 | void processAddPasswordEntry() {
100 | // Retrieves references to the APDU and its buffer
101 | APDU apdu = APDU.getCurrentAPDU();
102 | byte[] buf = APDU.getCurrentAPDUBuffer();
103 |
104 | // Checks the value of P1&P2
105 | if (Util.getShort(buf, ISO7816.OFFSET_P1) != 0)
106 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
107 | // Checks the minimum length
108 | if ((short) (buf[ISO7816.OFFSET_LC] & 0xFF) < 3)
109 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
110 | // Receives data and checks its length
111 | if (apdu.setIncomingAndReceive() !=
112 | (short) (buf[ISO7816.OFFSET_LC] & 0xFF))
113 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
114 |
115 | // Checks the identifier
116 | short ofsId = ISO7816.OFFSET_CDATA;
117 | short ofsUserName = checkTLV(buf, ofsId, TAG_IDENTIFIER, PasswordEntry.SIZE_ID);
118 |
119 | if (buf[ISO7816.OFFSET_LC] < (short) (ofsUserName - 3))
120 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
121 |
122 | // Checks the user name
123 | short ofsPassword = checkTLV(buf, ofsUserName, TAG_USERNAME, PasswordEntry.SIZE_USERNAME);
124 |
125 | if (buf[ISO7816.OFFSET_LC] < (short) (ofsPassword - 3))
126 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
127 |
128 | // Checks the password
129 | if (checkTLV(buf, ofsPassword, TAG_PASSWORD, PasswordEntry.SIZE_PASSWORD) !=
130 | (short) (ISO7816.OFFSET_CDATA + (short) (buf[ISO7816.OFFSET_LC] & 0xFF)))
131 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
132 |
133 | // Search the identifier in the current base
134 | if (PasswordEntry.search(buf, (short) (ofsId + 2), buf[(short) (ofsId + 1)]) != null)
135 | ISOException.throwIt(SW_DUPLICATE_IDENTIFIER);
136 |
137 | // Allocates and initializes a password entry
138 | JCSystem.beginTransaction();
139 | PasswordEntry pe = PasswordEntry.getInstance();
140 | pe.setId(buf, (short) (ofsId + 2), buf[(short) (ofsId + 1)]);
141 | pe.setUserName(buf, (short) (ofsUserName + 2), buf[(short) (ofsUserName + 1)]);
142 | pe.setPassword(buf, (short) (ofsPassword + 2), buf[(short) (ofsPassword + 1)]);
143 | JCSystem.commitTransaction();
144 | }
145 |
146 | void processDeletePasswordEntry() {
147 | // Retrieves references to the APDU and its buffer
148 | APDU apdu = APDU.getCurrentAPDU();
149 | byte[] buf = APDU.getCurrentAPDUBuffer();
150 |
151 | // Checks the value of P1&P2
152 | if (Util.getShort(buf, ISO7816.OFFSET_P1) != 0)
153 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
154 | // Checks the minimum length
155 | if ((short) (buf[ISO7816.OFFSET_LC] & 0xFF) < 3)
156 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
157 | // Receives data and checks its length
158 | if (apdu.setIncomingAndReceive() != (short) (buf[ISO7816.OFFSET_LC] & 0xFF))
159 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
160 |
161 | // Checks the identifier
162 | short ofsId = ISO7816.OFFSET_CDATA;
163 | if (checkTLV(buf, ISO7816.OFFSET_CDATA, TAG_IDENTIFIER, PasswordEntry.SIZE_ID) !=
164 | (short) (ISO7816.OFFSET_CDATA + (short) (buf[ISO7816.OFFSET_LC] & 0xFF)))
165 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
166 |
167 | // Search the identifier in the current base
168 | PasswordEntry pe = PasswordEntry.search(buf,
169 | (short) (ISO7816.OFFSET_CDATA + 2),
170 | buf[ISO7816.OFFSET_CDATA + 1]);
171 | if (pe == null)
172 | ISOException.throwIt(SW_IDENTIFIER_NOT_FOUND);
173 |
174 | PasswordEntry.delete(buf, (short) (ofsId + 2), buf[(short) (ofsId + 1)]);
175 | }
176 |
177 | void processRetrievePasswordEntry() {
178 | // Retrieves references to the APDU and its buffer
179 | APDU apdu = APDU.getCurrentAPDU();
180 | byte[] buf = APDU.getCurrentAPDUBuffer();
181 |
182 | // INITIAL CHECKS
183 |
184 | // Checks the value of P1&P2
185 | if (Util.getShort(buf, ISO7816.OFFSET_P1) != 0)
186 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
187 | // Checks the minimum length
188 | if ((short) (buf[ISO7816.OFFSET_LC] & 0xFF) < 3)
189 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
190 | // Receives data and checks its length
191 | if (apdu.setIncomingAndReceive() !=
192 | (short) (buf[ISO7816.OFFSET_LC] & 0xFF))
193 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
194 |
195 |
196 | // INTERPRETS AND CHECKS THE DATA
197 |
198 | // Checks the identifier
199 | if (checkTLV(buf, ISO7816.OFFSET_CDATA, TAG_IDENTIFIER, PasswordEntry.SIZE_ID) !=
200 | (short) (ISO7816.OFFSET_CDATA + (short) (buf[ISO7816.OFFSET_LC] & 0xFF)))
201 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
202 |
203 | // Search the identifier in the current base
204 | PasswordEntry pe = PasswordEntry.search(buf, (short) (ISO7816.OFFSET_CDATA + 2), buf[ISO7816.OFFSET_CDATA + 1]);
205 | if (pe == null)
206 | ISOException.throwIt(SW_IDENTIFIER_NOT_FOUND);
207 |
208 | // Builds the result, starting with the user name
209 | short outOfs = 0;
210 | buf[outOfs++] = TAG_USERNAME;
211 | byte len = pe.getUserName(buf, (short) (outOfs + 1));
212 | buf[outOfs++] = len;
213 | outOfs += len;
214 |
215 | // Builds the result, continuing with the password
216 | buf[outOfs++] = TAG_PASSWORD;
217 | len = pe.getPassword(buf, (short) (outOfs + 1));
218 | buf[outOfs++] = len;
219 | outOfs += len;
220 |
221 | // Sends the result
222 | apdu.setOutgoingAndSend((short) 0, outOfs);
223 | }
224 |
225 | void processListIdentifiers() {
226 | // Retrieves references to the APDU and its buffer
227 | APDU apdu = APDU.getCurrentAPDU();
228 | byte[] buf = APDU.getCurrentAPDUBuffer();
229 |
230 | // Checks P1 and initializes the "current" value
231 | if (buf[ISO7816.OFFSET_P1] == 0)
232 | current = PasswordEntry.getFirst();
233 | else if ((buf[ISO7816.OFFSET_P1] != 1) || (current == null))
234 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
235 |
236 |
237 | // Builds the response
238 | short offset = 0;
239 | while (current != null) {
240 | // Checks that the identifier record fits in the APDU
241 | // WARNING: assumes a 256-byte APDU buffer
242 | byte len = current.getIdLength();
243 | if ((short) ((short) (offset + len) + 2) > 255)
244 | break;
245 |
246 | // Copies the identifier in the buffer
247 | buf[offset++] = TAG_IDENTIFIER;
248 | buf[offset++] = len;
249 | current.getId(buf, offset);
250 |
251 | // Gest to the next record
252 | offset += len;
253 | current = current.getNext();
254 | }
255 | apdu.setOutgoingAndSend((short) 0, offset);
256 | }
257 | }
--------------------------------------------------------------------------------
/jc101-password/src/test/java/fr/bmartel/passwords/JavaCardTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import javax.smartcardio.CardException;
4 | import javax.smartcardio.CommandAPDU;
5 | import javax.smartcardio.ResponseAPDU;
6 |
7 | public class JavaCardTest {
8 |
9 | public ResponseAPDU transmitCommand(CommandAPDU data) throws CardException {
10 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
11 | return TestSuite.getCard().getBasicChannel().transmit(data);
12 | } else {
13 | return TestSuite.getSimulator().transmitCommand(data);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/jc101-password/src/test/java/fr/bmartel/passwords/Password.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import fr.bmartel.helloworld.util.TestUtils;
4 |
5 | public class Password {
6 |
7 | private byte[] id;
8 | private byte[] username;
9 | private byte[] password;
10 |
11 | public Password(byte[] id, byte[] username, byte[] password) {
12 | this.id = id;
13 | this.username = username;
14 | this.password = password;
15 | }
16 |
17 | public byte[] getFullApdu() {
18 | return TestUtils.concatByteArray(id, username, password);
19 | }
20 |
21 | public byte[] getId() {
22 | return id;
23 | }
24 |
25 | public byte[] getData() {
26 | return TestUtils.concatByteArray(username, password);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/jc101-password/src/test/java/fr/bmartel/passwords/PasswordEntryTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import fr.bmartel.helloworld.util.TestUtils;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import java.lang.reflect.Field;
8 |
9 | import static org.junit.Assert.*;
10 |
11 | public class PasswordEntryTest {
12 |
13 | private PasswordEntry entry;
14 |
15 | private final static byte[] ID_BASIC = new byte[]{0x01, 0x02};
16 | private final static byte[] USERNAME_BASIC = new byte[]{0x03, 0x04, 0x05};
17 | private final static byte[] PASSWORD_BASIC = new byte[]{0x05, 0x06, 0x07, 0x08};
18 |
19 | private final static byte[] ID_BASIC1 = new byte[]{0x11, 0x12};
20 | private final static byte[] USERNAME_BASIC1 = new byte[]{0x13, 0x14, 0x15};
21 | private final static byte[] PASSWORD_BASIC1 = new byte[]{0x15, 0x16, 0x17, 0x18};
22 |
23 | private final static byte[] ID_BASIC2 = new byte[]{0x21, 0x22};
24 | private final static byte[] USERNAME_BASIC2 = new byte[]{0x23, 0x24, 0x25};
25 | private final static byte[] PASSWORD_BASIC2 = new byte[]{0x25, 0x26, 0x27, 0x28};
26 |
27 | private final static byte[] ID_BASIC3 = new byte[]{0x31, 0x32};
28 | private final static byte[] USERNAME_BASIC3 = new byte[]{0x33, 0x34, 0x35};
29 | private final static byte[] PASSWORD_BASIC3 = new byte[]{0x35, 0x36, 0x37, 0x38};
30 |
31 | private final static byte[] ID_BASIC4 = new byte[]{0x41, 0x42};
32 |
33 | /**
34 | * Get static field "first" by reflection
35 | *
36 | * @return first PasswordEntry
37 | * @throws NoSuchFieldException
38 | * @throws IllegalAccessException
39 | */
40 | private PasswordEntry getFirst() throws NoSuchFieldException, IllegalAccessException {
41 | Field f = TestUtils.getField(PasswordEntry.class, "first");
42 | if (f == null)
43 | throw new NoSuchFieldException();
44 | return (PasswordEntry) f.get(null);
45 | }
46 |
47 | /**
48 | * Get static field "deleted" by reflection
49 | *
50 | * @return item to recycle
51 | * @throws NoSuchFieldException
52 | * @throws IllegalAccessException
53 | */
54 | private PasswordEntry getDeleted() throws NoSuchFieldException, IllegalAccessException {
55 | Field f = TestUtils.getField(PasswordEntry.class, "deleted");
56 | if (f == null)
57 | throw new NoSuchFieldException();
58 | return (PasswordEntry) f.get(null);
59 | }
60 |
61 | /**
62 | * Get the next password entry for a specific instance
63 | *
64 | * @param entry password entry instance
65 | * @return
66 | * @throws NoSuchFieldException
67 | * @throws IllegalAccessException
68 | */
69 | private PasswordEntry getNext(PasswordEntry entry) throws NoSuchFieldException, IllegalAccessException {
70 | Field f = TestUtils.getField(entry.getClass(), "next");
71 | if (f == null)
72 | throw new NoSuchFieldException();
73 | return (PasswordEntry) f.get(entry);
74 | }
75 |
76 | /**
77 | * Get id length property by reflection.
78 | *
79 | * @param entry Password Entry instance
80 | * @return id length
81 | * @throws NoSuchFieldException
82 | * @throws IllegalAccessException
83 | */
84 | private byte getIdLength(PasswordEntry entry) throws NoSuchFieldException, IllegalAccessException {
85 | Field f = TestUtils.getField(entry.getClass(), "idLength");
86 | if (f == null)
87 | throw new NoSuchFieldException();
88 | return f.getByte(entry);
89 | }
90 |
91 | /**
92 | * Get username length property by reflection.
93 | *
94 | * @param entry Password entry instance
95 | * @return username length
96 | * @throws NoSuchFieldException
97 | * @throws IllegalAccessException
98 | */
99 | private byte getUsernameLength(PasswordEntry entry) throws NoSuchFieldException, IllegalAccessException {
100 | Field f = TestUtils.getField(entry.getClass(), "userNameLength");
101 | if (f == null)
102 | throw new NoSuchFieldException();
103 | return f.getByte(entry);
104 | }
105 |
106 | /**
107 | * Get password length property by reflection.
108 | *
109 | * @param entry Password entry instance
110 | * @return password length
111 | * @throws NoSuchFieldException
112 | * @throws IllegalAccessException
113 | */
114 | private byte getPasswordLength(PasswordEntry entry) throws NoSuchFieldException, IllegalAccessException {
115 | Field f = TestUtils.getField(entry.getClass(), "passwordLength");
116 | if (f == null)
117 | throw new NoSuchFieldException();
118 | return f.getByte(entry);
119 | }
120 |
121 | /**
122 | * Get byte array property
123 | *
124 | * @param object Object instance
125 | * @param name field name
126 | * @return byte array object matching the specified field name
127 | * @throws IllegalAccessException
128 | * @throws NoSuchFieldException
129 | */
130 | private byte[] getByteArray(Object object, String name) throws IllegalAccessException, NoSuchFieldException {
131 | Field f = TestUtils.getField(object.getClass(), name);
132 | if (f == null)
133 | throw new NoSuchFieldException();
134 | return (byte[]) f.get(object);
135 | }
136 |
137 | /**
138 | * Check size of byte array properties
139 | *
140 | * @param entry Password entry instance
141 | * @throws IllegalAccessException
142 | * @throws NoSuchFieldException
143 | */
144 | private void checkDataSize(PasswordEntry entry) throws IllegalAccessException, NoSuchFieldException {
145 | assertNotNull("id array not null", getByteArray(entry, "id"));
146 | assertEquals("id array size check", getByteArray(entry, "id").length, PasswordEntry.SIZE_ID);
147 | assertNotNull("username array not null", getByteArray(entry, "username"));
148 | assertEquals("username array size check", getByteArray(entry, "username").length, PasswordEntry.SIZE_USERNAME);
149 | assertNotNull("password array not null", getByteArray(entry, "password"));
150 | assertEquals("password array size check", getByteArray(entry, "password").length, PasswordEntry.SIZE_PASSWORD);
151 | }
152 |
153 | private void addItem(byte[] id, byte[] username, byte[] password) throws NoSuchFieldException, IllegalAccessException {
154 | entry = PasswordEntry.getInstance();
155 |
156 | assertNotNull("created instance exist", entry);
157 |
158 | PasswordEntry firstElement = getFirst();
159 |
160 | assertNotNull("first element exists", firstElement);
161 | assertEquals("first element is the created element", firstElement, entry);
162 |
163 | assertNull("no item to recycle", getDeleted());
164 |
165 | assertEquals("next getter valid", getNext(entry), entry.getNext());
166 | assertEquals("first getter valid", getFirst(), PasswordEntry.getFirst());
167 |
168 | assertEquals("check id length getter", 0, entry.getIdLength());
169 |
170 | assertEquals("check empty password length", 0, entry.getPassword(new byte[PasswordEntry.SIZE_PASSWORD], (short) 0));
171 | assertEquals("check empty username length", 0, entry.getUserName(new byte[PasswordEntry.SIZE_USERNAME], (short) 0));
172 | assertEquals("check empty id length", 0, entry.getId(new byte[PasswordEntry.SIZE_ID], (short) 0));
173 |
174 | assertEquals("check password length via reflection", 0, getPasswordLength(entry));
175 | assertEquals("check username length via reflection", 0, getUsernameLength(entry));
176 | assertEquals("check id length via reflection", 0, getIdLength(entry));
177 |
178 | entry.setId(id, (short) 0, (byte) id.length);
179 | entry.setUserName(username, (short) 0, (byte) username.length);
180 | entry.setPassword(password, (short) 0, (byte) password.length);
181 |
182 | checkDataSize(entry);
183 | }
184 |
185 | private void deleteItem(byte[] id) {
186 | PasswordEntry.delete(id, (short) 0, (byte) id.length);
187 | }
188 |
189 | private void checkData(PasswordEntry entry, byte[] expectedId, byte[] expectedUsername, byte[] expectedPassword) {
190 | byte[] passwordOut = new byte[expectedPassword.length];
191 | byte[] usernameOut = new byte[expectedUsername.length];
192 | byte[] idOut = new byte[expectedId.length];
193 |
194 | assertEquals("check password length", expectedPassword.length, entry.getPassword(passwordOut, (short) 0));
195 | assertEquals("check username length", expectedUsername.length, entry.getUserName(usernameOut, (short) 0));
196 | assertEquals("check id length", expectedId.length, entry.getId(idOut, (short) 0));
197 |
198 | assertArrayEquals("check password data", expectedPassword, passwordOut);
199 | assertArrayEquals("check username data", expectedUsername, usernameOut);
200 | assertArrayEquals("check id data", expectedId, idOut);
201 | }
202 |
203 | private int getLength() {
204 | int length = 0;
205 | PasswordEntry current = PasswordEntry.getFirst();
206 | while (current != null) {
207 | length++;
208 | current = current.getNext();
209 | }
210 | return length;
211 | }
212 |
213 | @Before
214 | public void initTest() throws NoSuchFieldException, IllegalAccessException {
215 | Field f = TestUtils.getField(PasswordEntry.class, "first");
216 | if (f == null)
217 | throw new NoSuchFieldException();
218 | f.set(null, null);
219 | f = TestUtils.getField(PasswordEntry.class, "deleted");
220 | if (f == null)
221 | throw new NoSuchFieldException();
222 | f.set(null, null);
223 | assertNull("no first element", getFirst());
224 | assertNull("no item to recycle", getDeleted());
225 | }
226 |
227 | @Test
228 | public void setPropertiesTest() throws NoSuchFieldException, IllegalAccessException {
229 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
230 | assertNull("no next element", entry.getNext());
231 |
232 | entry.setId(ID_BASIC, (short) 0, (byte) ID_BASIC.length);
233 | entry.setUserName(USERNAME_BASIC, (short) 0, (byte) USERNAME_BASIC.length);
234 | entry.setPassword(PASSWORD_BASIC, (short) 0, (byte) PASSWORD_BASIC.length);
235 |
236 | assertEquals("check id length getter", ID_BASIC.length, entry.getIdLength());
237 |
238 | checkData(entry, ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
239 | }
240 |
241 | @Test
242 | public void setPropertiesOffsetTest() throws NoSuchFieldException, IllegalAccessException {
243 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
244 | assertNull("no next element", entry.getNext());
245 |
246 | short offset = 2;
247 |
248 | entry.setId(TestUtils.addOffset(offset, ID_BASIC), offset, (byte) ID_BASIC.length);
249 | entry.setUserName(TestUtils.addOffset(offset, USERNAME_BASIC), offset, (byte) USERNAME_BASIC.length);
250 | entry.setPassword(TestUtils.addOffset(offset, PASSWORD_BASIC), offset, (byte) PASSWORD_BASIC.length);
251 |
252 | assertEquals("check id length getter", ID_BASIC.length, entry.getIdLength());
253 |
254 | checkData(entry, ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
255 | }
256 |
257 | @Test
258 | public void addMultipleEntries() throws NoSuchFieldException, IllegalAccessException {
259 | assertEquals(0, getLength());
260 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
261 | assertEquals(1, getLength());
262 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
263 | assertEquals(2, getLength());
264 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
265 | assertEquals(3, getLength());
266 | }
267 |
268 | private PasswordEntry checkSearchedItem(byte[] id, byte[] username, byte[] password) {
269 | PasswordEntry searchEntry = PasswordEntry.search(id, (short) 0, (byte) id.length);
270 | assertNotNull("search result not null", searchEntry);
271 | checkData(searchEntry, id, username, password);
272 | return searchEntry;
273 | }
274 |
275 | @Test
276 | public void searchIdValues() throws NoSuchFieldException, IllegalAccessException {
277 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
278 | addItem(ID_BASIC1, USERNAME_BASIC1, PASSWORD_BASIC1);
279 | addItem(ID_BASIC2, USERNAME_BASIC2, PASSWORD_BASIC2);
280 | addItem(ID_BASIC3, USERNAME_BASIC3, PASSWORD_BASIC3);
281 |
282 | assertEquals("init length", 4, getLength());
283 |
284 | assertNull("search value check next null (eg first added item)", checkSearchedItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC).getNext());
285 | assertNotNull("search value next not null", checkSearchedItem(ID_BASIC1, USERNAME_BASIC1, PASSWORD_BASIC1).getNext());
286 | assertNotNull("search value next not null", checkSearchedItem(ID_BASIC2, USERNAME_BASIC2, PASSWORD_BASIC2).getNext());
287 | assertNotNull("search value next not null", checkSearchedItem(ID_BASIC3, USERNAME_BASIC3, PASSWORD_BASIC3).getNext());
288 |
289 | PasswordEntry searchEntry = PasswordEntry.search(ID_BASIC4, (short) 0, (byte) ID_BASIC4.length);
290 | assertNull("search value invalid", searchEntry);
291 | }
292 |
293 | @Test
294 | public void deleteTest() throws NoSuchFieldException, IllegalAccessException {
295 | assertEquals("init length", 0, getLength());
296 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
297 | assertEquals("length after addition", 1, getLength());
298 | deleteItem(ID_BASIC);
299 | assertEquals("length after deletion", 0, getLength());
300 | }
301 |
302 | @Test
303 | public void deleteMultipleTest() throws NoSuchFieldException, IllegalAccessException {
304 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
305 | addItem(ID_BASIC1, USERNAME_BASIC1, PASSWORD_BASIC1);
306 | addItem(ID_BASIC2, USERNAME_BASIC2, PASSWORD_BASIC2);
307 | addItem(ID_BASIC3, USERNAME_BASIC3, PASSWORD_BASIC3);
308 | assertEquals("init length", 4, getLength());
309 | deleteItem(ID_BASIC);
310 | deleteItem(ID_BASIC1);
311 | deleteItem(ID_BASIC2);
312 | deleteItem(ID_BASIC3);
313 | assertEquals("length after deletion", 0, getLength());
314 | }
315 |
316 | @Test
317 | public void checkRecycledItem() throws NoSuchFieldException, IllegalAccessException {
318 | assertNull("no item to recycle", getDeleted());
319 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
320 | deleteItem(ID_BASIC);
321 | assertNotNull("1 item to recycle", getDeleted());
322 | }
323 |
324 | @Test
325 | public void interTwinedAddDelete() throws NoSuchFieldException, IllegalAccessException {
326 | for (int i = 0; i < 10; i++) {
327 | assertEquals("init length, iteration n°" + i, 0, getLength());
328 | addItem(ID_BASIC, USERNAME_BASIC, PASSWORD_BASIC);
329 | assertEquals("length after addition, iteration n°" + i, 1, getLength());
330 | deleteItem(ID_BASIC);
331 | assertEquals("length after deletion, iteration n°" + i, 0, getLength());
332 | }
333 | }
334 | }
--------------------------------------------------------------------------------
/jc101-password/src/test/java/fr/bmartel/passwords/PasswordManagerTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import fr.bmartel.helloworld.util.TestUtils;
4 | import javacard.framework.ISO7816;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import javax.smartcardio.CardException;
9 | import javax.smartcardio.CommandAPDU;
10 | import javax.smartcardio.ResponseAPDU;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import static org.junit.Assert.assertArrayEquals;
15 | import static org.junit.Assert.assertEquals;
16 |
17 | public class PasswordManagerTest extends JavaCardTest {
18 |
19 | private final static byte[] CMD_ADD_PASSWORD = new byte[]{0x00, 0x30, 0x00, 0x00};
20 | private final static byte[] CMD_GET_PASSWORD = new byte[]{0x00, 0x32, 0x00, 0x00};
21 | private final static byte[] CMD_DELETE_PASSWORD = new byte[]{0x00, 0x34, 0x00, 0x00};
22 | private final static byte[] CMD_LIST_ID = new byte[]{0x00, 0x36, 0x00, 0x00};
23 |
24 | private final static Password DATA_ENTRY_VALID = new Password(
25 | new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65},
26 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
27 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
28 |
29 | private final static Password DATA_ENTRY_VALID1 = new Password(
30 | new byte[]{(byte) 0xF1, 0x04, 0x49, 0x6F, 0x6D, 0x65},
31 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
32 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
33 |
34 | private final static Password DATA_ENTRY_VALID2 = new Password(
35 | new byte[]{(byte) 0xF1, 0x04, 0x50, 0x6F, 0x6D, 0x65},
36 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
37 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
38 |
39 | private final static Password DATA_ENTRY_VALID3 = new Password(
40 | new byte[]{(byte) 0xF1, 0x04, 0x51, 0x6F, 0x6D, 0x65},
41 | new byte[]{(byte) 0xF2, 0x03, 0x62, 0x6F, 0x62},
42 | new byte[]{(byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73});
43 |
44 | //invalid data for addition
45 | private final static byte[] DATA_ENTRY_INVALID_TAG1 = new byte[]{0x01, 0x02, 0x03, 0x04};
46 | private final static byte[] DATA_ENTRY_INVALID_TAG2 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65};
47 | private final static byte[] DATA_ENTRY_INVALID_TAG3 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62};
48 | private final static byte[] DATA_ENTRY_INVLID_LENGTH1 = new byte[]{(byte) 0xF1, 0x03, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
49 | private final static byte[] DATA_ENTRY_INVLID_LENGTH2 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x02, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
50 | private final static byte[] DATA_ENTRY_INVLID_LENGTH3 = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x02, 0x70, 0x61, 0x73, 0x73};
51 | private final static byte[] DATA_ENTRY_ID_OVERFLOW = new byte[]{(byte) 0xF1, 0x11, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
52 | private final static byte[] DATA_ENTRY_USERNAME_OVERFLOW = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, (byte) 0xF3, 0x04, 0x70, 0x61, 0x73, 0x73};
53 | private final static byte[] DATA_ENTRY_PASSWORD_OVERFLOW = new byte[]{(byte) 0xF1, 0x04, 0x48, 0x6F, 0x6D, 0x65, (byte) 0xF2, 0x03, 0x62, 0x6F, 0x62, (byte) 0xF3, 0x11, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12};
54 |
55 | private void sendAddPassword(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
56 | TestUtils.sendCmdBatch(this, CMD_ADD_PASSWORD, data, expectedSw, expectedResponse);
57 | }
58 |
59 | private void sendGetPassword(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
60 | TestUtils.sendCmdBatch(this, CMD_GET_PASSWORD, data, expectedSw, expectedResponse);
61 | }
62 |
63 | private void sendDeletePassword(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
64 | TestUtils.sendCmdBatch(this, CMD_DELETE_PASSWORD, data, expectedSw, expectedResponse);
65 | }
66 |
67 | private void sendListId(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
68 | TestUtils.sendCmdBatch(this, CMD_LIST_ID, data, expectedSw, expectedResponse);
69 | }
70 |
71 | private void deleteAllPassword() throws CardException {
72 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_LIST_ID, new byte[]{}));
73 | ResponseAPDU response = transmitCommand(commandAPDU);
74 | assertEquals(0x9000, response.getSW());
75 | if (response.getData().length > 0) {
76 | List ids = new ArrayList<>();
77 | int state = 0;
78 | int length = 0;
79 | int index = 0;
80 | byte[] current = new byte[]{};
81 |
82 | for (int i = 0; i < response.getData().length; i++) {
83 | switch (state) {
84 | case 0:
85 | if ((response.getData()[i] & 0xFF) == 0xF1) {
86 | state = 1;
87 | }
88 | break;
89 | case 1:
90 | //set length
91 | length = response.getData()[i] & 0xFF;
92 | current = new byte[length + 2];
93 | current[0] = (byte) 0xF1;
94 | current[1] = response.getData()[i];
95 | index = 2;
96 | state = 2;
97 | break;
98 | case 2:
99 | //set data
100 | current[index++] = response.getData()[i];
101 | length--;
102 | if (length == 0) {
103 | ids.add(current);
104 | state = 0;
105 | }
106 | }
107 |
108 | }
109 |
110 | for (int i = 0; i < ids.size(); i++) {
111 | commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_DELETE_PASSWORD, ids.get(i)));
112 | response = transmitCommand(commandAPDU);
113 | assertEquals(0x9000, response.getSW());
114 | }
115 |
116 | commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_LIST_ID, new byte[]{}));
117 | response = transmitCommand(commandAPDU);
118 | assertEquals(0x9000, response.getSW());
119 | assertEquals(0, response.getData().length);
120 | }
121 | }
122 |
123 | @Before
124 | public void initTest() throws NoSuchFieldException, IllegalAccessException, CardException {
125 | TestSuite.setup();
126 | deleteAllPassword();
127 | }
128 |
129 | @Test
130 | public void ListEmptyTest() throws CardException {
131 | sendListId(new byte[]{}, 0x9000, new byte[]{});
132 | }
133 |
134 | @Test
135 | public void addPasswordTest() throws CardException {
136 | sendListId(new byte[]{}, 0x9000, new byte[]{});
137 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
138 | sendListId(new byte[]{}, 0x9000, DATA_ENTRY_VALID.getId());
139 | }
140 |
141 | @Test
142 | public void multipleAddPasswordTest() throws CardException {
143 | sendListId(new byte[]{}, 0x9000, new byte[]{});
144 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
145 | sendAddPassword(DATA_ENTRY_VALID1.getFullApdu(), 0x9000, new byte[]{});
146 | sendAddPassword(DATA_ENTRY_VALID2.getFullApdu(), 0x9000, new byte[]{});
147 | sendAddPassword(DATA_ENTRY_VALID3.getFullApdu(), 0x9000, new byte[]{});
148 | sendListId(new byte[]{}, 0x9000, TestUtils.concatByteArray(DATA_ENTRY_VALID3.getId(),
149 | DATA_ENTRY_VALID2.getId(),
150 | DATA_ENTRY_VALID1.getId(),
151 | DATA_ENTRY_VALID.getId()));
152 | }
153 |
154 | @Test
155 | public void invalidAddTagTest() throws CardException {
156 | sendListId(new byte[]{}, 0x9000, new byte[]{});
157 | sendAddPassword(DATA_ENTRY_INVALID_TAG1, ISO7816.SW_DATA_INVALID, new byte[]{});
158 | sendAddPassword(DATA_ENTRY_INVALID_TAG2, ISO7816.SW_DATA_INVALID, new byte[]{});
159 | sendAddPassword(DATA_ENTRY_INVALID_TAG3, ISO7816.SW_DATA_INVALID, new byte[]{});
160 | sendListId(new byte[]{}, 0x9000, new byte[]{});
161 | }
162 |
163 | @Test
164 | public void invalidAddDataLengthTest() throws CardException {
165 | sendListId(new byte[]{}, 0x9000, new byte[]{});
166 | sendAddPassword(DATA_ENTRY_INVLID_LENGTH1, ISO7816.SW_DATA_INVALID, new byte[]{});
167 | sendAddPassword(DATA_ENTRY_INVLID_LENGTH2, ISO7816.SW_DATA_INVALID, new byte[]{});
168 | sendAddPassword(DATA_ENTRY_INVLID_LENGTH3, ISO7816.SW_DATA_INVALID, new byte[]{});
169 | sendListId(new byte[]{}, 0x9000, new byte[]{});
170 | }
171 |
172 | @Test
173 | public void invalidAddDataOverflowTest() throws CardException {
174 | sendListId(new byte[]{}, 0x9000, new byte[]{});
175 | sendAddPassword(DATA_ENTRY_ID_OVERFLOW, ISO7816.SW_DATA_INVALID, new byte[]{});
176 | sendAddPassword(DATA_ENTRY_USERNAME_OVERFLOW, ISO7816.SW_DATA_INVALID, new byte[]{});
177 | sendAddPassword(DATA_ENTRY_PASSWORD_OVERFLOW, ISO7816.SW_DATA_INVALID, new byte[]{});
178 | sendListId(new byte[]{}, 0x9000, new byte[]{});
179 | }
180 |
181 | @Test
182 | public void duplicateAddTest() throws CardException {
183 | sendListId(new byte[]{}, 0x9000, new byte[]{});
184 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
185 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), PasswordManager.SW_DUPLICATE_IDENTIFIER, new byte[]{});
186 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), PasswordManager.SW_DUPLICATE_IDENTIFIER, new byte[]{});
187 | sendListId(new byte[]{}, 0x9000, DATA_ENTRY_VALID.getId());
188 | }
189 |
190 | @Test
191 | public void getPasswordTest() throws CardException {
192 | sendListId(new byte[]{}, 0x9000, new byte[]{});
193 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
194 | sendGetPassword(DATA_ENTRY_VALID.getId(), 0x9000, DATA_ENTRY_VALID.getData());
195 | sendAddPassword(DATA_ENTRY_VALID1.getFullApdu(), 0x9000, new byte[]{});
196 | sendGetPassword(DATA_ENTRY_VALID1.getId(), 0x9000, DATA_ENTRY_VALID1.getData());
197 | }
198 |
199 | @Test
200 | public void getInvalidIdTest() throws CardException {
201 | sendListId(new byte[]{}, 0x9000, new byte[]{});
202 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
203 | sendGetPassword(DATA_ENTRY_VALID1.getId(), PasswordManager.SW_IDENTIFIER_NOT_FOUND, new byte[]{});
204 | }
205 |
206 | @Test
207 | public void deleteIdTest() throws CardException {
208 | sendListId(new byte[]{}, 0x9000, new byte[]{});
209 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
210 | sendDeletePassword(DATA_ENTRY_VALID.getId(), 0x9000, new byte[]{});
211 | }
212 |
213 | @Test
214 | public void deleteIdInvalidTest() throws CardException {
215 | sendListId(new byte[]{}, 0x9000, new byte[]{});
216 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
217 | sendDeletePassword(DATA_ENTRY_VALID1.getId(), PasswordManager.SW_IDENTIFIER_NOT_FOUND, new byte[]{});
218 | }
219 |
220 | @Test
221 | public void intertwinedAddDeleteTest() throws CardException {
222 | for (int i = 0; i < 10; i++) {
223 | sendListId(new byte[]{}, 0x9000, new byte[]{});
224 | sendAddPassword(DATA_ENTRY_VALID.getFullApdu(), 0x9000, new byte[]{});
225 | sendListId(new byte[]{}, 0x9000, DATA_ENTRY_VALID.getId());
226 | sendDeletePassword(DATA_ENTRY_VALID.getId(), 0x9000, new byte[]{});
227 | }
228 | }
229 | }
--------------------------------------------------------------------------------
/jc101-password/src/test/java/fr/bmartel/passwords/TestSuite.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.passwords;
2 |
3 | import apdu4j.LoggingCardTerminal;
4 | import apdu4j.TerminalManager;
5 | import com.licel.jcardsim.smartcardio.CardSimulator;
6 | import com.licel.jcardsim.utils.AIDUtil;
7 | import javacard.framework.AID;
8 | import org.junit.AfterClass;
9 | import org.junit.BeforeClass;
10 | import org.junit.runner.RunWith;
11 | import org.junit.runners.Suite;
12 | import org.junit.runners.Suite.SuiteClasses;
13 |
14 | import javax.smartcardio.*;
15 | import javax.smartcardio.CardTerminals.State;
16 | import java.io.OutputStream;
17 | import java.security.NoSuchAlgorithmException;
18 | import java.util.List;
19 |
20 | import static org.junit.Assert.fail;
21 | import static org.junit.Assert.assertEquals;
22 |
23 | @RunWith(Suite.class)
24 | @SuiteClasses({PasswordEntryTest.class, PasswordManagerTest.class})
25 | public class TestSuite {
26 |
27 | private final static String APPLET_AID = "01020304050607080901";
28 |
29 | private static CardSimulator mSimulator;
30 |
31 | private static boolean mInitialized;
32 |
33 | private static Card mCard;
34 |
35 | private static Card initGp() {
36 |
37 | try {
38 | TerminalFactory tf = TerminalManager.getTerminalFactory(null);
39 |
40 | CardTerminals terminals = tf.terminals();
41 |
42 | System.out.println("# Detected readers from " + tf.getProvider().getName());
43 | for (CardTerminal term : terminals.list()) {
44 | System.out.println((term.isCardPresent() ? "[*] " : "[ ] ") + term.getName());
45 | }
46 |
47 | // Select terminal(s) to work on
48 | List do_readers;
49 | do_readers = terminals.list(State.CARD_PRESENT);
50 |
51 | if (do_readers.size() == 0) {
52 | fail("No smart card readers with a card found");
53 | }
54 | // Work all readers
55 | for (CardTerminal reader : do_readers) {
56 | if (do_readers.size() > 1) {
57 | System.out.println("# " + reader.getName());
58 | }
59 |
60 | OutputStream o = null;
61 | reader = LoggingCardTerminal.getInstance(reader, o);
62 |
63 | Card card = null;
64 | // Establish connection
65 | try {
66 | card = reader.connect("*");
67 | // Use use apdu4j which by default uses jnasmartcardio
68 | // which uses real SCardBeginTransaction
69 | card.beginExclusive();
70 |
71 | return card;
72 |
73 | } catch (CardException e) {
74 | System.err.println("Could not connect to " + reader.getName() + ": " + TerminalManager.getExceptionMessage(e));
75 | continue;
76 | }
77 | }
78 | } catch (CardException e) {
79 | // Sensible wrapper for the different PC/SC exceptions
80 | if (TerminalManager.getExceptionMessage(e) != null) {
81 | System.out.println("PC/SC failure: " + TerminalManager.getExceptionMessage(e));
82 | } else {
83 | e.printStackTrace(); // TODO: remove
84 | fail("CardException, terminating");
85 | }
86 | } catch (NoSuchAlgorithmException e) {
87 | e.printStackTrace();
88 | }
89 | return null;
90 | }
91 |
92 | @BeforeClass
93 | public static void setup() throws CardException {
94 | if (mInitialized) {
95 | return;
96 | }
97 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
98 | mCard = initGp();
99 | CommandAPDU c = new CommandAPDU(AIDUtil.select(APPLET_AID));
100 | ResponseAPDU response = mCard.getBasicChannel().transmit(c);
101 | assertEquals(0x9000, response.getSW());
102 | } else {
103 | mSimulator = new CardSimulator();
104 | AID appletAID = AIDUtil.create(APPLET_AID);
105 | mSimulator.installApplet(appletAID, PasswordManager.class);
106 | mSimulator.selectApplet(appletAID);
107 | }
108 | mInitialized = true;
109 | }
110 |
111 | public static Card getCard() {
112 | return mCard;
113 | }
114 |
115 | public static CardSimulator getSimulator() {
116 | return mSimulator;
117 | }
118 |
119 | @AfterClass
120 | public static void close() throws CardException {
121 | if (mCard != null) {
122 | mCard.endExclusive();
123 | mCard.disconnect(true);
124 | mCard = null;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/jc101-password/src/test/java/fr/bmartel/passwords/util/TestUtils.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.helloworld.util;
2 |
3 | import fr.bmartel.passwords.JavaCardTest;
4 |
5 | import javax.smartcardio.CardException;
6 | import javax.smartcardio.CommandAPDU;
7 | import javax.smartcardio.ResponseAPDU;
8 | import java.lang.reflect.Field;
9 | import java.nio.ByteBuffer;
10 | import java.util.Arrays;
11 |
12 | import static org.junit.Assert.assertArrayEquals;
13 | import static org.junit.Assert.assertEquals;
14 |
15 | public class TestUtils {
16 |
17 | public static byte[] buildApdu(byte[] command, byte[] data) {
18 | byte[] apdu = new byte[command.length + data.length + 1];
19 | System.arraycopy(command, 0, apdu, 0, command.length);
20 | apdu[command.length] = (byte) data.length;
21 | System.arraycopy(data, 0, apdu, command.length + 1, data.length);
22 | return apdu;
23 | }
24 |
25 | public static void logData(byte[] data) {
26 | System.out.println(Arrays.toString(data));
27 | }
28 |
29 | public static int getInt(byte[] data) {
30 | return (((data[0] & 0xff) << 8) | (data[1] & 0xff));
31 | }
32 |
33 | public static byte[] getByte(int data) {
34 | byte[] out = ByteBuffer.allocate(4).putInt(data).array();
35 | return new byte[]{out[2], out[3]};
36 | }
37 |
38 | /**
39 | * Add n x 0x00 offset to a byte array (at the beginning)
40 | *
41 | * @param offset number of byte to set to 0x00 before the data
42 | * @param data the data
43 | * @return byte array with offset before data
44 | */
45 | public static byte[] addOffset(short offset, byte[] data) {
46 | byte[] resp = new byte[data.length + offset];
47 | for (int i = 0; i < offset; i++) {
48 | resp[i] = 0x00;
49 | }
50 | System.arraycopy(resp, offset, data, 0, data.length);
51 | return resp;
52 | }
53 |
54 | public static byte[] concatByteArray(byte[]... data) {
55 | int length = 0;
56 | for (byte[] item : data) {
57 | length += item.length;
58 | }
59 | byte[] resp = new byte[length];
60 | int offset = 0;
61 | for (byte[] item : data) {
62 | System.arraycopy(item, 0, resp, offset, item.length);
63 | offset += item.length;
64 | }
65 | return resp;
66 | }
67 |
68 | /**
69 | * Get class field by reflection.
70 | *
71 | * @param object class
72 | * @param name field name
73 | * @return field
74 | */
75 | public static Field getField(Class object, String name) {
76 | for (Field f : object.getDeclaredFields()) {
77 | f.setAccessible(true);
78 | if (f != null && f.getName().equals(name)) {
79 | return f;
80 | }
81 | }
82 | return null;
83 | }
84 |
85 | public static void sendCmdBatch(JavaCardTest card, byte[] cmd, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
86 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(cmd, data));
87 | ResponseAPDU response = card.transmitCommand(commandAPDU);
88 | assertEquals(expectedSw, response.getSW());
89 | assertArrayEquals(expectedResponse, response.getData());
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':jc101-1c',':jc101-2c',':jc101-password',':jc101-password-pin'
2 |
3 |
--------------------------------------------------------------------------------