├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ic_launcher-web.png ├── proguard-rules.pro └── src ├── androidTest └── java │ └── org │ └── mariotaku │ └── pass │ └── ApplicationTest.java ├── debug └── java │ └── org │ └── mariotaku │ └── pass │ └── util │ └── DebugModeUtils.java ├── main ├── AndroidManifest.xml ├── assets │ └── databases │ │ └── pass.db ├── java │ └── org │ │ ├── mariotaku │ │ └── pass │ │ │ ├── Constants.java │ │ │ ├── PassApplication.java │ │ │ ├── activity │ │ │ ├── PassGenDialogActivity.java │ │ │ └── SettingsActivity.java │ │ │ ├── fragment │ │ │ ├── AppWindowsListFragment.java │ │ │ ├── NfcTagsListFragment.java │ │ │ ├── RememberMasterPasswordFingerprintFragment.java │ │ │ ├── RememberMasterPasswordPatternFragment.java │ │ │ └── SettingsDetailsFragment.java │ │ │ ├── model │ │ │ └── AccessibilityExtra.java │ │ │ ├── preference │ │ │ └── NumberPickerPreference.java │ │ │ ├── provider │ │ │ └── PassProvider.java │ │ │ ├── service │ │ │ ├── ClipboardRestoreService.java │ │ │ ├── PasswordHelperService.java │ │ │ └── QuickSettingsTileService.java │ │ │ ├── util │ │ │ ├── ContextCompat.java │ │ │ ├── FingerprintCryptoHelper.java │ │ │ ├── FingerprintHelper.java │ │ │ ├── MasterPasswordEncrypter.java │ │ │ ├── Utils.java │ │ │ └── passgen │ │ │ │ ├── PassGen.java │ │ │ │ └── impl │ │ │ │ ├── HotpPinImpl.java │ │ │ │ ├── PasswordComposerImpl.java │ │ │ │ └── SuperGenPassImpl.java │ │ │ └── view │ │ │ ├── PasswordContainer.java │ │ │ ├── RememberMasterPasswordContainer.java │ │ │ └── controller │ │ │ ├── PatternPasswordController.java │ │ │ ├── RememberPasswordFingerprintController.java │ │ │ ├── RememberPasswordFingerprintNoticeController.java │ │ │ ├── RememberPasswordInputController.java │ │ │ ├── RememberPasswordPatternController.java │ │ │ ├── RememberPasswordPatternNoticeController.java │ │ │ └── TextPasswordController.java │ │ └── openauthentication │ │ └── otp │ │ └── OneTimePasswordAlgorithm.java ├── res-localized │ └── values-zh-rCN │ │ └── strings.xml └── res │ ├── anim │ ├── slide_in_left.xml │ ├── slide_in_right.xml │ ├── slide_out_left.xml │ └── slide_out_right.xml │ ├── drawable-hdpi │ ├── ic_fp_40px.png │ ├── ic_launcher.png │ ├── ic_pw_40px.png │ ├── ic_qs_tile_mdpi.png │ └── ic_stat_password.png │ ├── drawable-mdpi │ ├── ic_fp_40px.png │ ├── ic_launcher.png │ ├── ic_pw_40px.png │ ├── ic_qs_tile_mdpi.png │ └── ic_stat_password.png │ ├── drawable-xhdpi │ ├── ic_fp_40px.png │ ├── ic_launcher.png │ ├── ic_pw_40px.png │ ├── ic_qs_tile_mdpi.png │ └── ic_stat_password.png │ ├── drawable-xxhdpi │ ├── ic_fp_40px.png │ ├── ic_launcher.png │ ├── ic_pw_40px.png │ ├── ic_qs_tile_mdpi.png │ └── ic_stat_password.png │ ├── drawable-xxxhdpi │ ├── ic_fp_40px.png │ ├── ic_launcher.png │ ├── ic_pw_40px.png │ └── ic_qs_tile_mdpi.png │ ├── drawable │ ├── ic_action_back.xml │ ├── ic_action_clear.xml │ ├── ic_action_copy.xml │ ├── ic_action_info.xml │ ├── ic_action_settings.xml │ └── ic_btn_ok.xml │ ├── layout │ ├── activity_pass_gen_dialog.xml │ ├── dialog_add_card.xml │ ├── fragment_remember_master_password_fingerprint.xml │ ├── fragment_remember_master_password_pattern.xml │ ├── layout_dialog_title_pass_gen.xml │ ├── layout_password_pattern.xml │ └── layout_password_text.xml │ ├── menu │ ├── menu_pass_dialog.xml │ └── menu_tags_list.xml │ ├── raw │ └── two_level_tlds.txt │ ├── values │ ├── arrays.xml │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── accessibility_service.xml │ └── pref_general.xml ├── release └── java │ └── org │ └── mariotaku │ └── pass │ └── util │ └── DebugModeUtils.java └── test └── java └── org └── mariotaku └── pass └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | /build 3 | 4 | # Local configuration file (sdk path, etc) 5 | local.properties 6 | 7 | # Gradle generated files 8 | .gradle/ 9 | 10 | # Signing files 11 | .signing/ 12 | 13 | # User-specific configurations 14 | /.idea 15 | *.iml 16 | 17 | # OS-specific files 18 | .DS_Store 19 | .DS_Store? 20 | ._* 21 | .Spotlight-V100 22 | .Trashes 23 | ehthumbs.db 24 | Thumbs.db 25 | 26 | # Private files 27 | /signing.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Pass 2 | 3 | Password Generator for Android -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.1.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | 16 | android { 17 | compileSdkVersion 24 18 | buildToolsVersion "24.0.1" 19 | 20 | defaultConfig { 21 | applicationId "org.mariotaku.pass" 22 | minSdkVersion 21 23 | targetSdkVersion 24 24 | versionCode 3 25 | versionName "0.9.2" 26 | } 27 | 28 | lintOptions { 29 | abortOnError false 30 | } 31 | 32 | sourceSets { 33 | main { 34 | res.srcDirs += project.files("src/$name/res-localized") 35 | } 36 | } 37 | 38 | buildTypes { 39 | release { 40 | minifyEnabled false 41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 42 | } 43 | } 44 | 45 | buildTypes.each { buildType -> 46 | def file = rootProject.file('signing.properties') 47 | if (file.exists()) { 48 | def cfg = signingConfigs.maybeCreate(buildType.name) 49 | loadSigningConfig(cfg, file) 50 | buildType.signingConfig = cfg 51 | } 52 | } 53 | } 54 | 55 | repositories { 56 | jcenter() 57 | maven { url 'https://jitpack.io/' } 58 | } 59 | 60 | dependencies { 61 | compile 'com.eftimoff:android-patternview:1.0.3@aar' 62 | compile 'com.android.support:support-annotations:24.2.0' 63 | compile 'com.android.support:support-v4:24.2.0' 64 | compile 'com.github.mariotaku:SQLiteQB:e3b8e3b6ac' 65 | compile 'com.squareup.okio:okio:1.6.0' 66 | compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1' 67 | debugCompile 'com.facebook.stetho:stetho:1.2.0' 68 | compile fileTree(dir: 'libs', include: ['*.jar']) 69 | testCompile 'junit:junit:4.12' 70 | } 71 | 72 | def loadSigningConfig(def cfg, def file) { 73 | Properties signingProp = new Properties() 74 | signingProp.load(file.newInputStream()) 75 | cfg.setStoreFile(new File((String) signingProp.get('storeFile'))) 76 | cfg.setStorePassword((String) signingProp.get('storePassword')) 77 | cfg.setKeyAlias((String) signingProp.get('keyAlias')) 78 | cfg.setKeyPassword((String) signingProp.get('keyPassword')) 79 | } 80 | -------------------------------------------------------------------------------- /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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 01 09:57:31 CST 2016 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-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/ic_launcher-web.png -------------------------------------------------------------------------------- /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 /Users/mariotaku/Library/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 | -------------------------------------------------------------------------------- /src/androidTest/java/org/mariotaku/pass/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /src/debug/java/org/mariotaku/pass/util/DebugModeUtils.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.stetho.Stetho; 6 | 7 | /** 8 | * Created by mariotaku on 15/11/19. 9 | */ 10 | public class DebugModeUtils { 11 | 12 | public static void init(Application application) { 13 | Stetho.initializeWithDefaults(application); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 46 | 47 | 50 | 51 | 52 | 53 | 56 | 57 | 62 | 63 | 64 | 65 | 66 | 67 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/assets/databases/pass.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/assets/databases/pass.db -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/Constants.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass; 2 | 3 | /** 4 | * Created by mariotaku on 15/10/29. 5 | */ 6 | public interface Constants { 7 | 8 | String LOGTAG = "Pass"; 9 | 10 | String SHARED_PREFERENCES_NAME = "preferences"; 11 | String KEY_PATTERN_PASSWORD = "pattern_password"; 12 | String KEY_PATTERN_IV = "pattern_iv"; 13 | String KEY_FINGERPRINT_PASSWORD = "fingerprint_password"; 14 | String KEY_FINGERPRINT_IV = "fingerprint_iv"; 15 | String KEY_PASSWORD_SALT = "password_salt"; 16 | String KEY_PASSWORD_LENGTH = "password_length"; 17 | String KEY_PIN_LENGTH = "pin_length"; 18 | String KEY_PASSWORD_GENERATOR_TYPE = "password_generator_type"; 19 | String KEY_PASSWORD_RETRY_COUNT = "password_retry_count"; 20 | String KEY_CLEAR_COPIED_PASSWORD_AUTOMATICALLY = "clear_copied_password_automatically"; 21 | String KEY_PASSWORD_INCLUDE_SPECIAL_CHARACTERS = "password_include_special_characters"; 22 | 23 | String KEY_COPIED_PASSWORD_VALIDITY = "copied_password_validity"; 24 | String ACTION_TAG_DISCOVERED = BuildConfig.APPLICATION_ID + ".TAG_DISCOVERED"; 25 | 26 | String ACTION_PASSWORD_CALLBACK = BuildConfig.APPLICATION_ID + ".PASSWORD_CALLBACK"; 27 | String EXTRA_RESID = "resid"; 28 | 29 | String EXTRA_ACCESSIBILITY_EXTRA = "accessibility_extra"; 30 | 31 | int REQUEST_REQUEST_PERMISSION = 101; 32 | int NOTIFICATION_ID_CLIPBOARD = 100; 33 | int NOTIFICATION_ID_ACCESSIBILITY = 101; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/PassApplication.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass; 2 | 3 | import android.app.Application; 4 | 5 | import org.mariotaku.pass.util.DebugModeUtils; 6 | 7 | public class PassApplication extends Application implements Constants { 8 | public void onCreate() { 9 | super.onCreate(); 10 | DebugModeUtils.init(this); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/activity/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.activity; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Fragment; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.preference.PreferenceActivity; 8 | 9 | import org.mariotaku.pass.Constants; 10 | import org.mariotaku.pass.R; 11 | import org.mariotaku.pass.fragment.SettingsDetailsFragment; 12 | 13 | /** 14 | * Created by mariotaku on 15/10/31. 15 | */ 16 | public class SettingsActivity extends PreferenceActivity implements Constants { 17 | 18 | @Override 19 | protected void onCreate(final Bundle savedInstanceState) { 20 | final Intent intent = getIntent(); 21 | if (!intent.hasExtra(EXTRA_SHOW_FRAGMENT) && !intent.hasExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS)) { 22 | final Bundle fragmentArgs = new Bundle(); 23 | fragmentArgs.putInt(EXTRA_RESID, R.xml.pref_general); 24 | intent.putExtra(EXTRA_SHOW_FRAGMENT, SettingsDetailsFragment.class.getName()); 25 | intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); 26 | } 27 | super.onCreate(savedInstanceState); 28 | 29 | final ActionBar actionBar = getActionBar(); 30 | if (actionBar != null) { 31 | actionBar.setDisplayHomeAsUpEnabled(true); 32 | } 33 | } 34 | 35 | @Override 36 | protected boolean isValidFragment(final String fragmentName) { 37 | try { 38 | return Fragment.class.isAssignableFrom(Class.forName(fragmentName)); 39 | } catch (ClassNotFoundException e) { 40 | return false; 41 | } 42 | } 43 | 44 | @Override 45 | public boolean onNavigateUp() { 46 | finish(); 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/fragment/AppWindowsListFragment.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.fragment; 2 | 3 | import android.app.ListFragment; 4 | import android.app.LoaderManager; 5 | import android.content.CursorLoader; 6 | import android.content.Loader; 7 | import android.database.Cursor; 8 | import android.os.Bundle; 9 | import android.widget.SimpleCursorAdapter; 10 | 11 | import org.mariotaku.pass.Constants; 12 | import org.mariotaku.pass.provider.PassProvider.PassDataStore.AppWindows; 13 | 14 | /** 15 | * Created by mariotaku on 15/11/17. 16 | */ 17 | public class AppWindowsListFragment extends ListFragment implements Constants, LoaderManager.LoaderCallbacks { 18 | 19 | @Override 20 | public void onActivityCreated(final Bundle savedInstanceState) { 21 | super.onActivityCreated(savedInstanceState); 22 | setListAdapter(new SimpleCursorAdapter(getActivity(), 23 | android.R.layout.simple_list_item_activated_2, null, 24 | new String[]{AppWindows.WINDOW_NAME, AppWindows.DOMAIN}, 25 | new int[]{android.R.id.text1, android.R.id.text2}, 0)); 26 | getLoaderManager().initLoader(0, null, this); 27 | setListShownNoAnimation(false); 28 | } 29 | 30 | @Override 31 | public Loader onCreateLoader(final int id, final Bundle args) { 32 | return new CursorLoader(getActivity(), AppWindows.CONTENT_URI, AppWindows.COLUMNS, null, null, null); 33 | } 34 | 35 | @Override 36 | public void onLoadFinished(final Loader loader, final Cursor data) { 37 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(data); 38 | setListShown(true); 39 | } 40 | 41 | @Override 42 | public void onLoaderReset(final Loader loader) { 43 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(null); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/fragment/NfcTagsListFragment.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.fragment; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.Dialog; 6 | import android.app.DialogFragment; 7 | import android.app.ListFragment; 8 | import android.app.LoaderManager; 9 | import android.app.PendingIntent; 10 | import android.content.BroadcastReceiver; 11 | import android.content.ContentValues; 12 | import android.content.Context; 13 | import android.content.CursorLoader; 14 | import android.content.DialogInterface; 15 | import android.content.Intent; 16 | import android.content.IntentFilter; 17 | import android.content.Loader; 18 | import android.database.Cursor; 19 | import android.nfc.NfcAdapter; 20 | import android.nfc.Tag; 21 | import android.os.Bundle; 22 | import android.text.Editable; 23 | import android.text.TextUtils; 24 | import android.text.TextWatcher; 25 | import android.view.Menu; 26 | import android.view.MenuInflater; 27 | import android.widget.Button; 28 | import android.widget.EditText; 29 | import android.widget.SimpleCursorAdapter; 30 | import android.widget.Toast; 31 | 32 | import org.mariotaku.pass.BuildConfig; 33 | import org.mariotaku.pass.Constants; 34 | import org.mariotaku.pass.R; 35 | import org.mariotaku.pass.provider.PassProvider.PassDataStore.NfcTags; 36 | import org.mariotaku.sqliteqb.library.Expression; 37 | 38 | import okio.ByteString; 39 | 40 | /** 41 | * Created by mariotaku on 15/11/17. 42 | */ 43 | public class NfcTagsListFragment extends ListFragment implements Constants, LoaderManager.LoaderCallbacks { 44 | 45 | private BroadcastReceiver mNfcReceiver = new BroadcastReceiver() { 46 | 47 | @Override 48 | public void onReceive(final Context context, final Intent intent) { 49 | final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); 50 | final byte[] id = tag.getId(); 51 | if (id.length > 0) { 52 | final String where = Expression.equalsArgs(NfcTags.TAG_ID).getSQL(); 53 | final String[] whereArgs = {ByteString.of(id).hex()}; 54 | try (Cursor cursor = getActivity().getContentResolver().query(NfcTags.CONTENT_URI, 55 | NfcTags.COLUMNS, where, whereArgs, null)) { 56 | if (cursor != null && cursor.moveToFirst()) { 57 | } else { 58 | showAddTagDialog(tag); 59 | } 60 | } 61 | } else { 62 | Toast.makeText(getActivity(), R.string.unsupported_tag, Toast.LENGTH_SHORT).show(); 63 | } 64 | } 65 | }; 66 | 67 | private void showAddTagDialog(final Tag tag) { 68 | final AddCardDialogFragment df = new AddCardDialogFragment(); 69 | final Bundle args = new Bundle(); 70 | args.putParcelable(NfcAdapter.EXTRA_TAG, tag); 71 | df.setArguments(args); 72 | df.show(getFragmentManager(), "add_card"); 73 | } 74 | 75 | @Override 76 | public void onActivityCreated(final Bundle savedInstanceState) { 77 | super.onActivityCreated(savedInstanceState); 78 | setHasOptionsMenu(true); 79 | setListAdapter(new SimpleCursorAdapter(getActivity(), 80 | android.R.layout.simple_list_item_activated_2, null, 81 | new String[]{NfcTags.NAME, NfcTags.DOMAIN}, 82 | new int[]{android.R.id.text1, android.R.id.text2}, 0)); 83 | getLoaderManager().initLoader(0, null, this); 84 | setListShownNoAnimation(false); 85 | } 86 | 87 | @Override 88 | public Loader onCreateLoader(final int id, final Bundle args) { 89 | return new CursorLoader(getActivity(), NfcTags.CONTENT_URI, NfcTags.COLUMNS, null, null, null); 90 | } 91 | 92 | @Override 93 | public void onLoadFinished(final Loader loader, final Cursor data) { 94 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(data); 95 | setListShown(true); 96 | } 97 | 98 | @Override 99 | public void onLoaderReset(final Loader loader) { 100 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(null); 101 | } 102 | 103 | @Override 104 | public void onPause() { 105 | super.onPause(); 106 | final Activity activity = getActivity(); 107 | final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity); 108 | if (nfcAdapter != null && nfcAdapter.isEnabled()) { 109 | nfcAdapter.disableForegroundDispatch(activity); 110 | } 111 | activity.unregisterReceiver(mNfcReceiver); 112 | } 113 | 114 | @Override 115 | public void onResume() { 116 | final Activity activity = getActivity(); 117 | activity.registerReceiver(mNfcReceiver, new IntentFilter(ACTION_TAG_DISCOVERED)); 118 | final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity); 119 | if (nfcAdapter != null && nfcAdapter.isEnabled()) { 120 | final Intent intent = new Intent(ACTION_TAG_DISCOVERED); 121 | intent.setPackage(BuildConfig.APPLICATION_ID); 122 | nfcAdapter.enableForegroundDispatch(activity, PendingIntent.getBroadcast(activity, 123 | 0, intent, PendingIntent.FLAG_UPDATE_CURRENT), null, null); 124 | } 125 | super.onResume(); 126 | } 127 | 128 | @Override 129 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 130 | inflater.inflate(R.menu.menu_tags_list, menu); 131 | } 132 | 133 | public static class AddCardDialogFragment extends DialogFragment implements DialogInterface.OnClickListener, DialogInterface.OnShowListener, TextWatcher { 134 | @Override 135 | public Dialog onCreateDialog(final Bundle savedInstanceState) { 136 | final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 137 | builder.setTitle(R.string.add_card); 138 | builder.setView(R.layout.dialog_add_card); 139 | builder.setNegativeButton(android.R.string.cancel, null); 140 | builder.setPositiveButton(android.R.string.ok, this); 141 | final AlertDialog dialog = builder.create(); 142 | dialog.setOnShowListener(this); 143 | return dialog; 144 | } 145 | 146 | @Override 147 | public void onClick(final DialogInterface dialog, final int which) { 148 | switch (which) { 149 | case DialogInterface.BUTTON_POSITIVE: { 150 | final ContentValues values = new ContentValues(); 151 | final AlertDialog alertDialog = (AlertDialog) getDialog(); 152 | final EditText editHost = (EditText) alertDialog.findViewById(R.id.edit_host); 153 | final EditText editName = (EditText) alertDialog.findViewById(R.id.edit_name); 154 | final Editable editHostText = editHost.getText(), editNameText = editName.getText(); 155 | if (TextUtils.isEmpty(editHostText)) return; 156 | values.put(NfcTags.DOMAIN, editHostText.toString()); 157 | values.put(NfcTags.NAME, editNameText.toString()); 158 | values.put(NfcTags.TAG_ID, ByteString.of(getNfcTag().getId()).hex()); 159 | getActivity().getContentResolver().insert(NfcTags.CONTENT_URI, values); 160 | break; 161 | } 162 | } 163 | } 164 | 165 | public Tag getNfcTag() { 166 | return getArguments().getParcelable(NfcAdapter.EXTRA_TAG); 167 | } 168 | 169 | @Override 170 | public void onShow(final DialogInterface dialog) { 171 | final AlertDialog alertDialog = (AlertDialog) dialog; 172 | final EditText editHost = (EditText) alertDialog.findViewById(R.id.edit_host); 173 | final EditText editName = (EditText) alertDialog.findViewById(R.id.edit_name); 174 | final byte[] id = getNfcTag().getId(); 175 | final ByteString bs = ByteString.of(id); 176 | final String idHex = bs.hex(); 177 | if (idHex.length() > 8) { 178 | editName.setText(getString(R.string.tag_name_prefix, idHex.substring(idHex.length() - 8))); 179 | } else { 180 | editName.setText(getString(R.string.tag_name_prefix, idHex)); 181 | } 182 | editHost.addTextChangedListener(this); 183 | 184 | updatePositiveButton(); 185 | } 186 | 187 | @Override 188 | public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { 189 | 190 | } 191 | 192 | @Override 193 | public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { 194 | 195 | } 196 | 197 | @Override 198 | public void afterTextChanged(final Editable s) { 199 | updatePositiveButton(); 200 | } 201 | 202 | private void updatePositiveButton() { 203 | final AlertDialog alertDialog = (AlertDialog) getDialog(); 204 | final Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); 205 | final EditText editHost = (EditText) alertDialog.findViewById(R.id.edit_host); 206 | final EditText editName = (EditText) alertDialog.findViewById(R.id.edit_name); 207 | button.setEnabled(editHost.length() > 0); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/fragment/RememberMasterPasswordFingerprintFragment.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.fragment; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | import android.widget.Toast; 11 | 12 | import org.mariotaku.pass.Constants; 13 | import org.mariotaku.pass.R; 14 | import org.mariotaku.pass.util.FingerprintHelper; 15 | import org.mariotaku.pass.view.RememberMasterPasswordContainer; 16 | 17 | /** 18 | * Created by mariotaku on 15/10/31. 19 | */ 20 | public class RememberMasterPasswordFingerprintFragment extends Fragment implements Constants, 21 | View.OnClickListener, RememberMasterPasswordContainer.PageListener { 22 | private RememberMasterPasswordContainer mViewPages; 23 | private FingerprintHelper mFingerprintHelper; 24 | private Button mPreviousButton, mNextButton; 25 | 26 | @Override 27 | public void onActivityCreated(final Bundle savedInstanceState) { 28 | super.onActivityCreated(savedInstanceState); 29 | mFingerprintHelper = FingerprintHelper.getInstance(getActivity()); 30 | mNextButton.setOnClickListener(this); 31 | mPreviousButton.setOnClickListener(this); 32 | mViewPages.setPageListener(this); 33 | 34 | final boolean hasPermission = mFingerprintHelper.requestPermission(this, REQUEST_REQUEST_PERMISSION); 35 | mNextButton.setEnabled(hasPermission); 36 | if (hasPermission) { 37 | checkFingerprintSupport(); 38 | } 39 | updatePage(); 40 | } 41 | 42 | public FingerprintHelper getFingerprintHelper() { 43 | return mFingerprintHelper; 44 | } 45 | 46 | @Override 47 | 48 | public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, 49 | @NonNull final int[] grantResults) { 50 | switch (requestCode) { 51 | case REQUEST_REQUEST_PERMISSION: { 52 | final boolean hasPermission = mFingerprintHelper.hasPermission(); 53 | mNextButton.setEnabled(hasPermission); 54 | if (hasPermission) { 55 | checkFingerprintSupport(); 56 | } 57 | break; 58 | } 59 | } 60 | } 61 | 62 | private void checkFingerprintSupport() { 63 | if (!mFingerprintHelper.isHardwareDetected()) { 64 | Toast.makeText(getActivity(), R.string.fingerprint_not_supported, Toast.LENGTH_LONG).show(); 65 | getActivity().finish(); 66 | } else if (!mFingerprintHelper.hasEnrolledFingerprints()) { 67 | Toast.makeText(getActivity(), R.string.no_fingerprint_enrolled, Toast.LENGTH_LONG).show(); 68 | getActivity().finish(); 69 | } else { 70 | mNextButton.setEnabled(true); 71 | } 72 | } 73 | 74 | @Override 75 | public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { 76 | return inflater.inflate(R.layout.fragment_remember_master_password_fingerprint, container, false); 77 | } 78 | 79 | @Override 80 | public void onViewCreated(final View view, final Bundle savedInstanceState) { 81 | super.onViewCreated(view, savedInstanceState); 82 | mViewPages = ((RememberMasterPasswordContainer) view.findViewById(R.id.view_pages)); 83 | mPreviousButton = (Button) view.findViewById(R.id.previous); 84 | mNextButton = (Button) view.findViewById(R.id.next); 85 | } 86 | 87 | @Override 88 | public void onClick(final View v) { 89 | switch (v.getId()) { 90 | case R.id.next: { 91 | mViewPages.showNext(); 92 | break; 93 | } 94 | case R.id.previous: { 95 | mViewPages.showPrevious(); 96 | break; 97 | } 98 | } 99 | } 100 | 101 | @Override 102 | public void onPageChanged(final int current) { 103 | updatePage(); 104 | } 105 | 106 | private void updatePage() { 107 | final int displayedChild = mViewPages.getDisplayedChild(); 108 | if (displayedChild == 0) { 109 | mPreviousButton.setText(R.string.cancel); 110 | } else if (displayedChild == mViewPages.getChildCount() - 1) { 111 | mNextButton.setText(R.string.finish); 112 | } else { 113 | mPreviousButton.setText(R.string.previous); 114 | mNextButton.setText(R.string.next); 115 | } 116 | } 117 | 118 | @Override 119 | public void onReachedEnd() { 120 | getActivity().finish(); 121 | } 122 | 123 | @Override 124 | public void onReachedStart() { 125 | getActivity().finish(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/fragment/RememberMasterPasswordPatternFragment.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.fragment; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | 10 | import org.mariotaku.pass.R; 11 | import org.mariotaku.pass.view.RememberMasterPasswordContainer; 12 | 13 | /** 14 | * Created by mariotaku on 15/10/31. 15 | */ 16 | public class RememberMasterPasswordPatternFragment extends Fragment implements View.OnClickListener, RememberMasterPasswordContainer.PageListener { 17 | private RememberMasterPasswordContainer mViewPages; 18 | private Button mPreviousButton, mNextButton; 19 | 20 | @Override 21 | public void onActivityCreated(final Bundle savedInstanceState) { 22 | super.onActivityCreated(savedInstanceState); 23 | mNextButton.setOnClickListener(this); 24 | mPreviousButton.setOnClickListener(this); 25 | mViewPages.setPageListener(this); 26 | 27 | updatePage(); 28 | } 29 | 30 | @Override 31 | public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { 32 | return inflater.inflate(R.layout.fragment_remember_master_password_pattern, container, false); 33 | } 34 | 35 | @Override 36 | public void onViewCreated(final View view, final Bundle savedInstanceState) { 37 | super.onViewCreated(view, savedInstanceState); 38 | mViewPages = ((RememberMasterPasswordContainer) view.findViewById(R.id.view_pages)); 39 | mPreviousButton = (Button) view.findViewById(R.id.previous); 40 | mNextButton = (Button) view.findViewById(R.id.next); 41 | } 42 | 43 | @Override 44 | public void onClick(final View v) { 45 | switch (v.getId()) { 46 | case R.id.next: { 47 | mViewPages.showNext(); 48 | break; 49 | } 50 | case R.id.previous: { 51 | mViewPages.showPrevious(); 52 | break; 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public void onPageChanged(final int current) { 59 | updatePage(); 60 | } 61 | 62 | private void updatePage() { 63 | final int displayedChild = mViewPages.getDisplayedChild(); 64 | if (displayedChild == 0) { 65 | mPreviousButton.setText(R.string.cancel); 66 | } else if (displayedChild == mViewPages.getChildCount() - 1) { 67 | mNextButton.setText(R.string.finish); 68 | } else { 69 | mPreviousButton.setText(R.string.previous); 70 | mNextButton.setText(R.string.next); 71 | } 72 | } 73 | 74 | @Override 75 | public void onReachedEnd() { 76 | getActivity().finish(); 77 | } 78 | 79 | @Override 80 | public void onReachedStart() { 81 | getActivity().finish(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/fragment/SettingsDetailsFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Twidere - Twitter client for Android 3 | * 4 | * Copyright (C) 2012-2014 Mariotaku Lee 5 | * 6 | * This program 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 | * This program 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 this program. If not, see . 18 | */ 19 | 20 | package org.mariotaku.pass.fragment; 21 | 22 | import android.content.SharedPreferences; 23 | import android.os.Bundle; 24 | import android.preference.Preference; 25 | import android.preference.PreferenceFragment; 26 | import android.preference.PreferenceManager; 27 | import android.preference.PreferenceScreen; 28 | 29 | import org.mariotaku.pass.Constants; 30 | import org.mariotaku.pass.util.Utils; 31 | 32 | 33 | public class SettingsDetailsFragment extends PreferenceFragment implements Constants, SharedPreferences.OnSharedPreferenceChangeListener { 34 | 35 | @Override 36 | public void onActivityCreated(final Bundle savedInstanceState) { 37 | super.onActivityCreated(savedInstanceState); 38 | final PreferenceManager preferenceManager = getPreferenceManager(); 39 | preferenceManager.setSharedPreferencesName(SHARED_PREFERENCES_NAME); 40 | final PreferenceScreen defaultScreen = getPreferenceScreen(); 41 | final PreferenceScreen preferenceScreen; 42 | if (defaultScreen != null) { 43 | defaultScreen.removeAll(); 44 | preferenceScreen = defaultScreen; 45 | } else { 46 | preferenceScreen = preferenceManager.createPreferenceScreen(getActivity()); 47 | } 48 | setPreferenceScreen(preferenceScreen); 49 | final Bundle args = getArguments(); 50 | final Object rawResId = args.get(EXTRA_RESID); 51 | final int resId; 52 | if (rawResId instanceof Integer) { 53 | resId = (Integer) rawResId; 54 | } else if (rawResId instanceof String) { 55 | resId = Utils.getResId(getActivity(), (String) rawResId); 56 | } else { 57 | resId = 0; 58 | } 59 | if (resId != 0) { 60 | addPreferencesFromResource(resId); 61 | } 62 | } 63 | 64 | @Override 65 | public void onStart() { 66 | super.onStart(); 67 | final SharedPreferences preferences = getPreferenceManager().getSharedPreferences(); 68 | preferences.registerOnSharedPreferenceChangeListener(this); 69 | } 70 | 71 | @Override 72 | public void onStop() { 73 | final SharedPreferences preferences = getPreferenceManager().getSharedPreferences(); 74 | preferences.unregisterOnSharedPreferenceChangeListener(this); 75 | super.onStop(); 76 | } 77 | 78 | @Override 79 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 80 | final Preference preference = findPreference(key); 81 | if (preference == null) return; 82 | final Bundle extras = preference.getExtras(); 83 | // if (extras != null && extras.containsKey(EXTRA_NOTIFY_CHANGE)) { 84 | // SettingsActivity.setShouldNotifyChange(getActivity()); 85 | // } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/model/AccessibilityExtra.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.support.annotation.NonNull; 6 | import android.view.accessibility.AccessibilityEvent; 7 | 8 | /** 9 | * Created by mariotaku on 15/11/18. 10 | */ 11 | public class AccessibilityExtra implements Parcelable { 12 | 13 | @NonNull 14 | public AccessibilityEvent window, view; 15 | 16 | public AccessibilityExtra(@NonNull final AccessibilityEvent window, @NonNull final AccessibilityEvent view) { 17 | this.window = window; 18 | this.view = view; 19 | } 20 | 21 | protected AccessibilityExtra(Parcel in) { 22 | window = in.readParcelable(AccessibilityEvent.class.getClassLoader()); 23 | view = in.readParcelable(AccessibilityEvent.class.getClassLoader()); 24 | } 25 | 26 | @Override 27 | public void writeToParcel(Parcel dest, int flags) { 28 | dest.writeParcelable(window, flags); 29 | dest.writeParcelable(view, flags); 30 | } 31 | 32 | @Override 33 | public int describeContents() { 34 | return 0; 35 | } 36 | 37 | public static final Creator CREATOR = new Creator() { 38 | @Override 39 | public AccessibilityExtra createFromParcel(Parcel in) { 40 | return new AccessibilityExtra(in); 41 | } 42 | 43 | @Override 44 | public AccessibilityExtra[] newArray(int size) { 45 | return new AccessibilityExtra[size]; 46 | } 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/preference/NumberPickerPreference.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.preference; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.preference.DialogPreference; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.NumberPicker; 9 | 10 | import org.mariotaku.pass.R; 11 | 12 | public class NumberPickerPreference extends DialogPreference { 13 | private final int mMinValue, mMaxValue; 14 | private NumberPicker mPicker; 15 | private Integer mNumber = 0; 16 | 17 | public NumberPickerPreference(Context context, AttributeSet attrs) { 18 | this(context, attrs, android.R.attr.dialogPreferenceStyle); 19 | } 20 | 21 | public NumberPickerPreference(Context context, AttributeSet attrs, int defStyle) { 22 | super(context, attrs, defStyle); 23 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreferences); 24 | mMinValue = a.getInt(R.styleable.NumberPickerPreferences_min, 0); 25 | mMaxValue = a.getInt(R.styleable.NumberPickerPreferences_max, 10); 26 | a.recycle(); 27 | setPositiveButtonText(android.R.string.ok); 28 | setNegativeButtonText(android.R.string.cancel); 29 | } 30 | 31 | @Override 32 | protected View onCreateDialogView() { 33 | mPicker = new NumberPicker(getContext()); 34 | mPicker.setMinValue(mMinValue); 35 | mPicker.setMaxValue(mMaxValue); 36 | mPicker.setValue(mNumber); 37 | return mPicker; 38 | } 39 | 40 | @Override 41 | protected void onDialogClosed(boolean positiveResult) { 42 | if (positiveResult) { 43 | // needed when user edits the text field and clicks OK 44 | mPicker.clearFocus(); 45 | setValue(mPicker.getValue()); 46 | } 47 | } 48 | 49 | @Override 50 | protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 51 | setValue(restoreValue ? getPersistedInt(mNumber) : (Integer) defaultValue); 52 | } 53 | 54 | public void setValue(int value) { 55 | if (shouldPersist()) { 56 | persistInt(value); 57 | } 58 | 59 | if (value != mNumber) { 60 | mNumber = value; 61 | notifyChanged(); 62 | } 63 | } 64 | 65 | @Override 66 | protected Object onGetDefaultValue(TypedArray a, int index) { 67 | return a.getInt(index, 0); 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/provider/PassProvider.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.provider; 2 | 3 | import android.content.ContentProvider; 4 | import android.content.ContentResolver; 5 | import android.content.ContentValues; 6 | import android.content.Context; 7 | import android.content.UriMatcher; 8 | import android.database.Cursor; 9 | import android.database.sqlite.SQLiteDatabase; 10 | import android.net.Uri; 11 | import android.provider.BaseColumns; 12 | import android.support.annotation.NonNull; 13 | import android.support.annotation.Nullable; 14 | 15 | import com.readystatesoftware.sqliteasset.SQLiteAssetHelper; 16 | 17 | import org.mariotaku.pass.BuildConfig; 18 | import org.mariotaku.sqliteqb.library.Columns; 19 | import org.mariotaku.sqliteqb.library.Constraint; 20 | import org.mariotaku.sqliteqb.library.DataType; 21 | import org.mariotaku.sqliteqb.library.NewColumn; 22 | import org.mariotaku.sqliteqb.library.OnConflict; 23 | import org.mariotaku.sqliteqb.library.SQLQueryBuilder; 24 | 25 | /** 26 | * Created by mariotaku on 15/11/17. 27 | */ 28 | public class PassProvider extends ContentProvider { 29 | 30 | public static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 31 | .authority(BuildConfig.APPLICATION_ID).build(); 32 | public static final int TABLE_ID_RECENT_DOMAINS = 1; 33 | public static final int TABLE_ID_NFC_TAGS = 2; 34 | public static final int TABLE_ID_APP_WINDOWS = 3; 35 | private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 36 | 37 | static { 38 | URI_MATCHER.addURI(BuildConfig.APPLICATION_ID, "/recent_domains", TABLE_ID_RECENT_DOMAINS); 39 | URI_MATCHER.addURI(BuildConfig.APPLICATION_ID, "/nfc_tags", TABLE_ID_NFC_TAGS); 40 | URI_MATCHER.addURI(BuildConfig.APPLICATION_ID, "/app_windows", TABLE_ID_APP_WINDOWS); 41 | } 42 | 43 | private SQLiteDatabase mDatabase; 44 | 45 | @Override 46 | public boolean onCreate() { 47 | mDatabase = new SQLiteAssetHelper(getContext(), "pass.db", null, 1).getWritableDatabase(); 48 | return false; 49 | } 50 | 51 | @Nullable 52 | @Override 53 | public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) { 54 | final Cursor cursor = mDatabase.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder); 55 | setNotificationUri(cursor, uri); 56 | return cursor; 57 | } 58 | 59 | @Nullable 60 | @Override 61 | public String getType(@NonNull final Uri uri) { 62 | return null; 63 | } 64 | 65 | @Nullable 66 | @Override 67 | public Uri insert(@NonNull final Uri uri, final ContentValues values) { 68 | final long insertedId = mDatabase.insert(getTable(uri), null, values); 69 | notifyChange(uri); 70 | return Uri.withAppendedPath(uri, String.valueOf(insertedId)); 71 | } 72 | 73 | @Override 74 | public int delete(@NonNull final Uri uri, final String selection, final String[] selectionArgs) { 75 | final int rowsAffected = mDatabase.delete(getTable(uri), selection, selectionArgs); 76 | if (rowsAffected > 0) { 77 | notifyChange(uri); 78 | } 79 | return rowsAffected; 80 | } 81 | 82 | @Override 83 | public int update(@NonNull final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) { 84 | final int rowsAffected = mDatabase.update(getTable(uri), values, selection, selectionArgs); 85 | if (rowsAffected > 0) { 86 | notifyChange(uri); 87 | } 88 | return rowsAffected; 89 | } 90 | 91 | private void setNotificationUri(final Cursor cursor, final Uri uri) { 92 | final Context context = getContext(); 93 | assert context != null; 94 | cursor.setNotificationUri(context.getContentResolver(), uri); 95 | } 96 | 97 | private String getTable(final Uri uri) { 98 | switch (URI_MATCHER.match(uri)) { 99 | case TABLE_ID_RECENT_DOMAINS: { 100 | return PassDataStore.RecentDomains.TABLE_NAME; 101 | } 102 | case TABLE_ID_NFC_TAGS: { 103 | return PassDataStore.NfcTags.TABLE_NAME; 104 | } 105 | case TABLE_ID_APP_WINDOWS: { 106 | return PassDataStore.AppWindows.TABLE_NAME; 107 | } 108 | } 109 | return null; 110 | } 111 | 112 | private void notifyChange(final Uri uri) { 113 | final Context context = getContext(); 114 | assert context != null; 115 | context.getContentResolver().notifyChange(uri, null); 116 | } 117 | 118 | 119 | public interface PassDataStore { 120 | interface RecentDomains extends BaseColumns { 121 | 122 | String TABLE_NAME = "recent_domains"; 123 | String DOMAIN = "domain"; 124 | String RECENT = "recent"; 125 | String[] COLUMNS = {_ID, DOMAIN, RECENT}; 126 | Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, "recent_domains"); 127 | } 128 | 129 | interface NfcTags extends BaseColumns { 130 | String TABLE_NAME = "nfc_tags"; 131 | String TAG_ID = "tag_id"; 132 | String DOMAIN = "domain"; 133 | String NAME = "name"; 134 | String[] COLUMNS = {_ID, TAG_ID, DOMAIN, NAME}; 135 | Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, "nfc_tags"); 136 | } 137 | 138 | interface AppWindows extends BaseColumns { 139 | String TABLE_NAME = "app_windows"; 140 | String PACKAGE_NAME = "package_name"; 141 | String WINDOW_NAME = "window_name"; 142 | String DOMAIN = "domain"; 143 | String[] COLUMNS = {_ID, PACKAGE_NAME, WINDOW_NAME, DOMAIN}; 144 | Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, "app_windows"); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/service/ClipboardRestoreService.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.service; 2 | 3 | import android.app.IntentService; 4 | import android.app.Notification; 5 | import android.app.NotificationManager; 6 | import android.content.ClipData; 7 | import android.content.ClipboardManager; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.SharedPreferences; 11 | import android.os.SystemClock; 12 | 13 | import org.mariotaku.pass.Constants; 14 | import org.mariotaku.pass.R; 15 | 16 | 17 | public class ClipboardRestoreService extends IntentService implements Constants { 18 | 19 | public static void startCopy(Context context, ClipData clipData) { 20 | Intent intent = new Intent(context, ClipboardRestoreService.class); 21 | intent.setClipData(clipData); 22 | context.startService(intent); 23 | } 24 | 25 | public ClipboardRestoreService() { 26 | super("ClipboardRestoreService"); 27 | } 28 | 29 | @Override 30 | protected void onHandleIntent(Intent intent) { 31 | if (intent == null) return; 32 | final ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 33 | final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 34 | final SharedPreferences pref = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE); 35 | if (!pref.getBoolean(KEY_CLEAR_COPIED_PASSWORD_AUTOMATICALLY, true)) { 36 | cm.setPrimaryClip(intent.getClipData()); 37 | return; 38 | } 39 | final ClipboardChangedListener listener = new ClipboardChangedListener(cm); 40 | // Get primary clip as backup 41 | listener.current = cm.getPrimaryClip(); 42 | 43 | final ClipData newData = intent.getClipData(); 44 | cm.setPrimaryClip(newData); 45 | cm.addPrimaryClipChangedListener(listener); 46 | // Wait for time up 47 | final Notification.Builder nb = new Notification.Builder(this); 48 | nb.setSmallIcon(R.drawable.ic_stat_password); 49 | nb.setContentTitle(getString(R.string.app_name)); 50 | nb.setContentText(getString(R.string.password_copied)); 51 | nb.setOngoing(true); 52 | final int duration = Integer.parseInt(pref.getString(KEY_COPIED_PASSWORD_VALIDITY, "15000")); 53 | long start = SystemClock.uptimeMillis(), end = start + duration, curr = start; 54 | while (curr < end) { 55 | final int progress = (int) ((end - curr) / (double) duration * 100); 56 | try { 57 | Thread.sleep(100); 58 | } catch (InterruptedException e) { 59 | // Ignore 60 | } 61 | nb.setProgress(100, progress, false); 62 | nm.notify(NOTIFICATION_ID_CLIPBOARD, nb.build()); 63 | curr = SystemClock.uptimeMillis(); 64 | } 65 | // Clipboard may change during password waiting time, now restore them 66 | if (listener.current != null) { 67 | cm.setPrimaryClip(listener.current); 68 | } else { 69 | cm.setPrimaryClip(ClipData.newPlainText("", "")); 70 | } 71 | cm.removePrimaryClipChangedListener(listener); 72 | nm.cancel(NOTIFICATION_ID_CLIPBOARD); 73 | } 74 | 75 | private static class ClipboardChangedListener implements ClipboardManager.OnPrimaryClipChangedListener { 76 | 77 | private final ClipboardManager cm; 78 | private ClipData current; 79 | 80 | ClipboardChangedListener(ClipboardManager cm) { 81 | this.cm = cm; 82 | } 83 | 84 | @Override 85 | public void onPrimaryClipChanged() { 86 | current = cm.getPrimaryClip(); 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/service/PasswordHelperService.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.service; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.app.Notification; 5 | import android.app.NotificationManager; 6 | import android.app.PendingIntent; 7 | import android.content.BroadcastReceiver; 8 | import android.content.ClipData; 9 | import android.content.ClipboardManager; 10 | import android.content.Context; 11 | import android.content.Intent; 12 | import android.content.IntentFilter; 13 | import android.os.Bundle; 14 | import android.support.annotation.NonNull; 15 | import android.support.annotation.Nullable; 16 | import android.support.v4.content.LocalBroadcastManager; 17 | import android.text.TextUtils; 18 | import android.util.Log; 19 | import android.util.Patterns; 20 | import android.view.accessibility.AccessibilityEvent; 21 | import android.view.accessibility.AccessibilityNodeInfo; 22 | import android.webkit.WebView; 23 | import android.widget.EditText; 24 | import android.widget.Toast; 25 | 26 | import org.mariotaku.pass.BuildConfig; 27 | import org.mariotaku.pass.Constants; 28 | import org.mariotaku.pass.R; 29 | import org.mariotaku.pass.activity.PassGenDialogActivity; 30 | import org.mariotaku.pass.model.AccessibilityExtra; 31 | 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | import java.util.concurrent.atomic.AtomicReference; 35 | 36 | /** 37 | * Created by mariotaku on 15/11/18. 38 | */ 39 | public class PasswordHelperService extends AccessibilityService implements Constants { 40 | 41 | private BroadcastReceiver mCallbackReceiver = new BroadcastReceiver() { 42 | @Override 43 | public void onReceive(final Context context, final Intent intent) { 44 | final AccessibilityExtra extra = intent.getParcelableExtra(EXTRA_ACCESSIBILITY_EXTRA); 45 | final CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT); 46 | if (extra == null || text == null) return; 47 | mCallbackData.set(new PasswordCallbackData(extra, text)); 48 | 49 | } 50 | }; 51 | 52 | private final AtomicReference mCurrentWindow = new AtomicReference<>(); 53 | private final AtomicReference mCurrentView = new AtomicReference<>(); 54 | 55 | private final AtomicReference mPasswordWindow = new AtomicReference<>(); 56 | private final AtomicReference mPasswordView = new AtomicReference<>(); 57 | 58 | private final AtomicReference mCallbackData = new AtomicReference<>(); 59 | 60 | private AtomicReference mCurrentViewingLink = new AtomicReference<>(); 61 | 62 | 63 | @Override 64 | public void onCreate() { 65 | super.onCreate(); 66 | LocalBroadcastManager.getInstance(this).registerReceiver(mCallbackReceiver, new IntentFilter(ACTION_PASSWORD_CALLBACK)); 67 | } 68 | 69 | @Override 70 | public void onDestroy() { 71 | LocalBroadcastManager.getInstance(this).unregisterReceiver(mCallbackReceiver); 72 | super.onDestroy(); 73 | } 74 | 75 | @Override 76 | public void onInterrupt() { 77 | } 78 | 79 | @Override 80 | public void onAccessibilityEvent(final AccessibilityEvent event) { 81 | // Ignore changes inside myself 82 | if (TextUtils.equals(getPackageName(), event.getPackageName())) return; 83 | switch (event.getEventType()) { 84 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { 85 | // Not an activity window, skip 86 | if (event.getCurrentItemIndex() >= 0 || event.getItemCount() >= 0) { 87 | if (BuildConfig.DEBUG) { 88 | Log.d(LOGTAG, "Ignoring window state " + event); 89 | } 90 | break; 91 | } 92 | final AccessibilityEvent passwordWindowEvent = mPasswordWindow.get(); 93 | final AccessibilityEvent passwordViewEvent = mPasswordView.get(); 94 | // Went back to password input window 95 | if (passwordWindowEvent != null && passwordViewEvent != null && isSameWindow(event, passwordWindowEvent)) { 96 | final AccessibilityNodeInfo inputField = findFocus(AccessibilityNodeInfo.FOCUS_INPUT); 97 | final PasswordCallbackData data = mCallbackData.getAndSet(null); 98 | // Current focusing view is password view 99 | if (isSameView(inputField, passwordViewEvent.getSource())) { 100 | // Not finishing from PassGen, just switch back 101 | if (data == null) { 102 | onFocusedToPasswordView(passwordViewEvent, passwordWindowEvent, mCurrentViewingLink.get()); 103 | } else if (pasteText(inputField, data.password)) { 104 | // Do something after copied successfully 105 | } else { 106 | Toast.makeText(this, R.string.past_manually_hint, Toast.LENGTH_SHORT).show(); 107 | } 108 | } else { 109 | // Clear notification 110 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); 111 | nm.cancel(NOTIFICATION_ID_ACCESSIBILITY); 112 | } 113 | } else { 114 | // Clear notification 115 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); 116 | nm.cancel(NOTIFICATION_ID_ACCESSIBILITY); 117 | } 118 | final AccessibilityNodeInfo webViewNode = findWebViewNode(getRootInActiveWindow()); 119 | if (webViewNode != null) { 120 | handleBrowserLink(event, webViewNode); 121 | } 122 | mCurrentWindow.set(AccessibilityEvent.obtain(event)); 123 | mCurrentViewingLink.set(null); 124 | break; 125 | } 126 | case AccessibilityEvent.TYPE_VIEW_FOCUSED: { 127 | // Handle password fields 128 | mCurrentView.getAndSet(AccessibilityEvent.obtain(event)); 129 | final AccessibilityEvent windowEvent = mCurrentWindow.get(); 130 | AccessibilityEvent passwordView = mPasswordView.get(); 131 | if (event.isPassword() && windowEvent != null 132 | && TextUtils.equals(event.getPackageName(), windowEvent.getPackageName())) { 133 | final BrowserState browserState = mCurrentViewingLink.get(); 134 | if (browserState != null && TextUtils.equals(event.getPackageName(), browserState.packageName)) { 135 | onFocusedToPasswordView(event, windowEvent, browserState); 136 | } else { 137 | onFocusedToPasswordView(event, windowEvent, null); 138 | } 139 | } else if (passwordView == null || !isSameView(passwordView.getSource(), event.getSource())) { 140 | // Clear notification 141 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); 142 | nm.cancel(NOTIFICATION_ID_ACCESSIBILITY); 143 | } 144 | break; 145 | } 146 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { 147 | final AccessibilityNodeInfo source = event.getSource(); 148 | if (source != null && TextUtils.equals(source.getClassName(), WebView.class.getName())) { 149 | handleBrowserLink(event, source); 150 | } 151 | break; 152 | } 153 | } 154 | } 155 | 156 | @Nullable 157 | private AccessibilityNodeInfo findWebViewNode(@Nullable final AccessibilityNodeInfo node) { 158 | if (node == null) return null; 159 | if (TextUtils.equals(node.getClassName(), WebView.class.getName())) return node; 160 | AccessibilityNodeInfo webViewNode = null; 161 | for (int i = 0, j = node.getChildCount(); i < j; i++) { 162 | webViewNode = findWebViewNode(node.getChild(i)); 163 | if (webViewNode != null) break; 164 | } 165 | return webViewNode; 166 | } 167 | 168 | private void handleBrowserLink(final AccessibilityEvent event, final AccessibilityNodeInfo source) { 169 | final List editTextValues = new ArrayList<>(); 170 | findEditTextValues(getRootInActiveWindow(), source, editTextValues); 171 | CharSequence desiredUri = null; 172 | for (final CharSequence value : editTextValues) { 173 | if (TextUtils.isEmpty(value)) continue; 174 | if (Patterns.WEB_URL.matcher(value).matches()) { 175 | desiredUri = value; 176 | break; 177 | } 178 | } 179 | mCurrentViewingLink.set(new BrowserState(event.getPackageName(), desiredUri)); 180 | } 181 | 182 | private void onFocusedToPasswordView(final AccessibilityEvent viewEvent, 183 | final AccessibilityEvent windowEvent, 184 | final BrowserState browserState) { 185 | mPasswordWindow.set(AccessibilityEvent.obtain(windowEvent)); 186 | mPasswordView.set(AccessibilityEvent.obtain(viewEvent)); 187 | 188 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); 189 | final Notification.Builder builder = new Notification.Builder(this); 190 | builder.setSmallIcon(R.drawable.ic_stat_password); 191 | builder.setPriority(Notification.PRIORITY_MAX); 192 | builder.setVibrate(new long[0]); 193 | builder.setAutoCancel(true); 194 | builder.setContentTitle(getString(R.string.app_name)); 195 | builder.setContentText(getString(R.string.accessibility_password_hint)); 196 | final Intent passGenIntent = new Intent(this, PassGenDialogActivity.class); 197 | passGenIntent.putExtra(EXTRA_ACCESSIBILITY_EXTRA, new AccessibilityExtra(windowEvent, viewEvent)); 198 | if (browserState != null && !TextUtils.isEmpty(browserState.link)) { 199 | passGenIntent.putExtra(Intent.EXTRA_TEXT, browserState.link); 200 | } 201 | builder.setContentIntent(PendingIntent.getActivity(this, 0, passGenIntent, PendingIntent.FLAG_UPDATE_CURRENT)); 202 | //noinspection deprecation 203 | //builder.addAction(new Notification.Action(R.drawable.ic_action_clear, "Never for this screen", null)); 204 | nm.notify(NOTIFICATION_ID_ACCESSIBILITY, builder.build()); 205 | } 206 | 207 | private boolean pasteText(AccessibilityNodeInfo source, CharSequence text) { 208 | if (source.getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT)) { 209 | final Bundle arguments = new Bundle(); 210 | arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text); 211 | return source.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments); 212 | } else { 213 | final ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 214 | final ClipData clipBackup = cm.getPrimaryClip(); 215 | cm.setPrimaryClip(ClipData.newPlainText(getString(R.string.copied_password), text)); 216 | final boolean result = source.performAction(AccessibilityNodeInfo.ACTION_PASTE); 217 | if (clipBackup != null) { 218 | cm.setPrimaryClip(clipBackup); 219 | } else { 220 | cm.setPrimaryClip(ClipData.newPlainText("", "")); 221 | } 222 | return result; 223 | } 224 | } 225 | 226 | private void findEditTextValues(@Nullable final AccessibilityNodeInfo root, final AccessibilityNodeInfo exclude, List out) { 227 | if (root == null) return; 228 | if (TextUtils.equals(root.getClassName(), EditText.class.getName())) { 229 | out.add(root.getText()); 230 | } 231 | for (int i = 0, j = root.getChildCount(); i < j; i++) { 232 | final AccessibilityNodeInfo info = root.getChild(i); 233 | if (info != null && !info.equals(exclude)) { 234 | findEditTextValues(info, exclude, out); 235 | } 236 | } 237 | } 238 | 239 | private boolean isSameView(final AccessibilityNodeInfo left, final AccessibilityNodeInfo right) { 240 | if (left == null || right == null) return false; 241 | return left.equals(right); 242 | } 243 | 244 | private boolean isSameWindow(final AccessibilityEvent left, final AccessibilityEvent right) { 245 | if (left == null || right == null) return false; 246 | return TextUtils.equals(left.getClassName(), right.getClassName()) 247 | && TextUtils.equals(left.getPackageName(), right.getPackageName()); 248 | } 249 | 250 | static class PasswordCallbackData { 251 | @NonNull 252 | AccessibilityExtra extra; 253 | @NonNull 254 | CharSequence password; 255 | 256 | public PasswordCallbackData(@NonNull final AccessibilityExtra extra, @NonNull final CharSequence password) { 257 | this.extra = extra; 258 | this.password = password; 259 | } 260 | } 261 | 262 | static class BrowserState { 263 | @NonNull 264 | CharSequence packageName; 265 | @Nullable 266 | CharSequence link; 267 | 268 | public BrowserState(@NonNull final CharSequence packageName, @Nullable final CharSequence link) { 269 | this.packageName = packageName; 270 | this.link = link; 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/service/QuickSettingsTileService.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.service; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.service.quicksettings.TileService; 7 | 8 | import org.mariotaku.pass.activity.PassGenDialogActivity; 9 | 10 | /** 11 | * Created by mariotaku on 16/9/1. 12 | */ 13 | @TargetApi(Build.VERSION_CODES.N) 14 | public class QuickSettingsTileService extends TileService { 15 | @Override 16 | public void onClick() { 17 | unlockAndRun(new Runnable() { 18 | @Override 19 | public void run() { 20 | Intent intent = new Intent(QuickSettingsTileService.this, PassGenDialogActivity.class); 21 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 22 | startActivity(intent); 23 | sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/ContextCompat.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util; 2 | 3 | /** 4 | * Created by mariotaku on 15/11/2. 5 | */ 6 | public class ContextCompat { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/FingerprintCryptoHelper.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.security.keystore.KeyGenParameterSpec; 6 | import android.security.keystore.KeyProperties; 7 | 8 | import org.mariotaku.pass.BuildConfig; 9 | 10 | import java.security.KeyStore; 11 | import java.security.KeyStoreException; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.security.NoSuchProviderException; 14 | 15 | import javax.crypto.Cipher; 16 | import javax.crypto.KeyGenerator; 17 | import javax.crypto.NoSuchPaddingException; 18 | 19 | /** 20 | * Created by mariotaku on 15/11/2. 21 | */ 22 | @TargetApi(Build.VERSION_CODES.M) 23 | public class FingerprintCryptoHelper { 24 | 25 | public static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; 26 | public static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; 27 | public static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; 28 | 29 | public static final String SECURITY_KEY_NAME = BuildConfig.APPLICATION_ID + ".security_key"; 30 | 31 | public static KeyStore getKeystore() { 32 | try { 33 | return KeyStore.getInstance("AndroidKeyStore"); 34 | } catch (KeyStoreException e) { 35 | throw new RuntimeException(e); 36 | } 37 | } 38 | 39 | public static Cipher getCipher() { 40 | try { 41 | return Cipher.getInstance(KEY_ALGORITHM + "/" + BLOCK_MODE + "/" + ENCRYPTION_PADDING); 42 | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { 43 | throw new RuntimeException(e); 44 | } 45 | } 46 | 47 | public static KeyGenerator getKeyGenerator() { 48 | try { 49 | return KeyGenerator.getInstance(KEY_ALGORITHM, "AndroidKeyStore"); 50 | } catch (NoSuchAlgorithmException | NoSuchProviderException e) { 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | public static KeyGenParameterSpec createKeyGenParameterSpec() { 56 | return new KeyGenParameterSpec.Builder(SECURITY_KEY_NAME, 57 | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 58 | .setBlockModes(BLOCK_MODE) 59 | // Require the user to authenticate with a fingerprint to authorize every use 60 | // of the key 61 | .setUserAuthenticationRequired(true) 62 | .setEncryptionPaddings(ENCRYPTION_PADDING) 63 | .build(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/FingerprintHelper.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util; 2 | 3 | import android.Manifest; 4 | import android.annotation.TargetApi; 5 | import android.app.Activity; 6 | import android.app.Fragment; 7 | import android.content.Context; 8 | import android.content.pm.PackageManager; 9 | import android.os.Build; 10 | import android.os.Handler; 11 | import android.support.annotation.NonNull; 12 | import android.support.annotation.Nullable; 13 | import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; 14 | import android.support.v4.os.CancellationSignal; 15 | 16 | /** 17 | * Created by mariotaku on 15/11/1. 18 | */ 19 | public abstract class FingerprintHelper { 20 | 21 | final Context context; 22 | final FingerprintManagerCompat delegate; 23 | 24 | public FingerprintHelper(final Context context) { 25 | this.context = context; 26 | this.delegate = FingerprintManagerCompat.from(context); 27 | } 28 | 29 | public static FingerprintHelper getInstance(Activity activity) { 30 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return new NoOp(activity); 31 | return new ImplM(activity); 32 | } 33 | 34 | public void authenticate(@Nullable final FingerprintManagerCompat.CryptoObject crypto, final int flags, 35 | @Nullable final CancellationSignal cancel, @NonNull final FingerprintManagerCompat.AuthenticationCallback callback, @Nullable final Handler handler) throws SecurityException { 36 | delegate.authenticate(crypto, flags, cancel, callback, handler); 37 | } 38 | 39 | public boolean isHardwareDetected() throws SecurityException { 40 | return delegate.isHardwareDetected(); 41 | } 42 | 43 | public boolean hasEnrolledFingerprints() throws SecurityException { 44 | return delegate.hasEnrolledFingerprints(); 45 | } 46 | 47 | public abstract boolean requestPermission(Activity activity, final int requestCode); 48 | 49 | public abstract boolean requestPermission(Fragment fragment, final int requestCode); 50 | 51 | public abstract boolean hasPermission(); 52 | 53 | 54 | @TargetApi(Build.VERSION_CODES.M) 55 | private static class ImplM extends FingerprintHelper { 56 | 57 | public ImplM(final Activity activity) { 58 | super(activity); 59 | } 60 | 61 | @Override 62 | public boolean requestPermission(Activity activity, final int requestCode) { 63 | if (hasPermission()) return true; 64 | activity.requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, requestCode); 65 | return false; 66 | } 67 | 68 | @Override 69 | public boolean requestPermission(final Fragment fragment, final int requestCode) { 70 | if (hasPermission()) return true; 71 | fragment.requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, requestCode); 72 | return false; 73 | } 74 | 75 | @Override 76 | public boolean hasPermission() { 77 | return context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED; 78 | } 79 | } 80 | 81 | private static class NoOp extends FingerprintHelper { 82 | public NoOp(final Activity activity) { 83 | super(activity); 84 | } 85 | 86 | @Override 87 | public boolean requestPermission(Activity activity, final int requestCode) { 88 | return true; 89 | } 90 | 91 | @Override 92 | public boolean requestPermission(final Fragment fragment, final int requestCode) { 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean hasPermission() { 98 | return false; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/MasterPasswordEncrypter.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util; 2 | 3 | import android.content.Context; 4 | import android.util.Pair; 5 | 6 | import java.security.GeneralSecurityException; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.NoSuchProviderException; 9 | import java.security.SecureRandom; 10 | 11 | import javax.crypto.BadPaddingException; 12 | import javax.crypto.Cipher; 13 | import javax.crypto.KeyGenerator; 14 | import javax.crypto.SecretKey; 15 | import javax.crypto.spec.IvParameterSpec; 16 | import javax.crypto.spec.SecretKeySpec; 17 | 18 | /** 19 | * Created by mariotaku on 15/10/30. 20 | */ 21 | public class MasterPasswordEncrypter { 22 | 23 | public MasterPasswordEncrypter() { 24 | } 25 | 26 | 27 | public byte[] decrypt(byte[] in, byte[] key, byte[] iv) throws InternalErrorException, WrongPasswordException { 28 | try { 29 | final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 30 | cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(getRawKey(key), "AES"), new IvParameterSpec(iv)); 31 | return cipher.doFinal(in); 32 | } catch (BadPaddingException e) { 33 | throw new WrongPasswordException(e); 34 | } catch (GeneralSecurityException e) { 35 | throw new InternalErrorException(e); 36 | } 37 | } 38 | 39 | public Pair encrypt(byte[] in, byte[] key) throws InternalErrorException { 40 | try { 41 | final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 42 | cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(getRawKey(key), "AES")); 43 | return Pair.create(cipher.doFinal(in), cipher.getIV()); 44 | } catch (GeneralSecurityException e) { 45 | throw new InternalErrorException(e); 46 | } 47 | } 48 | 49 | 50 | /** 51 | * Get 256-bit encryption key 52 | *

