├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable
│ │ │ ├── sim_logo.png
│ │ │ ├── line_divider.xml
│ │ │ └── ripple.xml
│ │ ├── drawable-hdpi
│ │ │ ├── ic_add.png
│ │ │ ├── ic_close.png
│ │ │ ├── ic_delete.png
│ │ │ ├── ic_memory.png
│ │ │ ├── ic_save.png
│ │ │ ├── ic_security.png
│ │ │ ├── ic_sim_card.png
│ │ │ ├── ic_view_comfy.png
│ │ │ └── ic_info_outline.png
│ │ ├── drawable-mdpi
│ │ │ ├── ic_add.png
│ │ │ ├── ic_close.png
│ │ │ ├── ic_delete.png
│ │ │ ├── ic_memory.png
│ │ │ ├── ic_save.png
│ │ │ ├── ic_security.png
│ │ │ ├── ic_sim_card.png
│ │ │ ├── ic_view_comfy.png
│ │ │ └── ic_info_outline.png
│ │ ├── drawable-xhdpi
│ │ │ ├── ic_add.png
│ │ │ ├── ic_close.png
│ │ │ ├── ic_save.png
│ │ │ ├── ic_delete.png
│ │ │ ├── ic_memory.png
│ │ │ ├── ic_security.png
│ │ │ ├── ic_sim_card.png
│ │ │ ├── ic_view_comfy.png
│ │ │ └── ic_info_outline.png
│ │ ├── drawable-xxhdpi
│ │ │ ├── ic_add.png
│ │ │ ├── ic_save.png
│ │ │ ├── ic_close.png
│ │ │ ├── ic_delete.png
│ │ │ ├── ic_memory.png
│ │ │ ├── ic_security.png
│ │ │ ├── ic_sim_card.png
│ │ │ ├── ic_view_comfy.png
│ │ │ └── ic_info_outline.png
│ │ ├── drawable-xxxhdpi
│ │ │ ├── ic_add.png
│ │ │ ├── ic_close.png
│ │ │ ├── ic_save.png
│ │ │ ├── ic_delete.png
│ │ │ ├── ic_memory.png
│ │ │ ├── ic_security.png
│ │ │ ├── ic_sim_card.png
│ │ │ ├── ic_view_comfy.png
│ │ │ └── ic_info_outline.png
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── styles.xml
│ │ │ └── strings.xml
│ │ ├── drawable-v21
│ │ │ └── ripple.xml
│ │ ├── layout
│ │ │ ├── open_source_list.xml
│ │ │ ├── toolbar.xml
│ │ │ ├── nav_header_main.xml
│ │ │ ├── open_source_items.xml
│ │ │ ├── password_item.xml
│ │ │ ├── about_dialog.xml
│ │ │ ├── password_fragment.xml
│ │ │ ├── mode_dialog.xml
│ │ │ ├── password_item_fragment.xml
│ │ │ ├── activity_main.xml
│ │ │ └── keyguard_fragment.xml
│ │ └── menu
│ │ │ ├── toolbar_menu.xml
│ │ │ └── drawer_view.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── fr
│ │ └── bmartel
│ │ └── smartcard
│ │ └── passwordwallet
│ │ ├── uicc
│ │ ├── ApduResponse.java
│ │ ├── PinCodeResult.java
│ │ ├── CommandApdu.java
│ │ ├── UiccUtils.java
│ │ └── Uicc.java
│ │ ├── utils
│ │ ├── HexUtils.java
│ │ └── MenuUtils.java
│ │ ├── common
│ │ └── SimpleDividerItemDecoration.java
│ │ ├── inter
│ │ ├── IServiceConnection.java
│ │ ├── IFragmentOptions.java
│ │ ├── IDeletionListener.java
│ │ ├── ICompletionListener.java
│ │ ├── IDialog.java
│ │ ├── IViewHolderClickListener.java
│ │ └── IBaseActivity.java
│ │ ├── fragment
│ │ ├── MainFragmentAbstr.java
│ │ ├── ListFragmentAbstr.java
│ │ ├── PasswordFragment.java
│ │ └── PasswordItemFragment.java
│ │ ├── model
│ │ └── Password.java
│ │ ├── dialog
│ │ ├── OpenSourceItemsDialog.java
│ │ ├── AboutDialog.java
│ │ └── ModeDialog.java
│ │ ├── adapter
│ │ ├── OpenSourceItemsAdapter.java
│ │ └── PasswordAdapter.java
│ │ ├── db
│ │ └── PasswordReaderDbHelper.java
│ │ ├── MainActivity.java
│ │ └── application
│ │ └── PasswordApplication.java
├── libs
│ └── org.simalliance.openmobileapi.jar
├── build.gradle
└── proguard-rules.pro
├── settings.gradle
├── img
└── web_hi_res_512.png
├── .idea
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitmodules
├── .gitignore
├── gradle.properties
├── circle.yml
├── applet
├── src
│ ├── test
│ │ └── java
│ │ │ ├── fr
│ │ │ └── bmartel
│ │ │ │ └── smartcard
│ │ │ │ └── passwordwallet
│ │ │ │ ├── JavaCardTest.java
│ │ │ │ ├── model
│ │ │ │ └── Password.java
│ │ │ │ ├── StateTest.java
│ │ │ │ ├── ModeTest.java
│ │ │ │ ├── utils
│ │ │ │ └── TestUtils.java
│ │ │ │ ├── TestSuite.java
│ │ │ │ └── EncryptDecryptTest.java
│ │ │ └── org
│ │ │ └── globalplatform
│ │ │ └── GPSystem.java
│ └── main
│ │ └── java
│ │ └── fr
│ │ └── bmartel
│ │ └── smartcard
│ │ └── passwordwallet
│ │ └── PasswordEntry.java
├── LICENSE.md
└── build.gradle
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app',':applet'
2 |
--------------------------------------------------------------------------------
/img/web_hi_res_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/img/web_hi_res_512.png
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sim_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable/sim_logo.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "oracle_javacard_sdks"]
2 | path = oracle_javacard_sdks
3 | url = git://github.com/martinpaljak/oracle_javacard_sdks.git
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_add.png
--------------------------------------------------------------------------------
/app/libs/org.simalliance.openmobileapi.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/libs/org.simalliance.openmobileapi.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_memory.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_memory.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_security.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_security.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_sim_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_sim_card.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_security.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_security.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_sim_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_sim_card.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_memory.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_memory.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_view_comfy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_view_comfy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_view_comfy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_view_comfy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_security.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_security.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_sim_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_sim_card.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_view_comfy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_view_comfy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_security.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_security.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_sim_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_sim_card.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_memory.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_security.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_security.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_sim_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_sim_card.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-hdpi/ic_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-mdpi/ic_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xhdpi/ic_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_view_comfy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_view_comfy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_view_comfy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_view_comfy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxhdpi/ic_info_outline.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_info_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bertrandmartel/sim-password-wallet/HEAD/app/src/main/res/drawable-xxxhdpi/ic_info_outline.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | **/build/
11 | **/out/
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 01 19:53: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 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #342347
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ripple.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/open_source_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/line_divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ripple.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 | 12dp
7 | 60dp
8 |
9 | 9sp
10 | 5sp
11 | 6sp
12 | 6sp
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/open_source_items.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
16 |
17 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/password_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
23 |
24 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion "26.0.1"
6 | defaultConfig {
7 | applicationId "fr.bmartel.smartcard.passwordwallet"
8 | minSdkVersion 17
9 | targetSdkVersion 26
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | lintOptions {
21 | abortOnError false
22 | }
23 | }
24 |
25 | dependencies {
26 | provided files('libs/org.simalliance.openmobileapi.jar')
27 | compile "com.android.support:appcompat-v7:26.0.2"
28 | compile 'com.android.support:design:26.0.2'
29 | compile 'com.android.support:recyclerview-v7:26.0.2'
30 | compile 'fr.bmartel:lollipin:2.01'
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/about_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
21 |
27 |
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/akinaru/Android/Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | java:
3 | version: oraclejdk8
4 |
5 | test:
6 | override:
7 | - ./gradlew build jacocoTestReport
8 | post:
9 | - ./gradlew jacocoRootReport coveralls
10 | - mkdir $CIRCLE_ARTIFACTS/apk
11 | - mkdir $CIRCLE_ARTIFACTS/cap
12 | - mv applet/build/javacard/*.cap $CIRCLE_ARTIFACTS/cap
13 |
14 | dependencies:
15 | pre:
16 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
17 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
18 | - echo y | android update sdk -u -a -t tools
19 | - echo y | android update sdk -u -a -t platform-tools
20 | - echo y | android update sdk -u -a -t build-tools-26.0.1
21 | - echo y | android update sdk -u -a -t android-26
22 | - echo y | android update sdk -u -a -t extra-google-m2repository
23 | - echo y | android update sdk -u -a -t extra-android-m2repository
24 | - git submodule update --init --recursive
25 | cache_directories:
26 | - ~/.m2
27 | - ~/.gradle
28 |
29 | general:
30 | branches:
31 | ignore:
32 | - gh-pages
33 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/drawer_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/applet/src/test/java/fr/bmartel/smartcard/passwordwallet/JavaCardTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet;
2 |
3 | import javax.smartcardio.CardException;
4 | import javax.smartcardio.CommandAPDU;
5 | import javax.smartcardio.ResponseAPDU;
6 |
7 | import fr.bmartel.smartcard.passwordwallet.utils.TestUtils;
8 |
9 | public class JavaCardTest {
10 |
11 | private final static byte[] CMD_VERIFY_PIN_CODE = new byte[]{(byte) 0x90, 0x20, 0x00, (byte) 0x80};
12 |
13 | public ResponseAPDU transmitCommand(CommandAPDU data) throws CardException {
14 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
15 | return TestSuite.getCard().getBasicChannel().transmit(data);
16 | } else {
17 | return TestSuite.getSimulator().transmitCommand(data);
18 | }
19 | }
20 |
21 | protected void verifyPinCode(byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
22 | TestUtils.sendCmdBatch(this, CMD_VERIFY_PIN_CODE, data, expectedSw, expectedResponse);
23 | }
24 | }
--------------------------------------------------------------------------------
/applet/src/test/java/fr/bmartel/smartcard/passwordwallet/model/Password.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet.model;
2 |
3 | import fr.bmartel.smartcard.passwordwallet.utils.TestUtils;
4 |
5 | public class Password {
6 |
7 | private byte[] id;
8 | private byte[] username;
9 | private byte[] password;
10 | private byte[] oldId;
11 |
12 | public Password(byte[] oldId, byte[] id, byte[] username, byte[] password) {
13 | this.id = id;
14 | this.username = username;
15 | this.password = password;
16 | this.oldId = oldId;
17 | }
18 |
19 | public Password(byte[] id, byte[] username, byte[] password) {
20 | this.id = id;
21 | this.username = username;
22 | this.password = password;
23 | }
24 |
25 | public byte[] getFullApdu() {
26 | return TestUtils.concatByteArray(oldId, id, username, password);
27 | }
28 |
29 | public byte[] getId() {
30 | return id;
31 | }
32 |
33 | public byte[] getData() {
34 | return TestUtils.concatByteArray(username, password);
35 | }
36 | }
--------------------------------------------------------------------------------
/applet/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 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/uicc/ApduResponse.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet.uicc;
2 |
3 | import java.util.Arrays;
4 |
5 | /**
6 | * APDU Response object.
7 | */
8 | public class ApduResponse {
9 |
10 | private byte[] data;
11 |
12 | public ApduResponse(byte[] data) {
13 | this.data = data;
14 | }
15 |
16 | /**
17 | * Check if state is successful (0x9000 received).
18 | *
19 | * @return success state
20 | */
21 | public boolean isSuccessful() {
22 | if (data.length >= 2) {
23 | return (data[data.length - 2] == (byte) 0x90) && data[data.length - 1] == (byte) 0x00;
24 | }
25 | return false;
26 | }
27 |
28 | /**
29 | * Get data payload.
30 | *
31 | * @return
32 | */
33 | public byte[] getData() {
34 | if (data.length < 2) {
35 | return new byte[]{};
36 | }
37 | return Arrays.copyOfRange(data, 0, data.length - 2);
38 | }
39 |
40 | /**
41 | * Get Status word.
42 | *
43 | * @return
44 | */
45 | public short getStatus() {
46 | if (data.length < 2) {
47 | return 0x0000;
48 | }
49 | return (short) (((data[data.length - 2] & 0xFF) << 8) + (data[data.length - 1] & 0xFF));
50 | }
51 |
52 | public byte[] getResponse() {
53 | return data;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/utils/HexUtils.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet.utils;
2 |
3 | /**
4 | * Some functions used to manage hex
5 | *
6 | * @author Bertrand Martel
7 | */
8 | public class HexUtils {
9 |
10 | /**
11 | * https://stackoverflow.com/a/18714790/2614364
12 | *
13 | * @param s
14 | * @return
15 | */
16 | public static byte[] hexStringToByteArray(String s) {
17 | int len = s.length();
18 | byte[] data = new byte[len / 2];
19 |
20 | for (int i = 0; i < len; i += 2) {
21 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
22 | }
23 | return data;
24 | }
25 |
26 | final protected static char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
27 |
28 | /**
29 | * https://stackoverflow.com/a/18714790/2614364
30 | *
31 | * @param bytes
32 | * @return
33 | */
34 | public static String byteArrayToHexString(byte[] bytes) {
35 | char[] hexChars = new char[bytes.length * 2];
36 | int v;
37 |
38 | for (int j = 0; j < bytes.length; j++) {
39 | v = bytes[j] & 0xFF;
40 | hexChars[j * 2] = hexArray[v >>> 4];
41 | hexChars[j * 2 + 1] = hexArray[v & 0x0F];
42 | }
43 | return new String(hexChars);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/applet/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 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/common/SimpleDividerItemDecoration.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet.common;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.drawable.Drawable;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.View;
8 |
9 | import fr.bmartel.smartcard.passwordwallet.R;
10 |
11 | /**
12 | * draw line separation for all item of recyclerview.
13 | *
14 | * @author Bertrand Martel
15 | */
16 | public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration {
17 | private Drawable mDivider;
18 |
19 | public SimpleDividerItemDecoration(Context context) {
20 | mDivider = context.getResources().getDrawable(R.drawable.line_divider);
21 | }
22 |
23 | @Override
24 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
25 | int left = parent.getPaddingLeft();
26 | int right = parent.getWidth() - parent.getPaddingRight();
27 |
28 | int childCount = parent.getChildCount();
29 | for (int i = 0; i < childCount; i++) {
30 | View child = parent.getChildAt(i);
31 |
32 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
33 |
34 | int top = child.getBottom() + params.bottomMargin;
35 | int bottom = top + mDivider.getIntrinsicHeight();
36 |
37 | mDivider.setBounds(left, top, right, bottom);
38 | mDivider.draw(c);
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/applet/src/test/java/fr/bmartel/smartcard/passwordwallet/StateTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet;
2 |
3 | import org.globalplatform.GPSystem;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import javax.smartcardio.CardException;
8 | import javax.smartcardio.CommandAPDU;
9 | import javax.smartcardio.ResponseAPDU;
10 |
11 | import fr.bmartel.smartcard.passwordwallet.utils.TestUtils;
12 |
13 | import static org.junit.Assert.assertEquals;
14 |
15 | public class StateTest extends JavaCardTest {
16 |
17 | @Before
18 | public void setup() throws CardException {
19 | TestSuite.setup();
20 | verifyPinCode(TestUtils.TEST_PIN_CODE, 0x9000, new byte[]{});
21 | }
22 |
23 | @Test
24 | public void checkState() throws CardException {
25 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x50, 0x00, 0x00}, new byte[]{}));
26 | ResponseAPDU response = transmitCommand(c);
27 | assertEquals(0x9000, response.getSW());
28 | assertEquals("data length", 1, response.getData().length);
29 | assertEquals(GPSystem.CARD_SECURED, response.getData()[0]);
30 | }
31 |
32 | @Test
33 | public void checkPinCodeState() throws CardException {
34 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x51, 0x00, 0x00}, new byte[]{}));
35 | ResponseAPDU response = transmitCommand(c);
36 | assertEquals(0x9000, response.getSW());
37 | assertEquals("data length", 0, response.getData().length);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/inter/IServiceConnection.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.inter;
20 |
21 | public interface IServiceConnection {
22 | void onServiceConnected();
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/password_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
20 |
21 |
26 |
27 |
28 |
29 |
34 |
35 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/inter/IFragmentOptions.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.inter;
20 |
21 | /**
22 | * Fragment options inteface.
23 | */
24 | public interface IFragmentOptions {
25 |
26 | /**
27 | * Called when toolbar should be updated.
28 | */
29 | void onUpdateToolbar();
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/inter/IDeletionListener.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.inter;
20 |
21 | /**
22 | * Delete button listener.
23 | *
24 | * @author Bertrand Martel
25 | */
26 | public interface IDeletionListener {
27 |
28 | /**
29 | * Called when user click on delete button.
30 | */
31 | void onDelete();
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/inter/ICompletionListener.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.inter;
20 |
21 | /**
22 | * Completion listener for updating the working mode.
23 | *
24 | * @author Bertrand Martel
25 | */
26 | public interface ICompletionListener {
27 |
28 | /**
29 | * Called when migration from a mode to another has completed.
30 | */
31 | void onComplete();
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/inter/IDialog.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.inter;
20 |
21 | import android.app.Dialog;
22 |
23 | /**
24 | * interface keeping track of opened dialog.
25 | *
26 | * @author Bertrand Martel
27 | */
28 | public interface IDialog {
29 |
30 | /**
31 | * set opened dialog in activity
32 | *
33 | * @param dialog
34 | */
35 | void setCurrentDialog(Dialog dialog);
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/mode_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
21 |
22 |
27 |
28 |
33 |
34 |
35 |
43 |
44 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/inter/IViewHolderClickListener.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.inter;
20 |
21 | import android.view.View;
22 |
23 | /**
24 | * click listener for recyclerview item.
25 | *
26 | * @author Bertrand Martel
27 | */
28 | public interface IViewHolderClickListener {
29 |
30 | /**
31 | * triggered when user click on packet in recycler view
32 | *
33 | * @param view
34 | */
35 | void onClick(View view);
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/fragment/MainFragmentAbstr.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.fragment;
20 |
21 | import fr.bmartel.smartcard.passwordwallet.inter.IBaseActivity;
22 |
23 | /**
24 | * Common fragment abstract class for All fragments.
25 | *
26 | * @author Bertrand Martel
27 | */
28 | public class MainFragmentAbstr extends android.support.v4.app.Fragment {
29 |
30 | protected IBaseActivity getRootActivity() {
31 | return (IBaseActivity) getActivity();
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/password_item_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
27 |
28 |
37 |
38 |
44 |
45 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SIM Password Wallet
3 | SIM Wallet
4 | Open navigation drawer
5 | Close navigation drawer
6 | open source components
7 | close session
8 | change pin code
9 | about
10 | OK
11 | about
12 | Open Source components
13 | \u00a9 2017 Bertrand Martel
14 | https://github.com/bertrandmartel/sim-password-wallet
15 | save
16 | delete
17 | switch mode
18 | SIM Password Wallet
19 | add password entry
20 | no password created yet
21 | Password list
22 | Edit password
23 | Create password
24 | show decrypted password
25 | show password
26 | store password on SIM
27 | store password on App
28 | Select the location where password should be stored (only encrypted password are stored, for App storage encryption/decryption is done on SIM card)
29 | Storage mode
30 |
31 |
--------------------------------------------------------------------------------
/applet/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'javacard'
2 |
3 | javacard {
4 |
5 | config {
6 | jckit '../oracle_javacard_sdks/jc221_kit'
7 |
8 | cap {
9 | packageName 'fr.bmartel.smartcard.passwordwallet'
10 | version '0.1'
11 | aid 'D2:76:00:01:18:00:02:FF:49:50:25:89:C0:01:00:00'
12 | output 'applet.cap'
13 | applet {
14 | className 'fr.bmartel.smartcard.passwordwallet.PasswordWalletApplet'
15 | aid 'D2:76:00:01:18:00:02:FF:49:50:25:89:C0:01:9B:01'
16 | }
17 |
18 | dependencies {
19 | remote 'fr.bmartel:gplatform:2.1.1'
20 | }
21 | }
22 | }
23 | scripts {
24 | script {
25 | name 'select'
26 | apdu '00:A4:04:00:10:D2:76:00:01:18:00:02:FF:49:50:25:89:C0:01:9B:01'
27 | }
28 | script {
29 | name 'encryptEmpty'
30 | apdu '90:10:00:00:00'
31 | }
32 | script {
33 | name 'encrypt16'
34 | apdu '90:10:00:00:02:01:02'
35 | }
36 | script {
37 | name 'encrypt32'
38 | apdu '90:10:00:00:11:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F:10:11'
39 | }
40 | script {
41 | name 'mode'
42 | apdu '9040000000'
43 | }
44 | task {
45 | name 'sendEncryptEmpty'
46 | scripts 'select', 'encryptEmpty'
47 | }
48 | task {
49 | name 'sendEncrypt16'
50 | scripts 'select', 'encrypt16'
51 | }
52 | task {
53 | name 'sendEncrypt32'
54 | scripts 'select', 'encrypt32'
55 | }
56 | task {
57 | name 'getMode'
58 | scripts 'select', 'mode'
59 | }
60 |
61 | }
62 | }
63 |
64 | test {
65 | systemProperty 'testMode', System.getProperty("testMode") ?: 'simulator'
66 | include '**/TestSuite.class'
67 | outputs.upToDateWhen { false }
68 | testLogging {
69 | showStandardStreams = true
70 | exceptionFormat = 'full'
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/uicc/PinCodeResult.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.uicc;
20 |
21 | /**
22 | * Pin code response data model.
23 | *
24 | * @author Bertrand Martel
25 | */
26 | public class PinCodeResult {
27 |
28 | /**
29 | * pin code state.
30 | */
31 | private boolean valid;
32 |
33 | /**
34 | * number of retry.
35 | */
36 | private int retry;
37 |
38 | public PinCodeResult(boolean valid, int retry) {
39 | this.valid = valid;
40 | this.retry = retry;
41 | }
42 |
43 | public boolean isValid() {
44 | return valid;
45 | }
46 |
47 | public int getRetry() {
48 | return retry;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
14 |
15 |
19 |
20 |
21 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
50 |
51 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/model/Password.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.model;
20 |
21 | /**
22 | * Password model.
23 | *
24 | * @author Bertrand Martel
25 | */
26 | public class Password {
27 |
28 | private String mTitle;
29 |
30 | private String mUsername;
31 |
32 | private byte[] mPassword;
33 |
34 | public Password(String title, String username, byte[] password) {
35 | mTitle = title;
36 | mUsername = username;
37 | mPassword = password;
38 | }
39 |
40 | public String getTitle() {
41 | return mTitle;
42 | }
43 |
44 | public String getUsername() {
45 | return mUsername;
46 | }
47 |
48 | public byte[] getPassword() {
49 | return mPassword;
50 | }
51 |
52 | public void setTitle(String title) {
53 | mTitle = title;
54 | }
55 |
56 | public void setUsername(String username) {
57 | mUsername = username;
58 | }
59 |
60 | public void setPassword(byte[] password) {
61 | mPassword = password;
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/fragment/ListFragmentAbstr.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.fragment;
20 |
21 | import android.support.v4.widget.SwipeRefreshLayout;
22 | import android.support.v7.widget.RecyclerView;
23 | import android.widget.FrameLayout;
24 | import android.widget.RelativeLayout;
25 |
26 | import java.util.List;
27 |
28 | import fr.bmartel.smartcard.passwordwallet.adapter.PasswordAdapter;
29 | import fr.bmartel.smartcard.passwordwallet.model.Password;
30 |
31 | /**
32 | * Common fragment.
33 | *
34 | * @author Bertrand Martel
35 | */
36 | public abstract class ListFragmentAbstr extends MainFragmentAbstr {
37 |
38 | protected RecyclerView mPasswordListView;
39 |
40 | protected PasswordAdapter mPasswordAdapter;
41 |
42 | protected List mPasswordList;
43 |
44 | protected SwipeRefreshLayout mSwipeRefreshLayout;
45 |
46 | protected FrameLayout mEmptyFrame;
47 |
48 | protected RelativeLayout mDisplayFrame;
49 |
50 | protected void setTitle(String title) {
51 | getRootActivity().setToolbarTitle(title);
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/dialog/OpenSourceItemsDialog.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.dialog;
20 |
21 | import android.app.AlertDialog;
22 | import android.content.Context;
23 | import android.content.DialogInterface;
24 | import android.view.LayoutInflater;
25 | import android.widget.ListView;
26 |
27 | import fr.bmartel.smartcard.passwordwallet.R;
28 | import fr.bmartel.smartcard.passwordwallet.adapter.OpenSourceItemsAdapter;
29 |
30 | /**
31 | * open source components dialog.
32 | *
33 | * @author Bertrand Martel
34 | */
35 | public class OpenSourceItemsDialog extends AlertDialog {
36 |
37 | public OpenSourceItemsDialog(Context context) {
38 | super(context);
39 |
40 | LayoutInflater inflater = LayoutInflater.from(context);
41 | ListView listview = (ListView) inflater.inflate(R.layout.open_source_list, null);
42 | listview.setAdapter(new OpenSourceItemsAdapter(context));
43 |
44 | setView(listview);
45 | setTitle(R.string.open_source_items);
46 | setButton(DialogInterface.BUTTON_POSITIVE, context.getResources().getString(R.string.dialog_ok),
47 | (OnClickListener) null);
48 | }
49 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/dialog/AboutDialog.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.dialog;
20 |
21 | import android.app.AlertDialog;
22 | import android.content.Context;
23 | import android.content.DialogInterface;
24 | import android.view.LayoutInflater;
25 | import android.view.View;
26 | import android.widget.TextView;
27 |
28 | import fr.bmartel.smartcard.passwordwallet.BuildConfig;
29 | import fr.bmartel.smartcard.passwordwallet.R;
30 |
31 | /**
32 | * About dialog
33 | *
34 | * @author Bertrand Martel
35 | */
36 | public class AboutDialog extends AlertDialog {
37 |
38 | public AboutDialog(Context context) {
39 | super(context);
40 |
41 | LayoutInflater inflater = getLayoutInflater();
42 | View dialoglayout = inflater.inflate(R.layout.about_dialog, null);
43 | setView(dialoglayout);
44 |
45 | TextView name = dialoglayout.findViewById(R.id.name);
46 | TextView copyright = dialoglayout.findViewById(R.id.copyright);
47 | TextView github_link = dialoglayout.findViewById(R.id.github_link);
48 |
49 | name.setText(context.getResources().getString(R.string.app_name) + " v" + BuildConfig.VERSION_NAME);
50 | copyright.setText(R.string.copyright);
51 | github_link.setText(R.string.github_link);
52 |
53 | setTitle(R.string.about);
54 | setButton(DialogInterface.BUTTON_POSITIVE, context.getResources().getString(R.string.dialog_ok),
55 | (OnClickListener) null);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/applet/src/test/java/fr/bmartel/smartcard/passwordwallet/ModeTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import javax.smartcardio.CardException;
7 | import javax.smartcardio.CommandAPDU;
8 | import javax.smartcardio.ResponseAPDU;
9 |
10 | import fr.bmartel.smartcard.passwordwallet.utils.TestUtils;
11 | import javacard.framework.ISO7816;
12 |
13 | import static org.junit.Assert.assertEquals;
14 |
15 | public class ModeTest extends JavaCardTest {
16 |
17 | @Before
18 | public void setup() throws CardException {
19 | TestSuite.setup();
20 | verifyPinCode(TestUtils.TEST_PIN_CODE, 0x9000, new byte[]{});
21 | }
22 |
23 | private void checkMode(byte expectedMode) throws CardException {
24 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x40, 0x00, 0x00}, new byte[]{}));
25 | ResponseAPDU response = transmitCommand(c);
26 | assertEquals(0x9000, response.getSW());
27 | assertEquals("data length", 1, response.getData().length);
28 | assertEquals(expectedMode, response.getData()[0]);
29 | }
30 |
31 | private void setMode(byte mode) throws CardException {
32 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x41, 0x00, 0x00, 0x01, mode}, new byte[]{}));
33 | ResponseAPDU response = transmitCommand(c);
34 | assertEquals(0x9000, response.getSW());
35 | assertEquals("data length", 0, response.getData().length);
36 | }
37 |
38 | @Test
39 | public void getMode() throws CardException {
40 | checkMode((byte) 0x01);
41 | }
42 |
43 | @Test
44 | public void getModeInvalid() throws CardException {
45 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x40, 0x00, 0x00, 0x01, 0x02}, new byte[]{}));
46 | ResponseAPDU response = transmitCommand(c);
47 | assertEquals(ISO7816.SW_DATA_INVALID, response.getSW());
48 | assertEquals("data length", 0, response.getData().length);
49 | }
50 |
51 | @Test
52 | public void setMode() throws CardException {
53 | setMode((byte) 0x02);
54 | checkMode((byte) 0x02);
55 | setMode((byte) 0x01);
56 | checkMode((byte) 0x01);
57 | }
58 |
59 | @Test
60 | public void setModeInvalidLength() throws CardException {
61 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x41, 0x00, 0x00, 0x02, 0x01, 0x02}, new byte[]{}));
62 | ResponseAPDU response = transmitCommand(c);
63 | assertEquals(ISO7816.SW_DATA_INVALID, response.getSW());
64 | assertEquals("data length", 0, response.getData().length);
65 | }
66 |
67 | @Test
68 | public void setModeInvalidMode() throws CardException {
69 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x41, 0x00, 0x00, 0x01, 0x03}, new byte[]{}));
70 | ResponseAPDU response = transmitCommand(c);
71 | assertEquals(ISO7816.SW_DATA_INVALID, response.getSW());
72 | assertEquals("data length", 0, response.getData().length);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SIM password wallet
2 |
3 | [](https://circleci.com/gh/bertrandmartel/sim-password-wallet)
4 | [](https://coveralls.io/github/bertrandmartel/sim-password-wallet?branch=master)
5 | [](LICENSE.md)
6 |
7 | SIM password wallet is an Android application interacting with a JavaCard applet installed on SIM card. This application stores a list of encrypted password. The encryption/decryption process only takes place on SIM card (UICC). It uses [Open Mobile API](https://github.com/seek-for-android/pool/wiki/SmartcardAPI) to communicate with SIM card.
8 |
9 | 2 modes are available :
10 |
11 | * passwords stored on application (SQLite db)
12 | * passwords stored on SIM card (EEPROM)
13 |
14 | You can switch from one mode to another easily and data are migrated correctly from the app storage to SIM EEPROM and vice versa.
15 |
16 | In both modes, AES 128 CBC is used on UICC to encrypt/decrypt passwords.
17 |
18 | A pin code must be configured the first time user open the app. The pin code security part comes from [this tutorial from Eric Vétillard](https://github.com/bertrandmartel/javacard-tutorial#jc101-password-pin--password-application-with-pin-security). This pin code can be changed using the options menu.
19 |
20 | 
21 |
22 | ## Build
23 |
24 | ```bash
25 | git clone git@github.com:bertrandmartel/sim-password-wallet.git
26 | cd sim-password-wallet
27 | ./gradlew clean build
28 | ```
29 |
30 | * to use the emulator with pcsc support, check [these instructions](https://github.com/bertrandmartel/pcsc-android-emulator)
31 |
32 | ## External libraries
33 |
34 | * [Lollipin](https://github.com/omadahealth/LolliPin)
35 |
36 | ## Dev libraries
37 |
38 | * [javacard tutorial](https://github.com/bertrandmartel/javacard-tutorial)
39 | * [seek for Android](https://github.com/seek-for-android/pool)
40 | * [JavaCard Gradle plugin](https://github.com/bertrandmartel/javacard-gradle-plugin)
41 | * [pcsc emulator](https://github.com/bertrandmartel/pcsc-android-emulator)
42 |
43 | ## License
44 |
45 | * applet is released under MIT :
46 |
47 | The MIT License (MIT) Copyright (c) 2017 Bertrand Martel
48 |
49 | * application is released under GPLV3 :
50 |
51 | Copyright (C) 2017 Bertrand Martel
52 |
53 | This program is free software; you can redistribute it and/or
54 | modify it under the terms of the GNU General Public License
55 | as published by the Free Software Foundation; either version 3
56 | of the License, or (at your option) any later version.
57 |
58 | SIM password wallet is distributed in the hope that it will be useful,
59 | but WITHOUT ANY WARRANTY; without even the implied warranty of
60 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61 | GNU General Public License for more details.
62 |
63 | You should have received a copy of the GNU General Public License
64 | along with SIM password wallet. If not, see .
65 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/adapter/OpenSourceItemsAdapter.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.adapter;
20 |
21 | import android.content.Context;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.widget.BaseAdapter;
26 | import android.widget.TextView;
27 |
28 | import fr.bmartel.smartcard.passwordwallet.R;
29 |
30 | /**
31 | * Adapter for open source projects
32 | *
33 | * @author Bertrand Martel
34 | */
35 | public class OpenSourceItemsAdapter extends BaseAdapter {
36 |
37 | private static final String[][] COMPONENTS = new String[][]{
38 | {"Lollipin", "https://github.com/omadahealth/LolliPin"},
39 | {"javacard tutorial", "https://github.com/bertrandmartel/javacard-tutorial"},
40 | {"(dev) pcsc emulator", "https://github.com/bertrandmartel/pcsc-android-emulator"},
41 | {"(dev) JavaCard Gradle plugin", "https://github.com/bertrandmartel/javacard-gradle-plugin"},
42 | {"(dev) seek for Android", " https://github.com/seek-for-android/pool"}
43 | };
44 |
45 | private LayoutInflater mInflater;
46 |
47 | public OpenSourceItemsAdapter(Context context) {
48 | mInflater = LayoutInflater.from(context);
49 | }
50 |
51 | @Override
52 | public int getCount() {
53 | return COMPONENTS.length;
54 | }
55 |
56 | @Override
57 | public Object getItem(int position) {
58 | return COMPONENTS[position];
59 | }
60 |
61 | @Override
62 | public long getItemId(int position) {
63 | return position;
64 | }
65 |
66 | @Override
67 | public View getView(int position, View convertView, ViewGroup parent) {
68 | if (convertView == null) {
69 | convertView = mInflater.inflate(R.layout.open_source_items, parent, false);
70 | }
71 |
72 | TextView title = convertView.findViewById(R.id.title);
73 | TextView url = convertView.findViewById(R.id.url);
74 |
75 | title.setText(COMPONENTS[position][0]);
76 | url.setText(COMPONENTS[position][1]);
77 |
78 | return convertView;
79 | }
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/db/PasswordReaderDbHelper.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.db;
20 |
21 | import android.content.Context;
22 | import android.database.sqlite.SQLiteDatabase;
23 | import android.database.sqlite.SQLiteOpenHelper;
24 | import android.provider.BaseColumns;
25 |
26 | /**
27 | * Helper for local database.
28 | *
29 | * @author Bertrand Martel
30 | */
31 | public class PasswordReaderDbHelper extends SQLiteOpenHelper {
32 |
33 | public final static short TITLE_MAX_SIZE = 32;
34 | public final static short USERNAME_MAX_SIZE = 64;
35 | public final static short PASSWORD_MAX_SIZE = 127;
36 |
37 | public static class PasswordEntry implements BaseColumns {
38 | public static final String TABLE_NAME = "pass";
39 | public static final String COLUMN_NAME_TITLE = "title";
40 | public static final String COLUMN_NAME_USERNAME = "username";
41 | public static final String COLUMN_NAME_PASSWORD = "password";
42 | }
43 |
44 | /**
45 | * create table.
46 | */
47 | private static final String SQL_CREATE_ENTRIES =
48 | "CREATE TABLE " + PasswordEntry.TABLE_NAME + " (" +
49 | PasswordEntry._ID + " INTEGER PRIMARY KEY," +
50 | PasswordEntry.COLUMN_NAME_TITLE + " VARCHAR(" + TITLE_MAX_SIZE + ")," +
51 | PasswordEntry.COLUMN_NAME_USERNAME + " VARCHAR(" + USERNAME_MAX_SIZE + ")," +
52 | PasswordEntry.COLUMN_NAME_PASSWORD + " VARCHAR(" + PASSWORD_MAX_SIZE + "))";
53 |
54 | /**
55 | * drop table.
56 | */
57 | private static final String SQL_DELETE_ENTRIES =
58 | "DROP TABLE IF EXISTS " + PasswordEntry.TABLE_NAME;
59 |
60 | public static final int DATABASE_VERSION = 1;
61 | public static final String DATABASE_NAME = "PasswordReader.db";
62 |
63 | public PasswordReaderDbHelper(Context context) {
64 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
65 | }
66 |
67 | public void onCreate(SQLiteDatabase db) {
68 | db.execSQL(SQL_CREATE_ENTRIES);
69 | }
70 |
71 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
72 | db.execSQL(SQL_DELETE_ENTRIES);
73 | onCreate(db);
74 | }
75 |
76 | public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
77 | onUpgrade(db, oldVersion, newVersion);
78 | }
79 | }
--------------------------------------------------------------------------------
/applet/src/test/java/fr/bmartel/smartcard/passwordwallet/utils/TestUtils.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet.utils;
2 |
3 | import com.licel.jcardsim.smartcardio.CardSimulator;
4 |
5 | import java.lang.reflect.Field;
6 | import java.nio.ByteBuffer;
7 | import java.util.Arrays;
8 |
9 | import javax.smartcardio.Card;
10 | import javax.smartcardio.CardException;
11 | import javax.smartcardio.CommandAPDU;
12 | import javax.smartcardio.ResponseAPDU;
13 |
14 | import fr.bmartel.smartcard.passwordwallet.JavaCardTest;
15 |
16 | import static org.junit.Assert.assertArrayEquals;
17 | import static org.junit.Assert.assertEquals;
18 |
19 | public class TestUtils {
20 |
21 | private final static byte[] CMD_SET_PIN_CODE = new byte[]{(byte) 0x90, 0x24, 0x00, (byte) 0x80};
22 |
23 | public final static byte[] TEST_PIN_CODE = new byte[]{0x00, 0x00, 0x00, 0x00};
24 |
25 | public static byte[] buildApdu(byte[] command, byte[] data) {
26 | byte[] apdu = new byte[command.length + data.length + 1];
27 | System.arraycopy(command, 0, apdu, 0, command.length);
28 | apdu[command.length] = (byte) data.length;
29 | System.arraycopy(data, 0, apdu, command.length + 1, data.length);
30 | return apdu;
31 | }
32 |
33 | public static void logData(byte[] data) {
34 | System.out.println(Arrays.toString(data));
35 | }
36 |
37 | public static int getInt(byte[] data) {
38 | return (((data[0] & 0xff) << 8) | (data[1] & 0xff));
39 | }
40 |
41 | public static byte[] getByte(int data) {
42 | byte[] out = ByteBuffer.allocate(4).putInt(data).array();
43 | return new byte[]{out[2], out[3]};
44 | }
45 |
46 | /**
47 | * Add n x 0x00 offset to a byte array (at the beginning)
48 | *
49 | * @param offset number of byte to set to 0x00 before the data
50 | * @param data the data
51 | * @return byte array with offset before data
52 | */
53 | public static byte[] addOffset(short offset, byte[] data) {
54 | byte[] resp = new byte[data.length + offset];
55 | for (int i = 0; i < offset; i++) {
56 | resp[i] = 0x00;
57 | }
58 | System.arraycopy(resp, offset, data, 0, data.length);
59 | return resp;
60 | }
61 |
62 | public static byte[] concatByteArray(byte[]... data) {
63 | int length = 0;
64 | for (byte[] item : data) {
65 | if (item != null)
66 | length += item.length;
67 | }
68 | byte[] resp = new byte[length];
69 | int offset = 0;
70 | for (byte[] item : data) {
71 | if (item != null) {
72 | System.arraycopy(item, 0, resp, offset, item.length);
73 | offset += item.length;
74 | }
75 | }
76 | return resp;
77 | }
78 |
79 | /**
80 | * Get class field by reflection.
81 | *
82 | * @param object class
83 | * @param name field name
84 | * @return field
85 | */
86 | public static Field getField(Class object, String name) {
87 | for (Field f : object.getDeclaredFields()) {
88 | f.setAccessible(true);
89 | if (f != null && f.getName().equals(name)) {
90 | return f;
91 | }
92 | }
93 | return null;
94 | }
95 |
96 | public static void sendCmdBatch(JavaCardTest card, byte[] cmd, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
97 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(cmd, data));
98 | ResponseAPDU response = card.transmitCommand(commandAPDU);
99 | assertEquals(expectedSw, response.getSW());
100 | assertArrayEquals(expectedResponse, response.getData());
101 | }
102 |
103 | public static void setPinCode(CardSimulator card, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
104 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_SET_PIN_CODE, data));
105 | ResponseAPDU response = card.transmitCommand(commandAPDU);
106 | assertEquals(expectedSw, response.getSW());
107 | assertArrayEquals(expectedResponse, response.getData());
108 | }
109 |
110 | public static void setPinCode(Card card, byte[] data, int expectedSw, byte[] expectedResponse) throws CardException {
111 | CommandAPDU commandAPDU = new CommandAPDU(TestUtils.buildApdu(CMD_SET_PIN_CODE, data));
112 | ResponseAPDU response = card.getBasicChannel().transmit(commandAPDU);
113 | //assertEquals(expectedSw, response.getSW());
114 | //assertArrayEquals(expectedResponse, response.getData());
115 | }
116 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/inter/IBaseActivity.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.inter;
20 |
21 | import android.support.v7.widget.Toolbar;
22 | import android.widget.ProgressBar;
23 |
24 | import java.util.List;
25 |
26 | import fr.bmartel.smartcard.passwordwallet.model.Password;
27 |
28 | /**
29 | * Interface used for the fragment to communicate with the main activity.
30 | *
31 | * @author Bertrand Martel
32 | */
33 | public interface IBaseActivity extends IDialog {
34 |
35 | /**
36 | * Get password list.
37 | *
38 | * @return
39 | */
40 | List getPasswordList();
41 |
42 | /**
43 | * Get toolbar object.
44 | *
45 | * @return
46 | */
47 | Toolbar getToolbar();
48 |
49 | /**
50 | * Set toolbar title.
51 | *
52 | * @param title
53 | */
54 | void setToolbarTitle(String title);
55 |
56 | /**
57 | * hide button in toolbar.
58 | */
59 | void hideMenuButton();
60 |
61 | /**
62 | * Set the deletion listener to be called when user click on delete button.
63 | *
64 | * @param listener
65 | */
66 | void setDeletionListener(IDeletionListener listener);
67 |
68 | /**
69 | * called when a new password should be created.
70 | *
71 | * @param title password title
72 | * @param username username
73 | * @param password password value
74 | * @return data payload
75 | */
76 | byte[] saveNewPassword(String title, String username, String password);
77 |
78 | /**
79 | * called when a password should be updated.
80 | *
81 | * @param formerTitle former password title
82 | * @param newTitle new password title
83 | * @param username new username value
84 | * @param password new password value
85 | * @return data payload
86 | */
87 | byte[] saveExistingPassword(String formerTitle, String newTitle, String username, String password);
88 |
89 | /**
90 | * Delete password entry.
91 | *
92 | * @param title password title
93 | */
94 | void deletePassword(String title);
95 |
96 | /**
97 | * Check if password is duplicate.
98 | *
99 | * @param title password tittle
100 | * @return true if password is duplicated
101 | */
102 | boolean checkDuplicatePassword(String title);
103 |
104 | /**
105 | * Decrypt password.
106 | *
107 | * @param password encrypted password
108 | * @return clear text password
109 | */
110 | String decrypt(byte[] password);
111 |
112 | /**
113 | * Set working mode.
114 | *
115 | * @param mode working mode
116 | * @param progress progress bar
117 | * @param listener deletion listener
118 | */
119 | void setMode(byte mode, ProgressBar progress, ICompletionListener listener);
120 |
121 | /**
122 | * called when correct pin code have been set and password fragment should be opened.
123 | */
124 | void onReady();
125 |
126 | /**
127 | * Get password entry.
128 | *
129 | * @param index index in password list
130 | * @return password entry
131 | */
132 | Password getPassword(int index);
133 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/utils/MenuUtils.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.utils;
20 |
21 | import android.app.Activity;
22 | import android.content.Intent;
23 | import android.support.v4.widget.DrawerLayout;
24 | import android.util.Log;
25 | import android.view.MenuItem;
26 |
27 | import com.github.orangegangsters.lollipin.lib.managers.AppLock;
28 |
29 | import java.io.IOException;
30 |
31 | import fr.bmartel.smartcard.passwordwallet.CustomPinActivity;
32 | import fr.bmartel.smartcard.passwordwallet.MainActivity;
33 | import fr.bmartel.smartcard.passwordwallet.R;
34 | import fr.bmartel.smartcard.passwordwallet.application.PasswordApplication;
35 | import fr.bmartel.smartcard.passwordwallet.dialog.AboutDialog;
36 | import fr.bmartel.smartcard.passwordwallet.dialog.OpenSourceItemsDialog;
37 | import fr.bmartel.smartcard.passwordwallet.inter.IDialog;
38 |
39 | /**
40 | * Some functions used to manage Menu.
41 | *
42 | * @author Bertrand Martel
43 | */
44 | public class MenuUtils {
45 |
46 | private final static String TAG = MenuUtils.class.getSimpleName();
47 |
48 | /**
49 | * Execute actions according to selected menu item.
50 | *
51 | * @param menuItem MenuItem object
52 | * @param mDrawer navigation drawer
53 | */
54 | public static void selectDrawerItem(Activity currentActivity,
55 | PasswordApplication application,
56 | MenuItem menuItem,
57 | DrawerLayout mDrawer,
58 | IDialog activity) {
59 |
60 | switch (menuItem.getItemId()) {
61 | case R.id.close_session:
62 | application.getUicc().closeChannel();
63 | try {
64 | application.getUicc().openChannel();
65 | } catch (IOException e) {
66 | Log.e(TAG, "IOException", e);
67 | }
68 | Intent intent = new Intent(currentActivity, CustomPinActivity.class);
69 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN);
70 | currentActivity.startActivityForResult(intent, MainActivity.REQUEST_UNLOCK_PIN);
71 | break;
72 | case R.id.change_pincode:
73 | application.getUicc().closeChannel();
74 | try {
75 | application.getUicc().openChannel();
76 | } catch (IOException e) {
77 | Log.e(TAG, "IOException", e);
78 | }
79 | intent = new Intent(currentActivity, CustomPinActivity.class);
80 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.CHANGE_PIN);
81 | currentActivity.startActivityForResult(intent, MainActivity.REQUEST_UNLOCK_PIN);
82 | break;
83 | case R.id.open_source_components: {
84 | OpenSourceItemsDialog dialog = new OpenSourceItemsDialog(currentActivity);
85 | activity.setCurrentDialog(dialog);
86 | dialog.show();
87 | break;
88 | }
89 | case R.id.about_app: {
90 | AboutDialog dialog = new AboutDialog(currentActivity);
91 | activity.setCurrentDialog(dialog);
92 | dialog.show();
93 | break;
94 | }
95 | }
96 | mDrawer.closeDrawers();
97 | }
98 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/dialog/ModeDialog.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.dialog;
20 |
21 | import android.app.Activity;
22 | import android.app.Dialog;
23 | import android.view.View;
24 | import android.view.WindowManager;
25 | import android.widget.Button;
26 | import android.widget.ProgressBar;
27 | import android.widget.RadioButton;
28 |
29 | import fr.bmartel.smartcard.passwordwallet.R;
30 | import fr.bmartel.smartcard.passwordwallet.application.PasswordApplication;
31 | import fr.bmartel.smartcard.passwordwallet.inter.IBaseActivity;
32 | import fr.bmartel.smartcard.passwordwallet.inter.ICompletionListener;
33 |
34 | /**
35 | * Mode dialog.
36 | *
37 | * @author Bertrand Martel
38 | */
39 | public class ModeDialog extends Dialog {
40 |
41 | public ModeDialog(final Activity rootActivity) {
42 | super(rootActivity);
43 |
44 | setContentView(R.layout.mode_dialog);
45 | WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
46 | lp.copyFrom(getWindow().getAttributes());
47 | lp.width = WindowManager.LayoutParams.MATCH_PARENT;
48 | lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
49 | getWindow().setAttributes(lp);
50 |
51 | final RadioButton simRadio = findViewById(R.id.radio_sim);
52 | final RadioButton appRadio = findViewById(R.id.radio_app);
53 | final ProgressBar transferProgress = findViewById(R.id.transferProgress);
54 |
55 | final IBaseActivity activity = (IBaseActivity) rootActivity;
56 |
57 | final PasswordApplication app = (PasswordApplication) rootActivity.getApplication();
58 |
59 | switch (app.mode) {
60 | case PasswordApplication.MODE_APP_STORAGE:
61 | appRadio.setChecked(true);
62 | break;
63 | case PasswordApplication.MODE_SIM_STORAGE:
64 | simRadio.setChecked(true);
65 | break;
66 | default:
67 | break;
68 | }
69 |
70 | setTitle(R.string.mode_title);
71 |
72 | final Button button = findViewById(R.id.confirm);
73 |
74 | button.setOnClickListener(new View.OnClickListener() {
75 | @Override
76 | public void onClick(View view) {
77 | button.setEnabled(false);
78 | if ((appRadio.isChecked() && app.mode == PasswordApplication.MODE_APP_STORAGE) ||
79 | (simRadio.isChecked() && app.mode == PasswordApplication.MODE_SIM_STORAGE)) {
80 | cancel();
81 | }
82 | if (appRadio.isChecked() && app.mode != PasswordApplication.MODE_APP_STORAGE) {
83 | activity.setMode(PasswordApplication.MODE_APP_STORAGE, transferProgress, new ICompletionListener() {
84 | @Override
85 | public void onComplete() {
86 | cancel();
87 | }
88 | });
89 | } else if (simRadio.isChecked() && app.mode != PasswordApplication.MODE_SIM_STORAGE) {
90 | activity.setMode(PasswordApplication.MODE_SIM_STORAGE, transferProgress, new ICompletionListener() {
91 | @Override
92 | public void onComplete() {
93 | cancel();
94 | }
95 | });
96 | }
97 | }
98 | });
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/uicc/CommandApdu.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet.uicc;
2 |
3 | /*
4 | * Copyright 2010 Giesecke & Devrient GmbH.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | /**
20 | * class needed for compatibility between open mobile API versions.
21 | */
22 | public class CommandApdu {
23 |
24 | protected int mCla = 0x00;
25 |
26 | protected int mIns = 0x00;
27 |
28 | protected int mP1 = 0x00;
29 |
30 | protected int mP2 = 0x00;
31 |
32 | protected int mLc = 0x00;
33 |
34 | protected byte[] mData = new byte[0];
35 |
36 | protected int mLe = 0x00;
37 |
38 | protected boolean mLeUsed = false;
39 |
40 | public CommandApdu(int cla, int ins, int p1, int p2) {
41 | mCla = cla;
42 | mIns = ins;
43 | mP1 = p1;
44 | mP2 = p2;
45 | }
46 |
47 | public CommandApdu() {
48 |
49 | }
50 |
51 | public CommandApdu(int cla, int ins, int p1, int p2, byte[] data) {
52 | mCla = cla;
53 | mIns = ins;
54 | mLc = data.length;
55 | mP1 = p1;
56 | mP2 = p2;
57 | mData = data;
58 | }
59 |
60 | public CommandApdu(int cla, int ins, int p1, int p2, byte[] data, int le) {
61 | mCla = cla;
62 | mIns = ins;
63 | mLc = data.length;
64 | mP1 = p1;
65 | mP2 = p2;
66 | mData = data;
67 | mLe = le;
68 | mLeUsed = true;
69 | }
70 |
71 | public CommandApdu(int cla, int ins, int p1, int p2, int le) {
72 | mCla = cla;
73 | mIns = ins;
74 | mP1 = p1;
75 | mP2 = p2;
76 | mLe = le;
77 | mLeUsed = true;
78 | }
79 |
80 | public void setP1(int p1) {
81 | mP1 = p1;
82 | }
83 |
84 | public void setP2(int p2) {
85 | mP2 = p2;
86 | }
87 |
88 | public void setData(byte[] data) {
89 | mLc = data.length;
90 | mData = data;
91 | }
92 |
93 | public void setLe(int le) {
94 | mLe = le;
95 | mLeUsed = true;
96 | }
97 |
98 | public int getP1() {
99 | return mP1;
100 | }
101 |
102 | public int getP2() {
103 | return mP2;
104 | }
105 |
106 | public int getLc() {
107 | return mLc;
108 | }
109 |
110 | public byte[] getData() {
111 | return mData;
112 | }
113 |
114 | public int getLe() {
115 | return mLe;
116 | }
117 |
118 | public byte[] toBytes() {
119 | int length = 4; // CLA, INS, P1, P2
120 | if (mData.length != 0) {
121 | length += 1; // LC
122 | length += mData.length; // DATA
123 | }
124 | if (mLeUsed) {
125 | length += 1; // LE
126 | }
127 |
128 | byte[] apdu = new byte[length];
129 |
130 | int index = 0;
131 | apdu[index] = (byte) mCla;
132 | index++;
133 | apdu[index] = (byte) mIns;
134 | index++;
135 | apdu[index] = (byte) mP1;
136 | index++;
137 | apdu[index] = (byte) mP2;
138 | index++;
139 | if (mData.length != 0) {
140 | apdu[index] = (byte) mLc;
141 | index++;
142 | System.arraycopy(mData, 0, apdu, index, mData.length);
143 | index += mData.length;
144 | }
145 | if (mLeUsed) {
146 | apdu[index] += (byte) mLe; // LE
147 | }
148 |
149 | return apdu;
150 | }
151 |
152 | public static boolean compareHeaders(byte[] header1, byte[] mask, byte[] header2) {
153 | if (header1.length < 4 || header2.length < 4) {
154 | return false;
155 | }
156 | byte[] compHeader = new byte[4];
157 | compHeader[0] = (byte) (header1[0] & mask[0]);
158 | compHeader[1] = (byte) (header1[1] & mask[1]);
159 | compHeader[2] = (byte) (header1[2] & mask[2]);
160 | compHeader[3] = (byte) (header1[3] & mask[3]);
161 |
162 | if (((byte) compHeader[0] == (byte) header2[0])
163 | && ((byte) compHeader[1] == (byte) header2[1])
164 | && ((byte) compHeader[2] == (byte) header2[2])
165 | && ((byte) compHeader[3] == (byte) header2[3])) {
166 | return true;
167 | }
168 | return false;
169 | }
170 |
171 | public CommandApdu clone() {
172 | CommandApdu apdu = new CommandApdu();
173 | apdu.mCla = mCla;
174 | apdu.mIns = mIns;
175 | apdu.mP1 = mP1;
176 | apdu.mP2 = mP2;
177 | apdu.mLc = mLc;
178 | apdu.mData = new byte[mData.length];
179 | System.arraycopy(mData, 0, apdu.mData, 0, mData.length);
180 | apdu.mLe = mLe;
181 | apdu.mLeUsed = mLeUsed;
182 | return apdu;
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------
/applet/src/main/java/fr/bmartel/smartcard/passwordwallet/PasswordEntry.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Bertrand Martel
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package fr.bmartel.smartcard.passwordwallet;
25 |
26 | import javacard.framework.JCSystem;
27 | import javacard.framework.Util;
28 |
29 | /**
30 | * Password Entry model used to store title, username and password.
31 | *
32 | * @author Bertrand Martel
33 | */
34 | public class PasswordEntry {
35 |
36 | public static short SIZE_ID = 32;
37 | public static short SIZE_USERNAME = 64;
38 | public static short SIZE_PASSWORD = 128;
39 |
40 | private PasswordEntry next;
41 | private static PasswordEntry first;
42 | private static PasswordEntry deleted;
43 |
44 | private byte[] id;
45 | private byte[] username;
46 | private byte[] password;
47 |
48 | private byte idLength;
49 | private byte userNameLength;
50 | private byte passwordLength;
51 |
52 | private PasswordEntry() {
53 | id = new byte[SIZE_ID];
54 | username = new byte[SIZE_USERNAME];
55 | password = new byte[SIZE_PASSWORD];
56 | next = first;
57 | first = this;
58 | }
59 |
60 | static PasswordEntry getInstance() {
61 | if (deleted == null) {
62 | return new PasswordEntry();
63 | } else {
64 | PasswordEntry instance = deleted;
65 | deleted = instance.next;
66 | instance.next = first;
67 | first = instance;
68 | return instance;
69 | }
70 | }
71 |
72 | static PasswordEntry search(byte[] buf, short ofs, byte len) {
73 | for (PasswordEntry pe = first; pe != null; pe = pe.next) {
74 | if (pe.idLength != len) continue;
75 | if (Util.arrayCompare(pe.id, (short) 0, buf, ofs, len) == 0)
76 | return pe;
77 | }
78 | return null;
79 | }
80 |
81 | public static PasswordEntry getFirst() {
82 | return first;
83 | }
84 |
85 | private void remove() {
86 | if (first == this) {
87 | first = next;
88 | } else {
89 | for (PasswordEntry pe = first; pe != null; pe = pe.next)
90 | if (pe.next == this)
91 | pe.next = next;
92 | }
93 | }
94 |
95 | private void recycle() {
96 | next = deleted;
97 | idLength = 0;
98 | userNameLength = 0;
99 | passwordLength = 0;
100 | deleted = this;
101 | }
102 |
103 | static void delete(byte[] buf, short ofs, byte len) {
104 | PasswordEntry pe = search(buf, ofs, len);
105 | if (pe != null) {
106 | JCSystem.beginTransaction();
107 | pe.remove();
108 | pe.recycle();
109 | JCSystem.commitTransaction();
110 | }
111 | }
112 |
113 | byte getId(byte[] buf, short ofs) {
114 | Util.arrayCopy(id, (short) 0, buf, ofs, idLength);
115 | return idLength;
116 | }
117 |
118 | byte getUserName(byte[] buf, short ofs) {
119 | Util.arrayCopy(username, (short) 0, buf, ofs, userNameLength);
120 | return userNameLength;
121 | }
122 |
123 | byte getPassword(byte[] buf, short ofs) {
124 | Util.arrayCopy(password, (short) 0, buf, ofs, passwordLength);
125 | return passwordLength;
126 | }
127 |
128 | public byte getIdLength() {
129 | return idLength;
130 | }
131 |
132 | public PasswordEntry getNext() {
133 | return next;
134 | }
135 |
136 | public void setId(byte[] buf, short ofs, byte len) {
137 | Util.arrayCopy(buf, ofs, id, (short) 0, len);
138 | idLength = len;
139 | }
140 |
141 | public void setUserName(byte[] buf, short ofs, byte len) {
142 | Util.arrayCopy(buf, ofs, username, (short) 0, len);
143 | userNameLength = len;
144 | }
145 |
146 | public void setPassword(byte[] buf, short ofs, byte len) {
147 | Util.arrayCopy(buf, ofs, password, (short) 0, len);
148 | passwordLength = len;
149 | }
150 |
151 | public byte getPasswordLength() {
152 | return passwordLength;
153 | }
154 |
155 | public byte[] getPasword() {
156 | return password;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet;
20 |
21 | import android.app.Dialog;
22 | import android.content.Intent;
23 | import android.os.Bundle;
24 | import android.view.Menu;
25 | import android.view.MenuItem;
26 |
27 | import com.github.orangegangsters.lollipin.lib.managers.AppLock;
28 |
29 | import fr.bmartel.smartcard.passwordwallet.application.PasswordApplication;
30 | import fr.bmartel.smartcard.passwordwallet.fragment.PasswordFragment;
31 | import fr.bmartel.smartcard.passwordwallet.inter.IServiceConnection;
32 |
33 | /**
34 | * Main activity.
35 | *
36 | * @author Bertrand Martel
37 | */
38 | public class MainActivity extends BaseActivity {
39 |
40 | /**
41 | * one dialog to show above the activity. We dont want to have multiple Dialog above each other.
42 | */
43 | private Dialog mDialog;
44 |
45 | /**
46 | * code received when the activity is unlocked.
47 | */
48 | public static final int REQUEST_UNLOCK_PIN = 11;
49 |
50 | private PasswordApplication mApplication;
51 |
52 | @Override
53 | public void onCreate(Bundle savedInstanceState) {
54 | setLayout(R.layout.activity_main);
55 | super.onCreate(savedInstanceState);
56 |
57 | mApplication = (PasswordApplication) getApplication();
58 |
59 | if (mApplication.isConnected()) {
60 | unlock();
61 | } else {
62 | mApplication.setServiceConnection(new IServiceConnection() {
63 | @Override
64 | public void onServiceConnected() {
65 | unlock();
66 | }
67 | });
68 | }
69 |
70 | }
71 |
72 | /**
73 | * unlock activity.
74 | */
75 | private void unlock() {
76 | if (!mApplication.isPinCodeChecked()) {
77 | Intent intent = new Intent(MainActivity.this, CustomPinActivity.class);
78 | if (mApplication.isCardSecured()) {
79 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN);
80 | startActivityForResult(intent, REQUEST_UNLOCK_PIN);
81 | } else {
82 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.ENABLE_PINLOCK);
83 | startActivityForResult(intent, REQUEST_UNLOCK_PIN);
84 | }
85 | } else {
86 | onCorrectPinCode();
87 | }
88 | }
89 |
90 | @Override
91 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
92 | super.onActivityResult(requestCode, resultCode, data);
93 |
94 | switch (requestCode) {
95 | case REQUEST_UNLOCK_PIN:
96 | if (mApplication.isPinCodeChecked()) {
97 | onCorrectPinCode();
98 | } else {
99 | finish();
100 | }
101 | break;
102 | }
103 | }
104 |
105 | @Override
106 | public void onReady() {
107 | mFragment = new PasswordFragment();
108 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment_frame, mFragment).commit();
109 | }
110 |
111 | @Override
112 | protected void onResume() {
113 | super.onResume();
114 | }
115 |
116 | @Override
117 | public boolean onPrepareOptionsMenu(Menu menu) {
118 | super.onPrepareOptionsMenu(menu);
119 | return true;
120 | }
121 |
122 | @Override
123 | public boolean onOptionsItemSelected(MenuItem item) {
124 | return true;
125 | }
126 |
127 |
128 | @Override
129 | public void setCurrentDialog(Dialog dialog) {
130 | mDialog = dialog;
131 | }
132 |
133 | @Override
134 | protected void onDestroy() {
135 | super.onDestroy();
136 | if (mDialog != null) {
137 | mDialog.dismiss();
138 | }
139 | }
140 |
141 | @Override
142 | public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
143 | mFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
144 | }
145 | }
--------------------------------------------------------------------------------
/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 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/applet/src/test/java/fr/bmartel/smartcard/passwordwallet/TestSuite.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet;
2 |
3 | import com.licel.jcardsim.smartcardio.CardSimulator;
4 | import com.licel.jcardsim.utils.AIDUtil;
5 |
6 | import org.junit.AfterClass;
7 | import org.junit.BeforeClass;
8 | import org.junit.runner.RunWith;
9 | import org.junit.runners.Suite;
10 | import org.junit.runners.Suite.SuiteClasses;
11 |
12 | import java.io.OutputStream;
13 | import java.security.NoSuchAlgorithmException;
14 | import java.util.List;
15 |
16 | import javax.smartcardio.Card;
17 | import javax.smartcardio.CardException;
18 | import javax.smartcardio.CardTerminal;
19 | import javax.smartcardio.CardTerminals;
20 | import javax.smartcardio.CardTerminals.State;
21 | import javax.smartcardio.CommandAPDU;
22 | import javax.smartcardio.ResponseAPDU;
23 | import javax.smartcardio.TerminalFactory;
24 |
25 | import apdu4j.LoggingCardTerminal;
26 | import apdu4j.TerminalManager;
27 | import fr.bmartel.smartcard.passwordwallet.utils.TestUtils;
28 | import javacard.framework.AID;
29 |
30 | import static org.junit.Assert.assertEquals;
31 | import static org.junit.Assert.fail;
32 |
33 | @RunWith(Suite.class)
34 | @SuiteClasses({
35 | EncryptDecryptTest.class,
36 | PasswordEntryTest.class,
37 | PasswordManagerTest.class,
38 | ModeTest.class,
39 | StateTest.class
40 | })
41 | public class TestSuite {
42 |
43 | private final static String APPLET_AID = "D2760001180002FF49502589C0019B01";
44 |
45 | private static CardSimulator mSimulator;
46 |
47 | private static boolean mInitialized;
48 |
49 | private static Card mCard;
50 |
51 | private static Card initGp() {
52 |
53 | try {
54 | TerminalFactory tf = TerminalManager.getTerminalFactory(null);
55 |
56 | CardTerminals terminals = tf.terminals();
57 |
58 | System.out.println("# Detected readers from " + tf.getProvider().getName());
59 | for (CardTerminal term : terminals.list()) {
60 | System.out.println((term.isCardPresent() ? "[*] " : "[ ] ") + term.getName());
61 | }
62 |
63 | // Select terminal(s) to work on
64 | List do_readers;
65 | do_readers = terminals.list(State.CARD_PRESENT);
66 |
67 | if (do_readers.size() == 0) {
68 | fail("No smart card readers with a card found");
69 | }
70 | // Work all readers
71 | for (CardTerminal reader : do_readers) {
72 | if (do_readers.size() > 1) {
73 | System.out.println("# " + reader.getName());
74 | }
75 |
76 | OutputStream o = null;
77 | reader = LoggingCardTerminal.getInstance(reader, o);
78 |
79 | Card card = null;
80 | // Establish connection
81 | try {
82 | card = reader.connect("*");
83 | // Use use apdu4j which by default uses jnasmartcardio
84 | // which uses real SCardBeginTransaction
85 | card.beginExclusive();
86 |
87 | return card;
88 |
89 | } catch (CardException e) {
90 | System.err.println("Could not connect to " + reader.getName() + ": " + TerminalManager.getExceptionMessage(e));
91 | continue;
92 | }
93 | }
94 | } catch (CardException e) {
95 | // Sensible wrapper for the different PC/SC exceptions
96 | if (TerminalManager.getExceptionMessage(e) != null) {
97 | System.out.println("PC/SC failure: " + TerminalManager.getExceptionMessage(e));
98 | } else {
99 | e.printStackTrace(); // TODO: remove
100 | fail("CardException, terminating");
101 | }
102 | } catch (NoSuchAlgorithmException e) {
103 | e.printStackTrace();
104 | }
105 | return null;
106 | }
107 |
108 | @BeforeClass
109 | public static void setup() throws CardException {
110 | if (mInitialized) {
111 | return;
112 | }
113 | if (System.getProperty("testMode") != null && System.getProperty("testMode").equals("smartcard")) {
114 | mCard = initGp();
115 | CommandAPDU c = new CommandAPDU(AIDUtil.select(APPLET_AID));
116 | ResponseAPDU response = mCard.getBasicChannel().transmit(c);
117 | assertEquals(0x9000, response.getSW());
118 |
119 | try {
120 | TestUtils.setPinCode(mCard, TestUtils.concatByteArray(new byte[]{(byte) TestUtils.TEST_PIN_CODE.length}, TestUtils.TEST_PIN_CODE), 0x9000, new byte[]{});
121 | } catch (CardException e) {
122 | System.out.println("set pin code failed");
123 | }
124 | } else {
125 | mSimulator = new CardSimulator();
126 | AID appletAID = AIDUtil.create(APPLET_AID);
127 | mSimulator.installApplet(appletAID, fr.bmartel.smartcard.passwordwallet.PasswordWalletApplet.class);
128 | mSimulator.selectApplet(appletAID);
129 | TestUtils.setPinCode(mSimulator, TestUtils.concatByteArray(new byte[]{(byte) TestUtils.TEST_PIN_CODE.length}, TestUtils.TEST_PIN_CODE), 0x9000, new byte[]{});
130 | }
131 | mInitialized = true;
132 | }
133 |
134 | public static Card getCard() {
135 | return mCard;
136 | }
137 |
138 | public static CardSimulator getSimulator() {
139 | return mSimulator;
140 | }
141 |
142 | @AfterClass
143 | public static void close() throws CardException {
144 | if (mCard != null) {
145 | mCard.endExclusive();
146 | mCard.disconnect(true);
147 | mCard = null;
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/keyguard_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
20 |
21 |
28 |
29 |
36 |
37 |
44 |
45 |
51 |
52 |
58 |
59 |
67 |
68 |
78 |
79 |
83 |
84 |
90 |
91 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
116 |
117 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/adapter/PasswordAdapter.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.adapter;
20 |
21 | import android.content.Context;
22 | import android.graphics.Color;
23 | import android.support.v4.content.ContextCompat;
24 | import android.support.v7.widget.RecyclerView;
25 | import android.view.LayoutInflater;
26 | import android.view.View;
27 | import android.view.ViewGroup;
28 | import android.widget.LinearLayout;
29 | import android.widget.TextView;
30 |
31 | import java.util.ArrayList;
32 | import java.util.List;
33 |
34 | import fr.bmartel.smartcard.passwordwallet.R;
35 | import fr.bmartel.smartcard.passwordwallet.inter.IBaseActivity;
36 | import fr.bmartel.smartcard.passwordwallet.inter.IViewHolderClickListener;
37 | import fr.bmartel.smartcard.passwordwallet.model.Password;
38 |
39 | /**
40 | * Password Adapter
41 | *
42 | * @author Bertrand Martel
43 | */
44 | public class PasswordAdapter extends RecyclerView.Adapter {
45 |
46 | List passwordList = new ArrayList<>();
47 |
48 | /**
49 | * Android context
50 | */
51 | private Context context = null;
52 |
53 | /**
54 | * click listener
55 | */
56 | private IViewHolderClickListener mListener;
57 |
58 | private int selected_position = -1;
59 |
60 | private IBaseActivity mActivity;
61 |
62 | public PasswordAdapter(IBaseActivity activity, List list, Context context, IViewHolderClickListener listener) {
63 | this.passwordList = list;
64 | this.context = context;
65 | this.mActivity = activity;
66 | this.mListener = listener;
67 | }
68 |
69 | @Override
70 | public PasswordAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
71 | View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.password_item, parent, false);
72 | return new ViewHolder(v, mListener);
73 | }
74 |
75 | @Override
76 | public void onBindViewHolder(ViewHolder holder, final int position) {
77 | Password item = passwordList.get(position);
78 | holder.passwordTitle.setText("" + item.getTitle());
79 |
80 | if (selected_position == position) {
81 | holder.itemView.setBackgroundColor(Color.parseColor("#e1e1e1"));
82 | mActivity.getToolbar().getMenu().findItem(R.id.button_delete).setVisible(true);
83 | } else {
84 | mActivity.getToolbar().getMenu().findItem(R.id.button_delete).setVisible(false);
85 | holder.itemView.setBackground(ContextCompat.getDrawable(context, R.drawable.ripple));
86 | }
87 |
88 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
89 | @Override
90 | public boolean onLongClick(View view) {
91 | notifyItemChanged(selected_position);
92 | selected_position = position;
93 | notifyItemChanged(selected_position);
94 | return true;
95 | }
96 | });
97 | }
98 |
99 | public boolean isSelected(int position) {
100 | return (selected_position == position);
101 | }
102 |
103 | public void unselect() {
104 | selected_position = -1;
105 | notifyDataSetChanged();
106 | }
107 |
108 | @Override
109 | public long getItemId(int position) {
110 | return position;
111 | }
112 |
113 | @Override
114 | public int getItemCount() {
115 | return passwordList.size();
116 | }
117 |
118 | public int getSelectedItem() {
119 | return selected_position;
120 | }
121 |
122 | /**
123 | * ViewHolder for Password item
124 | */
125 | public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
126 |
127 | /**
128 | * layout
129 | */
130 | public LinearLayout layout;
131 |
132 | /**
133 | * password title
134 | */
135 | public TextView passwordTitle;
136 |
137 | /**
138 | * click listener
139 | */
140 | public IViewHolderClickListener mListener;
141 |
142 | /**
143 | * ViewHolder for Contact item
144 | *
145 | * @param v
146 | * @param listener
147 | */
148 | public ViewHolder(View v, IViewHolderClickListener listener) {
149 | super(v);
150 | mListener = listener;
151 | passwordTitle = v.findViewById(R.id.password_title);
152 | layout = v.findViewById(R.id.group_layout);
153 | v.setOnClickListener(this);
154 | }
155 |
156 | @Override
157 | public void onClick(View v) {
158 | mListener.onClick(v);
159 | }
160 | }
161 |
162 | }
--------------------------------------------------------------------------------
/applet/src/test/java/fr/bmartel/smartcard/passwordwallet/EncryptDecryptTest.java:
--------------------------------------------------------------------------------
1 | package fr.bmartel.smartcard.passwordwallet;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import javax.smartcardio.CardException;
7 | import javax.smartcardio.CommandAPDU;
8 | import javax.smartcardio.ResponseAPDU;
9 |
10 | import fr.bmartel.smartcard.passwordwallet.utils.TestUtils;
11 | import javacard.framework.ISO7816;
12 |
13 | import static org.junit.Assert.assertArrayEquals;
14 | import static org.junit.Assert.assertEquals;
15 |
16 | public class EncryptDecryptTest extends JavaCardTest {
17 |
18 | private final static byte[] DATA16_WITHPADDING = new byte[]{0x01, 0x02};
19 | private final static byte[] DATA16_NOPADDING = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
20 | private final static byte[] DATA32_WITHPADDING = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11};
21 | private final static byte[] DATA32_NOPADDING = new byte[]{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, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F};
22 |
23 | @Before
24 | public void setup() throws CardException {
25 | TestSuite.setup();
26 | verifyPinCode(TestUtils.TEST_PIN_CODE, 0x9000, new byte[]{});
27 | }
28 |
29 | @Test
30 | public void encryptEmpty() throws CardException {
31 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x10, 0x00, 0x00}, new byte[]{}));
32 | ResponseAPDU response = transmitCommand(c);
33 | assertEquals(ISO7816.SW_DATA_INVALID, response.getSW());
34 | assertEquals("encrypted length", 0, response.getData().length);
35 | }
36 |
37 | @Test
38 | public void encryptWithPadding16() throws CardException {
39 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x10, 0x00, 0x00}, DATA16_WITHPADDING));
40 | ResponseAPDU response = transmitCommand(c);
41 | assertEquals(0x9000, response.getSW());
42 | assertEquals("encrypted length", 16, response.getData().length);
43 | }
44 |
45 | @Test
46 | public void encryptWithPadding32() throws CardException {
47 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x10, 0x00, 0x00}, DATA32_WITHPADDING));
48 | ResponseAPDU response = transmitCommand(c);
49 | assertEquals(0x9000, response.getSW());
50 | assertEquals("encrypted length", 32, response.getData().length);
51 | }
52 |
53 | @Test
54 | public void badCla() throws CardException {
55 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x80, (byte) 0x10, 0x00, 0x00}, new byte[]{}));
56 | ResponseAPDU response = transmitCommand(c);
57 | assertEquals(ISO7816.SW_CLA_NOT_SUPPORTED, response.getSW());
58 | assertEquals("data length", 0, response.getData().length);
59 | }
60 |
61 | @Test
62 | public void badP1() throws CardException {
63 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x10, 0x01, 0x00}, new byte[]{}));
64 | ResponseAPDU response = transmitCommand(c);
65 | assertEquals(ISO7816.SW_INCORRECT_P1P2, response.getSW());
66 | assertEquals("data length", 0, response.getData().length);
67 | }
68 |
69 | @Test
70 | public void badP2() throws CardException {
71 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x10, 0x00, 0x01}, new byte[]{}));
72 | ResponseAPDU response = transmitCommand(c);
73 | assertEquals(ISO7816.SW_INCORRECT_P1P2, response.getSW());
74 | assertEquals("data length", 0, response.getData().length);
75 | }
76 |
77 | @Test
78 | public void decryptEmpty() throws CardException {
79 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x11, 0x00, 0x00}, new byte[]{}));
80 | ResponseAPDU response = transmitCommand(c);
81 | assertEquals(ISO7816.SW_DATA_INVALID, response.getSW());
82 | assertEquals("data length", 0, response.getData().length);
83 | }
84 |
85 | private void encryptDecryptTest(byte[] data, int expectedLength) throws CardException {
86 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x10, 0x00, 0x00}, data));
87 | ResponseAPDU response = transmitCommand(c);
88 | assertEquals(0x9000, response.getSW());
89 | assertEquals("encrypted length", expectedLength, response.getData().length);
90 |
91 | c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x11, 0x00, 0x00}, response.getData()));
92 | response = transmitCommand(c);
93 | assertEquals(0x9000, response.getSW());
94 | assertArrayEquals("original decrypted data", data, response.getData());
95 | }
96 |
97 | @Test
98 | public void encryptDecryptWithPadding16() throws CardException {
99 | encryptDecryptTest(DATA16_WITHPADDING, 16);
100 | }
101 |
102 | @Test
103 | public void encryptDecryptNoPadding16() throws CardException {
104 | encryptDecryptTest(DATA16_NOPADDING, 16);
105 | }
106 |
107 | @Test
108 | public void encryptDecryptWithPadding32() throws CardException {
109 | encryptDecryptTest(DATA32_WITHPADDING, 32);
110 | }
111 |
112 | @Test
113 | public void encryptDecryptNoPadding32() throws CardException {
114 | encryptDecryptTest(DATA32_NOPADDING, 32);
115 | }
116 |
117 | @Test
118 | public void invalidInstruction() throws CardException {
119 | CommandAPDU c = new CommandAPDU(TestUtils.buildApdu(new byte[]{(byte) 0x90, (byte) 0x09, 0x00, 0x00}, DATA16_WITHPADDING));
120 | ResponseAPDU response = transmitCommand(c);
121 | assertEquals(ISO7816.SW_FUNC_NOT_SUPPORTED, response.getSW());
122 | }
123 |
124 | @Test
125 | public void invalidDataLength() throws CardException {
126 | CommandAPDU c = new CommandAPDU(new byte[]{(byte) 0x90, (byte) 0x10, 0x00, 0x00, 0x00});
127 | ResponseAPDU response = transmitCommand(c);
128 | assertEquals(ISO7816.SW_DATA_INVALID, response.getSW());
129 | }
130 |
131 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/application/PasswordApplication.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.application;
20 |
21 | import android.app.Application;
22 | import android.os.Handler;
23 | import android.util.Log;
24 | import android.widget.Toast;
25 |
26 | import com.github.orangegangsters.lollipin.lib.managers.LockManager;
27 |
28 | import org.simalliance.openmobileapi.SEService;
29 |
30 | import java.io.IOException;
31 | import java.util.concurrent.Executors;
32 | import java.util.concurrent.ScheduledExecutorService;
33 |
34 | import fr.bmartel.smartcard.passwordwallet.CustomPinActivity;
35 | import fr.bmartel.smartcard.passwordwallet.R;
36 | import fr.bmartel.smartcard.passwordwallet.inter.IServiceConnection;
37 | import fr.bmartel.smartcard.passwordwallet.uicc.ApduResponse;
38 | import fr.bmartel.smartcard.passwordwallet.uicc.Uicc;
39 |
40 | /**
41 | * Password Application which bounds to SEService.
42 | *
43 | * @author Bertrand Martel
44 | */
45 | public class PasswordApplication extends Application implements SEService.CallBack {
46 |
47 | private final static String TAG = PasswordApplication.class.getSimpleName();
48 |
49 | /**
50 | * SEService used to interact with SmartCard API.
51 | */
52 | private SEService seService;
53 |
54 | /**
55 | * UICC object used to manage UICC I/O.
56 | */
57 | private Uicc mUicc;
58 |
59 | /**
60 | * thread pool.
61 | */
62 | private ScheduledExecutorService mExecutor;
63 |
64 | /**
65 | * working mode.
66 | */
67 | public byte mode = 0x00;
68 |
69 | //store encrypted passwords on application or on SIM card
70 | public final static byte MODE_APP_STORAGE = 0x01;
71 | public final static byte MODE_SIM_STORAGE = 0x02;
72 |
73 | /**
74 | * Value for Global Platform secured state.
75 | */
76 | public static final byte CARD_SECURED = 15;
77 |
78 | /**
79 | * service connection object.
80 | */
81 | private IServiceConnection mServiceConnection;
82 |
83 | private boolean connected = false;
84 |
85 | private Handler mHandler;
86 |
87 | @Override
88 | public void onCreate() {
89 | super.onCreate();
90 |
91 | mHandler = new Handler();
92 |
93 | LockManager lockManager = LockManager.getInstance();
94 | lockManager.enableAppLock(this, CustomPinActivity.class);
95 | lockManager.getAppLock().setShouldShowForgot(false);
96 | lockManager.getAppLock().setLogoId(R.drawable.sim_logo);
97 |
98 | initSeService();
99 | mExecutor = Executors.newScheduledThreadPool(1);
100 | }
101 |
102 | @Override
103 | public void onTerminate() {
104 | mUicc.closeChannel();
105 | if (seService != null && seService.isConnected()) {
106 | seService.shutdown();
107 | }
108 | super.onTerminate();
109 | }
110 |
111 | public void setServiceConnection(IServiceConnection callback) {
112 | mServiceConnection = callback;
113 | }
114 |
115 | /**
116 | * Bind to SEService.
117 | */
118 | private void initSeService() {
119 | try {
120 | Log.v(TAG, "creating SEService object");
121 | seService = new SEService(this, this);
122 | mUicc = new Uicc(seService);
123 | } catch (SecurityException e) {
124 | Log.e(TAG, "Binding not allowed, uses-permission org.simalliance.openmobileapi.SMARTCARD?");
125 | } catch (Exception e) {
126 | Log.e(TAG, "Exception: " + e.getMessage());
127 | }
128 | }
129 |
130 | /**
131 | * Connection callback for SEService.
132 | *
133 | * @param service
134 | */
135 | public void serviceConnected(SEService service) {
136 | Log.v(TAG, "serviceConnected()");
137 | mExecutor.execute(new Runnable() {
138 | @Override
139 | public void run() {
140 | try {
141 | //open logical channel
142 | mUicc.openChannel();
143 | } catch (final SecurityException e) {
144 | Log.e(TAG, "SecurityException", e);
145 | mHandler.post(new Runnable() {
146 | @Override
147 | public void run() {
148 | if (e.getMessage().contains("no APDU access allowed")) {
149 | Toast.makeText(PasswordApplication.this, "Application not authorized.\nCheck Access Control Rules on this card", Toast.LENGTH_LONG).show();
150 | } else {
151 | Toast.makeText(PasswordApplication.this, "SIM card not inserted", Toast.LENGTH_LONG).show();
152 | }
153 | }
154 | });
155 | } catch (final IOException e) {
156 | Log.e(TAG, "IOException", e);
157 | mHandler.post(new Runnable() {
158 | @Override
159 | public void run() {
160 | if (e.getMessage().contains("iccOpenLogicalChannel failed")) {
161 | Toast.makeText(PasswordApplication.this, "smartcard has been disconnected or applet not installed ?", Toast.LENGTH_LONG).show();
162 | } else {
163 | Toast.makeText(PasswordApplication.this, "SIM card not inserted", Toast.LENGTH_LONG).show();
164 | }
165 | }
166 | });
167 | }
168 | if (mServiceConnection != null) {
169 | mServiceConnection.onServiceConnected();
170 | }
171 | connected = true;
172 | }
173 | });
174 | }
175 |
176 | /**
177 | * Get the current mode from UICC.
178 | */
179 | public void refreshMode() {
180 | ApduResponse modeRes = mUicc.getMode();
181 | if (!modeRes.isSuccessful()) {
182 | Log.e(TAG, "get mode failed");
183 | } else {
184 | mode = modeRes.getData()[0];
185 | }
186 | }
187 |
188 | public Uicc getUicc() {
189 | return mUicc;
190 | }
191 |
192 | public ScheduledExecutorService getExecutor() {
193 | return mExecutor;
194 | }
195 |
196 | public void setMode(byte mode) {
197 | this.mode = mode;
198 | }
199 |
200 | /**
201 | * Check if card is secured from UICC (eg if the pin code has been already set before).
202 | *
203 | * @return
204 | */
205 | public boolean isCardSecured() {
206 | ApduResponse cardStateRes = mUicc.getCardState();
207 | if (cardStateRes.getData().length != 0) {
208 | return (cardStateRes.getData()[0] == CARD_SECURED);
209 | }
210 | return true;
211 | }
212 |
213 | public boolean isPinCodeChecked() {
214 | return mUicc.getPinCodeState().isSuccessful();
215 | }
216 |
217 | public boolean isConnected() {
218 | return connected;
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/uicc/UiccUtils.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.uicc;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | import fr.bmartel.smartcard.passwordwallet.model.Password;
25 |
26 | /**
27 | * UICC utility functions used to generate/parse payload.
28 | *
29 | * @author Bertrand Martel
30 | */
31 | public class UiccUtils {
32 |
33 | /**
34 | * Build create password entry payload.
35 | *
36 | * @param title password title
37 | * @param username username
38 | * @param password password value
39 | * @return data payload
40 | */
41 | public static byte[] buildAddPassword(String title, String username, String password) {
42 | byte[] titleBa = title.getBytes();
43 | byte[] usernameBa = username.getBytes();
44 | byte[] passwordBa = password.getBytes();
45 |
46 | byte[] res = new byte[titleBa.length + usernameBa.length + passwordBa.length + 3 + 3];
47 |
48 | int offset = 0;
49 |
50 | res[offset++] = (byte) 0xF1;
51 | res[offset++] = (byte) titleBa.length;
52 | System.arraycopy(titleBa, 0, res, offset, titleBa.length);
53 | offset += titleBa.length;
54 | res[offset++] = (byte) 0xF2;
55 | res[offset++] = (byte) usernameBa.length;
56 | System.arraycopy(usernameBa, 0, res, offset, usernameBa.length);
57 | offset += usernameBa.length;
58 | res[offset++] = (byte) 0xF3;
59 | res[offset++] = (byte) passwordBa.length;
60 | System.arraycopy(passwordBa, 0, res, offset, passwordBa.length);
61 | return res;
62 | }
63 |
64 | /**
65 | * Build update password entry payload.
66 | *
67 | * @param oldTitle former password title
68 | * @param title new password title
69 | * @param username new username value
70 | * @param password new password value
71 | * @return data payload
72 | */
73 | public static byte[] buildEditPassword(String oldTitle, String title, String username, String password) {
74 | byte[] oldTitleBa = oldTitle.getBytes();
75 | byte[] titleBa = title.getBytes();
76 | byte[] usernameBa = username.getBytes();
77 | byte[] passwordBa = password.getBytes();
78 |
79 | byte[] res = new byte[oldTitleBa.length + titleBa.length + usernameBa.length + passwordBa.length + 4 + 4];
80 |
81 | int offset = 0;
82 |
83 | res[offset++] = (byte) 0xF4;
84 | res[offset++] = (byte) oldTitleBa.length;
85 | System.arraycopy(oldTitleBa, 0, res, offset, oldTitleBa.length);
86 | offset += oldTitleBa.length;
87 |
88 | res[offset++] = (byte) 0xF1;
89 | res[offset++] = (byte) titleBa.length;
90 | System.arraycopy(titleBa, 0, res, offset, titleBa.length);
91 | offset += titleBa.length;
92 |
93 | res[offset++] = (byte) 0xF2;
94 | res[offset++] = (byte) usernameBa.length;
95 | System.arraycopy(usernameBa, 0, res, offset, usernameBa.length);
96 | offset += usernameBa.length;
97 |
98 | res[offset++] = (byte) 0xF3;
99 | res[offset++] = (byte) passwordBa.length;
100 | System.arraycopy(passwordBa, 0, res, offset, passwordBa.length);
101 |
102 | return res;
103 | }
104 |
105 | /**
106 | * Parse GET password list response.
107 | *
108 | * @param data data payload.
109 | * @return list of password entry
110 | */
111 | public static List parsePaswordList(byte[] data) {
112 | int state = 0;
113 | byte[] currentTag = null;
114 | int index = 0;
115 | List passwordList = new ArrayList<>();
116 |
117 | for (int i = 0; i < data.length; i++) {
118 | switch (state) {
119 | case 0:
120 | if ((data[i] & 0xFF) == 0xF1) {
121 | state = 1;
122 | }
123 | break;
124 | case 1:
125 | currentTag = new byte[data[i] & 0xFF];
126 | state = 2;
127 | index = 0;
128 | break;
129 | case 2:
130 | currentTag[index++] = data[i];
131 | if (index == currentTag.length) {
132 | state = 0;
133 | passwordList.add(new Password(new String(currentTag), null, null));
134 | }
135 | break;
136 | default:
137 | break;
138 | }
139 | }
140 | return passwordList;
141 | }
142 |
143 | /**
144 | * Build delete password entry data payload.
145 | *
146 | * @param title password title
147 | * @return data payload
148 | */
149 | public static byte[] buildDeletePassword(String title) {
150 | byte[] titleBa = title.getBytes();
151 |
152 | byte[] res = new byte[titleBa.length + 2];
153 | res[0] = (byte) 0xF1;
154 | res[1] = (byte) titleBa.length;
155 | System.arraycopy(titleBa, 0, res, 2, titleBa.length);
156 | return res;
157 | }
158 |
159 | /**
160 | * Build get password entry data payload.
161 | *
162 | * @param title password title
163 | * @return data payload
164 | */
165 | public static byte[] buildGetPassword(String title) {
166 | byte[] titleBa = title.getBytes();
167 |
168 | byte[] res = new byte[titleBa.length + 2];
169 | res[0] = (byte) 0xF1;
170 | res[1] = (byte) titleBa.length;
171 | System.arraycopy(titleBa, 0, res, 2, titleBa.length);
172 | return res;
173 | }
174 |
175 | /**
176 | * Parse Get password entry response.
177 | *
178 | * @param title password title (from the request)
179 | * @param data data payload
180 | * @return password entry object
181 | */
182 | public static Password parsePassword(String title, byte[] data) {
183 | int state = 0;
184 | byte[] currentTag = null;
185 | int index = 0;
186 | String username = null;
187 | byte[] password = null;
188 |
189 | for (int i = 0; i < data.length; i++) {
190 | switch (state) {
191 | case 0:
192 | if ((data[i] & 0xFF) == 0xF2) {
193 | state = 1;
194 | }
195 | break;
196 | case 1:
197 | currentTag = new byte[data[i] & 0xFF];
198 | state = 2;
199 | index = 0;
200 | break;
201 | case 2:
202 | currentTag[index++] = data[i];
203 | if (index == currentTag.length) {
204 | state = 3;
205 | username = new String(currentTag);
206 | }
207 | break;
208 | case 3:
209 | if ((data[i] & 0xFF) == 0xF3) {
210 | state = 4;
211 | }
212 | break;
213 | case 4:
214 | currentTag = new byte[data[i] & 0xFF];
215 | state = 5;
216 | index = 0;
217 | break;
218 | case 5:
219 | currentTag[index++] = data[i];
220 | if (index == currentTag.length) {
221 | state = 0;
222 | password = currentTag;
223 | }
224 | break;
225 | default:
226 | break;
227 | }
228 | }
229 | if (username != null && password != null) {
230 | return new Password(title, username, password);
231 | }
232 | return null;
233 | }
234 |
235 | /**
236 | * Convert string to byte array with pin code values.
237 | */
238 | public static byte[] convertPinCode(String pass) {
239 | if (isNumeric(pass)) {
240 | byte[] data = new byte[pass.length()];
241 | for (int i = 0; i < pass.length(); i++) {
242 | data[i] = (byte) ((int) pass.charAt(i) - 48);
243 | }
244 | return data;
245 | }
246 | return null;
247 | }
248 |
249 | /**
250 | * https://stackoverflow.com/a/1102916/2614364.
251 | *
252 | * @param str
253 | * @return
254 | */
255 | public static boolean isNumeric(String str) {
256 | return str.matches("-?\\d+(\\.\\d+)?"); //match a number with optional '-' and decimal.
257 | }
258 | }
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/fragment/PasswordFragment.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.fragment;
20 |
21 | import android.app.Activity;
22 | import android.content.Context;
23 | import android.os.Bundle;
24 | import android.os.Handler;
25 | import android.support.annotation.Nullable;
26 | import android.support.v4.app.FragmentActivity;
27 | import android.support.v4.app.FragmentTransaction;
28 | import android.support.v4.widget.SwipeRefreshLayout;
29 | import android.support.v7.widget.GridLayoutManager;
30 | import android.support.v7.widget.LinearLayoutManager;
31 | import android.view.LayoutInflater;
32 | import android.view.Menu;
33 | import android.view.MenuInflater;
34 | import android.view.MenuItem;
35 | import android.view.View;
36 | import android.view.ViewGroup;
37 |
38 | import java.util.Collections;
39 | import java.util.Comparator;
40 |
41 | import fr.bmartel.smartcard.passwordwallet.BaseActivity;
42 | import fr.bmartel.smartcard.passwordwallet.R;
43 | import fr.bmartel.smartcard.passwordwallet.adapter.PasswordAdapter;
44 | import fr.bmartel.smartcard.passwordwallet.common.SimpleDividerItemDecoration;
45 | import fr.bmartel.smartcard.passwordwallet.dialog.ModeDialog;
46 | import fr.bmartel.smartcard.passwordwallet.inter.IDeletionListener;
47 | import fr.bmartel.smartcard.passwordwallet.inter.IFragmentOptions;
48 | import fr.bmartel.smartcard.passwordwallet.inter.IViewHolderClickListener;
49 | import fr.bmartel.smartcard.passwordwallet.model.Password;
50 |
51 | /**
52 | * Password Fragment.
53 | *
54 | * @author Bertrand Martel
55 | */
56 | public class PasswordFragment extends ListFragmentAbstr implements IFragmentOptions {
57 |
58 | private PasswordItemFragment mFragment;
59 |
60 | private FragmentActivity mActivity;
61 |
62 | public PasswordFragment() {
63 | }
64 |
65 | @Override
66 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
67 | Bundle savedInstanceState) {
68 | final View view = inflater.inflate(R.layout.password_fragment, container, false);
69 | return view;
70 | }
71 |
72 | @Override
73 | public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
74 | super.onViewCreated(view, savedInstanceState);
75 |
76 | setDeletionListener();
77 |
78 | mEmptyFrame = view.findViewById(R.id.waiting_frame);
79 | mDisplayFrame = view.findViewById(R.id.display_frame);
80 |
81 | if (getRootActivity().getPasswordList().size() > 0) {
82 | mEmptyFrame.setVisibility(View.GONE);
83 | mDisplayFrame.setVisibility(View.VISIBLE);
84 | }
85 |
86 | mPasswordListView = view.findViewById(R.id.password_list);
87 |
88 | mPasswordList = ((BaseActivity) getActivity()).getPasswordList();
89 |
90 | //sort by topic
91 | if (mPasswordList.size() > 0) {
92 | Collections.sort(mPasswordList, new Comparator() {
93 | @Override
94 | public int compare(final Password object1, final Password object2) {
95 | return object1.getTitle().compareTo(object2.getTitle());
96 | }
97 | });
98 | }
99 |
100 | mPasswordAdapter = new PasswordAdapter(getRootActivity(), mPasswordList, getActivity(), new IViewHolderClickListener() {
101 | @Override
102 | public void onClick(View view) {
103 | final int index = mPasswordListView.getChildAdapterPosition(view);
104 | if (!mPasswordAdapter.isSelected(index)) {
105 | new Handler().postDelayed(new Runnable() {
106 | @Override
107 | public void run() {
108 | mFragment = new PasswordItemFragment();
109 | Bundle args = new Bundle();
110 | args.putInt("index", index);
111 | mFragment.setArguments(args);
112 |
113 | final FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
114 | ft.replace(R.id.fragment_frame, mFragment, "PaswordItem");
115 | ft.addToBackStack(null);
116 | ft.commit();
117 | }
118 | }, 200);
119 | } else {
120 | mPasswordAdapter.unselect();
121 | }
122 | }
123 | });
124 |
125 | //set layout manager
126 | mPasswordListView.setLayoutManager(new GridLayoutManager(getActivity(), 1, LinearLayoutManager.VERTICAL, false));
127 |
128 | //set line decoration
129 | mPasswordListView.addItemDecoration(new SimpleDividerItemDecoration(
130 | getActivity().getApplicationContext()
131 | ));
132 |
133 | mPasswordListView.setAdapter(mPasswordAdapter);
134 |
135 | //setup swipe refresh
136 | mSwipeRefreshLayout = view.findViewById(R.id.swiperefresh);
137 | mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
138 | @Override
139 | public void onRefresh() {
140 | mPasswordList = ((BaseActivity) getActivity()).getPasswordList();
141 | mPasswordAdapter.notifyDataSetChanged();
142 | mSwipeRefreshLayout.setRefreshing(false);
143 | }
144 | });
145 | onUpdateToolbar();
146 | }
147 |
148 | @Override
149 | public void onUpdateToolbar() {
150 | MenuItem buttonCreate = getRootActivity().getToolbar().getMenu().findItem(R.id.button_add_password);
151 | buttonCreate.setVisible(true);
152 | buttonCreate.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
153 | @Override
154 | public boolean onMenuItemClick(MenuItem menuItem) {
155 | final FragmentTransaction ft = getFragmentManager().beginTransaction();
156 | mFragment = new PasswordItemFragment();
157 | ft.replace(R.id.fragment_frame, mFragment, "PasswordItem");
158 | ft.addToBackStack(null);
159 | ft.commit();
160 | return false;
161 | }
162 | });
163 |
164 | MenuItem buttonMode = getRootActivity().getToolbar().getMenu().findItem(R.id.button_mode);
165 | buttonMode.setVisible(true);
166 | buttonMode.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
167 | @Override
168 | public boolean onMenuItemClick(MenuItem menuItem) {
169 | ModeDialog dialog = new ModeDialog(getActivity());
170 | getRootActivity().setCurrentDialog(dialog);
171 | dialog.show();
172 | return false;
173 | }
174 | });
175 | }
176 |
177 | @Override
178 | public void onAttach(Context context) {
179 | super.onAttach(context);
180 | if (context instanceof Activity) {
181 | mActivity = (FragmentActivity) context;
182 | }
183 | }
184 |
185 | @Override
186 | public void onResume() {
187 | super.onResume();
188 | setDeletionListener();
189 | setTitle(getString(R.string.title_password));
190 |
191 | getRootActivity().hideMenuButton();
192 | getRootActivity().getToolbar().getMenu().findItem(R.id.button_mode).setVisible(true);
193 | getRootActivity().getToolbar().getMenu().findItem(R.id.button_add_password).setVisible(true);
194 |
195 | mPasswordList = ((BaseActivity) getActivity()).getPasswordList();
196 | mPasswordAdapter.notifyDataSetChanged();
197 | }
198 |
199 |
200 | @Override
201 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
202 | super.onCreateOptionsMenu(menu, inflater);
203 | }
204 |
205 | private void setDeletionListener() {
206 |
207 | getRootActivity().setDeletionListener(new IDeletionListener() {
208 | @Override
209 | public void onDelete() {
210 | getRootActivity().deletePassword(mPasswordList.get(mPasswordAdapter.getSelectedItem()).getTitle());
211 | mPasswordList = ((BaseActivity) getActivity()).getPasswordList();
212 | mPasswordAdapter.notifyDataSetChanged();
213 |
214 | if (getRootActivity().getPasswordList().size() > 0) {
215 | mEmptyFrame.setVisibility(View.GONE);
216 | mDisplayFrame.setVisibility(View.VISIBLE);
217 | } else {
218 | mEmptyFrame.setVisibility(View.VISIBLE);
219 | mDisplayFrame.setVisibility(View.GONE);
220 | }
221 | getRootActivity().getToolbar().getMenu().findItem(R.id.button_delete).setVisible(false);
222 | }
223 | });
224 | }
225 |
226 | @Override
227 | public void onPause() {
228 | super.onPause();
229 | }
230 |
231 | @Override
232 | public void onRequestPermissionsResult(int requestCode,
233 | String permissions[], int[] grantResults) {
234 | if (mFragment != null) {
235 | mFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
236 | }
237 | }
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/uicc/Uicc.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.uicc;
20 |
21 | import android.util.Log;
22 |
23 | import org.simalliance.openmobileapi.Channel;
24 | import org.simalliance.openmobileapi.Reader;
25 | import org.simalliance.openmobileapi.SEService;
26 | import org.simalliance.openmobileapi.Session;
27 |
28 | import java.io.IOException;
29 | import java.util.List;
30 |
31 | import fr.bmartel.smartcard.passwordwallet.model.Password;
32 | import fr.bmartel.smartcard.passwordwallet.utils.HexUtils;
33 |
34 | /**
35 | * Manage all I/O with UICC.
36 | *
37 | * @author Bertrand Martel
38 | */
39 | public class Uicc {
40 |
41 | private final static String TAG = Uicc.class.getSimpleName();
42 |
43 | private final static String APPLET_ID = "D2760001180002FF49502589C0019B01";
44 |
45 | private final static byte INS_ENCRYPT = (byte) 0x10;
46 | private final static byte INS_DECRYPT = (byte) 0x11;
47 | private final static byte INS_GET_MODE = (byte) 0x40;
48 | private final static byte INS_SET_MODE = (byte) 0x41;
49 | private final static byte INS_ADD_PASSWORD = (byte) 0x30;
50 | private final static byte INS_LIST_PASSWORD = (byte) 0x36;
51 | private final static byte INS_DELETE_PASSWORD = (byte) 0x34;
52 | private final static byte INS_GET_PASSWORD = (byte) 0x32;
53 | private final static byte INS_EDIT_PASSWORD = (byte) 0x33;
54 | private final static byte INS_VERIFY = (byte) 0x20;
55 | private final static byte INS_GET_STATE = (byte) 0x50;
56 | private final static byte INS_PIN_CHECK = (byte) 0x51;
57 | private final static byte INS_CHANGE_REFERENCE_DATA = (byte) 0x24;
58 |
59 | private SEService mService;
60 |
61 | private Channel mChannel;
62 |
63 | /**
64 | * Init with SEService.
65 | *
66 | * @param service
67 | */
68 | public Uicc(SEService service) {
69 | mService = service;
70 | }
71 |
72 | /**
73 | * Open logical channel with applet.
74 | *
75 | * @throws SecurityException
76 | * @throws IOException
77 | */
78 | public void openChannel() throws SecurityException, IOException {
79 | Reader[] readers = mService.getReaders();
80 | if (readers.length < 1)
81 | return;
82 | Session session = readers[0].openSession();
83 |
84 | mChannel = session.openLogicalChannel(HexUtils.hexStringToByteArray(APPLET_ID));
85 | }
86 |
87 | /**
88 | * Send APDU to UICC.
89 | *
90 | * @param data data payload
91 | * @param operation instruction
92 | * @return APDU response object
93 | */
94 | private ApduResponse requestSE(byte[] data, byte operation) {
95 | return requestSE(data, (byte) 0x00, (byte) 0x00, operation);
96 | }
97 |
98 | /**
99 | * Send APDU to UICC.
100 | *
101 | * @param data data payload
102 | * @param P1 P1 param
103 | * @param P2 P2 param
104 | * @param operation instruction
105 | * @return APDU response object
106 | */
107 | private ApduResponse requestSE(byte[] data, byte P1, byte P2, byte operation) {
108 | if (mChannel != null) {
109 | try {
110 | byte[] respApdu;
111 |
112 | if (data.length > 0) {
113 | respApdu = mChannel.transmit(new CommandApdu((byte) 0x90, operation, P1, P2, data).toBytes());
114 | } else {
115 | respApdu = mChannel.transmit(new CommandApdu((byte) 0x90, operation, P1, P2, 0x00).toBytes());
116 | }
117 | return new ApduResponse(respApdu);
118 | } catch (Exception e) {
119 | Log.e(TAG, "Error occurred:", e);
120 | }
121 | }
122 | return new ApduResponse(new byte[]{});
123 | }
124 |
125 | /**
126 | * Get password list.
127 | *
128 | * @return
129 | */
130 | public List getPasswordList() {
131 | ApduResponse result = requestSE(new byte[]{}, INS_LIST_PASSWORD);
132 |
133 | if (result.isSuccessful()) {
134 | return UiccUtils.parsePaswordList(result.getData());
135 | }
136 | return null;
137 | }
138 |
139 | /**
140 | * Encrypt data on UICC.
141 | *
142 | * @param data clear text data
143 | * @return APDU response
144 | */
145 | public ApduResponse encrypt(byte[] data) {
146 | return requestSE(data, INS_ENCRYPT);
147 | }
148 |
149 | /**
150 | * Decrypt data on UICC.
151 | *
152 | * @param data encrypted data
153 | * @return clear text data
154 | */
155 | public ApduResponse decrypt(byte[] data) {
156 | return requestSE(data, INS_DECRYPT);
157 | }
158 |
159 | /**
160 | * Update a password entry.
161 | *
162 | * @param formerTitle former password title
163 | * @param newTitle new password title
164 | * @param username updated username
165 | * @param password updated password value
166 | * @return APDU response
167 | */
168 | public ApduResponse editPassword(String formerTitle, String newTitle, String username, String password) {
169 | return requestSE(UiccUtils.buildEditPassword(formerTitle, newTitle, username, password), INS_EDIT_PASSWORD);
170 | }
171 |
172 | /**
173 | * Add a new password entry.
174 | *
175 | * @param title password title
176 | * @param username username
177 | * @param password password value
178 | * @return APDU response
179 | */
180 | public ApduResponse addPassword(String title, String username, String password) {
181 | return requestSE(UiccUtils.buildAddPassword(title, username, password), INS_ADD_PASSWORD);
182 | }
183 |
184 | /**
185 | * Get password entry from password title.
186 | *
187 | * @param title password title
188 | * @return APDU response
189 | */
190 | public ApduResponse getPassword(String title) {
191 | return requestSE(UiccUtils.buildGetPassword(title), INS_GET_PASSWORD);
192 | }
193 |
194 | /**
195 | * Delete password entry.
196 | *
197 | * @param title password title
198 | * @return APDU response
199 | */
200 | public ApduResponse deletePassword(String title) {
201 | return requestSE(UiccUtils.buildDeletePassword(title), INS_DELETE_PASSWORD);
202 | }
203 |
204 | /**
205 | * Set working mode (storage on app or on UICC).
206 | *
207 | * @param mode mode value
208 | * @return APDU response
209 | */
210 | public ApduResponse setMode(byte mode) {
211 | return requestSE(new byte[]{mode}, INS_SET_MODE);
212 | }
213 |
214 | /**
215 | * Get working mode
216 | *
217 | * @return mode
218 | */
219 | public ApduResponse getMode() {
220 | return requestSE(new byte[]{}, INS_GET_MODE);
221 | }
222 |
223 | /**
224 | * Get the card state (secured mean the pin code has already been set).
225 | *
226 | * @return GP card state
227 | */
228 | public ApduResponse getCardState() {
229 | return requestSE(new byte[]{}, INS_GET_STATE);
230 | }
231 |
232 | /**
233 | * Check if pin code is already checked for this session.
234 | *
235 | * @return APDU response
236 | */
237 | public ApduResponse getPinCodeState() {
238 | return requestSE(new byte[]{}, INS_PIN_CHECK);
239 | }
240 |
241 | /**
242 | * Check pin code.
243 | *
244 | * @param data pincode
245 | * @return pin code result
246 | */
247 | public PinCodeResult checkPin(byte[] data) {
248 | ApduResponse res = requestSE(data, (byte) 0x00, (byte) 0x80, INS_VERIFY);
249 |
250 | if (res.isSuccessful()) {
251 | return new PinCodeResult(true, -1);
252 | } else {
253 | if ((res.getStatus() & 0xFFF0) != 0x63C0) {
254 | return new PinCodeResult(false, 0);
255 | } else {
256 | return new PinCodeResult(false, res.getStatus() & 0x000F);
257 | }
258 | }
259 | }
260 |
261 | /**
262 | * Update pin code.
263 | *
264 | * @param noPin true if it's the first time we set pin code
265 | * @param oldPin old pin code (if noPin is false)
266 | * @param newPin new pin code
267 | * @return APDU response
268 | */
269 | public ApduResponse updatePin(boolean noPin, byte[] oldPin, byte[] newPin) {
270 | if (noPin) {
271 | byte[] request = new byte[newPin.length + 1];
272 | request[0] = (byte) newPin.length;
273 | System.arraycopy(newPin, 0, request, 1, newPin.length);
274 | return requestSE(request, (byte) 0x00, (byte) 0x80, INS_CHANGE_REFERENCE_DATA);
275 | } else {
276 | byte[] request = new byte[oldPin.length + 1 + newPin.length + 1];
277 | int index = 0;
278 | request[index++] = (byte) oldPin.length;
279 | System.arraycopy(oldPin, 0, request, index, oldPin.length);
280 | index += oldPin.length;
281 | request[index++] = (byte) newPin.length;
282 | System.arraycopy(newPin, 0, request, index, newPin.length);
283 | return requestSE(request, (byte) 0x01, (byte) 0x80, INS_CHANGE_REFERENCE_DATA);
284 | }
285 | }
286 |
287 | /**
288 | * Close current session.
289 | */
290 | public void closeChannel() {
291 | mChannel.close();
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/app/src/main/java/fr/bmartel/smartcard/passwordwallet/fragment/PasswordItemFragment.java:
--------------------------------------------------------------------------------
1 | /*********************************************************************************
2 | * This file is part of SIM Password Wallet *
3 | * *
4 | * Copyright (C) 2017 Bertrand Martel *
5 | * *
6 | * SIM Password Wallet is free software: you can redistribute it and/or modify *
7 | * it under the terms of the GNU General Public License as published by *
8 | * the Free Software Foundation, either version 3 of the License, or *
9 | * (at your option) any later version. *
10 | * *
11 | * SIM Password Wallet is distributed in the hope that it will be useful, *
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 | * GNU General Public License for more details. *
15 | * *
16 | * You should have received a copy of the GNU General Public License *
17 | * along with SIM Password Wallet. If not, see . *
18 | */
19 | package fr.bmartel.smartcard.passwordwallet.fragment;
20 |
21 | import android.content.Context;
22 | import android.os.Bundle;
23 | import android.support.annotation.Nullable;
24 | import android.support.v7.widget.Toolbar;
25 | import android.text.InputType;
26 | import android.text.method.PasswordTransformationMethod;
27 | import android.view.LayoutInflater;
28 | import android.view.MenuItem;
29 | import android.view.View;
30 | import android.view.ViewGroup;
31 | import android.view.inputmethod.InputMethodManager;
32 | import android.widget.CheckBox;
33 | import android.widget.EditText;
34 | import android.widget.TextView;
35 | import android.widget.Toast;
36 |
37 | import fr.bmartel.smartcard.passwordwallet.R;
38 | import fr.bmartel.smartcard.passwordwallet.application.PasswordApplication;
39 | import fr.bmartel.smartcard.passwordwallet.db.PasswordReaderDbHelper;
40 | import fr.bmartel.smartcard.passwordwallet.inter.IFragmentOptions;
41 | import fr.bmartel.smartcard.passwordwallet.model.Password;
42 | import fr.bmartel.smartcard.passwordwallet.utils.HexUtils;
43 |
44 | /**
45 | * Password item Fragment.
46 | *
47 | * @author Bertrand Martel
48 | */
49 | public class PasswordItemFragment extends MainFragmentAbstr implements IFragmentOptions {
50 |
51 | private EditText mPasswordTitleEt;
52 |
53 | private EditText mPasswordUsernameEt;
54 |
55 | private EditText mDecryptedPasswordEt;
56 |
57 | private TextView mEncryptedPasswordTv;
58 |
59 | private CheckBox mTextObsufactionToggle;
60 |
61 | private int mPasswordIndex = -1;
62 |
63 | public PasswordItemFragment() {
64 | }
65 |
66 | @Override
67 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
68 | Bundle savedInstanceState) {
69 |
70 | final View view = inflater.inflate(R.layout.password_item_fragment, container, false);
71 | return view;
72 | }
73 |
74 | @Override
75 | public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
76 | super.onViewCreated(view, savedInstanceState);
77 |
78 | Bundle args = getArguments();
79 | mPasswordIndex = (args != null) ? args.getInt("index", -1) : -1;
80 |
81 | mPasswordTitleEt = view.findViewById(R.id.password_title);
82 | mPasswordTitleEt.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
83 |
84 | mPasswordUsernameEt = view.findViewById(R.id.password_username);
85 | mPasswordUsernameEt.setInputType(InputType.TYPE_CLASS_TEXT);
86 |
87 | mDecryptedPasswordEt = view.findViewById(R.id.password_decrypted);
88 | mEncryptedPasswordTv = view.findViewById(R.id.password_encrypted);
89 |
90 | mTextObsufactionToggle = view.findViewById(R.id.text_obfuscation_toggle);
91 |
92 | PasswordApplication app = (PasswordApplication) getActivity().getApplication();
93 |
94 | if (mPasswordIndex == -1) {
95 | mEncryptedPasswordTv.setVisibility(View.GONE);
96 | mTextObsufactionToggle.setText(R.string.text_obfuscation_toggle_caption);
97 | } else {
98 | mEncryptedPasswordTv.setVisibility(View.VISIBLE);
99 | mTextObsufactionToggle.setText(R.string.text_obfuscation_toggle_caption_decrypt);
100 | }
101 |
102 | mTextObsufactionToggle.setOnClickListener(new View.OnClickListener() {
103 |
104 | @Override
105 | public void onClick(View v) {
106 | boolean hidePassword = mTextObsufactionToggle.isChecked();
107 | if (!hidePassword) {
108 | mDecryptedPasswordEt.setTransformationMethod(new PasswordTransformationMethod());
109 | } else {
110 | mDecryptedPasswordEt.setTransformationMethod(null);
111 | }
112 | }
113 | });
114 |
115 | mTextObsufactionToggle.setChecked(false);
116 |
117 | if (mTextObsufactionToggle.isChecked()) {
118 | mDecryptedPasswordEt.setTransformationMethod(new PasswordTransformationMethod());
119 | }
120 |
121 | if (mPasswordIndex != -1) {
122 | getRootActivity().setToolbarTitle(getString(R.string.title_edit_password));
123 | Password password = getRootActivity().getPassword(mPasswordIndex);
124 |
125 | if (password == null) {
126 | return;
127 | }
128 | mPasswordTitleEt.setText(password.getTitle());
129 | mPasswordUsernameEt.setText(password.getUsername());
130 |
131 | if (app.mode == PasswordApplication.MODE_APP_STORAGE) {
132 | mEncryptedPasswordTv.setText(HexUtils.byteArrayToHexString(password.getPassword()));
133 |
134 | String pass = getRootActivity().decrypt(password.getPassword());
135 | if (pass != null) {
136 | mDecryptedPasswordEt.setText(pass);
137 | } else {
138 | Toast.makeText(getActivity(), "password decryption failed", Toast.LENGTH_SHORT).show();
139 | }
140 | } else {
141 | mDecryptedPasswordEt.setText(new String(password.getPassword()));
142 | }
143 | } else {
144 | getRootActivity().setToolbarTitle(getString(R.string.title_create_password));
145 | }
146 | onUpdateToolbar();
147 | }
148 |
149 | @Override
150 | public void onUpdateToolbar() {
151 | getRootActivity().hideMenuButton();
152 | Toolbar toolbar = getRootActivity().getToolbar();
153 | MenuItem saveButton = toolbar.getMenu().findItem(R.id.button_save);
154 | MenuItem deleteButton = toolbar.getMenu().findItem(R.id.button_delete);
155 | saveButton.setVisible(true);
156 | deleteButton.setVisible(false);
157 |
158 | saveButton.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
159 | @Override
160 | public boolean onMenuItemClick(MenuItem menuItem) {
161 |
162 | InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
163 | imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
164 |
165 | final String title = mPasswordTitleEt.getText().toString().trim();
166 | final String username = mPasswordUsernameEt.getText().toString().trim();
167 | final String password = mDecryptedPasswordEt.getText().toString().trim();
168 |
169 | if (title.isEmpty()) {
170 | Toast.makeText(getActivity(), "title can't be empty", Toast.LENGTH_SHORT).show();
171 | return false;
172 | }
173 | if (username.isEmpty()) {
174 | Toast.makeText(getActivity(), "username can't be empty", Toast.LENGTH_SHORT).show();
175 | return false;
176 | }
177 | if (password.isEmpty()) {
178 | Toast.makeText(getActivity(), "password value can't be empty", Toast.LENGTH_SHORT).show();
179 | return false;
180 | }
181 |
182 | if (title.length() > PasswordReaderDbHelper.TITLE_MAX_SIZE) {
183 | Toast.makeText(getActivity(), "title max length is " + PasswordReaderDbHelper.TITLE_MAX_SIZE + " characters", Toast.LENGTH_SHORT).show();
184 | return false;
185 | }
186 | if (username.length() > PasswordReaderDbHelper.USERNAME_MAX_SIZE) {
187 | Toast.makeText(getActivity(), "username max length is " + PasswordReaderDbHelper.USERNAME_MAX_SIZE + " characters", Toast.LENGTH_SHORT).show();
188 | return false;
189 | }
190 | if (password.length() > PasswordReaderDbHelper.PASSWORD_MAX_SIZE) {
191 | Toast.makeText(getActivity(), "password max length is " + PasswordReaderDbHelper.PASSWORD_MAX_SIZE + " characters", Toast.LENGTH_SHORT).show();
192 | return false;
193 | }
194 |
195 | if (mPasswordIndex == -1 && getRootActivity().checkDuplicatePassword(title)) {
196 | Toast.makeText(getActivity(), "password title " + title + " already exist", Toast.LENGTH_SHORT).show();
197 | } else if (mPasswordIndex == -1) {
198 |
199 | byte[] res = getRootActivity().saveNewPassword(title, username, password);
200 | if (res != null) {
201 | Toast.makeText(getActivity(), "password " + title + " has been saved", Toast.LENGTH_SHORT).show();
202 | getActivity().onBackPressed();
203 | } else {
204 | Toast.makeText(getActivity(), "operation failed", Toast.LENGTH_SHORT).show();
205 | }
206 | } else {
207 | String formerTitle = getRootActivity().getPasswordList().get(mPasswordIndex).getTitle();
208 | getRootActivity().getPasswordList().get(mPasswordIndex).setTitle(title);
209 | getRootActivity().getPasswordList().get(mPasswordIndex).setUsername(username);
210 | byte[] res = getRootActivity().saveExistingPassword(formerTitle, title, username, password);
211 | if (res != null) {
212 | getRootActivity().getPasswordList().get(mPasswordIndex).setPassword(res);
213 | Toast.makeText(getActivity(), "password " + title + " has been saved", Toast.LENGTH_SHORT).show();
214 | getActivity().onBackPressed();
215 | } else {
216 | Toast.makeText(getActivity(), "operation failed", Toast.LENGTH_SHORT).show();
217 | }
218 | }
219 | return false;
220 | }
221 | });
222 | }
223 | }
--------------------------------------------------------------------------------