├── .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 | 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 | [![Build Status](https://travis-ci.org/bertrandmartel/javacard-tutorial.svg?branch=master)](https://travis-ci.org/bertrandmartel/javacard-tutorial) 4 | [![Coverage Status](https://coveralls.io/repos/github/bertrandmartel/javacard-tutorial/badge.svg?branch=master)](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 | --------------------------------------------------------------------------------