53 | * From http://blog.csdn.net/zhaokaiqiang1992/article/details/41142883 54 | * 55 | * @param seed 56 | * @return 57 | * @throws Exception 58 | */ 59 | private static byte[] getRawKey(byte[] seed) throws NoSuchAlgorithmException, NoSuchProviderException { 60 | final KeyGenerator kgen = KeyGenerator.getInstance("AES"); 61 | final SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto"); 62 | sr.setSeed(seed); 63 | // 256 bits or 128 bits,192bits 64 | kgen.init(256, sr); 65 | final SecretKey key = kgen.generateKey(); 66 | return key.getEncoded(); 67 | } 68 | 69 | public static class InternalErrorException extends GeneralSecurityException { 70 | 71 | public InternalErrorException(final Exception e) { 72 | super(e); 73 | } 74 | } 75 | 76 | public static class WrongPasswordException extends GeneralSecurityException { 77 | 78 | public WrongPasswordException(final Exception e) { 79 | super(e); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/Utils.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.res.Resources; 6 | import android.net.Uri; 7 | import android.support.annotation.NonNull; 8 | import android.text.TextUtils; 9 | import android.util.Patterns; 10 | 11 | import org.mariotaku.pass.R; 12 | 13 | import java.io.BufferedReader; 14 | import java.io.IOException; 15 | import java.io.InputStreamReader; 16 | import java.nio.charset.Charset; 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.regex.Matcher; 21 | import java.util.regex.Pattern; 22 | 23 | /** 24 | * Created by mariotaku on 15/10/30. 25 | */ 26 | public class Utils { 27 | 28 | public static final Pattern PATTERN_XML_RESOURCE_IDENTIFIER = Pattern.compile("res/xml/([\\w_]+)\\.xml"); 29 | public static final Pattern PATTERN_RESOURCE_IDENTIFIER = Pattern.compile("@([\\w_]+)/([\\w_]+)"); 30 | 31 | public static List extractLinks(CharSequence text) { 32 | if (TextUtils.isEmpty(text)) return Collections.emptyList(); 33 | List links = new ArrayList<>(); 34 | Matcher m = Patterns.WEB_URL.matcher(text); 35 | while (m.find()) { 36 | String url = m.group(); 37 | links.add(url); 38 | } 39 | 40 | return links; 41 | } 42 | 43 | @NonNull 44 | public static List getHosts(final Context context, final Uri uri, int level) { 45 | final String host = uri.getHost(); 46 | if (host == null) return Collections.emptyList(); 47 | if (isValidIPAddress(host)) return Collections.singletonList(host); 48 | if (isMultiLevelTLD(context, host)) { 49 | level = level + 1; 50 | } 51 | final String[] hostSegs = host.split(Pattern.quote(".")); 52 | List hosts = new ArrayList<>(); 53 | for (int i = 0, j = hostSegs.length - level + 1; i < j; i++) { 54 | StringBuilder sb = new StringBuilder(); 55 | for (int k = i, l = hostSegs.length; k < l; k++) { 56 | if (k > i) { 57 | sb.append('.'); 58 | } 59 | sb.append(hostSegs[k]); 60 | } 61 | hosts.add(0, sb.toString()); 62 | } 63 | return hosts; 64 | } 65 | 66 | private static boolean isMultiLevelTLD(final Context context, @NonNull final String host) { 67 | try (BufferedReader br = new BufferedReader(new InputStreamReader(context.getResources() 68 | .openRawResource(R.raw.two_level_tlds), Charset.defaultCharset()))) { 69 | for (String s; (s = br.readLine()) != null; ) { 70 | if (host.endsWith("." + s)) return true; 71 | } 72 | } catch (IOException e) { 73 | } 74 | return false; 75 | } 76 | 77 | public static boolean isValidIPAddress(final String host) { 78 | if (TextUtils.isEmpty(host)) { 79 | return false; 80 | } 81 | return Patterns.IP_ADDRESS.matcher(host).matches(); 82 | } 83 | 84 | public static int getResId(final Context context, final String string) { 85 | if (context == null || string == null) return 0; 86 | Matcher m = PATTERN_RESOURCE_IDENTIFIER.matcher(string); 87 | final Resources res = context.getResources(); 88 | if (m.matches()) return res.getIdentifier(m.group(2), m.group(1), context.getPackageName()); 89 | m = PATTERN_XML_RESOURCE_IDENTIFIER.matcher(string); 90 | if (m.matches()) return res.getIdentifier(m.group(1), "xml", context.getPackageName()); 91 | return 0; 92 | } 93 | 94 | public static boolean isWebBrowser(final Context context, final String packageName) { 95 | final Intent intent = new Intent(Intent.ACTION_VIEW); 96 | intent.addCategory(Intent.CATEGORY_BROWSABLE); 97 | intent.setPackage(packageName); 98 | intent.setData(Uri.parse("http://")); 99 | return context.getPackageManager().resolveActivity(intent, 0) != null; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/passgen/PassGen.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util.passgen; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.text.TextUtils; 5 | 6 | import org.mariotaku.pass.util.passgen.impl.HotpPinImpl; 7 | import org.mariotaku.pass.util.passgen.impl.PasswordComposerImpl; 8 | import org.mariotaku.pass.util.passgen.impl.SuperGenPassImpl; 9 | 10 | import java.security.GeneralSecurityException; 11 | 12 | /** 13 | * Created by mariotaku on 15/11/1. 14 | */ 15 | public abstract class PassGen { 16 | 17 | protected abstract String generateChecked(@NonNull String pass, @NonNull String domain, int length) throws PassGenException; 18 | 19 | public String generate(String pass, String domain, int length) throws PassGenException { 20 | if (TextUtils.isEmpty(pass) || TextUtils.isEmpty(domain)) { 21 | throw new IllegalArgumentException("Password and domain must not be null or empty"); 22 | } 23 | return generateChecked(pass, domain, length); 24 | } 25 | 26 | public static PassGen getInstance(String provider, boolean specialChar) { 27 | switch (provider) { 28 | case "sgp_md5": 29 | return new SuperGenPassImpl("MD5", specialChar); 30 | case "sgp_sha512": 31 | return new SuperGenPassImpl("SHA512", specialChar); 32 | case "pw_composer": 33 | return new PasswordComposerImpl(specialChar); 34 | case "hotp_pin": 35 | return new HotpPinImpl(); 36 | } 37 | throw new UnsupportedOperationException("Unsupported provider " + provider); 38 | } 39 | 40 | public static class PassGenException extends GeneralSecurityException { 41 | 42 | public PassGenException(final String message) { 43 | super(message); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/passgen/impl/HotpPinImpl.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util.passgen.impl; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import org.mariotaku.pass.util.passgen.PassGen; 6 | import org.openauthentication.otp.OneTimePasswordAlgorithm; 7 | 8 | import java.security.InvalidKeyException; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | /** 12 | *

13 | * This generates strong Personal Identification Numbers (PINs). 14 | *

15 | *

16 | *

17 | * PINs generated with this can be used for bank accounts, phone lock screens, ATMs, etc. The 18 | * generator avoids common bad PINs ("1234", "0000", "0007", etc.) detected using a variety of 19 | * techniques. 20 | *

21 | *

22 | * The generation algorithm is a modified version of HOTP which uses the master password for the HMAC 24 | * secret and the domain instead of the moving factor. If a bad PIN is detected, the text " 1" is 25 | * added to the end of the domain and it's recomputed. If a bad PIN is still generated, it suffixes 26 | * " 2" instead and will continue in this way until a good PIN comes out. 27 | *

28 | * 29 | * @author Steve Pomeroy 30 | * @see OneTimePasswordAlgorithm#generateOTPFromText(byte[], byte[], int, boolean, int) 31 | */ 32 | public class HotpPinImpl extends PassGen { 33 | 34 | @Override 35 | public String generateChecked(@NonNull String pass, @NonNull String domain, int length) 36 | throws PassGenException { 37 | 38 | if (length < 3 || length > 8) { 39 | throw new IllegalArgumentException("length must be >= 3 and <= 8"); 40 | } 41 | 42 | try { 43 | String pin = OneTimePasswordAlgorithm.generateOTPFromText(pass.getBytes(), 44 | domain.getBytes(), length, false, -1); 45 | 46 | if (pin.length() != length) { 47 | throw new PassGenException("PIN generator error; requested length " 48 | + length + ", but got " + pin.length()); 49 | } 50 | 51 | int suffix = 0; 52 | int loopOverrun = 0; 53 | 54 | while (isBadPin(pin)) { 55 | final String suffixedDomain = domain + " " + suffix; 56 | pin = OneTimePasswordAlgorithm.generateOTPFromText(pass.getBytes(), 57 | suffixedDomain.getBytes(), length, false, -1); 58 | 59 | loopOverrun++; 60 | suffix++; 61 | if (loopOverrun > 100) { 62 | throw new PassGenException("PIN generator programming error: looped too many times"); 63 | } 64 | } 65 | return pin; 66 | } catch (final InvalidKeyException | NoSuchAlgorithmException e) { 67 | throw new RuntimeException(e); 68 | } 69 | } 70 | 71 | /** 72 | * Tests the string to see if it contains a numeric run. For example, "123456", "0000", "9876", 73 | * and "2468" would all match. 74 | * 75 | * @param pin 76 | * @return true if the string is a numeric run 77 | */ 78 | public boolean isNumericalRun(String pin) { 79 | final int len = pin.length(); 80 | // int[] diff = new int[len - 1]; 81 | int prevDigit = Character.digit(pin.charAt(0), 10); 82 | int prevDiff = Integer.MAX_VALUE; 83 | boolean isRun = true; // assume it's true... 84 | 85 | for (int i = 1; isRun && i < len; i++) { 86 | final int digit = Character.digit(pin.charAt(i), 10); 87 | 88 | final int diff = digit - prevDigit; 89 | if (prevDiff != Integer.MAX_VALUE && diff != prevDiff) { 90 | isRun = false; // ... and prove it's false 91 | } 92 | 93 | prevDiff = diff; 94 | prevDigit = digit; 95 | } 96 | 97 | return isRun; 98 | } 99 | 100 | /** 101 | * Tests the string to see if it contains a partial numeric run. Eg. 3000, 5553 102 | * 103 | * @param pin 104 | * @return 105 | */ 106 | public boolean isIncompleteNumericalRun(String pin) { 107 | final int len = pin.length(); 108 | int consecutive = 0; 109 | char last = pin.charAt(0); 110 | for (int i = 1; i < len; i++) { 111 | final char c = pin.charAt(i); 112 | if (last == c) { 113 | consecutive++; 114 | } else { 115 | consecutive = 0; 116 | } 117 | last = c; 118 | if (consecutive >= 2) { 119 | return true; 120 | } 121 | } 122 | return false; 123 | } 124 | 125 | /** 126 | * This is a hard-coded list of specific PINs that have cultural meaning. While they may be 127 | * improbable, none the less they won't output from the generation. 128 | */ 129 | private static final String[] BLACKLISTED_PINS = new String[]{"90210", 130 | "8675309" /* Jenny */, 131 | "1004" /* 10-4 */, 132 | // in this document http://www.datagenetics.com/blog/september32012/index.html 133 | // these were shown to be the least commonly used. Now they won't be used at all. 134 | "8068", "8093", "9629", "6835", "7637", "0738", "8398", "6793", "9480", "8957", "0859", 135 | "7394", "6827", "6093", "7063", "8196", "9539", "0439", "8438", "9047", "8557"}; 136 | 137 | /** 138 | * Tests to see if the PIN is a "bad" pin. That is, one that is easily guessable. Essentially, 139 | * this is a blacklist of the most commonly used PINs like "1234", "0000" and "1984". 140 | * 141 | * @param pin 142 | * @return true if the PIN matches the bad PIN criteria 143 | */ 144 | public boolean isBadPin(String pin) { 145 | final int len = pin.length(); 146 | 147 | // special cases for 4-digit PINs (which are quite common) 148 | if (len == 4) { 149 | final int start = Integer.parseInt(pin.subSequence(0, 2).toString()); 150 | final int end = Integer.parseInt(pin.subSequence(2, 4).toString()); 151 | 152 | // 19xx pins look like years, so might as well ditch them. 153 | if (start == 19 || (start == 20 && end < 30)) { 154 | return true; 155 | } 156 | 157 | // 1515 158 | if (start == end) { 159 | return true; 160 | } 161 | } 162 | 163 | // find case where all digits are in pairs 164 | // eg 1122 3300447722 165 | 166 | if (len % 2 == 0) { 167 | boolean paired = true; 168 | for (int i = 0; i < len - 1; i += 2) { 169 | if (pin.charAt(i) != pin.charAt(i + 1)) { 170 | paired = false; 171 | } 172 | } 173 | if (paired) { 174 | return true; 175 | } 176 | } 177 | 178 | if (isNumericalRun(pin)) { 179 | return true; 180 | } 181 | 182 | if (isIncompleteNumericalRun(pin)) { 183 | return true; 184 | } 185 | 186 | // filter out special numbers 187 | for (final String blacklisted : BLACKLISTED_PINS) { 188 | if (blacklisted.equals(pin)) { 189 | return true; 190 | } 191 | } 192 | 193 | return false; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/passgen/impl/PasswordComposerImpl.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util.passgen.impl; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import org.mariotaku.pass.util.passgen.PassGen; 6 | 7 | import java.security.MessageDigest; 8 | import java.security.NoSuchAlgorithmException; 9 | 10 | /** 11 | * Created by mariotaku on 15/11/1. 12 | */ 13 | public class PasswordComposerImpl extends PassGen { 14 | 15 | private final MessageDigest md5; 16 | private final boolean specialChar; 17 | 18 | public PasswordComposerImpl(boolean specialChar) { 19 | this.specialChar = specialChar; 20 | try { 21 | md5 = MessageDigest.getInstance("MD5"); 22 | } catch (NoSuchAlgorithmException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | 27 | @Override 28 | protected String generateChecked(@NonNull final String pass, @NonNull final String domain, final int length) throws PassGenException { 29 | if (length < 1 || length > 31) { 30 | throw new IllegalArgumentException("Password length must between 1 and 31 inclusive."); 31 | } 32 | //noinspection RedundantStringConstructorCall 33 | final String md5str = toHexString(md5.digest(new String(pass + ":" + domain).getBytes())); 34 | if (specialChar) { 35 | return md5str.substring(0, length - 1) + "."; 36 | } 37 | return md5str.substring(0, length); 38 | } 39 | 40 | private String toHexString(final byte[] bytes) { 41 | final StringBuilder sb = new StringBuilder(); 42 | for (final byte b : bytes) { 43 | String hex = Integer.toHexString(0xFF & b); 44 | if (hex.length() == 1) { 45 | sb.append('0'); 46 | } 47 | sb.append(hex); 48 | } 49 | return sb.toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/util/passgen/impl/SuperGenPassImpl.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.util.passgen.impl; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.util.Base64; 5 | 6 | import org.mariotaku.pass.util.passgen.PassGen; 7 | 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | /** 12 | * Created by mariotaku on 15/11/1. 13 | */ 14 | public class SuperGenPassImpl extends PassGen { 15 | 16 | private static final int ROUNDS_REQUIREMENT = 10; 17 | private final MessageDigest digest; 18 | private final boolean specialChar; 19 | 20 | public SuperGenPassImpl(String algorithm, boolean specialChar) { 21 | this.specialChar = specialChar; 22 | try { 23 | digest = MessageDigest.getInstance(algorithm); 24 | } catch (NoSuchAlgorithmException e) { 25 | throw new RuntimeException(e); 26 | } 27 | } 28 | 29 | @Override 30 | protected String generateChecked(@NonNull final String pass, @NonNull final String domain, final int length) throws PassGenException { 31 | if (length < 4 || length > 24) 32 | throw new IllegalArgumentException("Password length must between 4 and 24 inclusive"); 33 | 34 | String generated = pass + ":" + domain; 35 | 36 | for (int i = 0; i < ROUNDS_REQUIREMENT; i++) { 37 | generated = hash(generated.getBytes()); 38 | } 39 | while (!validate(generated, length)) { 40 | generated = hash(generated.getBytes()); 41 | } 42 | return generated.substring(0, length); 43 | } 44 | 45 | /* From http://supergenpass.com/about/#PasswordComplexity : 46 | * * Consist of alphanumerics (A-Z, a-z, 0-9) 47 | * * Always start with a lowercase letter of the alphabet 48 | * * Always contain at least one uppercase letter of the alphabet 49 | * * Always contain at least one numeral 50 | * * Can be any length from 4 to 24 characters (default: 10) 51 | */ 52 | private boolean validate(final String generated, final int length) { 53 | final CharSequence pw = generated.subSequence(0, length); 54 | final char firstCh = pw.charAt(0); 55 | if (firstCh < 'a' || firstCh > 'z') return false; 56 | boolean hasUppercase = false, hasNumeral = false, hasSpecial = false; 57 | for (int i = 1; i < length; i++) { 58 | final char ch = pw.charAt(i); 59 | if (ch >= 'A' && ch <= 'Z') { 60 | hasUppercase = true; 61 | } else if (ch >= '0' && ch <= '9') { 62 | hasNumeral = true; 63 | } else if (ch == '=' || ch == '/' || ch == '+') { 64 | hasSpecial = true; 65 | } 66 | } 67 | return hasUppercase && hasNumeral && (!specialChar || hasSpecial); 68 | } 69 | 70 | @NonNull 71 | private String hash(final byte[] bytes) { 72 | String hashed = Base64.encodeToString(digest.digest(bytes), Base64.NO_WRAP); 73 | if (specialChar) return hashed; 74 | return hashed.replace('=', 'A').replace('/', '8').replace('+', '9'); 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/PasswordContainer.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.res.TypedArray; 6 | import android.text.TextUtils; 7 | import android.util.AttributeSet; 8 | import android.view.InflateException; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | 13 | import org.mariotaku.pass.Constants; 14 | import org.mariotaku.pass.R; 15 | 16 | import java.lang.reflect.InvocationTargetException; 17 | 18 | /** 19 | * Created by mariotaku on 15/10/30. 20 | */ 21 | public class PasswordContainer extends FrameLayout { 22 | public PasswordContainer(final Context context, final AttributeSet attrs, final int defStyleAttr) { 23 | super(context, attrs, defStyleAttr); 24 | } 25 | 26 | public PasswordContainer(final Context context, final AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | public PasswordContainer(final Context context) { 31 | super(context); 32 | } 33 | 34 | @Override 35 | public void addView(final View child, final int index, final ViewGroup.LayoutParams params) { 36 | super.addView(child, index, params); 37 | if (getChildCount() > 1) { 38 | throw new InflateException("PasswordContainer must have one exact child"); 39 | } 40 | final View view = getChildAt(0); 41 | final PasswordController controller = getPasswordController(); 42 | controller.onAttach(view); 43 | } 44 | 45 | public PasswordController getPasswordController() { 46 | return ((LayoutParams) getChildAt(0).getLayoutParams()).getPasswordController(); 47 | } 48 | 49 | @Override 50 | protected boolean checkLayoutParams(final ViewGroup.LayoutParams p) { 51 | return p instanceof LayoutParams; 52 | } 53 | 54 | @Override 55 | public LayoutParams generateLayoutParams(final AttributeSet attrs) { 56 | return new LayoutParams(this, getContext(), attrs); 57 | } 58 | 59 | public static class LayoutParams extends FrameLayout.LayoutParams { 60 | 61 | private final PasswordController passwordController; 62 | 63 | public LayoutParams(final PasswordContainer container, final Context c, final AttributeSet attrs) { 64 | super(c, attrs); 65 | final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.PasswordContainer); 66 | passwordController = parsePasswordController(a.getString(R.styleable.PasswordContainer_layout_passwordController), 67 | container, c, attrs); 68 | a.recycle(); 69 | } 70 | 71 | private static PasswordController parsePasswordController(final String className, 72 | final PasswordContainer container, 73 | final Context c, final AttributeSet attrs) { 74 | if (TextUtils.isEmpty(className)) { 75 | throw new InflateException("You must give PasswordContainer's child a PasswordController class"); 76 | } 77 | try { 78 | return (PasswordController) Class.forName(className).getConstructor(PasswordContainer.class, Context.class, AttributeSet.class).newInstance(container, c, attrs); 79 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | ClassCastException e) { 80 | throw new InflateException(e); 81 | } 82 | } 83 | 84 | public PasswordController getPasswordController() { 85 | return passwordController; 86 | } 87 | } 88 | 89 | public static class PasswordController { 90 | private final PasswordContainer container; 91 | private final Context context; 92 | private final SharedPreferences preferences; 93 | private View view; 94 | private OnPasswordEnteredListener listener; 95 | 96 | protected PasswordController(PasswordContainer container, Context context, AttributeSet attributeSet) { 97 | this.container = container; 98 | this.context = context; 99 | this.preferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 100 | } 101 | 102 | public PasswordContainer getContainer() { 103 | return container; 104 | } 105 | 106 | public Context getContext() { 107 | return context; 108 | } 109 | 110 | public SharedPreferences getPreferences() { 111 | return preferences; 112 | } 113 | 114 | public View getView() { 115 | return view; 116 | } 117 | 118 | protected void onAttach(final View view) { 119 | this.view = view; 120 | } 121 | 122 | protected void notifyPasswordEntered(byte[] key) { 123 | if (listener != null) { 124 | listener.onPasswordEntered(key); 125 | } 126 | } 127 | 128 | public void setOnPasswordEnteredListener(final OnPasswordEnteredListener listener) { 129 | this.listener = listener; 130 | } 131 | 132 | public OnPasswordEnteredListener getOnPasswordEnteredListener() { 133 | return listener; 134 | } 135 | 136 | protected final void notifyPasswordWrong() { 137 | final int retryCount = preferences.getInt(Constants.KEY_PASSWORD_RETRY_COUNT, 0); 138 | onPasswordWrong(retryCount); 139 | preferences.edit().putInt(Constants.KEY_PASSWORD_RETRY_COUNT, retryCount + 1).apply(); 140 | } 141 | 142 | protected final void resetPasswordRetries() { 143 | preferences.edit().remove(Constants.KEY_PASSWORD_RETRY_COUNT).apply(); 144 | } 145 | 146 | protected void onPasswordWrong(int retries) { 147 | 148 | } 149 | } 150 | 151 | public interface OnPasswordEnteredListener { 152 | boolean onPasswordEntered(byte[] key); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/RememberMasterPasswordContainer.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.text.TextUtils; 6 | import android.util.AttributeSet; 7 | import android.view.InflateException; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.view.animation.Animation; 11 | import android.view.animation.AnimationUtils; 12 | import android.widget.FrameLayout; 13 | import android.widget.ViewAnimator; 14 | 15 | import org.mariotaku.pass.R; 16 | 17 | import java.lang.reflect.InvocationTargetException; 18 | 19 | /** 20 | * Created by mariotaku on 15/10/30. 21 | */ 22 | public class RememberMasterPasswordContainer extends ViewAnimator { 23 | 24 | private final Animation mSlideOutRightAnimation; 25 | private final Animation mSlideInRightAnimation; 26 | private final Animation mSlideOutLeftAnimation; 27 | private final Animation mSlideInLeftAnimation; 28 | 29 | private PageListener mPageListener; 30 | 31 | public RememberMasterPasswordContainer(final Context context, final AttributeSet attrs) { 32 | super(context, attrs); 33 | mSlideInLeftAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_in_left); 34 | mSlideOutRightAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_out_right); 35 | mSlideInRightAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_in_right); 36 | mSlideOutLeftAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_out_left); 37 | } 38 | 39 | public RememberMasterPasswordContainer(final Context context) { 40 | this(context, null); 41 | } 42 | 43 | @Override 44 | protected boolean checkLayoutParams(final ViewGroup.LayoutParams p) { 45 | return p instanceof LayoutParams; 46 | } 47 | 48 | @Override 49 | public LayoutParams generateLayoutParams(final AttributeSet attrs) { 50 | return new LayoutParams(this, getContext(), attrs); 51 | } 52 | 53 | @Override 54 | public void showNext() { 55 | final LayoutParams lpCurrent = (LayoutParams) getCurrentView().getLayoutParams(); 56 | if (lpCurrent.getPageController().onPageNextExit()) { 57 | if (getDisplayedChild() < getChildCount() - 1) { 58 | setInAnimation(mSlideInRightAnimation); 59 | setOutAnimation(mSlideOutLeftAnimation); 60 | super.showNext(); 61 | if (mPageListener != null) { 62 | mPageListener.onPageChanged(getDisplayedChild()); 63 | } 64 | } else { 65 | if (mPageListener != null) { 66 | mPageListener.onReachedEnd(); 67 | } 68 | } 69 | final LayoutParams lpNext = (LayoutParams) getCurrentView().getLayoutParams(); 70 | lpNext.getPageController().onPageNextEnter(); 71 | } 72 | } 73 | 74 | @Override 75 | public void addView(final View child, final int index, final ViewGroup.LayoutParams params) { 76 | super.addView(child, index, params); 77 | ((LayoutParams) params).getPageController().onAttach(child); 78 | } 79 | 80 | @Override 81 | public void showPrevious() { 82 | final LayoutParams lpCurrent = (LayoutParams) getCurrentView().getLayoutParams(); 83 | if (lpCurrent.getPageController().onPagePreviousExit()) { 84 | if (getDisplayedChild() > 0) { 85 | setInAnimation(mSlideInLeftAnimation); 86 | setOutAnimation(mSlideOutRightAnimation); 87 | super.showPrevious(); 88 | if (mPageListener != null) { 89 | mPageListener.onPageChanged(getDisplayedChild()); 90 | } 91 | } else { 92 | if (mPageListener != null) { 93 | mPageListener.onReachedStart(); 94 | } 95 | } 96 | final LayoutParams lpPrevious = (LayoutParams) getCurrentView().getLayoutParams(); 97 | lpPrevious.getPageController().onPagePreviousEnter(); 98 | } 99 | } 100 | 101 | public PageController findPageControllerById(int id) { 102 | for (int i = 0, j = getChildCount(); i < j; i++) { 103 | final View view = getChildAt(i); 104 | if (id == view.getId()) { 105 | return ((LayoutParams) view.getLayoutParams()).getPageController(); 106 | } 107 | } 108 | return null; 109 | } 110 | 111 | public PageListener getPageListener() { 112 | return mPageListener; 113 | } 114 | 115 | public void setPageListener(final PageListener pageListener) { 116 | mPageListener = pageListener; 117 | } 118 | 119 | public interface PageListener { 120 | void onPageChanged(int current); 121 | 122 | void onReachedEnd(); 123 | 124 | void onReachedStart(); 125 | } 126 | 127 | public static class LayoutParams extends FrameLayout.LayoutParams { 128 | 129 | private final PageController mPageController; 130 | 131 | public LayoutParams(final RememberMasterPasswordContainer container, final Context c, final AttributeSet attrs) { 132 | super(c, attrs); 133 | final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.RememberMasterPasswordContainer); 134 | mPageController = parsePasswordController(a.getString(R.styleable.RememberMasterPasswordContainer_layout_pageController), 135 | container, c, attrs); 136 | a.recycle(); 137 | } 138 | 139 | private static PageController parsePasswordController(final String className, final RememberMasterPasswordContainer container, 140 | final Context c, final AttributeSet attrs) { 141 | if (TextUtils.isEmpty(className)) { 142 | throw new InflateException("You must give PasswordContainer's child a PageController class"); 143 | } 144 | try { 145 | return (PageController) Class.forName(className).getConstructor(RememberMasterPasswordContainer.class, 146 | Context.class, AttributeSet.class).newInstance(container, c, attrs); 147 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | ClassCastException e) { 148 | throw new InflateException(e); 149 | } 150 | } 151 | 152 | public PageController getPageController() { 153 | return mPageController; 154 | } 155 | 156 | } 157 | 158 | public static class PageController { 159 | private final RememberMasterPasswordContainer container; 160 | private final Context context; 161 | private View view; 162 | 163 | protected PageController(RememberMasterPasswordContainer container, Context context, AttributeSet attributeSet) { 164 | this.container = container; 165 | this.context = context; 166 | } 167 | 168 | public RememberMasterPasswordContainer getContainer() { 169 | return container; 170 | } 171 | 172 | public Context getContext() { 173 | return context; 174 | } 175 | 176 | public View getView() { 177 | return view; 178 | } 179 | 180 | 181 | protected boolean onPageNextExit() { 182 | return true; 183 | } 184 | 185 | protected boolean onPageNextEnter() { 186 | return true; 187 | } 188 | 189 | protected boolean onPagePreviousExit() { 190 | return true; 191 | } 192 | 193 | protected boolean onPagePreviousEnter() { 194 | return true; 195 | } 196 | 197 | protected void onAttach(final View view) { 198 | this.view = view; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/controller/PatternPasswordController.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view.controller; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.text.TextUtils; 7 | import android.util.AttributeSet; 8 | import android.util.Base64; 9 | import android.view.View; 10 | import android.view.inputmethod.InputMethodManager; 11 | import android.widget.Toast; 12 | 13 | import com.eftimoff.patternview.PatternView; 14 | import com.eftimoff.patternview.cells.Cell; 15 | 16 | import org.mariotaku.pass.Constants; 17 | import org.mariotaku.pass.R; 18 | import org.mariotaku.pass.util.MasterPasswordEncrypter; 19 | import org.mariotaku.pass.view.PasswordContainer; 20 | 21 | import java.nio.ByteBuffer; 22 | import java.util.List; 23 | 24 | /** 25 | * Created by mariotaku on 15/10/30. 26 | */ 27 | public class PatternPasswordController extends PasswordContainer.PasswordController implements Constants, PatternView.OnPatternDetectedListener, PatternView.OnPatternStartListener { 28 | private final MasterPasswordEncrypter mEncrypter; 29 | private PatternView mPatternView; 30 | 31 | public PatternPasswordController(final PasswordContainer container, final Context context, final AttributeSet attributeSet) { 32 | super(container, context, attributeSet); 33 | mEncrypter = new MasterPasswordEncrypter(); 34 | } 35 | 36 | @Override 37 | public void onAttach(final View view) { 38 | super.onAttach(view); 39 | mPatternView = (PatternView) view.findViewById(R.id.password_pattern); 40 | mPatternView.setOnPatternDetectedListener(this); 41 | mPatternView.setOnPatternStartListener(this); 42 | } 43 | 44 | @Override 45 | public void onPatternDetected() { 46 | final List patterns = mPatternView.getPattern(); 47 | final byte[] key = getKeyByPatterns(patterns); 48 | try { 49 | final SharedPreferences preferences = getPreferences(); 50 | final String patternPw = preferences.getString(KEY_PATTERN_PASSWORD, null); 51 | final String patternIv = preferences.getString(KEY_PATTERN_IV, null); 52 | if (TextUtils.isEmpty(patternPw) || TextUtils.isEmpty(patternIv)) { 53 | // No password set, go to text password 54 | useTextPassword(); 55 | return; 56 | } 57 | final byte[] iv = Base64.decode(patternIv, Base64.URL_SAFE); 58 | notifyPasswordEntered(mEncrypter.decrypt(Base64.decode(patternPw, Base64.URL_SAFE), key, iv)); 59 | resetPasswordRetries(); 60 | } catch (MasterPasswordEncrypter.InternalErrorException | IllegalArgumentException e) { 61 | // TODO Show internal error message 62 | e.printStackTrace(); 63 | } catch (MasterPasswordEncrypter.WrongPasswordException e) { 64 | notifyPasswordWrong(); 65 | } 66 | mPatternView.clearPattern(); 67 | } 68 | 69 | @Override 70 | protected void onPasswordWrong(final int retries) { 71 | mPatternView.setDisplayMode(PatternView.DisplayMode.Wrong); 72 | if (retries >= 4) { 73 | getPreferences().edit().remove(KEY_PATTERN_PASSWORD).apply(); 74 | Toast.makeText(getContext(), R.string.pattern_password_cleared, Toast.LENGTH_SHORT).show(); 75 | resetPasswordRetries(); 76 | useTextPassword(); 77 | } 78 | } 79 | 80 | private void useTextPassword() { 81 | final PasswordContainer container = getContainer(); 82 | final PasswordContainer.OnPasswordEnteredListener listener = getOnPasswordEnteredListener(); 83 | container.removeAllViews(); 84 | View.inflate(getContext(), R.layout.layout_password_text, container); 85 | container.getPasswordController().setOnPasswordEnteredListener(listener); 86 | } 87 | 88 | public static byte[] getKeyByPatterns(final List patterns) { 89 | final ByteBuffer bb = ByteBuffer.allocate(patterns.size() * 2 * 4); 90 | for (final Cell pattern : patterns) { 91 | bb.putInt(pattern.getRow()); 92 | bb.putInt(pattern.getColumn()); 93 | } 94 | return bb.array(); 95 | } 96 | 97 | @Override 98 | public void onPatternStart() { 99 | final Activity activity = (Activity) getContext(); 100 | final View currentFocus = activity.getCurrentFocus(); 101 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 102 | if (currentFocus == null || !imm.isActive(currentFocus)) return; 103 | imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/controller/RememberPasswordFingerprintController.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view.controller; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; 6 | import android.support.v4.os.CancellationSignal; 7 | import android.util.AttributeSet; 8 | import android.util.Base64; 9 | import android.view.View; 10 | import android.widget.TextView; 11 | 12 | import org.mariotaku.pass.Constants; 13 | import org.mariotaku.pass.R; 14 | import org.mariotaku.pass.fragment.RememberMasterPasswordFingerprintFragment; 15 | import org.mariotaku.pass.util.FingerprintCryptoHelper; 16 | import org.mariotaku.pass.util.FingerprintHelper; 17 | import org.mariotaku.pass.view.RememberMasterPasswordContainer; 18 | 19 | import java.io.IOException; 20 | import java.security.GeneralSecurityException; 21 | import java.security.KeyStore; 22 | 23 | import javax.crypto.BadPaddingException; 24 | import javax.crypto.Cipher; 25 | import javax.crypto.IllegalBlockSizeException; 26 | import javax.crypto.KeyGenerator; 27 | import javax.crypto.SecretKey; 28 | 29 | /** 30 | * Created by mariotaku on 15/10/31. 31 | */ 32 | public class RememberPasswordFingerprintController extends RememberMasterPasswordContainer.PageController 33 | implements Constants { 34 | private final SharedPreferences mPreferences; 35 | private CancellationSignal cancellationSignal; 36 | private TextView mFingerprintHintView; 37 | 38 | public RememberPasswordFingerprintController(final RememberMasterPasswordContainer container, 39 | final Context context, final AttributeSet attributeSet) { 40 | super(container, context, attributeSet); 41 | mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 42 | } 43 | 44 | @Override 45 | protected void onAttach(final View view) { 46 | super.onAttach(view); 47 | mFingerprintHintView = (TextView) view.findViewById(R.id.fingerprint_hint); 48 | } 49 | 50 | @Override 51 | protected boolean onPageNextEnter() { 52 | startFingerprintAuthentication(); 53 | return true; 54 | } 55 | 56 | @Override 57 | protected boolean onPagePreviousEnter() { 58 | startFingerprintAuthentication(); 59 | return true; 60 | } 61 | 62 | private void startFingerprintAuthentication() { 63 | if (cancellationSignal != null && !cancellationSignal.isCanceled()) return; 64 | regenerateKey(); 65 | final RememberMasterPasswordContainer.PageListener listener = getContainer().getPageListener(); 66 | final RememberPasswordInputController controller = (RememberPasswordInputController) getContainer() 67 | .findPageControllerById(R.id.remember_input_password); 68 | if (listener instanceof RememberMasterPasswordFingerprintFragment) { 69 | FingerprintHelper helper = ((RememberMasterPasswordFingerprintFragment) listener).getFingerprintHelper(); 70 | final KeyStore keyStore = FingerprintCryptoHelper.getKeystore(); 71 | final Cipher cipher = FingerprintCryptoHelper.getCipher(); 72 | final FingerprintManagerCompat.CryptoObject crypto = new FingerprintManagerCompat.CryptoObject(cipher); 73 | cancellationSignal = new CancellationSignal(); 74 | try { 75 | keyStore.load(null); 76 | SecretKey key = (SecretKey) keyStore.getKey(FingerprintCryptoHelper.SECURITY_KEY_NAME, null); 77 | cipher.init(Cipher.ENCRYPT_MODE, key); 78 | } catch (GeneralSecurityException | IOException e) { 79 | throw new RuntimeException(e); 80 | } 81 | helper.authenticate(crypto, 0, cancellationSignal, new FingerprintManagerCompat.AuthenticationCallback() { 82 | @Override 83 | public void onAuthenticationError(final int errMsgId, final CharSequence errString) { 84 | mFingerprintHintView.setText(errString); 85 | } 86 | 87 | @Override 88 | public void onAuthenticationHelp(final int helpMsgId, final CharSequence helpString) { 89 | mFingerprintHintView.setText(helpString); 90 | } 91 | 92 | @Override 93 | public void onAuthenticationSucceeded(final FingerprintManagerCompat.AuthenticationResult result) { 94 | try { 95 | final Cipher cipher = result.getCryptoObject().getCipher(); 96 | final String encrypted = Base64.encodeToString(cipher.doFinal(controller.getPassword()), Base64.URL_SAFE); 97 | mPreferences.edit().putString(KEY_FINGERPRINT_PASSWORD, encrypted).apply(); 98 | mPreferences.edit().putString(KEY_FINGERPRINT_IV, Base64.encodeToString(cipher.getIV(), Base64.URL_SAFE)).apply(); 99 | final RememberMasterPasswordContainer container = getContainer(); 100 | container.showNext(); 101 | } catch (BadPaddingException | IllegalBlockSizeException e) { 102 | showInternalError(); 103 | } 104 | } 105 | 106 | @Override 107 | public void onAuthenticationFailed() { 108 | super.onAuthenticationFailed(); 109 | } 110 | }, getView().getHandler()); 111 | } 112 | } 113 | 114 | private boolean regenerateKey() { 115 | try { 116 | final KeyStore keyStore = FingerprintCryptoHelper.getKeystore(); 117 | final KeyGenerator keyGenerator = FingerprintCryptoHelper.getKeyGenerator(); 118 | keyStore.load(null); 119 | // Set the alias of the entry in Android KeyStore where the key will appear 120 | // and the constrains (purposes) in the constructor of the Builder 121 | keyGenerator.init(FingerprintCryptoHelper.createKeyGenParameterSpec()); 122 | keyGenerator.generateKey(); 123 | return true; 124 | } catch (GeneralSecurityException | IOException e) { 125 | throw new RuntimeException(e); 126 | } 127 | } 128 | 129 | private void showInternalError() { 130 | 131 | } 132 | 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/controller/RememberPasswordFingerprintNoticeController.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view.controller; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import org.mariotaku.pass.view.RememberMasterPasswordContainer; 7 | 8 | /** 9 | * Created by mariotaku on 15/10/31. 10 | */ 11 | public class RememberPasswordFingerprintNoticeController extends RememberMasterPasswordContainer.PageController { 12 | public RememberPasswordFingerprintNoticeController(final RememberMasterPasswordContainer container, 13 | final Context context, final AttributeSet attributeSet) { 14 | super(container, context, attributeSet); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/controller/RememberPasswordInputController.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view.controller; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.view.inputmethod.InputMethodManager; 8 | import android.widget.EditText; 9 | 10 | import org.mariotaku.pass.R; 11 | import org.mariotaku.pass.view.RememberMasterPasswordContainer; 12 | import org.mariotaku.pass.view.RememberMasterPasswordContainer.PageController; 13 | 14 | /** 15 | * Created by mariotaku on 15/10/31. 16 | */ 17 | public class RememberPasswordInputController extends PageController { 18 | private EditText mEditPassword; 19 | private EditText mPasswordConfirm; 20 | 21 | public RememberPasswordInputController(final RememberMasterPasswordContainer container, 22 | final Context context, final AttributeSet attributeSet) { 23 | super(container, context, attributeSet); 24 | } 25 | 26 | @Override 27 | protected void onAttach(final View view) { 28 | super.onAttach(view); 29 | mEditPassword = (EditText) view.findViewById(R.id.edit_password); 30 | mPasswordConfirm = (EditText) view.findViewById(R.id.password_confirm); 31 | } 32 | 33 | @Override 34 | protected boolean onPagePreviousExit() { 35 | hideIME(); 36 | return true; 37 | } 38 | 39 | @Override 40 | protected boolean onPagePreviousEnter() { 41 | showIME(); 42 | return true; 43 | } 44 | 45 | @Override 46 | protected boolean onPageNextEnter() { 47 | showIME(); 48 | return true; 49 | } 50 | 51 | @Override 52 | protected boolean onPageNextExit() { 53 | if (TextUtils.isEmpty(mEditPassword.getText())) { 54 | mEditPassword.setError(getContext().getString(R.string.empty_password)); 55 | return false; 56 | } 57 | if (!TextUtils.equals(mPasswordConfirm.getText(), mEditPassword.getText())) { 58 | mPasswordConfirm.setSelection(0, mPasswordConfirm.length()); 59 | mPasswordConfirm.setError(getContext().getString(R.string.password_not_match)); 60 | return false; 61 | } 62 | hideIME(); 63 | return true; 64 | } 65 | 66 | private void showIME() { 67 | if (mEditPassword.requestFocus()) { 68 | final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 69 | imm.showSoftInput(mEditPassword, InputMethodManager.SHOW_IMPLICIT); 70 | } 71 | } 72 | 73 | private void hideIME() { 74 | mEditPassword.clearFocus(); 75 | final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 76 | imm.hideSoftInputFromWindow(mEditPassword.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); 77 | } 78 | 79 | public byte[] getPassword() { 80 | return mEditPassword.getText().toString().getBytes(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/controller/RememberPasswordPatternController.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view.controller; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.util.AttributeSet; 6 | import android.util.Base64; 7 | import android.util.Pair; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | 11 | import com.eftimoff.patternview.PatternView; 12 | import com.eftimoff.patternview.cells.Cell; 13 | 14 | import org.mariotaku.pass.Constants; 15 | import org.mariotaku.pass.R; 16 | import org.mariotaku.pass.util.MasterPasswordEncrypter; 17 | import org.mariotaku.pass.view.RememberMasterPasswordContainer; 18 | import org.mariotaku.pass.view.RememberMasterPasswordContainer.PageController; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * Created by mariotaku on 15/10/31. 24 | */ 25 | public class RememberPasswordPatternController extends PageController implements Constants, 26 | PatternView.OnPatternDetectedListener { 27 | private final MasterPasswordEncrypter mEncrypter; 28 | private final SharedPreferences mPreferences; 29 | private PatternView mPatternView; 30 | private List mPattern; 31 | private TextView mPatternHint; 32 | 33 | public RememberPasswordPatternController(final RememberMasterPasswordContainer container, 34 | final Context context, final AttributeSet attributeSet) { 35 | super(container, context, attributeSet); 36 | mEncrypter = new MasterPasswordEncrypter(); 37 | mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 38 | } 39 | 40 | @Override 41 | protected void onAttach(final View view) { 42 | super.onAttach(view); 43 | mPatternView = ((PatternView) view.findViewById(R.id.pattern_view)); 44 | mPatternHint = (TextView) view.findViewById(R.id.pattern_hint); 45 | 46 | mPatternView.setOnPatternDetectedListener(this); 47 | } 48 | 49 | @Override 50 | public void onPatternDetected() { 51 | final List pattern = mPatternView.getPattern(); 52 | mPatternView.clearPattern(); 53 | if (mPattern != null) { 54 | // Check second pattern matches 55 | if (!mPattern.equals(pattern)) { 56 | mPatternView.setDisplayMode(PatternView.DisplayMode.Wrong); 57 | mPatternHint.setText(R.string.pattern_not_match_hint); 58 | } else { 59 | final RememberMasterPasswordContainer container = getContainer(); 60 | final byte[] key = PatternPasswordController.getKeyByPatterns(pattern); 61 | final RememberPasswordInputController controller = (RememberPasswordInputController) 62 | container.findPageControllerById(R.id.remember_input_password); 63 | try { 64 | final Pair encryptedAndIv = mEncrypter.encrypt(controller.getPassword(), key); 65 | final SharedPreferences.Editor editor = mPreferences.edit(); 66 | editor.putString(KEY_PATTERN_PASSWORD, Base64.encodeToString(encryptedAndIv.first, Base64.URL_SAFE)); 67 | editor.putString(KEY_PATTERN_IV, Base64.encodeToString(encryptedAndIv.second, Base64.URL_SAFE)); 68 | editor.apply(); 69 | container.showNext(); 70 | } catch (MasterPasswordEncrypter.InternalErrorException e) { 71 | mPatternHint.setText(R.string.internal_error); 72 | } 73 | } 74 | } else { 75 | mPatternHint.setText(R.string.pattern_confirm_hint); 76 | } 77 | mPattern = pattern; 78 | } 79 | 80 | @Override 81 | protected boolean onPagePreviousEnter() { 82 | mPatternView.clearPattern(); 83 | return true; 84 | } 85 | 86 | @Override 87 | protected boolean onPageNextEnter() { 88 | mPatternView.clearPattern(); 89 | return true; 90 | } 91 | 92 | @Override 93 | protected boolean onPageNextExit() { 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/controller/RememberPasswordPatternNoticeController.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view.controller; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import org.mariotaku.pass.view.RememberMasterPasswordContainer; 7 | 8 | /** 9 | * Created by mariotaku on 15/10/31. 10 | */ 11 | public class RememberPasswordPatternNoticeController extends RememberMasterPasswordContainer.PageController { 12 | public RememberPasswordPatternNoticeController(final RememberMasterPasswordContainer container, 13 | final Context context, final AttributeSet attributeSet) { 14 | super(container, context, attributeSet); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mariotaku/pass/view/controller/TextPasswordController.java: -------------------------------------------------------------------------------- 1 | package org.mariotaku.pass.view.controller; 2 | 3 | import android.content.Context; 4 | import android.text.Editable; 5 | import android.text.TextUtils; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.EditText; 9 | 10 | import org.mariotaku.pass.R; 11 | import org.mariotaku.pass.view.PasswordContainer; 12 | 13 | /** 14 | * Created by mariotaku on 15/10/31. 15 | */ 16 | public class TextPasswordController extends PasswordContainer.PasswordController implements View.OnClickListener { 17 | private EditText mEditPassword; 18 | private View mPasswordSubmit; 19 | 20 | public TextPasswordController(final PasswordContainer container, final Context context, final AttributeSet attributeSet) { 21 | super(container, context, attributeSet); 22 | } 23 | 24 | @Override 25 | protected void onAttach(final View view) { 26 | super.onAttach(view); 27 | mEditPassword = (EditText) view.findViewById(R.id.edit_password); 28 | mPasswordSubmit = view.findViewById(R.id.password_submit); 29 | 30 | mPasswordSubmit.setOnClickListener(this); 31 | } 32 | 33 | @Override 34 | public void onClick(final View v) { 35 | final Editable text = mEditPassword.getText(); 36 | if (TextUtils.isEmpty(text)) { 37 | // Show empty error 38 | return; 39 | } 40 | notifyPasswordEntered(String.valueOf(text).getBytes()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/openauthentication/otp/OneTimePasswordAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * OneTimePasswordAlgorithm.java 3 | * OATH Initiative, 4 | * HOTP one-time password algorithm 5 | * 6 | */ 7 | 8 | /* Copyright (C) 2004, OATH. All rights reserved. 9 | * 10 | * License to copy and use this software is granted provided that it 11 | * is identified as the "OATH HOTP Algorithm" in all material 12 | * mentioning or referencing this software or this function. 13 | * 14 | * License is also granted to make and use derivative works provided 15 | * that such works are identified as 16 | * "derived from OATH HOTP algorithm" 17 | * in all material mentioning or referencing the derived work. 18 | * 19 | * OATH (Open AuTHentication) and its members make no 20 | * representations concerning either the merchantability of this 21 | * software or the suitability of this software for any particular 22 | * purpose. 23 | * 24 | * It is provided "as is" without express or implied warranty 25 | * of any kind and OATH AND ITS MEMBERS EXPRESSaLY DISCLAIMS 26 | * ANY WARRANTY OR LIABILITY OF ANY KIND relating to this software. 27 | * 28 | * These notices must be retained in any copies of any part of this 29 | * documentation and/or software. 30 | */ 31 | 32 | package org.openauthentication.otp; 33 | 34 | import java.security.InvalidKeyException; 35 | import java.security.NoSuchAlgorithmException; 36 | 37 | import javax.crypto.Mac; 38 | import javax.crypto.spec.SecretKeySpec; 39 | 40 | /** 41 | * This class contains static methods that are used to calculate the One-Time Password (OTP) using 42 | * JCE to provide the HMAC-SHA-1. 43 | * 44 | * @author Loren Hart 45 | * @version 1.0 46 | */ 47 | public class OneTimePasswordAlgorithm { 48 | private OneTimePasswordAlgorithm() { 49 | } 50 | 51 | // These are used to calculate the check-sum digits. 52 | // 0 1 2 3 4 5 6 7 8 9 53 | private static final int[] doubleDigits = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 }; 54 | 55 | /** 56 | * Calculates the checksum using the credit card algorithm. This algorithm has the advantage 57 | * that it detects any single mistyped digit and any single transposition of adjacent digits. 58 | * 59 | * @param num 60 | * the number to calculate the checksum for 61 | * @param digits 62 | * number of significant places in the number 63 | * 64 | * @return the checksum of num 65 | */ 66 | public static int calcChecksum(long num, int digits) { 67 | boolean doubleDigit = true; 68 | int total = 0; 69 | while (0 < digits--) { 70 | int digit = (int) (num % 10); 71 | num /= 10; 72 | if (doubleDigit) { 73 | digit = doubleDigits[digit]; 74 | } 75 | total += digit; 76 | doubleDigit = !doubleDigit; 77 | } 78 | int result = total % 10; 79 | if (result > 0) { 80 | result = 10 - result; 81 | } 82 | return result; 83 | } 84 | 85 | /** 86 | * This method uses the JCE to provide the HMAC-SHA-1 algorithm. HMAC computes a Hashed Message 87 | * Authentication Code and in this case SHA1 is the hash algorithm used. 88 | * 89 | * @param keyBytes 90 | * the bytes to use for the HMAC-SHA-1 key 91 | * @param text 92 | * the message or text to be authenticated. 93 | * 94 | * @throws NoSuchAlgorithmException 95 | * if no provider makes either HmacSHA1 or HMAC-SHA-1 digest algorithms available. 96 | * @throws InvalidKeyException 97 | * The secret provided was not a valid HMAC-SHA-1 key. 98 | * 99 | */ 100 | 101 | public static byte[] hmac_sha1(byte[] keyBytes, byte[] text) throws NoSuchAlgorithmException, 102 | InvalidKeyException { 103 | Mac hmacSha1; 104 | try { 105 | hmacSha1 = Mac.getInstance("HmacSHA1"); 106 | } catch (final NoSuchAlgorithmException nsae) { 107 | hmacSha1 = Mac.getInstance("HMAC-SHA-1"); 108 | } 109 | final SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW"); 110 | hmacSha1.init(macKey); 111 | return hmacSha1.doFinal(text); 112 | } 113 | 114 | private static final int[] DIGITS_POWER 115 | // 0 1 2 3 4 5 6 7 8 116 | = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; 117 | 118 | /** 119 | * This method generates an OTP value for the given set of parameters. 120 | * 121 | * @param secret 122 | * the shared secret 123 | * @param movingFactor 124 | * the counter, time, or other value that changes on a per use basis. 125 | * @param codeDigits 126 | * the number of digits in the OTP, not including the checksum, if any. Valid values 127 | * are between 3 and 8 inclusive. 128 | * @param addChecksum 129 | * a flag that indicates if a checksum digit should be appended to the OTP. 130 | * @param truncationOffset 131 | * the offset into the MAC result to begin truncation. If this value is out of the 132 | * range of 0 ... 15, then dynamic truncation will be used. Dynamic truncation is 133 | * when the last 4 bits of the last byte of the MAC are used to determine the start 134 | * offset. 135 | * @throws NoSuchAlgorithmException 136 | * if no provider makes either HmacSHA1 or HMAC-SHA-1 digest algorithms available. 137 | * @throws InvalidKeyException 138 | * The secret provided was not a valid HMAC-SHA-1 key. 139 | * 140 | * @return A numeric String in base 10 that includes {@link codeDigits} digits plus the optional 141 | * checksum digit if requested. 142 | */ 143 | static public String generateOTP(byte[] secret, long movingFactor, int codeDigits, 144 | boolean addChecksum, int truncationOffset) throws NoSuchAlgorithmException, 145 | InvalidKeyException { 146 | // put movingFactor value into text byte array 147 | final byte[] text = new byte[8]; 148 | for (int i = text.length - 1; i >= 0; i--) { 149 | text[i] = (byte) (movingFactor & 0xff); 150 | movingFactor >>= 8; 151 | } 152 | 153 | return generateOTPFromText(secret, text, codeDigits, addChecksum, truncationOffset); 154 | } 155 | 156 | /** 157 | * This method generates an OTP value for the given set of parameters. 158 | * 159 | * @param secret 160 | * the shared secret 161 | * @param text 162 | * The data input. This is an alternative to the movingFactor in the other variant of 163 | * this method. 164 | * @param codeDigits 165 | * the number of digits in the OTP, not including the checksum, if any. Valid values 166 | * are between 3 and 8 inclusive. 167 | * @param addChecksum 168 | * a flag that indicates if a checksum digit should be appended to the OTP. 169 | * @param truncationOffset 170 | * the offset into the MAC result to begin truncation. If this value is out of the 171 | * range of 0 ... 15, then dynamic truncation will be used. Dynamic truncation is 172 | * when the last 4 bits of the last byte of the MAC are used to determine the start 173 | * offset. 174 | * @throws NoSuchAlgorithmException 175 | * if no provider makes either HmacSHA1 or HMAC-SHA-1 digest algorithms available. 176 | * @throws InvalidKeyException 177 | * The secret provided was not a valid HMAC-SHA-1 key. 178 | * 179 | * @return A numeric String in base 10 that includes {@link codeDigits} digits plus the optional 180 | * checksum digit if requested. 181 | */ 182 | static public String generateOTPFromText(byte[] secret, byte[] text, int codeDigits, 183 | boolean addChecksum, int truncationOffset) throws InvalidKeyException, 184 | NoSuchAlgorithmException { 185 | String result = null; 186 | final int digits = addChecksum ? (codeDigits + 1) : codeDigits; 187 | // compute hmac hash 188 | final byte[] hash = hmac_sha1(secret, text); 189 | 190 | // put selected bytes into result int 191 | int offset = hash[hash.length - 1] & 0xf; 192 | if ((0 <= truncationOffset) && (truncationOffset < (hash.length - 4))) { 193 | offset = truncationOffset; 194 | } 195 | final int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) 196 | | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff); 197 | 198 | int otp = binary % DIGITS_POWER[codeDigits]; 199 | if (addChecksum) { 200 | otp = (otp * 10) + calcChecksum(otp, codeDigits); 201 | } 202 | result = Integer.toString(otp); 203 | while (result.length() < digits) { 204 | result = "0" + result; 205 | } 206 | return result; 207 | } 208 | } -------------------------------------------------------------------------------- /src/main/res-localized/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pass 5 | 轻触传感器或输入密码 6 | 设置 7 | Pass是一个密码生成器,旨在帮助您轻松生成比较安全的密码。\n 8 | Pass只能生成密码,而不会帮您记住密码。因此,如果您忘记您的主密码,我们无法帮您恢复。\n 9 | 任何时候,最重要的安全防线都是您自己,请在日常生活中时时记得提高自己的安全意识。Pass希望能帮您兼得安全与轻松的体验,但是我们并不能保证您绝对的安全。 10 | 域名 11 | 密码 12 | 设置加盐 13 | 增强安全性 14 | 高级 15 | 密码生成器类型 16 | SuperGenPass (MD5) 17 | SuperGenPass (SHA-512) 18 | Password Composer 19 | 记住主密码 20 | 密码长度 21 | 您可以使用手势密码加密并记住您的主密码。\n\n请注意,手势密码不如强密码安全。如果您的设备已经 Root (或者解锁了 Bootloader ),则不建议您再使用此方式。因为通过暴力破解,您的主密码很容易泄露。\n\n在五次错误尝试之后,手势密码或者PIN码将会被清空。此时您必须输入主密码。 22 | 您可以使用指纹加密并记住您的主密码。\n\n请注意,指纹不如强密码安全。 23 | 主密码 24 | 上一步 25 | 下一步 26 | 空密码 27 | 取消 28 | 完成 29 | 画出手势密码 30 | 确认您的手势密码 31 | 手势密码不匹配 32 | 手势密码已清除 33 | 内部错误 34 | 手势密码 35 | 指纹 36 | 返回 37 | 关闭 38 | 复制密码 39 | 复制 PIN 40 | 不支持指纹功能 41 | 安全 -> 指纹 来设置指纹。]]> 42 | PIN 长度 43 | 触摸扫描器 44 | 复制密码有效期 45 | 自动清除复制的密码 46 | 密码已复制 47 | PIN 48 | 无法识别 49 | 需要输入域名 50 | 输入密码 51 | 不支持的标签 52 | 未注册的标签 53 | 专业版功能 54 | NFC 标签 55 | 添加卡片 56 | 域名 57 | 名称 58 | 点击生成密码 59 | 密码输入助手 60 | 复制的密码 61 | 这个界面不支持自动填充,请粘贴密码 62 | 自动填充密码 63 | 标签 %s 64 | 65 | -------------------------------------------------------------------------------- /src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_fp_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_fp_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_pw_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_pw_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_qs_tile_mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_qs_tile_mdpi.png -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_stat_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_stat_password.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_fp_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_fp_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_pw_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_pw_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_qs_tile_mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_qs_tile_mdpi.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_stat_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_stat_password.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_fp_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_fp_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_pw_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_pw_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_qs_tile_mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_qs_tile_mdpi.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_stat_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_stat_password.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_fp_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_fp_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_pw_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_pw_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_qs_tile_mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_qs_tile_mdpi.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_stat_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_stat_password.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxxhdpi/ic_fp_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_fp_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxxhdpi/ic_pw_40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_pw_40px.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxxhdpi/ic_qs_tile_mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_qs_tile_mdpi.png -------------------------------------------------------------------------------- /src/main/res/drawable/ic_action_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_action_clear.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_action_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_action_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_action_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_btn_ok.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/layout/activity_pass_gen_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 21 | 27 | 28 | 39 | 40 | 46 | 47 | 58 | 59 | 68 | 69 | 73 | 74 | 84 | 85 | 86 | 94 | 95 | 96 | 102 | 103 | 109 | 110 | 124 | 125 | 129 | 130 | 144 | 145 | 146 | 156 | 157 | 172 | 173 | 189 | 190 | 191 | 206 | 207 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /src/main/res/layout/dialog_add_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 16 | 17 | 22 | 23 | 29 | 30 | 36 | -------------------------------------------------------------------------------- /src/main/res/layout/fragment_remember_master_password_fingerprint.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 22 | 23 | 30 | 31 | 32 | 37 | 38 | 42 | 43 | 50 | 51 | 58 | 59 | 60 | 61 | 66 | 67 | 78 | 79 | 85 | 86 | 87 | 88 | 95 | 96 |