├── app
├── .gitignore
├── src
│ ├── androidTest
│ │ ├── assets
│ │ │ ├── invalid
│ │ │ │ ├── emptyzip.zip
│ │ │ │ ├── emptynote.7z
│ │ │ │ ├── broken_zipcrypto.zip
│ │ │ │ └── unencrypted_note.zip
│ │ │ ├── 4notes.aeszip
│ │ │ ├── subdirs.aeszip
│ │ │ ├── compression
│ │ │ │ ├── 100a.txt
│ │ │ │ ├── 100a_flat.aeszip
│ │ │ │ ├── 100a_lzma.aeszip
│ │ │ │ ├── 100a_ppmd.aeszip
│ │ │ │ ├── 100a_bzip2.aeszip
│ │ │ │ ├── 100a_deflate.aeszip
│ │ │ │ ├── 100a_deflate64.aeszip
│ │ │ │ ├── 100a_deflate_fast.aeszip
│ │ │ │ └── 100a_deflate_ultra.aeszip
│ │ │ ├── singlenote.aeszip
│ │ │ ├── twopasswords.ZIP
│ │ │ └── 4passwords_subdirs.aeszip
│ │ └── java
│ │ │ └── com
│ │ │ └── ditronic
│ │ │ └── securezipnotes
│ │ │ ├── contrib
│ │ │ └── NonFatalAbortTree.kt
│ │ │ ├── robotpattern
│ │ │ └── Preconditions.kt
│ │ │ ├── tests
│ │ │ ├── MultiPasswordTests.kt
│ │ │ └── SetupTests.kt
│ │ │ ├── onlinetests
│ │ │ └── SyncTests.kt
│ │ │ └── testutils
│ │ │ └── TestUtils.kt
│ ├── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── xml
│ │ │ │ ├── provider_paths.xml
│ │ │ │ └── backup_rules.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── menu
│ │ │ │ ├── menu_noteselect_longclick.xml
│ │ │ │ ├── menu_noteedit.xml
│ │ │ │ └── menu_noteselect.xml
│ │ │ ├── layout
│ │ │ │ ├── common_toolbar.xml
│ │ │ │ ├── cardview.xml
│ │ │ │ ├── activity_note_edit.xml
│ │ │ │ ├── activity_password_confirm.xml
│ │ │ │ ├── activity_new_password.xml
│ │ │ │ └── activity_main.xml
│ │ │ └── drawable
│ │ │ │ ├── ic_baseline_add_24px.xml
│ │ │ │ ├── ic_baseline_warning_24px.xml
│ │ │ │ ├── ic_baseline_arrow_forward_24px.xml
│ │ │ │ ├── ic_baseline_cloud_download_24px.xml
│ │ │ │ ├── ic_dropbox.xml
│ │ │ │ └── ic_drive.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── ditronic
│ │ │ │ └── securezipnotes
│ │ │ │ ├── app
│ │ │ │ ├── AppEnvironment.kt
│ │ │ │ ├── ApplicationExt.kt
│ │ │ │ └── ViewModelFactory.kt
│ │ │ │ ├── util
│ │ │ │ ├── OnThrottleClickListener.kt
│ │ │ │ ├── OnThrottleItemClickListener.kt
│ │ │ │ ├── BannerAds.kt
│ │ │ │ └── Boast.kt
│ │ │ │ ├── noteedit
│ │ │ │ ├── NoteEditViewModel.kt
│ │ │ │ ├── PlainEditText.kt
│ │ │ │ └── NoteEditUI.kt
│ │ │ │ ├── dialogs
│ │ │ │ ├── DeleteDialog.kt
│ │ │ │ ├── ShortLifeDialogFragment.kt
│ │ │ │ ├── RenameFileDialog.kt
│ │ │ │ └── PwDialog.kt
│ │ │ │ ├── logging
│ │ │ │ └── CrashlyticsTree.kt
│ │ │ │ ├── zip
│ │ │ │ ├── ZipUtil.kt
│ │ │ │ └── NotesImport.kt
│ │ │ │ ├── noteselect
│ │ │ │ └── NoteSelectAdapter.kt
│ │ │ │ ├── menu
│ │ │ │ └── MenuOptions.kt
│ │ │ │ ├── onboarding
│ │ │ │ ├── NewPasswordActivity.kt
│ │ │ │ └── PasswordConfirmActivity.kt
│ │ │ │ └── password
│ │ │ │ └── PwManager.kt
│ │ └── AndroidManifest.xml
│ ├── debug
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ ├── release
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ └── test
│ │ └── java
│ │ └── com
│ │ └── ditronic
│ │ └── securezipnotes
│ │ └── ExampleUnitTest.java
├── lint.xml
├── proguard-rules.pro
├── google-services.json
└── build.gradle
├── zip4j
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── net
│ │ └── lingala
│ │ └── zip4j
│ │ ├── io
│ │ ├── BaseInputStream.java
│ │ ├── ZipOutputStream.java
│ │ ├── BaseOutputStream.java
│ │ ├── ZipInputStream.java
│ │ └── DeflaterOutputStream.java
│ │ ├── crypto
│ │ ├── IDecrypter.java
│ │ ├── IEncrypter.java
│ │ ├── PBKDF2
│ │ │ ├── PRF.java
│ │ │ ├── PBKDF2HexFormatter.java
│ │ │ ├── BinTools.java
│ │ │ ├── PBKDF2Parameters.java
│ │ │ └── MacBasedPRF.java
│ │ ├── engine
│ │ │ └── ZipCryptoEngine.java
│ │ ├── StandardDecrypter.java
│ │ └── StandardEncrypter.java
│ │ ├── exception
│ │ ├── ZipExceptionConstants.java
│ │ └── ZipException.java
│ │ ├── model
│ │ ├── CentralDirectory.java
│ │ ├── ExtraDataRecord.java
│ │ ├── DataDescriptor.java
│ │ ├── DigitalSignature.java
│ │ ├── ArchiveExtraDataRecord.java
│ │ ├── Zip64EndCentralDirLocator.java
│ │ ├── Zip64ExtendedInfo.java
│ │ ├── AESExtraDataRecord.java
│ │ ├── UnzipEngineParameters.java
│ │ ├── UnzipParameters.java
│ │ ├── EndCentralDirRecord.java
│ │ ├── Zip64EndCentralDirRecord.java
│ │ └── ZipModel.java
│ │ ├── util
│ │ ├── Zip4jConstants.java
│ │ └── CRCUtil.java
│ │ ├── unzip
│ │ └── UnzipUtil.java
│ │ └── progress
│ │ └── ProgressMonitor.java
└── build.gradle
├── simplefilesync
├── .gitignore
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── ditronic
│ │ │ └── simplefilesync
│ │ │ └── util
│ │ │ ├── ResultCode.java
│ │ │ ├── SSyncResult.java
│ │ │ └── FilesUtil.java
│ ├── release
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ └── debug
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── build.gradle
└── README.md
├── settings.gradle
├── dmitri-report-f15-16.pdf
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── BACKLOG.md
├── .github
└── workflows
│ └── ci.yml
├── CHANGELOG.md
├── gradle.properties
├── LICENSE
├── PRIVACY_POLICY.md
├── run_instrumentation_tests.sh
├── gradlew.bat
├── README.md
└── .gitignore
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/zip4j/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/simplefilesync/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':simplefilesync', ':zip4j'
2 |
--------------------------------------------------------------------------------
/app/src/androidTest/assets/invalid/emptyzip.zip:
--------------------------------------------------------------------------------
1 | PK
--------------------------------------------------------------------------------
/zip4j/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/simplefilesync/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dmitri-report-f15-16.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/dmitri-report-f15-16.pdf
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/androidTest/assets/4notes.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/4notes.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/subdirs.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/subdirs.aeszip
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dev Secure Zip Notes
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/release/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Secure Zip Notes
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a.txt:
--------------------------------------------------------------------------------
1 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
--------------------------------------------------------------------------------
/app/src/androidTest/assets/singlenote.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/singlenote.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/twopasswords.ZIP:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/twopasswords.ZIP
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/androidTest/assets/invalid/emptynote.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/invalid/emptynote.7z
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/androidTest/assets/4passwords_subdirs.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/4passwords_subdirs.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_flat.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_flat.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_lzma.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_lzma.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_ppmd.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_ppmd.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/invalid/broken_zipcrypto.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/invalid/broken_zipcrypto.zip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/invalid/unencrypted_note.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/invalid/unencrypted_note.zip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_bzip2.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_bzip2.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_deflate.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_deflate.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_deflate64.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_deflate64.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_deflate_fast.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_deflate_fast.aeszip
--------------------------------------------------------------------------------
/app/src/androidTest/assets/compression/100a_deflate_ultra.aeszip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fkirc/secure-zip-notes/HEAD/app/src/androidTest/assets/compression/100a_deflate_ultra.aeszip
--------------------------------------------------------------------------------
/app/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #86DFEB
4 | #86DFEB
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Dec 13 19:23:51 CET 2020
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-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/simplefilesync/src/release/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 62deec4uibwo7e2
5 | db-62deec4uibwo7e2
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_noteselect_longclick.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/simplefilesync/src/main/java/com/ditronic/simplefilesync/util/ResultCode.java:
--------------------------------------------------------------------------------
1 | package com.ditronic.simplefilesync.util;
2 |
3 | public enum ResultCode {
4 | UPLOAD_SUCCESS,
5 | DOWNLOAD_SUCCESS,
6 | REMOTE_EQUALS_LOCAL,
7 | FILES_NOT_EXIST_OR_EMPTY,
8 | NO_CREDENTIALS_FAILURE,
9 | CONNECTION_FAILURE,
10 | }
11 |
--------------------------------------------------------------------------------
/simplefilesync/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | b7hsm39zkdqdgud
5 | db-b7hsm39zkdqdgud
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/common_toolbar.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_add_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/BACKLOG.md:
--------------------------------------------------------------------------------
1 | This file contains a list of potential tasks for the continuous development of Secure Zip Notes.
2 |
3 | - Simple search functionality for open notes.
4 |
5 | - Option to use a pseudo keyboard for typing the master password, that is, a set of buttons that aims to protect users against keylogging malware.
6 |
7 | - Password autofill with an autofill framework starting from Android 8.
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_warning_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_forward_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/app/AppEnvironment.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.app
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 |
6 | @SuppressLint("StaticFieldLeak")
7 | class AppEnvironment(val context: Context) {
8 |
9 | companion object {
10 | lateinit var current: AppEnvironment
11 |
12 | fun initialize(context: Context) {
13 | current = AppEnvironment(context = context)
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ditronic/securezipnotes/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_cloud_download_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/io/BaseInputStream.java:
--------------------------------------------------------------------------------
1 | package net.lingala.zip4j.io;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | import net.lingala.zip4j.unzip.UnzipEngine;
7 |
8 | public abstract class BaseInputStream extends InputStream {
9 |
10 | public int read() throws IOException {
11 | return 0;
12 | }
13 |
14 | public void seek(long pos) throws IOException {
15 | }
16 |
17 | public int available() throws IOException {
18 | return 0;
19 | }
20 |
21 | public UnzipEngine getUnzipEngine() {
22 | return null;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_noteedit.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/zip4j/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 29
5 |
6 | defaultConfig {
7 | minSdkVersion 19
8 | targetSdkVersion 29
9 | versionCode 1
10 | versionName "SNAPSHOT"
11 | }
12 |
13 | compileOptions {
14 | targetCompatibility = '1.8'
15 | sourceCompatibility = '1.8'
16 | }
17 |
18 | lintOptions {
19 | abortOnError false
20 | }
21 | }
22 |
23 | dependencies {
24 | implementation fileTree(dir: 'libs', include: ['*.jar'])
25 |
26 | implementation 'org.projectlombok:lombok:1.18.16'
27 | annotationProcessor 'org.projectlombok:lombok:1.18.16'
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | workflow_dispatch:
6 | schedule:
7 | - cron: '0 12 15 * *'
8 |
9 | jobs:
10 | pre_job:
11 | runs-on: ubuntu-latest
12 | outputs:
13 | should_skip: ${{ steps.skip_check.outputs.should_skip }}
14 | steps:
15 | - id: skip_check
16 | uses: fkirc/skip-duplicate-actions@master
17 | with:
18 | paths_ignore: '["**/*.md"]'
19 |
20 | chain:
21 | needs: pre_job
22 | if: ${{ needs.pre_job.outputs.should_skip == 'false' }}
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v2
26 | - name: check
27 | run: bash ./gradlew check
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Secure Zip Notes
3 |
4 | ca-app-pub-3394747202744753~4306949160
5 |
6 | Create New Zip File
7 | Import Zip File
8 | Export Zip File
9 | Master Password
10 | Sync with Dropbox
11 | Sync with Google Drive
12 |
13 | Notes empty, nothing to export
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/app/ApplicationExt.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.app
2 |
3 |
4 | import android.app.Application
5 | import com.ditronic.securezipnotes.BuildConfig
6 | import com.ditronic.securezipnotes.logging.CrashlyticsTree
7 | import timber.log.Timber
8 |
9 | class ApplicationExt : Application() {
10 |
11 | override fun onCreate() {
12 | super.onCreate()
13 | AppEnvironment.initialize(context = this)
14 |
15 | initLogging()
16 | }
17 |
18 | private fun initLogging() {
19 | if (BuildConfig.DEBUG) {
20 | Timber.plant(Timber.DebugTree())
21 | } else {
22 | Timber.plant(CrashlyticsTree())
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/util/OnThrottleClickListener.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.util
2 |
3 | import android.os.SystemClock
4 | import android.view.View
5 |
6 | abstract class OnThrottleClickListener : View.OnClickListener {
7 |
8 | private var lastClickTime: Long = 0
9 | protected abstract fun onThrottleClick(v: View)
10 |
11 | override fun onClick(v: View) {
12 | val clickTime = SystemClock.elapsedRealtime()
13 | if (clickTime - lastClickTime < THROTTLE_TIME) {
14 | return
15 | }
16 | lastClickTime = clickTime
17 | onThrottleClick(v)
18 | }
19 |
20 | companion object {
21 |
22 | private const val THROTTLE_TIME: Long = 400 // milliseconds
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_noteselect.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.2.0
2 |
3 | - Remove Google Drive sync to remove bugs and streamline maintenance.
4 |
5 | ## 1.1.4
6 |
7 | - Maintenance update (keeping up with Android updates and library updates)
8 |
9 | ## 1.1.3
10 |
11 | - Maintenance update
12 |
13 | ## 1.1.2
14 |
15 | - Maintenance update
16 |
17 | ## 1.1.1
18 |
19 | - Remove text formatting when pasting notes. From now on, everything is plain text.
20 |
21 | - More friendly error messages when parsing invalid or non-compliant ZIP files.
22 |
23 | ## 1.1.0
24 |
25 | - Uses fingerprints (or other biometrics) instead of PIN-Codes.
26 | This improves the usability while still retaining the security of hardware-protected keys.
27 |
28 | - Fixing a bug for Android 5.0.
29 |
30 | - No more hidden file extensions.
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/app/ViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.app
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.ditronic.securezipnotes.noteedit.NoteEditViewModel
6 |
7 | @Suppress("UNCHECKED_CAST")
8 | class ViewModelFactory(private val environment: AppEnvironment) : ViewModelProvider.Factory {
9 |
10 | override fun create(modelClass: Class): T {
11 | return when {
12 | modelClass.isAssignableFrom(NoteEditViewModel::class.java) -> NoteEditViewModel(
13 | appContext = environment.context
14 | ) as T
15 | else -> throw IllegalArgumentException("Unknown ViewModel - Maybe forgot to add in ViewModelFactory?")
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/noteedit/NoteEditViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.noteedit
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import androidx.lifecycle.ViewModel
6 | import com.ditronic.securezipnotes.zip.CryptoZip
7 | import net.lingala.zip4j.model.FileHeader
8 |
9 | @SuppressLint("StaticFieldLeak")
10 | class NoteEditViewModel(val appContext: Context) : ViewModel() {
11 |
12 | internal var secretContent: String? = null
13 | internal var editMode: Boolean = false
14 |
15 | lateinit var innerFileName: String
16 |
17 | internal val fileHeader: FileHeader
18 | get() = CryptoZip.instance(appContext).getFileHeader(innerFileName)!!
19 |
20 | internal val noteName: String
21 | get() = CryptoZip.getDisplayName(fileHeader)
22 | }
23 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | android.enableR8=true
12 | org.gradle.jvmargs=-Xmx1536m
13 | # When configured, Gradle will run in incubating parallel mode.
14 | # This option should only be used with decoupled projects. More details, visit
15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
16 | # org.gradle.parallel=true
17 |
--------------------------------------------------------------------------------
/simplefilesync/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 30
5 |
6 |
7 | defaultConfig {
8 | minSdkVersion 19
9 | targetSdkVersion 30
10 | versionCode 1
11 | versionName "1.0.0"
12 | }
13 |
14 | lintOptions {
15 | abortOnError true
16 | warning 'InvalidPackage'
17 | }
18 | }
19 |
20 | dependencies {
21 | implementation fileTree(dir: 'libs', include: ['*.jar'])
22 |
23 | implementation "com.jakewharton.timber:timber:4.7.1"
24 | implementation 'com.google.android.gms:play-services-auth:19.0.0'
25 | implementation ('com.google.http-client:google-http-client-gson:1.38.0') {
26 | exclude group: 'org.apache.httpcomponents'
27 | }
28 |
29 | implementation 'com.dropbox.core:dropbox-core-sdk:3.1.5'
30 | api 'com.squareup.okhttp3:okhttp:4.9.0'
31 | }
32 |
--------------------------------------------------------------------------------
/app/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/io/ZipOutputStream.java:
--------------------------------------------------------------------------------
1 | package net.lingala.zip4j.io;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | import net.lingala.zip4j.model.ZipModel;
7 |
8 | public class ZipOutputStream extends DeflaterOutputStream {
9 |
10 | public ZipOutputStream(OutputStream outputStream) {
11 | this(outputStream, null);
12 | }
13 |
14 | public ZipOutputStream(OutputStream outputStream, ZipModel zipModel) {
15 | super(outputStream, zipModel);
16 | }
17 |
18 | public void write(int bval) throws IOException {
19 | byte[] b = new byte[1];
20 | b[0] = (byte) bval;
21 | write(b, 0, 1);
22 | }
23 |
24 | public void write(byte[] b) throws IOException {
25 | write(b, 0, b.length);
26 | }
27 |
28 | public void write(byte[] b, int off, int len) throws IOException {
29 | crc.update(b, off, len);
30 | updateTotalBytesRead(len);
31 | super.write(b, off, len);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dropbox.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/noteedit/PlainEditText.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.noteedit
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.util.AttributeSet
6 | import androidx.appcompat.widget.AppCompatEditText
7 |
8 | class PlainEditText : AppCompatEditText {
9 | constructor(context: Context) : super(context)
10 |
11 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
12 |
13 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
14 |
15 | override fun onTextContextMenuItem(id: Int): Boolean {
16 | var newId = id
17 | if (id == android.R.id.paste) {
18 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
19 | newId = android.R.id.pasteAsPlainText
20 | }
21 | }
22 | return super.onTextContextMenuItem(newId)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/util/OnThrottleItemClickListener.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.util
2 |
3 | import android.os.SystemClock
4 | import android.view.View
5 | import android.widget.AdapterView
6 |
7 | abstract class OnThrottleItemClickListener : AdapterView.OnItemClickListener {
8 |
9 | private var lastClickTime: Long = 0
10 | protected abstract fun onThrottleItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long)
11 |
12 | override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
13 | val clickTime = SystemClock.elapsedRealtime()
14 | if (clickTime - lastClickTime < THROTTLE_TIME) {
15 | return
16 | }
17 | lastClickTime = clickTime
18 | onThrottleItemClick(parent, view, position, id)
19 | }
20 |
21 | companion object {
22 |
23 | private const val THROTTLE_TIME: Long = 500 // milliseconds
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/io/BaseOutputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.io;
18 |
19 | import java.io.IOException;
20 | import java.io.OutputStream;
21 |
22 | public abstract class BaseOutputStream extends OutputStream {
23 |
24 | public void write(int b) throws IOException {
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/IDecrypter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto;
18 |
19 | import net.lingala.zip4j.exception.ZipException;
20 |
21 | public interface IDecrypter {
22 |
23 | public int decryptData(byte[] buff, int start, int len) throws ZipException;
24 |
25 | public int decryptData(byte[] buff) throws ZipException;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/IEncrypter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto;
18 |
19 | import net.lingala.zip4j.exception.ZipException;
20 |
21 | public interface IEncrypter {
22 |
23 | public int encryptData(byte[] buff) throws ZipException;
24 |
25 | public int encryptData(byte[] buff, int start, int len) throws ZipException;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_drive.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/PBKDF2/PRF.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto.PBKDF2;
18 |
19 | /*
20 | * Source referred from Matthias Gartner's PKCS#5 implementation -
21 | * see http://rtner.de/software/PBKDF2.html
22 | */
23 |
24 | interface PRF
25 | {
26 | public void init(byte[] P);
27 |
28 | public byte[] doFinal(byte[] M);
29 |
30 | public int getHLen();
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/dialogs/DeleteDialog.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.dialogs
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AlertDialog
6 |
7 | abstract class DeleteDialogState(val message: String) {
8 | abstract fun onPositiveClick()
9 | abstract fun onNegativeClick()
10 | }
11 |
12 | class DeleteDialog: ShortLifeDialogFragment() {
13 |
14 | companion object {
15 | val TAG = FragmentTag("DeleteDialog")
16 | }
17 |
18 | override fun onCreateDialog(savedInstanceState: Bundle?, state: DeleteDialogState): Dialog {
19 | return AlertDialog.Builder(requireContext())
20 | .setIcon(android.R.drawable.ic_dialog_alert)
21 | .setMessage(state.message)
22 | .setPositiveButton("OK") { _, _ -> state.onPositiveClick() }
23 | .setNegativeButton("Cancel") { _, _ -> state.onNegativeClick() }
24 | .create()
25 | }
26 |
27 | override fun getFragmentTag() = TAG
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/exception/ZipExceptionConstants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.exception;
18 |
19 | public interface ZipExceptionConstants {
20 |
21 | public static int inputZipParamIsNull = 0001;
22 |
23 | public static int constuctorFileNotFoundException = 0002;
24 |
25 | public static int randomAccessFileNull = 0003;
26 |
27 | public static int notZipFile = 0004;
28 |
29 | public static int WRONG_PASSWORD = 0005;
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/logging/CrashlyticsTree.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.logging
2 |
3 | import android.util.Log
4 | import com.google.firebase.crashlytics.FirebaseCrashlytics
5 | import timber.log.Timber
6 |
7 | class CrashlyticsTree : Timber.Tree() {
8 |
9 | private val crashlytics by lazy {
10 | FirebaseCrashlytics.getInstance()
11 | }
12 |
13 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
14 | if (priority == Log.ERROR || priority == Log.ASSERT) {
15 | logErrorWithoutCrash(message, t)
16 | }
17 | }
18 |
19 | private fun logErrorWithoutCrash(message: String, t: Throwable?) {
20 | if (t != null) {
21 | // If we have an exception, then the message is most likely redundant since it will also contain the stacktrace.
22 | crashlytics.recordException(t)
23 | } else {
24 | // If we do not have an exception, then we wrap the message inside an exception.
25 | crashlytics.recordException(IllegalStateException(message))
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/PRIVACY_POLICY.md:
--------------------------------------------------------------------------------
1 | # Secure Zip Notes Privacy Policy
2 |
3 | DiTronic Apps built Secure Zip Notes with privacy and security as the top priority.
4 | By default, Secure Zip Notes is a pure "offline app".
5 | The only exception is the optional sync with a cloud backend (e.g. Dropbox).
6 | Even if cloud sync is enabled, the cloud provider is still not able to decrypt the data since Secure Zip Notes encrypts all notes locally before uploading them.
7 | Secure Zip Notes uses the oauth2 protocol to access Dropbox.
8 | Secure Zip Notes only requests the minimal oauth2 scopes that are necessary for creating app-specific files.
9 | Therefore, Secure Zip Notes does not have the permission to access any cloud files that have not been generated by Secure Zip Notes.
10 | Secure Zip Notes is open-source.
11 | If you are suspicious about the implementation of Secure Zip Notes, then you can review the source code at https://github.com/fkirc/secure-zip-notes.
12 |
13 | # Children’s Privacy
14 |
15 | Children are free to use our app since it only stores text that is typed in by the user.
16 | It does not display any content that is retrieved from the Internet or other users.
17 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | # Since this app is open-source I do not want to care about proguard deobfuscation.
24 | -dontobfuscate
25 |
26 | # ----------------------------------------------------------
27 | # This stuff is copied from the Dropbox Core SDK
28 | -dontwarn okio.**
29 | -dontwarn okhttp3.**
30 | -dontwarn com.squareup.okhttp.**
31 | -dontwarn com.google.appengine.**
32 | -dontwarn javax.servlet.**
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/util/BannerAds.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.util
2 |
3 | import android.app.Activity
4 |
5 | /*import com.google.android.gms.ads.AdRequest;
6 | import com.google.android.gms.ads.AdSize;
7 | import com.google.android.gms.ads.AdView;
8 | import com.google.android.gms.ads.MobileAds;*/
9 |
10 | @Suppress("UNUSED_PARAMETER")
11 | object BannerAds {
12 |
13 | const val TEST_AD_UNIT_ID = "ca-app-pub-3940256099942544/6300978111"
14 | const val PRODUCTION_AD_UNIT_ID = "ca-app-pub-3394747202744753/2613883108"
15 |
16 | fun loadBottomAdsBanner(ac: Activity) {
17 |
18 | /*MobileAds.initialize(ac, ac.getResources().getString(R.string.admob_app_id));
19 | final AdView mAdView = new AdView(ac);
20 | mAdView.setAdSize(AdSize.BANNER);
21 | if (BuildConfig.DEBUG) {
22 | mAdView.setAdUnitId(TEST_AD_UNIT_ID);
23 | } else {
24 | mAdView.setAdUnitId(PRODUCTION_AD_UNIT_ID);
25 | }
26 | // We need to create the AdView dynamically to set the ad unit id dynamically
27 | final FrameLayout frameLayout = ac.findViewById(R.id.adView);
28 | frameLayout.addView(mAdView);
29 | AdRequest adRequest = new AdRequest.Builder().build();
30 | mAdView.loadAd(adRequest);*/
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/CentralDirectory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | import java.util.ArrayList;
20 |
21 | public class CentralDirectory {
22 |
23 | private ArrayList fileHeaders;
24 |
25 | private DigitalSignature digitalSignature;
26 |
27 | public ArrayList getFileHeaders() {
28 | return fileHeaders;
29 | }
30 |
31 | public void setFileHeaders(ArrayList fileHeaders) {
32 | this.fileHeaders = fileHeaders;
33 | }
34 |
35 | public DigitalSignature getDigitalSignature() {
36 | return digitalSignature;
37 | }
38 |
39 | public void setDigitalSignature(DigitalSignature digitalSignature) {
40 | this.digitalSignature = digitalSignature;
41 | }
42 |
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/ExtraDataRecord.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class ExtraDataRecord {
20 |
21 | private long header;
22 |
23 | private int sizeOfData;
24 |
25 | private byte[] data;
26 |
27 | public long getHeader() {
28 | return header;
29 | }
30 |
31 | public void setHeader(long header) {
32 | this.header = header;
33 | }
34 |
35 | public int getSizeOfData() {
36 | return sizeOfData;
37 | }
38 |
39 | public void setSizeOfData(int sizeOfData) {
40 | this.sizeOfData = sizeOfData;
41 | }
42 |
43 | public byte[] getData() {
44 | return data;
45 | }
46 |
47 | public void setData(byte[] data) {
48 | this.data = data;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/simplefilesync/src/main/java/com/ditronic/simplefilesync/util/SSyncResult.java:
--------------------------------------------------------------------------------
1 | package com.ditronic.simplefilesync.util;
2 |
3 | import java.io.File;
4 |
5 | public class SSyncResult {
6 |
7 | private boolean syncTriggeredByUser;
8 | private File tmpDownloadFile;
9 | private ResultCode resultCode;
10 |
11 | public SSyncResult(final ResultCode resultCode) {
12 | this.resultCode = resultCode;
13 | }
14 |
15 | public boolean success() {
16 | if (resultCode == ResultCode.UPLOAD_SUCCESS
17 | || resultCode == ResultCode.DOWNLOAD_SUCCESS
18 | || resultCode == ResultCode.REMOTE_EQUALS_LOCAL
19 | || resultCode == ResultCode.FILES_NOT_EXIST_OR_EMPTY
20 | ) {
21 | return true;
22 | }
23 | return false;
24 | }
25 |
26 | public ResultCode getResultCode() {
27 | return resultCode;
28 | }
29 |
30 | public boolean isSyncTriggeredByUser() {
31 | return syncTriggeredByUser;
32 | }
33 |
34 | public void setSyncTriggeredByUser(boolean syncTriggeredByUser) {
35 | this.syncTriggeredByUser = syncTriggeredByUser;
36 | }
37 |
38 | public File getTmpDownloadFile() {
39 | return tmpDownloadFile;
40 | }
41 |
42 | public void setTmpDownloadFile(File tmpDownloadFile) {
43 | this.tmpDownloadFile = tmpDownloadFile;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ditronic/securezipnotes/contrib/NonFatalAbortTree.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.contrib
2 |
3 | import android.util.Log
4 | import androidx.test.espresso.Espresso
5 | import org.junit.Assert
6 | import timber.log.Timber
7 |
8 | /**
9 | * During UI tests, we want to fail the test immediately if any "non-fatal error" happens.
10 | * The purpose of non-fatal errors is to inform as about production failures without crashing the entire app.
11 | */
12 | object NonFatalAbortTree: Timber.Tree() {
13 | override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {
14 | if (priority == Log.ERROR || priority == Log.ASSERT) {
15 | abortTest(message=message, throwable = throwable)
16 | }
17 | }
18 |
19 | private fun abortTest(message: String, throwable: Throwable?) {
20 | if (disabled) {
21 | return
22 | }
23 | Assert.fail(message)
24 | throw IllegalStateException(message, throwable)
25 | }
26 |
27 | fun plantInTimber() {
28 | Timber.plant(this)
29 | }
30 |
31 | private var disabled = false
32 |
33 | fun runWithoutAborts(waitIdle: Boolean = true, runnable: () -> Unit) {
34 | disabled = true
35 | runnable()
36 | if (waitIdle) {
37 | Espresso.onIdle()
38 | }
39 | disabled = false
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/DataDescriptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class DataDescriptor {
20 |
21 | private String crc32;
22 |
23 | private int compressedSize;
24 |
25 | private int uncompressedSize;
26 |
27 | public String getCrc32() {
28 | return crc32;
29 | }
30 |
31 | public void setCrc32(String crc32) {
32 | this.crc32 = crc32;
33 | }
34 |
35 | public int getCompressedSize() {
36 | return compressedSize;
37 | }
38 |
39 | public void setCompressedSize(int compressedSize) {
40 | this.compressedSize = compressedSize;
41 | }
42 |
43 | public int getUncompressedSize() {
44 | return uncompressedSize;
45 | }
46 |
47 | public void setUncompressedSize(int uncompressedSize) {
48 | this.uncompressedSize = uncompressedSize;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/DigitalSignature.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class DigitalSignature {
20 |
21 | private int headerSignature;
22 |
23 | private int sizeOfData;
24 |
25 | private String signatureData;
26 |
27 | public int getHeaderSignature() {
28 | return headerSignature;
29 | }
30 |
31 | public void setHeaderSignature(int headerSignature) {
32 | this.headerSignature = headerSignature;
33 | }
34 |
35 | public int getSizeOfData() {
36 | return sizeOfData;
37 | }
38 |
39 | public void setSizeOfData(int sizeOfData) {
40 | this.sizeOfData = sizeOfData;
41 | }
42 |
43 | public String getSignatureData() {
44 | return signatureData;
45 | }
46 |
47 | public void setSignatureData(String signatureData) {
48 | this.signatureData = signatureData;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/ArchiveExtraDataRecord.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class ArchiveExtraDataRecord {
20 |
21 | private int signature;
22 |
23 | private int extraFieldLength;
24 |
25 | private String extraFieldData;
26 |
27 | public int getSignature() {
28 | return signature;
29 | }
30 |
31 | public void setSignature(int signature) {
32 | this.signature = signature;
33 | }
34 |
35 | public int getExtraFieldLength() {
36 | return extraFieldLength;
37 | }
38 |
39 | public void setExtraFieldLength(int extraFieldLength) {
40 | this.extraFieldLength = extraFieldLength;
41 | }
42 |
43 | public String getExtraFieldData() {
44 | return extraFieldData;
45 | }
46 |
47 | public void setExtraFieldData(String extraFieldData) {
48 | this.extraFieldData = extraFieldData;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/zip/ZipUtil.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.zip
2 |
3 | import android.content.Context
4 | import com.ditronic.securezipnotes.util.Boast
5 | import net.lingala.zip4j.io.ZipInputStream
6 | import java.io.IOException
7 | import java.io.InputStreamReader
8 | import java.nio.charset.StandardCharsets
9 |
10 |
11 | @Throws(IOException::class)
12 | fun inputStreamToString(`is`: ZipInputStream): String {
13 | val ir = InputStreamReader(`is`, StandardCharsets.UTF_8)
14 | val sb = StringBuilder()
15 | val buf = CharArray(1024)
16 | while (true) {
17 | val n : Int = ir.read(buf)
18 | if (n != -1) {
19 | sb.append(buf, 0, n)
20 | } else {
21 | break
22 | }
23 | }
24 | return sb.toString()
25 | }
26 |
27 |
28 | fun validateEntryNameToast(newEntryName: String, cx : Context) : Boolean {
29 | if (newEntryName.isEmpty()) {
30 | Boast.makeText(cx, "Empty file names are not allowed").show()
31 | return false
32 | }
33 | if (isIllegalEntryName(newEntryName)) {
34 | Boast.makeText(cx, newEntryName + " is an invalid entry name").show()
35 | return false
36 | }
37 | return true
38 | }
39 |
40 |
41 | private fun isIllegalEntryName(entryName: String) : Boolean {
42 | if (entryName.startsWith("/"))
43 | return true
44 | if (entryName.startsWith("\\"))
45 | return true
46 | if (entryName.endsWith("/"))
47 | return true
48 | if (entryName.endsWith("\\"))
49 | return true
50 | return false
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ditronic/securezipnotes/robotpattern/Preconditions.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.robotpattern
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import com.ditronic.securezipnotes.contrib.NonFatalAbortTree
5 | import com.ditronic.securezipnotes.noteselect.MainActivity
6 | import com.ditronic.securezipnotes.testutils.launchActivity
7 | import com.ditronic.securezipnotes.zip.CryptoZip
8 | import java.io.FileOutputStream
9 |
10 | private fun commonPreconditions() {
11 | NonFatalAbortTree.plantInTimber()
12 | launchActivity(MainActivity::class.java)
13 | }
14 |
15 | fun precondition_cleanStart() {
16 | commonPreconditions()
17 | }
18 |
19 | fun precondition_singleNote() {
20 | precondition_loadAsset("singlenote.aeszip")
21 | main_assertListState(listOf("Note 1"))
22 | }
23 |
24 | fun precondition_fourNotes() {
25 | precondition_loadAsset("4notes.aeszip")
26 | main_assertListState(listOf("Note 1", "Note 2", "Note 3", "Note 4").reversed())
27 | }
28 |
29 | fun precondition_loadAsset(assetPath: String) {
30 | loadAsset(assetPath)
31 | commonPreconditions()
32 | }
33 |
34 | private fun loadAsset(assetPath: String) {
35 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
36 | val testContext = InstrumentationRegistry.getInstrumentation().context
37 |
38 | val sourceStream = testContext.assets.open(assetPath)
39 | val targetStream = FileOutputStream(CryptoZip.getMainFilePath(appContext))
40 | sourceStream.copyTo(targetStream)
41 | sourceStream.close()
42 | targetStream.close()
43 | }
44 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/exception/ZipException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.exception;
18 |
19 | public class ZipException extends Exception {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | private int code = -1;
24 |
25 | public ZipException() {
26 | }
27 |
28 | public ZipException(String msg) {
29 | super(msg);
30 | }
31 |
32 | public ZipException(String message, Throwable cause) {
33 | super(message, cause);
34 | }
35 |
36 | public ZipException(String msg, int code) {
37 | super(msg);
38 | this.code = code;
39 | }
40 |
41 | public ZipException(String message, Throwable cause, int code) {
42 | super(message, cause);
43 | this.code = code;
44 | }
45 |
46 | public ZipException(Throwable cause) {
47 | super(cause);
48 | }
49 |
50 | public ZipException(Throwable cause, int code) {
51 | super(cause);
52 | this.code = code;
53 | }
54 |
55 | public int getCode() {
56 | return code;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/cardview.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
27 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_note_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
21 |
22 |
23 |
27 |
28 |
29 |
36 |
37 |
38 |
42 |
43 |
--------------------------------------------------------------------------------
/run_instrumentation_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 | set -x
4 |
5 | # This is just an experimental script to capture some commands.
6 | # Real testing is performed via gradle tasks or Android Studio.
7 |
8 | # Debug
9 | TARGET_APP=com.ditronic.securezipnotes.dev
10 | # Release
11 | #TARGET_APP=com.ditronic.securezipnotes
12 |
13 | WHICH_TESTS='com.ditronic.securezipnotes.tests.ITest#deleteLastNote'
14 | ORCHESTRATOR_VERSION='1.2.0'
15 |
16 | # Install app
17 | adb push app/build/outputs/apk/debug/app-debug.apk /data/local/tmp/$TARGET_APP
18 | adb shell pm install --full -t -r "/data/local/tmp/$TARGET_APP"
19 |
20 | # Install androidTest app
21 | adb push app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk /data/local/tmp/$TARGET_APP.test
22 | adb shell pm install --full -t -r "/data/local/tmp/$TARGET_APP.test"
23 |
24 | # Install orchestrator app
25 | adb push ~/.gradle/caches/modules-2/files-2.1/androidx.test/orchestrator/$ORCHESTRATOR_VERSION/e3edf4e08d2d3db127c1b56bbe405b90b64daa5/orchestrator-$ORCHESTRATOR_VERSION.apk /data/local/tmp/androidx.test.orchestrator
26 | adb shell pm install --full -t -r "/data/local/tmp/androidx.test.orchestrator"
27 |
28 | # Install test services apk
29 | adb push ~/.gradle/caches/modules-2/files-2.1/androidx.test.services/test-services/$ORCHESTRATOR_VERSION/58c9ac48a725a1a0437f4f6be352a42f60ed5a7d/test-services-$ORCHESTRATOR_VERSION.apk /data/local/tmp/androidx.test.services
30 | adb shell pm install --full -t -r "/data/local/tmp/androidx.test.services"
31 |
32 | # Run the tests
33 | adb shell 'CLASSPATH=$(pm path androidx.test.services)' app_process / androidx.test.services.shellexecutor.ShellMain am instrument -r -w -e targetInstrumentation $TARGET_APP.test/androidx.test.runner.AndroidJUnitRunner -e debug false -e class $WHICH_TESTS -e clearPackageData true androidx.test.orchestrator/androidx.test.orchestrator.AndroidTestOrchestrator
34 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/PBKDF2/PBKDF2HexFormatter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto.PBKDF2;
18 |
19 | /*
20 | * Source referred from Matthias Gartner's PKCS#5 implementation -
21 | * see http://rtner.de/software/PBKDF2.html
22 | */
23 |
24 | class PBKDF2HexFormatter
25 | {
26 | public boolean fromString(PBKDF2Parameters p, String s)
27 | {
28 | if (p == null || s == null)
29 | {
30 | return true;
31 | }
32 |
33 | String[] p123 = s.split(":");
34 | if (p123 == null || p123.length != 3)
35 | {
36 | return true;
37 | }
38 |
39 | byte salt[] = BinTools.hex2bin(p123[0]);
40 | int iterationCount = Integer.parseInt(p123[1]);
41 | byte bDK[] = BinTools.hex2bin(p123[2]);
42 |
43 | p.setSalt(salt);
44 | p.setIterationCount(iterationCount);
45 | p.setDerivedKey(bDK);
46 | return false;
47 | }
48 |
49 | public String toString(PBKDF2Parameters p)
50 | {
51 | String s = BinTools.bin2hex(p.getSalt()) + ":"
52 | + String.valueOf(p.getIterationCount()) + ":"
53 | + BinTools.bin2hex(p.getDerivedKey());
54 | return s;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ditronic/securezipnotes/tests/MultiPasswordTests.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.tests
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.filters.LargeTest
5 | import com.ditronic.securezipnotes.robotpattern.TESTPASSWORD
6 | import com.ditronic.securezipnotes.robotpattern.main_assertListState
7 | import com.ditronic.securezipnotes.robotpattern.main_clickAssertCloseNote
8 | import com.ditronic.securezipnotes.robotpattern.precondition_loadAsset
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 |
12 |
13 | @RunWith(AndroidJUnit4::class)
14 | @LargeTest
15 | class MultiPasswordTests {
16 |
17 | @Test
18 | fun twoPasswords() {
19 | precondition_loadAsset("twopasswords.ZIP")
20 | main_assertListState(entries = listOf("pw2_entry", "testpassword_entry"))
21 |
22 | main_clickAssertCloseNote(noteName = "testpassword_entry", secretContent = "My secret note", password = TESTPASSWORD)
23 | main_clickAssertCloseNote(noteName = "pw2_entry", secretContent = "secret", password = "pw2")
24 | main_clickAssertCloseNote(noteName = "testpassword_entry", secretContent = "My secret note", password = TESTPASSWORD)
25 | }
26 |
27 | @Test
28 | fun test4Passwords() {
29 | precondition_loadAsset("4passwords_subdirs.aeszip")
30 | main_assertListState(entries = listOf("pw4_entry", "pw3_entry/dir/dir/pw2", "pw2_entry", "pw1_entry/dir/pw1"))
31 |
32 | main_clickAssertCloseNote(noteName = "pw4_entry", secretContent = "pw4_secret", password = "pw4")
33 | main_clickAssertCloseNote(noteName = "pw3_entry/dir/dir/pw2", secretContent = "pw3_secret", password = "pw3")
34 | main_clickAssertCloseNote(noteName = "pw2_entry", secretContent = "pw2_secret", password = "pw2")
35 | main_clickAssertCloseNote(noteName = "pw2_entry", secretContent = "pw2_secret")
36 | main_clickAssertCloseNote(noteName = "pw1_entry/dir/pw1", secretContent = "pw1_secret", password = "pw1")
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/Zip64EndCentralDirLocator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class Zip64EndCentralDirLocator {
20 |
21 | private long signature;
22 |
23 | private int noOfDiskStartOfZip64EndOfCentralDirRec;
24 |
25 | private long offsetZip64EndOfCentralDirRec;
26 |
27 | private int totNumberOfDiscs;
28 |
29 | public long getSignature() {
30 | return signature;
31 | }
32 |
33 | public void setSignature(long signature) {
34 | this.signature = signature;
35 | }
36 |
37 | public int getNoOfDiskStartOfZip64EndOfCentralDirRec() {
38 | return noOfDiskStartOfZip64EndOfCentralDirRec;
39 | }
40 |
41 | public void setNoOfDiskStartOfZip64EndOfCentralDirRec(
42 | int noOfDiskStartOfZip64EndOfCentralDirRec) {
43 | this.noOfDiskStartOfZip64EndOfCentralDirRec = noOfDiskStartOfZip64EndOfCentralDirRec;
44 | }
45 |
46 | public long getOffsetZip64EndOfCentralDirRec() {
47 | return offsetZip64EndOfCentralDirRec;
48 | }
49 |
50 | public void setOffsetZip64EndOfCentralDirRec(long offsetZip64EndOfCentralDirRec) {
51 | this.offsetZip64EndOfCentralDirRec = offsetZip64EndOfCentralDirRec;
52 | }
53 |
54 | public int getTotNumberOfDiscs() {
55 | return totNumberOfDiscs;
56 | }
57 |
58 | public void setTotNumberOfDiscs(int totNumberOfDiscs) {
59 | this.totNumberOfDiscs = totNumberOfDiscs;
60 | }
61 |
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "680419627907",
4 | "firebase_url": "https://zip-notes.firebaseio.com",
5 | "project_id": "secure-zip-notes",
6 | "storage_bucket": "secure-zip-notes.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:680419627907:android:e452ae4cd6765d4a8a5cb6",
12 | "android_client_info": {
13 | "package_name": "com.ditronic.securezipnotes"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "680419627907-0tst6ljm7i300pcbfjh0cfledohd24ld.apps.googleusercontent.com",
19 | "client_type": 3
20 | }
21 | ],
22 | "api_key": [
23 | {
24 | "current_key": "AIzaSyB7HTjR6I997G8bYktR_Xv791ALUTRROyE"
25 | }
26 | ],
27 | "services": {
28 | "appinvite_service": {
29 | "other_platform_oauth_client": [
30 | {
31 | "client_id": "680419627907-0tst6ljm7i300pcbfjh0cfledohd24ld.apps.googleusercontent.com",
32 | "client_type": 3
33 | }
34 | ]
35 | }
36 | }
37 | },
38 | {
39 | "client_info": {
40 | "mobilesdk_app_id": "1:680419627907:android:9e6bbe18bfc6b03f8a5cb6",
41 | "android_client_info": {
42 | "package_name": "com.ditronic.securezipnotes.dev"
43 | }
44 | },
45 | "oauth_client": [
46 | {
47 | "client_id": "680419627907-0tst6ljm7i300pcbfjh0cfledohd24ld.apps.googleusercontent.com",
48 | "client_type": 3
49 | }
50 | ],
51 | "api_key": [
52 | {
53 | "current_key": "AIzaSyB7HTjR6I997G8bYktR_Xv791ALUTRROyE"
54 | }
55 | ],
56 | "services": {
57 | "appinvite_service": {
58 | "other_platform_oauth_client": [
59 | {
60 | "client_id": "680419627907-0tst6ljm7i300pcbfjh0cfledohd24ld.apps.googleusercontent.com",
61 | "client_type": 3
62 | }
63 | ]
64 | }
65 | }
66 | }
67 | ],
68 | "configuration_version": "1"
69 | }
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/engine/ZipCryptoEngine.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto.engine;
18 |
19 | public class ZipCryptoEngine {
20 |
21 | private final int keys[] = new int[3];
22 | private static final int[] CRC_TABLE = new int[256];
23 |
24 | static {
25 | for (int i = 0; i < 256; i++) {
26 | int r = i;
27 | for (int j = 0; j < 8; j++) {
28 | if ((r & 1) == 1) {
29 | r = (r >>> 1) ^ 0xedb88320;
30 | } else {
31 | r >>>= 1;
32 | }
33 | }
34 | CRC_TABLE[i] = r;
35 | }
36 | }
37 |
38 | public ZipCryptoEngine() {
39 | }
40 |
41 | public void initKeys(char[] password) {
42 | keys[0] = 305419896;
43 | keys[1] = 591751049;
44 | keys[2] = 878082192;
45 | for (int i = 0; i < password.length; i++) {
46 | updateKeys((byte) (password[i] & 0xff));
47 | }
48 | }
49 |
50 | public void updateKeys(byte charAt) {
51 | keys[0] = crc32(keys[0], charAt);
52 | keys[1] += keys[0] & 0xff;
53 | keys[1] = keys[1] * 134775813 + 1;
54 | keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
55 | }
56 |
57 | private int crc32(int oldCrc, byte charAt) {
58 | return ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff]);
59 | }
60 |
61 | public byte decryptByte() {
62 | int temp = keys[2] | 2;
63 | return (byte) ((temp * (temp ^ 1)) >>> 8);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/dialogs/ShortLifeDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.dialogs
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import androidx.annotation.CallSuper
6 | import androidx.fragment.app.DialogFragment
7 | import androidx.fragment.app.FragmentActivity
8 | import timber.log.Timber
9 |
10 | fun DialogFragment.dismissCrashSafe() {
11 | try {
12 | dismiss()
13 | } catch (exception: Exception) {
14 | Timber.e(exception)
15 | }
16 | }
17 |
18 | data class FragmentTag(val value: String)
19 |
20 | /**
21 | * Generic dialog that can hold an arbitrary state and gets dismissed upon activity re-creation.
22 | */
23 | abstract class ShortLifeDialogFragment: DialogFragment() {
24 |
25 | abstract fun getFragmentTag(): FragmentTag
26 |
27 | abstract fun onCreateDialog(savedInstanceState: Bundle?, state: EphemeralState): Dialog
28 |
29 | private var ephemeralState: EphemeralState? = null
30 |
31 | fun show(activity: FragmentActivity, state: EphemeralState) {
32 | this.ephemeralState = state
33 | dismissIfActive(activity = activity)
34 | show(activity.supportFragmentManager, getFragmentTag().value)
35 | }
36 |
37 | @CallSuper
38 | override fun onResume() {
39 | super.onResume()
40 | fetchStateOrDie()
41 | }
42 |
43 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
44 | val state = fetchStateOrDie()
45 | return if (state != null) {
46 | onCreateDialog(savedInstanceState, state)
47 | } else {
48 | androidx.appcompat.app.AlertDialog.Builder(requireContext()).create()
49 | }
50 | }
51 |
52 | protected fun fetchStateOrDie(): EphemeralState? {
53 | if (ephemeralState == null) {
54 | dismissCrashSafe()
55 | }
56 | return ephemeralState
57 | }
58 |
59 | private fun dismissIfActive(activity: FragmentActivity) {
60 | val fragmentManager = activity.supportFragmentManager
61 | val oldDialog = fragmentManager.findFragmentByTag(getFragmentTag().value) as? DialogFragment
62 | oldDialog?.dismissCrashSafe()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/noteselect/NoteSelectAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.noteselect
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.BaseAdapter
8 | import android.widget.TextView
9 |
10 | import java.text.SimpleDateFormat
11 | import java.util.Date
12 | import java.util.Locale
13 |
14 | import com.ditronic.securezipnotes.zip.CryptoZip
15 | import com.ditronic.securezipnotes.R
16 |
17 | import net.lingala.zip4j.model.FileHeader
18 | import net.lingala.zip4j.util.Zip4jUtil
19 |
20 | class NoteSelectAdapter(private val cx: Context) : BaseAdapter() {
21 |
22 | override fun getCount(): Int {
23 | return CryptoZip.instance(cx).numFileHeaders
24 | }
25 |
26 | override fun getItem(position: Int): Any {
27 | return CryptoZip.instance(cx).fileHeadersFast!![count - position - 1]
28 | }
29 |
30 | override fun getItemId(position: Int): Long {
31 | return position.toLong()
32 | }
33 |
34 | override fun getView(position: Int, convertViewF: View?, parent: ViewGroup): View {
35 | var convertView = convertViewF
36 |
37 | val fileHeader = getItem(position) as FileHeader
38 |
39 | if (convertView == null) {
40 | convertView = LayoutInflater.from(cx).inflate(R.layout.cardview, parent, false)
41 | }
42 |
43 | val tx = convertView!!.findViewById(R.id.txt_cardview)
44 | tx.text = CryptoZip.getDisplayName(fileHeader)
45 |
46 | val tx2 = convertView.findViewById(R.id.txt_cardview_2)
47 | val lastModZip = fileHeader.lastModFileTime
48 | val epochMillis = Date(Zip4jUtil.dosToJavaTme(lastModZip))
49 | val lastModStr = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(epochMillis)
50 | tx2.text = lastModStr
51 |
52 | val tx3 = convertView.findViewById(R.id.txt_cardview_3)
53 | tx3.text = "Size: " + fileHeader.uncompressedSize
54 |
55 | return convertView
56 | }
57 |
58 | companion object {
59 |
60 | private val TAG = NoteSelectAdapter::class.java.simpleName
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/Zip64ExtendedInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class Zip64ExtendedInfo {
20 |
21 | private int header;
22 |
23 | private int size;
24 |
25 | private long compressedSize;
26 |
27 | private long unCompressedSize;
28 |
29 | private long offsetLocalHeader;
30 |
31 | private int diskNumberStart;
32 |
33 | public Zip64ExtendedInfo() {
34 | compressedSize = -1;
35 | unCompressedSize = -1;
36 | offsetLocalHeader = -1;
37 | diskNumberStart = -1;
38 | }
39 |
40 | public int getHeader() {
41 | return header;
42 | }
43 |
44 | public void setHeader(int header) {
45 | this.header = header;
46 | }
47 |
48 | public int getSize() {
49 | return size;
50 | }
51 |
52 | public void setSize(int size) {
53 | this.size = size;
54 | }
55 |
56 | public long getCompressedSize() {
57 | return compressedSize;
58 | }
59 |
60 | public void setCompressedSize(long compressedSize) {
61 | this.compressedSize = compressedSize;
62 | }
63 |
64 | public long getUnCompressedSize() {
65 | return unCompressedSize;
66 | }
67 |
68 | public void setUnCompressedSize(long unCompressedSize) {
69 | this.unCompressedSize = unCompressedSize;
70 | }
71 |
72 | public long getOffsetLocalHeader() {
73 | return offsetLocalHeader;
74 | }
75 |
76 | public void setOffsetLocalHeader(long offsetLocalHeader) {
77 | this.offsetLocalHeader = offsetLocalHeader;
78 | }
79 |
80 | public int getDiskNumberStart() {
81 | return diskNumberStart;
82 | }
83 |
84 | public void setDiskNumberStart(int diskNumberStart) {
85 | this.diskNumberStart = diskNumberStart;
86 | }
87 |
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/AESExtraDataRecord.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class AESExtraDataRecord {
20 |
21 | private long signature;
22 | private int dataSize;
23 | private int versionNumber;
24 | private String vendorID;
25 | private int aesStrength;
26 | private int compressionMethod;
27 |
28 | public AESExtraDataRecord() {
29 | signature = -1;
30 | dataSize = -1;
31 | versionNumber = -1;
32 | vendorID = null;
33 | aesStrength = -1;
34 | compressionMethod = -1;
35 | }
36 |
37 |
38 | public long getSignature() {
39 | return signature;
40 | }
41 |
42 |
43 | public void setSignature(long signature) {
44 | this.signature = signature;
45 | }
46 |
47 |
48 | public int getDataSize() {
49 | return dataSize;
50 | }
51 |
52 |
53 | public void setDataSize(int dataSize) {
54 | this.dataSize = dataSize;
55 | }
56 |
57 |
58 | public int getVersionNumber() {
59 | return versionNumber;
60 | }
61 |
62 |
63 | public void setVersionNumber(int versionNumber) {
64 | this.versionNumber = versionNumber;
65 | }
66 |
67 |
68 | public String getVendorID() {
69 | return vendorID;
70 | }
71 |
72 |
73 | public void setVendorID(String vendorID) {
74 | this.vendorID = vendorID;
75 | }
76 |
77 |
78 | public int getAesStrength() {
79 | return aesStrength;
80 | }
81 |
82 |
83 | public void setAesStrength(int aesStrength) {
84 | this.aesStrength = aesStrength;
85 | }
86 |
87 |
88 | public int getCompressionMethod() {
89 | return compressionMethod;
90 | }
91 |
92 |
93 | public void setCompressionMethod(int compressionMethod) {
94 | this.compressionMethod = compressionMethod;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/dialogs/RenameFileDialog.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.dialogs
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import android.text.InputType
6 | import android.view.WindowManager
7 | import android.widget.EditText
8 | import androidx.appcompat.app.AlertDialog
9 | import com.ditronic.securezipnotes.password.PwResult
10 | import com.ditronic.securezipnotes.zip.CryptoZip
11 | import net.lingala.zip4j.model.FileHeader
12 |
13 | abstract class RenameFileDialogState(val pwResult: PwResult.Success,
14 | val fileHeader: FileHeader) {
15 | abstract fun onRenameReturned()
16 | }
17 |
18 | class RenameFileDialog: ShortLifeDialogFragment() {
19 |
20 | companion object {
21 | val TAG = FragmentTag("RenameFileDialog")
22 | }
23 |
24 | lateinit var editText: EditText
25 |
26 | override fun onCreateDialog(savedInstanceState: Bundle?, state: RenameFileDialogState): Dialog {
27 | editText = EditText(requireContext())
28 |
29 | val builder = AlertDialog.Builder(requireContext())
30 | builder.setTitle("Rename " + CryptoZip.getDisplayName(state.fileHeader))
31 | editText.inputType = InputType.TYPE_CLASS_TEXT
32 | editText.setText(CryptoZip.getDisplayName(state.fileHeader))
33 | builder.setView(editText)
34 | builder.setPositiveButton(android.R.string.ok) { _, _ ->
35 | onRenameConfirmed()
36 | }
37 | builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
38 | val dialog = builder.create()
39 | val window = dialog.window
40 | window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
41 | editText.requestFocus()
42 | return dialog
43 | }
44 |
45 | override fun getFragmentTag() = TAG
46 |
47 | private fun onRenameConfirmed() {
48 | val state = fetchStateOrDie() ?: return
49 |
50 | val newName = editText.text.toString()
51 | CryptoZip.instance(requireContext()).renameFile(
52 | pw = state.pwResult.password,
53 | fileHeader = state.fileHeader,
54 | newEntryName = newName,
55 | cx = requireContext())
56 |
57 | state.onRenameReturned()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/simplefilesync/src/main/java/com/ditronic/simplefilesync/util/FilesUtil.java:
--------------------------------------------------------------------------------
1 | package com.ditronic.simplefilesync.util;
2 |
3 | import com.google.android.gms.common.util.Hex;
4 |
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.FileOutputStream;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 | import java.io.OutputStream;
11 | import java.security.MessageDigest;
12 |
13 | public class FilesUtil {
14 |
15 | public static File streamToTmpFile (final InputStream is) {
16 | final OutputStream os;
17 | final File tmpFile;
18 | try {
19 | tmpFile = File.createTempFile("stream2file", ".tmp");
20 | tmpFile.deleteOnExit();
21 | os = new FileOutputStream(tmpFile);
22 | byte[] buffer = new byte[1024];
23 | int length;
24 | while ((length = is.read(buffer)) > 0) {
25 | os.write(buffer, 0, length);
26 | }
27 | is.close();
28 | os.close();
29 | } catch (IOException e) {
30 | throw new RuntimeException(e);
31 | }
32 | return tmpFile;
33 | }
34 |
35 |
36 | public static void copyFile(final File source, final File dest) throws IOException {
37 | InputStream is = null;
38 | OutputStream os = null;
39 | try {
40 | is = new FileInputStream(source);
41 | os = new FileOutputStream(dest);
42 | byte[] buffer = new byte[1024];
43 | int length;
44 | while ((length = is.read(buffer)) > 0) {
45 | os.write(buffer, 0, length);
46 | }
47 | } finally {
48 | is.close();
49 | os.close();
50 | }
51 | }
52 |
53 |
54 | public static String hashFromFile(final java.io.File file, final MessageDigest hasher) {
55 | final byte[] buf = new byte[1024];
56 | try {
57 | final InputStream in = new FileInputStream(file);
58 | while (true) {
59 | int n = in.read(buf);
60 | if (n < 0) break; // EOF
61 | hasher.update(buf, 0, n);
62 | }
63 | in.close();
64 | return Hex.bytesToStringUppercase(hasher.digest());
65 | } catch (Exception e) {
66 | throw new RuntimeException(e);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/UnzipEngineParameters.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | import java.io.FileOutputStream;
20 |
21 | import net.lingala.zip4j.crypto.IDecrypter;
22 | import net.lingala.zip4j.unzip.UnzipEngine;
23 |
24 | public class UnzipEngineParameters {
25 |
26 | private ZipModel zipModel;
27 |
28 | private FileHeader fileHeader;
29 |
30 | private LocalFileHeader localFileHeader;
31 |
32 | private IDecrypter iDecryptor;
33 |
34 | private FileOutputStream outputStream;
35 |
36 | private UnzipEngine unzipEngine;
37 |
38 | public ZipModel getZipModel() {
39 | return zipModel;
40 | }
41 |
42 | public void setZipModel(ZipModel zipModel) {
43 | this.zipModel = zipModel;
44 | }
45 |
46 | public FileHeader getFileHeader() {
47 | return fileHeader;
48 | }
49 |
50 | public void setFileHeader(FileHeader fileHeader) {
51 | this.fileHeader = fileHeader;
52 | }
53 |
54 | public LocalFileHeader getLocalFileHeader() {
55 | return localFileHeader;
56 | }
57 |
58 | public void setLocalFileHeader(LocalFileHeader localFileHeader) {
59 | this.localFileHeader = localFileHeader;
60 | }
61 |
62 | public IDecrypter getIDecryptor() {
63 | return iDecryptor;
64 | }
65 |
66 | public void setIDecryptor(IDecrypter decrypter) {
67 | iDecryptor = decrypter;
68 | }
69 |
70 | public FileOutputStream getOutputStream() {
71 | return outputStream;
72 | }
73 |
74 | public void setOutputStream(FileOutputStream outputStream) {
75 | this.outputStream = outputStream;
76 | }
77 |
78 | public UnzipEngine getUnzipEngine() {
79 | return unzipEngine;
80 | }
81 |
82 | public void setUnzipEngine(UnzipEngine unzipEngine) {
83 | this.unzipEngine = unzipEngine;
84 | }
85 |
86 |
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/util/Zip4jConstants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.util;
18 |
19 | public interface Zip4jConstants {
20 |
21 | // Compression Types
22 | static final int COMP_STORE = 0;
23 | // static final int COMP_FILE_SHRUNK = 1;
24 | // static final int COMP_FILE_RED_COMP_FACTOR_1 = 2;
25 | // static final int COMP_FILE_RED_COMP_FACTOR_2 = 3;
26 | // static final int COMP_FILE_RED_COMP_FACTOR_3 = 4;
27 | // static final int COMP_FILE_RED_COMP_FACTOR_4 = 5;
28 | // static final int COMP_FILE_IMPLODED = 6;
29 | static final int COMP_DEFLATE = 8;
30 | // static final int COMP_FILE_ENHANCED_DEFLATED = 9;
31 | // static final int COMP_PKWARE_DATA_COMP_LIB_IMPL = 10;
32 | // static final int COMP_BZIP2 = 12;
33 | // static final int COMP_LZMA = 14;
34 | // static final int COMP_IBM_TERSE = 18;
35 | // static final int COMP_IBM_LZ77 =19;
36 | // static final int COMP_WAVPACK = 97;
37 | // static final int COMP_PPMD = 98;
38 | static final int COMP_AES_ENC = 99;
39 |
40 | //Compression level for deflate algorithm
41 | static final int DEFLATE_LEVEL_FASTEST = 1;
42 | static final int DEFLATE_LEVEL_FAST = 3;
43 | static final int DEFLATE_LEVEL_NORMAL = 5;
44 | static final int DEFLATE_LEVEL_MAXIMUM = 7;
45 | static final int DEFLATE_LEVEL_ULTRA = 9;
46 |
47 | //Encryption types
48 | static final int ENC_NO_ENCRYPTION = -1;
49 | static final int ENC_METHOD_STANDARD = 0;
50 | // static final int ENC_METHOD_STRONG = 1;
51 | static final int ENC_METHOD_AES = 99;
52 |
53 | //AES Key Strength
54 | static final int AES_STRENGTH_128 = 0x01;
55 | static final int AES_STRENGTH_192 = 0x02;
56 | static final int AES_STRENGTH_256 = 0x03;
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ditronic/securezipnotes/onlinetests/SyncTests.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.onlinetests
2 |
3 | import androidx.test.espresso.Espresso
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import androidx.test.filters.LargeTest
6 | import com.ditronic.securezipnotes.robotpattern.*
7 | import com.ditronic.securezipnotes.testutils.pressBack
8 | import com.ditronic.securezipnotes.testutils.targetContext
9 | import com.ditronic.simplefilesync.AbstractFileSync
10 | import com.ditronic.simplefilesync.DropboxFileSync
11 | import com.ditronic.simplefilesync.util.ResultCode
12 | import org.junit.Assert
13 | import org.junit.Before
14 | import org.junit.FixMethodOrder
15 | import org.junit.Test
16 | import org.junit.runner.RunWith
17 | import org.junit.runners.MethodSorters
18 | import java.util.*
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | @FixMethodOrder(MethodSorters.NAME_ASCENDING)
22 | @LargeTest
23 | class SyncTests {
24 |
25 | // TODO: Assert popup messages instead of internal state
26 |
27 | companion object {
28 | private const val DROPBOX_OAUTH_TOKEN = "TODO set token via environment or property file"
29 | }
30 |
31 | @Before
32 | fun beforeEachTest() {
33 | DropboxFileSync.storeNewOauthToken(DROPBOX_OAUTH_TOKEN, targetContext())
34 | }
35 |
36 | @Test
37 | fun dbx1_dropBoxFreshRandomUpload() {
38 | precondition_singleNote()
39 | main_clickNote("Note 1", password = TESTPASSWORD)
40 | noteEdit_typeText(UUID.randomUUID().toString())
41 | pressBack()
42 | Espresso.onIdle()
43 | Assert.assertEquals(ResultCode.UPLOAD_SUCCESS, AbstractFileSync.getLastSyncResult()!!.resultCode)
44 | }
45 |
46 | @Test
47 | fun dbx2_dropBoxSingleNoteUpload() {
48 | precondition_singleNote()
49 | Espresso.onIdle()
50 | Assert.assertEquals(ResultCode.UPLOAD_SUCCESS, AbstractFileSync.getLastSyncResult()!!.resultCode)
51 | }
52 |
53 | @Test
54 | fun dbx3_dropBoxRemoteEqualsLocal() {
55 | precondition_singleNote()
56 | Espresso.onIdle()
57 | Assert.assertEquals(ResultCode.REMOTE_EQUALS_LOCAL, DropboxFileSync.getLastSyncResult()!!.resultCode)
58 | }
59 |
60 | @Test
61 | fun dbx4_dropBoxDownload() {
62 | precondition_cleanStart()
63 | Espresso.onIdle()
64 | Assert.assertEquals(ResultCode.DOWNLOAD_SUCCESS, DropboxFileSync.getLastSyncResult()!!.resultCode)
65 | main_assertNonEmpty()
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/util/Boast.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.res.Resources.NotFoundException
6 | import android.widget.Toast
7 | import java.lang.ref.WeakReference
8 |
9 | /**
10 | * [Toast] decorator allowing for easy cancellation of notifications. Use
11 | * this class if you want subsequent Toast notifications to overwrite current
12 | * ones.
13 | */
14 | class Boast private constructor(private val internalToast: Toast) {
15 |
16 | fun show() {
17 | val oldToast = globalBoast?.get()
18 | oldToast?.cancel()
19 | globalBoast = WeakReference(internalToast)
20 | internalToast.show()
21 | }
22 |
23 | companion object {
24 |
25 | @Volatile
26 | private var globalBoast: WeakReference? = null
27 |
28 | @SuppressLint("ShowToast")
29 | fun makeText(context: Context?, text: CharSequence?,
30 | duration: Int): Boast {
31 | return Boast(Toast.makeText(context, text, duration))
32 | }
33 |
34 | @SuppressLint("ShowToast")
35 | @Throws(NotFoundException::class)
36 | fun makeText(context: Context?, resId: Int, duration: Int): Boast {
37 | return Boast(Toast.makeText(context, resId, duration))
38 | }
39 |
40 | @SuppressLint("ShowToast")
41 | fun makeText(context: Context?, text: CharSequence?): Boast {
42 | return Boast(Toast.makeText(context, text, Toast.LENGTH_SHORT))
43 | }
44 |
45 | @SuppressLint("ShowToast")
46 | @Throws(NotFoundException::class)
47 | fun makeText(context: Context?, resId: Int): Boast {
48 | return Boast(Toast.makeText(context, resId, Toast.LENGTH_SHORT))
49 | }
50 |
51 | fun showText(context: Context?, text: CharSequence?, duration: Int) {
52 | makeText(context, text, duration).show()
53 | }
54 |
55 | @Throws(NotFoundException::class)
56 | fun showText(context: Context?, resId: Int, duration: Int) {
57 | makeText(context, resId, duration).show()
58 | }
59 |
60 | fun showText(context: Context?, text: CharSequence?) {
61 | makeText(context, text, Toast.LENGTH_SHORT).show()
62 | }
63 |
64 | @Throws(NotFoundException::class)
65 | fun showText(context: Context?, resId: Int) {
66 | makeText(context, resId, Toast.LENGTH_SHORT).show()
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_password_confirm.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
18 |
19 |
26 |
27 |
34 |
35 |
38 |
47 |
48 |
49 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/menu/MenuOptions.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.menu
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.view.MenuItem
6 | import androidx.core.content.FileProvider
7 | import com.ditronic.securezipnotes.R
8 | import com.ditronic.securezipnotes.util.Boast
9 | import com.ditronic.securezipnotes.zip.CryptoZip
10 | import com.ditronic.simplefilesync.DropboxFileSync
11 | import com.ditronic.simplefilesync.util.FilesUtil
12 | import java.io.File
13 | import java.io.IOException
14 | import java.text.SimpleDateFormat
15 | import java.util.*
16 |
17 | object MenuOptions {
18 |
19 |
20 | private val exportFileName: String
21 | get() {
22 | val dateString = SimpleDateFormat("yyyy-MM-dd-HH:mm", Locale.getDefault()).format(Calendar.getInstance().timeInMillis)
23 | return dateString + "_securezipnotes.aeszip"
24 | }
25 |
26 | private fun exportZipFile(ac: Activity) {
27 |
28 | val zipNotes = CryptoZip.getMainFilePath(ac)
29 | if (!zipNotes.exists()) {
30 | Boast.makeText(ac, R.string.toast_notes_empty_export).show()
31 | return
32 | }
33 |
34 | val tmpShareFile = File(ac.cacheDir, exportFileName)
35 | tmpShareFile.delete()
36 | tmpShareFile.deleteOnExit()
37 | try {
38 | FilesUtil.copyFile(zipNotes, tmpShareFile)
39 | } catch (e: IOException) {
40 | throw RuntimeException(e)
41 | }
42 |
43 | val intent = Intent()
44 | intent.action = Intent.ACTION_SEND
45 | val shareUri = FileProvider.getUriForFile(ac, ac.applicationContext.packageName + ".provider", tmpShareFile)
46 | intent.type = "application/octet-stream"
47 | intent.putExtra(Intent.EXTRA_STREAM, shareUri)
48 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
49 | ac.startActivity(Intent.createChooser(intent, null)) // This provides a better menu than startActivity(intent)
50 | }
51 |
52 | fun onOptionsSharedItemSelected(item: MenuItem, ac: Activity): Boolean {
53 | when (item.itemId) {
54 | android.R.id.home -> {
55 | ac.onBackPressed()
56 | return true
57 | }
58 | R.id.action_export_zip_file -> {
59 | exportZipFile(ac)
60 | return true
61 | }
62 | R.id.action_sync_dropbox -> {
63 | DropboxFileSync.launchInitialOauthActivity(ac)
64 | return true
65 | }
66 | else -> return false
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Secure Zip Notes
2 | Securely store notes within an encrypted Zip file.
3 |
4 |
5 |
6 |
7 |
8 |
9 | You do not trust bloated password managers with undocumented file formats?
10 | You want to retain 100% control over your data?
11 | Then Secure Zip Notes is a solution.
12 |
13 | # Deprecation Notice
14 |
15 | I deprecate this app because I believe the "XML-based UI" is no longer suitable for future development.
16 | Instead, I recommend embracing a modern declarative UI framework.
17 | Declarative UIs, popularized by frameworks like Flutter and React, are, in my opinion, vastly superior to traditional XML-UIs.
18 | However, due to other projects taking higher priority, I am unable to allocate time to rewriting this UI.
19 |
20 | Please note that notes created within this app can still be opened using independent programs (e.g., 7-Zip, WinZip, The Unarchiver for macOS, Gnome Archive Manager).
21 |
22 | ## Features
23 | - View and edit encrypted text files on any platform, using password-protected Zip files.
24 | - Optional sync with Dropbox (they cannot decrypt your data).
25 | - Simple import/export of Zip files.
26 | - Uses hardware-protected storage to avoid retyping the master password every time.
27 | - Open Source: Fetch this app from GitHub if you do not trust us.
28 |
29 | Our top priority is not only security and privacy, but also long-term stability.
30 | We take the responsibility to retain your data seriously.
31 | Secure Zip Notes guarantees that you can easily decrypt your data in 50 years even if DiTronic Apps ceases to exist.
32 |
33 | This app only supports text notes.
34 | If you are seeking advanced features like auto-fill passwords, then we recommend other apps like Keepass2Android.
35 |
36 | ## Technical details
37 | - Supported independent programs: 7-Zip, WinZip, The Unarchiver (macOS), Gnome Archive Manager
38 | - Encryption: AES-256 Counter Mode + HMAC-SHA1
39 | - Key derivation: PBKDF2
40 |
41 | Not all PC operating systems support Zip files with AES encryption by default.
42 | Therefore, you might need to install a PC software like 7-Zip.
43 | _____________________________________________________________________
44 |
45 | ## Attributions
46 | This app uses a modified version of the Zip4j library (Apache License 2.0).
47 |
48 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/io/ZipInputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.io;
18 |
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 |
22 | import net.lingala.zip4j.exception.ZipException;
23 |
24 | public class ZipInputStream extends InputStream {
25 |
26 | private BaseInputStream is;
27 |
28 | public ZipInputStream(BaseInputStream is) {
29 | this.is = is;
30 | }
31 |
32 | public int read() throws IOException {
33 | int readByte = is.read();
34 | if (readByte != -1) {
35 | is.getUnzipEngine().updateCRC(readByte);
36 | }
37 | return readByte;
38 | }
39 |
40 | public int read(byte[] b) throws IOException {
41 | return read(b, 0, b.length);
42 | }
43 |
44 | public int read(byte[] b, int off, int len) throws IOException {
45 | int readLen = is.read(b, off, len);
46 | if (readLen > 0 && is.getUnzipEngine() != null) {
47 | is.getUnzipEngine().updateCRC(b, off, readLen);
48 | }
49 | return readLen;
50 | }
51 |
52 | /**
53 | * Closes the input stream and releases any resources.
54 | * This method also checks for the CRC of the extracted file.
55 | * If CRC check has to be skipped use close(boolean skipCRCCheck) method
56 | *
57 | * @throws IOException
58 | */
59 | public void close() throws IOException {
60 | close(false);
61 | }
62 |
63 | /**
64 | * Closes the input stream and releases any resources.
65 | * If skipCRCCheck flag is set to true, this method skips CRC Check
66 | * of the extracted file
67 | *
68 | * @throws IOException
69 | */
70 | public void close(boolean skipCRCCheck) throws IOException {
71 | try {
72 | is.close();
73 | if (!skipCRCCheck && is.getUnzipEngine() != null) {
74 | is.getUnzipEngine().checkCRC();
75 | }
76 | } catch (ZipException e) {
77 | throw new IOException(e.getMessage());
78 | }
79 | }
80 |
81 | public int available() throws IOException {
82 | return is.available();
83 | }
84 |
85 | public long skip(long n) throws IOException {
86 | return is.skip(n);
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/UnzipParameters.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class UnzipParameters {
20 |
21 | private boolean ignoreReadOnlyFileAttribute;
22 | private boolean ignoreHiddenFileAttribute;
23 | private boolean ignoreArchiveFileAttribute;
24 | private boolean ignoreSystemFileAttribute;
25 | private boolean ignoreAllFileAttributes;
26 | private boolean ignoreDateTimeAttributes;
27 |
28 | public boolean isIgnoreReadOnlyFileAttribute() {
29 | return ignoreReadOnlyFileAttribute;
30 | }
31 |
32 | public void setIgnoreReadOnlyFileAttribute(boolean ignoreReadOnlyFileAttribute) {
33 | this.ignoreReadOnlyFileAttribute = ignoreReadOnlyFileAttribute;
34 | }
35 |
36 | public boolean isIgnoreHiddenFileAttribute() {
37 | return ignoreHiddenFileAttribute;
38 | }
39 |
40 | public void setIgnoreHiddenFileAttribute(boolean ignoreHiddenFileAttribute) {
41 | this.ignoreHiddenFileAttribute = ignoreHiddenFileAttribute;
42 | }
43 |
44 | public boolean isIgnoreArchiveFileAttribute() {
45 | return ignoreArchiveFileAttribute;
46 | }
47 |
48 | public void setIgnoreArchiveFileAttribute(boolean ignoreArchiveFileAttribute) {
49 | this.ignoreArchiveFileAttribute = ignoreArchiveFileAttribute;
50 | }
51 |
52 | public boolean isIgnoreSystemFileAttribute() {
53 | return ignoreSystemFileAttribute;
54 | }
55 |
56 | public void setIgnoreSystemFileAttribute(boolean ignoreSystemFileAttribute) {
57 | this.ignoreSystemFileAttribute = ignoreSystemFileAttribute;
58 | }
59 |
60 | public boolean isIgnoreAllFileAttributes() {
61 | return ignoreAllFileAttributes;
62 | }
63 |
64 | public void setIgnoreAllFileAttributes(boolean ignoreAllFileAttributes) {
65 | this.ignoreAllFileAttributes = ignoreAllFileAttributes;
66 | }
67 |
68 | public boolean isIgnoreDateTimeAttributes() {
69 | return ignoreDateTimeAttributes;
70 | }
71 |
72 | public void setIgnoreDateTimeAttributes(boolean ignoreDateTimeAttributes) {
73 | this.ignoreDateTimeAttributes = ignoreDateTimeAttributes;
74 | }
75 |
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/PBKDF2/BinTools.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto.PBKDF2;
18 |
19 | /*
20 | * Source referred from Matthias Gartner's PKCS#5 implementation -
21 | * see http://rtner.de/software/PBKDF2.html
22 | */
23 |
24 | class BinTools
25 | {
26 | public static final String hex = "0123456789ABCDEF";
27 |
28 | public static String bin2hex(final byte[] b)
29 | {
30 | if (b == null)
31 | {
32 | return "";
33 | }
34 | StringBuffer sb = new StringBuffer(2 * b.length);
35 | for (int i = 0; i < b.length; i++)
36 | {
37 | int v = (256 + b[i]) % 256;
38 | sb.append(hex.charAt((v / 16) & 15));
39 | sb.append(hex.charAt((v % 16) & 15));
40 | }
41 | return sb.toString();
42 | }
43 |
44 | public static byte[] hex2bin(final String s)
45 | {
46 | String m = s;
47 | if (s == null)
48 | {
49 | // Allow empty input string.
50 | m = "";
51 | }
52 | else if (s.length() % 2 != 0)
53 | {
54 | // Assume leading zero for odd string length
55 | m = "0" + s;
56 | }
57 | byte r[] = new byte[m.length() / 2];
58 | for (int i = 0, n = 0; i < m.length(); n++)
59 | {
60 | char h = m.charAt(i++);
61 | char l = m.charAt(i++);
62 | r[n] = (byte) (hex2bin(h) * 16 + hex2bin(l));
63 | }
64 | return r;
65 | }
66 |
67 | public static int hex2bin(char c)
68 | {
69 | if (c >= '0' && c <= '9')
70 | {
71 | return (c - '0');
72 | }
73 | if (c >= 'A' && c <= 'F')
74 | {
75 | return (c - 'A' + 10);
76 | }
77 | if (c >= 'a' && c <= 'f')
78 | {
79 | return (c - 'a' + 10);
80 | }
81 | throw new IllegalArgumentException(
82 | "Input string may only contain hex digits, but found '" + c
83 | + "'");
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/util/CRCUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.util;
18 |
19 | import java.io.File;
20 | import java.io.FileInputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.util.zip.CRC32;
24 |
25 | import net.lingala.zip4j.exception.ZipException;
26 | import net.lingala.zip4j.progress.ProgressMonitor;
27 |
28 | public class CRCUtil {
29 |
30 | private static final int BUF_SIZE = 1 << 14; //16384
31 |
32 | public static long computeFileCRC(String inputFile) throws ZipException {
33 | return computeFileCRC(inputFile, null);
34 | }
35 |
36 | /**
37 | * Calculates CRC of a file
38 | * @param inputFile - file for which crc has to be calculated
39 | * @return crc of the file
40 | * @throws ZipException
41 | */
42 | public static long computeFileCRC(String inputFile, ProgressMonitor progressMonitor) throws ZipException {
43 |
44 | if (!Zip4jUtil.isStringNotNullAndNotEmpty(inputFile)) {
45 | throw new ZipException("input file is null or empty, cannot calculate CRC for the file");
46 | }
47 | InputStream inputStream = null;
48 | try {
49 | Zip4jUtil.checkFileReadAccess(inputFile);
50 |
51 | inputStream = new FileInputStream(new File(inputFile));
52 |
53 | byte[] buff = new byte[BUF_SIZE];
54 | int readLen = -2;
55 | CRC32 crc32 = new CRC32();
56 |
57 | while ((readLen = inputStream.read(buff)) != -1) {
58 | crc32.update(buff, 0, readLen);
59 | if (progressMonitor != null) {
60 | progressMonitor.updateWorkCompleted(readLen);
61 | if (progressMonitor.isCancelAllTasks()) {
62 | progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
63 | progressMonitor.setState(ProgressMonitor.STATE_READY);
64 | return 0;
65 | }
66 | }
67 | }
68 |
69 | return crc32.getValue();
70 | } catch (IOException e) {
71 | throw new ZipException(e);
72 | } catch (Exception e) {
73 | throw new ZipException(e);
74 | } finally {
75 | if (inputStream != null) {
76 | try {
77 | inputStream.close();
78 | } catch (IOException e) {
79 | throw new ZipException("error while closing the file after calculating crc");
80 | }
81 | }
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/dialogs/PwDialog.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.dialogs
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import android.text.InputType
6 | import android.view.WindowManager
7 | import android.view.inputmethod.EditorInfo
8 | import android.widget.EditText
9 | import androidx.appcompat.app.AlertDialog
10 | import com.ditronic.securezipnotes.password.PwManager
11 | import com.ditronic.securezipnotes.password.PwRequest
12 | import com.ditronic.securezipnotes.password.PwResult
13 | import com.ditronic.securezipnotes.zip.CryptoZip
14 |
15 |
16 | class PwDialog: ShortLifeDialogFragment() {
17 |
18 | companion object {
19 | val TAG = FragmentTag("PwDialog")
20 | }
21 |
22 | lateinit var editText: EditText
23 |
24 | override fun getFragmentTag() = TAG
25 |
26 | override fun onCreateDialog(savedInstanceState: Bundle?, state: PwRequest): Dialog {
27 | editText = EditText(requireContext())
28 |
29 | val builder = AlertDialog.Builder(requireContext())
30 | builder.setTitle("Master password:")
31 | editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
32 | editText.hint = "Master password"
33 | builder.setView(editText)
34 | builder.setPositiveButton(android.R.string.ok, null)
35 | .setNeutralButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
36 | val dialog = builder.create()
37 | editText.setOnEditorActionListener { _, actionId, _ ->
38 | if (actionId == EditorInfo.IME_ACTION_DONE) {
39 | onPositiveButtonClick()
40 | return@setOnEditorActionListener true
41 | }
42 | false
43 | }
44 | val window = dialog.window
45 | window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
46 | editText.requestFocus()
47 | return dialog
48 | }
49 |
50 | override fun onResume() {
51 | super.onResume()
52 | val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
53 | positiveButton.setOnClickListener {
54 | onPositiveButtonClick()
55 | }
56 | }
57 |
58 | private fun onPositiveButtonClick() {
59 | val pwRequest = fetchStateOrDie() ?: return
60 | val typedPassword = editText.text.toString()
61 | val zipStream = CryptoZip.instance(requireActivity()).isPasswordValid(pwRequest.fileHeader, typedPassword)
62 | if (zipStream != null) {
63 | editText.error = null
64 | PwManager.saveUserProvidedPassword(requireActivity(), PwResult.Success(inputStream = zipStream, password = typedPassword), pwRequest.continuation)
65 | dismiss()
66 | } else {
67 | editText.error = "Wrong password"
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
49 |
50 |
55 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_new_password.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
20 |
21 |
28 |
29 |
34 |
35 |
42 |
43 |
46 |
54 |
55 |
56 |
62 |
63 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/PBKDF2/PBKDF2Parameters.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto.PBKDF2;
18 | /*
19 | * Source referred from Matthias Gartner's PKCS#5 implementation -
20 | * see http://rtner.de/software/PBKDF2.html
21 | */
22 | public class PBKDF2Parameters
23 | {
24 | protected byte[] salt;
25 |
26 | protected int iterationCount;
27 |
28 | protected String hashAlgorithm;
29 |
30 | protected String hashCharset;
31 |
32 | protected byte[] derivedKey;
33 |
34 | public PBKDF2Parameters()
35 | {
36 | this.hashAlgorithm = null;
37 | this.hashCharset = "UTF-8";
38 | this.salt = null;
39 | this.iterationCount = 1000;
40 | this.derivedKey = null;
41 | }
42 |
43 | public PBKDF2Parameters(String hashAlgorithm, String hashCharset,
44 | byte[] salt, int iterationCount)
45 | {
46 | this.hashAlgorithm = hashAlgorithm;
47 | this.hashCharset = hashCharset;
48 | this.salt = salt;
49 | this.iterationCount = iterationCount;
50 | this.derivedKey = null;
51 | }
52 |
53 | public PBKDF2Parameters(String hashAlgorithm, String hashCharset,
54 | byte[] salt, int iterationCount, byte[] derivedKey)
55 | {
56 | this.hashAlgorithm = hashAlgorithm;
57 | this.hashCharset = hashCharset;
58 | this.salt = salt;
59 | this.iterationCount = iterationCount;
60 | this.derivedKey = derivedKey;
61 | }
62 |
63 | public int getIterationCount()
64 | {
65 | return iterationCount;
66 | }
67 |
68 | public void setIterationCount(int iterationCount)
69 | {
70 | this.iterationCount = iterationCount;
71 | }
72 |
73 | public byte[] getSalt()
74 | {
75 | return salt;
76 | }
77 |
78 | public void setSalt(byte[] salt)
79 | {
80 | this.salt = salt;
81 | }
82 |
83 | public byte[] getDerivedKey()
84 | {
85 | return derivedKey;
86 | }
87 |
88 | public void setDerivedKey(byte[] derivedKey)
89 | {
90 | this.derivedKey = derivedKey;
91 | }
92 |
93 | public String getHashAlgorithm()
94 | {
95 | return hashAlgorithm;
96 | }
97 |
98 | public void setHashAlgorithm(String hashAlgorithm)
99 | {
100 | this.hashAlgorithm = hashAlgorithm;
101 | }
102 |
103 | public String getHashCharset()
104 | {
105 | return hashCharset;
106 | }
107 |
108 | public void setHashCharset(String hashCharset)
109 | {
110 | this.hashCharset = hashCharset;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/PBKDF2/MacBasedPRF.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto.PBKDF2;
18 |
19 | import java.security.InvalidKeyException;
20 | import java.security.NoSuchAlgorithmException;
21 | import java.security.NoSuchProviderException;
22 |
23 | import javax.crypto.Mac;
24 | import javax.crypto.spec.SecretKeySpec;
25 |
26 | /*
27 | * Source referred from Matthias Gartner's PKCS#5 implementation -
28 | * see http://rtner.de/software/PBKDF2.html
29 | */
30 |
31 | public class MacBasedPRF implements PRF
32 | {
33 | protected Mac mac;
34 |
35 | protected int hLen;
36 |
37 | protected String macAlgorithm;
38 |
39 | public MacBasedPRF(String macAlgorithm)
40 | {
41 | this.macAlgorithm = macAlgorithm;
42 | try
43 | {
44 | mac = Mac.getInstance(macAlgorithm);
45 | hLen = mac.getMacLength();
46 | }
47 | catch (NoSuchAlgorithmException e)
48 | {
49 | throw new RuntimeException(e);
50 | }
51 | }
52 |
53 | public MacBasedPRF(String macAlgorithm, String provider)
54 | {
55 | this.macAlgorithm = macAlgorithm;
56 | try
57 | {
58 | mac = Mac.getInstance(macAlgorithm, provider);
59 | hLen = mac.getMacLength();
60 | }
61 | catch (NoSuchAlgorithmException e)
62 | {
63 | throw new RuntimeException(e);
64 | }
65 | catch (NoSuchProviderException e)
66 | {
67 | throw new RuntimeException(e);
68 | }
69 | }
70 |
71 | public byte[] doFinal(byte[] M)
72 | {
73 | byte[] r = mac.doFinal(M);
74 | return r;
75 | }
76 |
77 | public byte[] doFinal() {
78 | byte[] r = mac.doFinal();
79 | return r;
80 | }
81 |
82 | public int getHLen()
83 | {
84 | return hLen;
85 | }
86 |
87 | public void init(byte[] P)
88 | {
89 | try
90 | {
91 | mac.init(new SecretKeySpec(P, macAlgorithm));
92 | }
93 | catch (InvalidKeyException e)
94 | {
95 | throw new RuntimeException(e);
96 | }
97 | }
98 |
99 | public void update(byte[] U) {
100 |
101 | try {
102 | mac.update(U);
103 | } catch (IllegalStateException e) {
104 | throw new RuntimeException(e);
105 | }
106 |
107 | }
108 |
109 | public void update (byte[] U, int start, int len) {
110 | try {
111 | mac.update(U, start, len);
112 | } catch (IllegalStateException e) {
113 | throw new RuntimeException(e);
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
12 |
13 | .idea
14 |
15 | ### Intellij ###
16 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
17 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
18 |
19 | # User-specific stuff:
20 | .idea/workspace.xml
21 | .idea/tasks.xml
22 | .idea/dictionaries
23 | .idea/vcs.xml
24 | .idea/jsLibraryMappings.xml
25 |
26 | # Sensitive or high-churn files:
27 | .idea/dataSources.ids
28 | .idea/dataSources.xml
29 | .idea/dataSources.local.xml
30 | .idea/sqlDataSources.xml
31 | .idea/dynamic.xml
32 | .idea/uiDesigner.xml
33 |
34 | # Gradle:
35 | .idea/gradle.xml
36 | .idea/libraries
37 |
38 | # Mongo Explorer plugin:
39 | .idea/mongoSettings.xml
40 |
41 | ## File-based project format:
42 | *.iws
43 |
44 | ## Plugin-specific files:
45 |
46 | # IntelliJ
47 | /out/
48 |
49 | # mpeltonen/sbt-idea plugin
50 | .idea_modules/
51 |
52 | # JIRA plugin
53 | atlassian-ide-plugin.xml
54 |
55 | # Crashlytics plugin (for Android Studio and IntelliJ)
56 | com_crashlytics_export_strings.xml
57 | crashlytics.properties
58 | crashlytics-build.properties
59 | fabric.properties
60 |
61 | ### Intellij Patch ###
62 | *.iml
63 |
64 | ### Android ###
65 | # Built application files
66 | *.apk
67 | *.aab
68 | *.ap_
69 |
70 | # Files for the Dalvik VM
71 | *.dex
72 |
73 | # Java class files
74 | *.class
75 |
76 | # Generated files
77 | bin/
78 | gen/
79 | out/
80 | app/release/output.json
81 |
82 | # Gradle files
83 | .gradle/
84 | build/
85 |
86 | # Local configuration file (sdk path, etc)
87 | local.properties
88 |
89 | # Proguard folder generated by Eclipse
90 | proguard/
91 |
92 | # Log Files
93 | *.log
94 |
95 | # Android Studio Navigation editor temp files
96 | .navigation/
97 |
98 | # Android Studio captures folder
99 | captures/
100 |
101 | # Intellij
102 | *.iml
103 |
104 | # Keystore files
105 | *.jks
106 |
107 | ### Android Patch ###
108 | gen-external-apklibs
109 |
110 |
111 | ### OSX ###
112 | .DS_Store
113 | .AppleDouble
114 | .LSOverride
115 |
116 | # Icon must end with two \r
117 | Icon
118 |
119 |
120 | # Thumbnails
121 | ._*
122 |
123 | # Files that might appear in the root of a volume
124 | .DocumentRevisions-V100
125 | .fseventsd
126 | .Spotlight-V100
127 | .TemporaryItems
128 | .Trashes
129 | .VolumeIcon.icns
130 |
131 | # Directories potentially created on remote AFP share
132 | .AppleDB
133 | .AppleDesktop
134 | Network Trash Folder
135 | Temporary Items
136 | .apdisk
137 |
138 |
139 | ### Windows ###
140 | # Windows image file caches
141 | Thumbs.db
142 | ehthumbs.db
143 |
144 | # Folder config file
145 | Desktop.ini
146 |
147 | # Recycle Bin used on file shares
148 | $RECYCLE.BIN/
149 |
150 | # Windows Installer files
151 | *.cab
152 | *.msi
153 | *.msm
154 | *.msp
155 |
156 | # Windows shortcuts
157 | *.lnk
158 |
159 |
160 | ### Linux ###
161 | *~
162 |
163 | # temporary files which can be created if a process still has a handle open of a deleted file
164 | .fuse_hidden*
165 |
166 | # KDE directory preferences
167 | .directory
168 |
169 | # Linux trash folder which might appear on any partition or disk
170 | .Trash-*
171 |
172 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/onboarding/NewPasswordActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.onboarding
2 |
3 | import android.os.Bundle
4 | import android.view.MenuItem
5 | import android.view.View
6 | import android.view.inputmethod.EditorInfo
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.ditronic.securezipnotes.databinding.ActivityNewPasswordBinding
9 | import com.ditronic.securezipnotes.util.OnThrottleClickListener
10 | import java.security.SecureRandom
11 |
12 |
13 | class NewPasswordActivity : AppCompatActivity() {
14 |
15 | private lateinit var binding: ActivityNewPasswordBinding
16 |
17 | public override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | binding = ActivityNewPasswordBinding.inflate(layoutInflater)
20 | setContentView(binding.root)
21 |
22 | val toolbar = binding.toolBar
23 | setSupportActionBar(toolbar)
24 |
25 | binding.inputPassword.setText(generatePassword())
26 |
27 | binding.btnGenerateMasterPassword.setOnClickListener { binding.inputPassword.setText(generatePassword()) }
28 |
29 | binding.btnNext.setOnClickListener(object : OnThrottleClickListener() {
30 | public override fun onThrottleClick(v: View) {
31 | btnNext()
32 | }
33 | })
34 |
35 | binding.inputPassword.setOnEditorActionListener { _, actionId, _ ->
36 | if (actionId == EditorInfo.IME_ACTION_DONE) {
37 | btnNext()
38 | true
39 | } else {
40 | false
41 | }
42 | }
43 |
44 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
45 | supportActionBar?.setDisplayShowHomeEnabled(true)
46 | supportActionBar?.title = "New Master Password"
47 | }
48 |
49 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
50 | return when (item.itemId) {
51 | android.R.id.home -> {
52 | onBackPressed()
53 | true
54 | }
55 | else -> super.onOptionsItemSelected(item)
56 | }
57 | }
58 |
59 | private fun btnNext() {
60 |
61 | val password = binding.inputPassword.text.toString()
62 |
63 | if (password.isEmpty() || password.length < MIN_PW_LEN) {
64 | binding.inputPassword.error = "Minimum length: $MIN_PW_LEN characters"
65 | return
66 | }
67 | binding.inputPassword.error = null
68 | PasswordConfirmActivity.launch(this, password)
69 | }
70 |
71 | companion object {
72 | private val TAG = NewPasswordActivity::class.java.name
73 |
74 | private const val ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
75 |
76 | private const val DEFAULT_PW_LEN = 20 // This is sufficient against offline attacks
77 | private const val MIN_PW_LEN = 8 // This is too short, but we won't hinder the user from shooting themselves
78 |
79 | private fun generatePassword(): String {
80 | val length = DEFAULT_PW_LEN
81 | val secureRandom = SecureRandom()
82 | val stringBuilder = StringBuilder(length)
83 | repeat(times = length) {
84 | stringBuilder.append(ALPHABET[secureRandom.nextInt(ALPHABET.length)])
85 | }
86 | return stringBuilder.toString()
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/onboarding/PasswordConfirmActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.onboarding
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.view.MenuItem
7 | import android.view.View
8 | import android.view.WindowManager
9 | import android.view.inputmethod.EditorInfo
10 | import androidx.appcompat.app.AppCompatActivity
11 | import com.ditronic.securezipnotes.databinding.ActivityPasswordConfirmBinding
12 | import com.ditronic.securezipnotes.noteselect.MainActivity
13 | import com.ditronic.securezipnotes.password.PwManager
14 | import com.ditronic.securezipnotes.password.PwResult
15 | import com.ditronic.securezipnotes.util.OnThrottleClickListener
16 |
17 | class PasswordConfirmActivity : AppCompatActivity() {
18 |
19 | private lateinit var password: String
20 | private lateinit var binding: ActivityPasswordConfirmBinding
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | binding = ActivityPasswordConfirmBinding.inflate(layoutInflater)
25 | setContentView(binding.root)
26 | password = intent.extras!!.getString(INTENT_PASSWORD)!!
27 |
28 | val toolbar = binding.toolBar
29 | setSupportActionBar(toolbar)
30 |
31 | binding.btnConfirmMasterPassword.setOnClickListener(object : OnThrottleClickListener() {
32 | public override fun onThrottleClick(v: View) {
33 | savePassword()
34 | }
35 | })
36 |
37 | binding.inputPasswordConfirm.setOnEditorActionListener { _, actionId, _ ->
38 | if (actionId == EditorInfo.IME_ACTION_DONE) {
39 | savePassword()
40 | return@setOnEditorActionListener true
41 | }
42 | false
43 | }
44 |
45 | // add back arrow to toolbar
46 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
47 | supportActionBar?.setDisplayShowHomeEnabled(true)
48 | supportActionBar?.title = "Confirm Master Password"
49 |
50 | val window = window
51 | window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
52 | binding.inputPasswordConfirm.requestFocus()
53 | }
54 |
55 | private fun savePassword() {
56 |
57 | val confirmedPassword = binding.inputPasswordConfirm.text.toString()
58 | if (confirmedPassword != password) {
59 | binding.inputPasswordConfirm.error = "Passwords do not match"
60 | return
61 | }
62 | binding.inputPasswordConfirm.error = null
63 |
64 | PwManager.saveUserProvidedPassword(this, PwResult.Success(password = confirmedPassword, inputStream = null)) {
65 | MainActivity.launchCleanWithNewNote(this)
66 | }
67 | }
68 |
69 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
70 | return when (item.itemId) {
71 | android.R.id.home -> {
72 | onBackPressed()
73 | true
74 | }
75 | else -> super.onOptionsItemSelected(item)
76 | }
77 | }
78 |
79 | companion object {
80 |
81 | private const val INTENT_PASSWORD = "intent_password"
82 |
83 | fun launch(cx: Context, password: String) {
84 | val intent = Intent(cx, PasswordConfirmActivity::class.java)
85 | intent.putExtra(INTENT_PASSWORD, password)
86 | cx.startActivity(intent)
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/EndCentralDirRecord.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class EndCentralDirRecord {
20 |
21 | private long signature;
22 |
23 | private int noOfThisDisk;
24 |
25 | private int noOfThisDiskStartOfCentralDir;
26 |
27 | private int totNoOfEntriesInCentralDirOnThisDisk;
28 |
29 | private int totNoOfEntriesInCentralDir;
30 |
31 | private int sizeOfCentralDir;
32 |
33 | private long offsetOfStartOfCentralDir;
34 |
35 | private int commentLength;
36 |
37 | private String comment;
38 |
39 | private byte[] commentBytes;
40 |
41 | public long getSignature() {
42 | return signature;
43 | }
44 |
45 | public void setSignature(long signature) {
46 | this.signature = signature;
47 | }
48 |
49 | public int getNoOfThisDisk() {
50 | return noOfThisDisk;
51 | }
52 |
53 | public void setNoOfThisDisk(int noOfThisDisk) {
54 | this.noOfThisDisk = noOfThisDisk;
55 | }
56 |
57 | public int getNoOfThisDiskStartOfCentralDir() {
58 | return noOfThisDiskStartOfCentralDir;
59 | }
60 |
61 | public void setNoOfThisDiskStartOfCentralDir(int noOfThisDiskStartOfCentralDir) {
62 | this.noOfThisDiskStartOfCentralDir = noOfThisDiskStartOfCentralDir;
63 | }
64 |
65 | public int getTotNoOfEntriesInCentralDirOnThisDisk() {
66 | return totNoOfEntriesInCentralDirOnThisDisk;
67 | }
68 |
69 | public void setTotNoOfEntriesInCentralDirOnThisDisk(
70 | int totNoOfEntriesInCentralDirOnThisDisk) {
71 | this.totNoOfEntriesInCentralDirOnThisDisk = totNoOfEntriesInCentralDirOnThisDisk;
72 | }
73 |
74 | public int getTotNoOfEntriesInCentralDir() {
75 | return totNoOfEntriesInCentralDir;
76 | }
77 |
78 | public void setTotNoOfEntriesInCentralDir(int totNoOfEntrisInCentralDir) {
79 | this.totNoOfEntriesInCentralDir = totNoOfEntrisInCentralDir;
80 | }
81 |
82 | public int getSizeOfCentralDir() {
83 | return sizeOfCentralDir;
84 | }
85 |
86 | public void setSizeOfCentralDir(int sizeOfCentralDir) {
87 | this.sizeOfCentralDir = sizeOfCentralDir;
88 | }
89 |
90 | public long getOffsetOfStartOfCentralDir() {
91 | return offsetOfStartOfCentralDir;
92 | }
93 |
94 | public void setOffsetOfStartOfCentralDir(long offSetOfStartOfCentralDir) {
95 | this.offsetOfStartOfCentralDir = offSetOfStartOfCentralDir;
96 | }
97 |
98 | public int getCommentLength() {
99 | return commentLength;
100 | }
101 |
102 | public void setCommentLength(int commentLength) {
103 | this.commentLength = commentLength;
104 | }
105 |
106 | public String getComment() {
107 | return comment;
108 | }
109 |
110 | public void setComment(String comment) {
111 | this.comment = comment;
112 | }
113 |
114 | public byte[] getCommentBytes() {
115 | return commentBytes;
116 | }
117 |
118 | public void setCommentBytes(byte[] commentBytes) {
119 | this.commentBytes = commentBytes;
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
18 |
22 |
23 |
28 |
29 |
34 |
37 |
47 |
48 |
51 |
61 |
62 |
65 |
75 |
76 |
77 |
78 |
79 |
80 |
84 |
85 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/io/DeflaterOutputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.io;
18 |
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.io.OutputStream;
22 | import java.util.zip.Deflater;
23 |
24 | import net.lingala.zip4j.exception.ZipException;
25 | import net.lingala.zip4j.model.ZipModel;
26 | import net.lingala.zip4j.model.ZipParameters;
27 | import net.lingala.zip4j.util.InternalZipConstants;
28 | import net.lingala.zip4j.util.Zip4jConstants;
29 |
30 | public class DeflaterOutputStream extends CipherOutputStream {
31 |
32 | private byte[] buff;
33 | protected Deflater deflater;
34 | private boolean firstBytesRead;
35 |
36 | public DeflaterOutputStream(OutputStream outputStream, ZipModel zipModel) {
37 | super(outputStream, zipModel);
38 | deflater = new Deflater();
39 | buff = new byte[InternalZipConstants.BUFF_SIZE];
40 | firstBytesRead = false;
41 | }
42 |
43 | public void putNextEntry(File file, ZipParameters zipParameters)
44 | throws ZipException {
45 | super.putNextEntry(file, zipParameters);
46 | if (zipParameters.getCompressionMethod() == Zip4jConstants.COMP_DEFLATE) {
47 | deflater.reset();
48 | if ((zipParameters.getCompressionLevel() < 0 || zipParameters
49 | .getCompressionLevel() > 9)
50 | && zipParameters.getCompressionLevel() != -1) {
51 | throw new ZipException(
52 | "invalid compression level for deflater. compression level should be in the range of 0-9");
53 | }
54 | deflater.setLevel(zipParameters.getCompressionLevel());
55 | }
56 | }
57 |
58 | public void write(byte[] b) throws IOException {
59 | write(b, 0, b.length);
60 | }
61 |
62 | private void deflate () throws IOException {
63 | int len = deflater.deflate(buff, 0, buff.length);
64 | if (len > 0) {
65 | if (deflater.finished()) {
66 | if (len == 4) return;
67 | if (len < 4) {
68 | decrementCompressedFileSize(4 - len);
69 | return;
70 | }
71 | len -= 4;
72 | }
73 | if (!firstBytesRead) {
74 | super.write(buff, 2, len - 2);
75 | firstBytesRead = true;
76 | } else {
77 | super.write(buff, 0, len);
78 | }
79 | }
80 | }
81 |
82 | public void write(int bval) throws IOException {
83 | byte[] b = new byte[1];
84 | b[0] = (byte) bval;
85 | write(b, 0, 1);
86 | }
87 |
88 | public void write(byte[] buf, int off, int len) throws IOException {
89 | if (zipParameters.getCompressionMethod() != Zip4jConstants.COMP_DEFLATE) {
90 | super.write(buf, off, len);
91 | } else {
92 | deflater.setInput(buf, off, len);
93 | while (!deflater.needsInput()) {
94 | deflate();
95 | }
96 | }
97 | }
98 |
99 | public void closeEntry() throws IOException, ZipException {
100 | if (zipParameters.getCompressionMethod() == Zip4jConstants.COMP_DEFLATE) {
101 | if (!deflater.finished()) {
102 | deflater.finish();
103 | while (!deflater.finished()) {
104 | deflate();
105 | }
106 | }
107 | firstBytesRead = false;
108 | deflater.end();
109 | }
110 | super.closeEntry();
111 | }
112 |
113 | public void finish() throws IOException, ZipException {
114 | super.finish();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/StandardDecrypter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto;
18 |
19 | import net.lingala.zip4j.crypto.engine.ZipCryptoEngine;
20 | import net.lingala.zip4j.exception.ZipException;
21 | import net.lingala.zip4j.exception.ZipExceptionConstants;
22 | import net.lingala.zip4j.model.FileHeader;
23 | import net.lingala.zip4j.util.InternalZipConstants;
24 |
25 | public class StandardDecrypter implements IDecrypter {
26 |
27 | private FileHeader fileHeader;
28 | private byte[] crc = new byte[4];
29 | private ZipCryptoEngine zipCryptoEngine;
30 |
31 | public StandardDecrypter(FileHeader fileHeader, byte[] headerBytes) throws ZipException{
32 | if (fileHeader == null) {
33 | throw new ZipException("one of more of the input parameters were null in StandardDecryptor");
34 | }
35 |
36 | this.fileHeader = fileHeader;
37 | this.zipCryptoEngine = new ZipCryptoEngine();
38 | init(headerBytes);
39 | }
40 |
41 | public int decryptData(byte[] buff) throws ZipException {
42 | return decryptData(buff, 0, buff.length);
43 | }
44 |
45 | public int decryptData(byte[] buff, int start, int len) throws ZipException {
46 | if (start < 0 || len < 0) {
47 | throw new ZipException("one of the input parameters were null in standard decrpyt data");
48 | }
49 |
50 | try {
51 | for (int i = start; i < start + len; i++) {
52 | int val = buff[i] & 0xff;
53 | val = (val ^ zipCryptoEngine.decryptByte()) & 0xff;
54 | zipCryptoEngine.updateKeys((byte) val);
55 | buff[i] = (byte)val;
56 | }
57 | return len;
58 | } catch (Exception e) {
59 | throw new ZipException(e);
60 | }
61 | }
62 |
63 | public void init(byte[] headerBytes) throws ZipException {
64 | byte[] crcBuff = fileHeader.getCrcBuff();
65 | crc[3] = (byte) (crcBuff[3] & 0xFF);
66 | crc[2] = (byte) ((crcBuff[3] >> 8) & 0xFF);
67 | crc[1] = (byte) ((crcBuff[3] >> 16) & 0xFF);
68 | crc[0] = (byte) ((crcBuff[3] >> 24) & 0xFF);
69 |
70 | if(crc[2] > 0 || crc[1] > 0 || crc[0] > 0)
71 | throw new IllegalStateException("Invalid CRC in File Header");
72 |
73 | if (fileHeader.getPassword() == null || fileHeader.getPassword().length <= 0) {
74 | throw new ZipException("Wrong password!", ZipExceptionConstants.WRONG_PASSWORD);
75 | }
76 |
77 | zipCryptoEngine.initKeys(fileHeader.getPassword());
78 |
79 | try {
80 | int result = headerBytes[0];
81 | for (int i = 0; i < InternalZipConstants.STD_DEC_HDR_SIZE; i++) {
82 | // Commented this as this check cannot always be trusted
83 | // New functionality: If there is an error in extracting a password protected file,
84 | // "Wrong Password?" text is appended to the exception message
85 | // if(i+1 == InternalZipConstants.STD_DEC_HDR_SIZE && ((byte)(result ^ zipCryptoEngine.decryptByte()) != crc[3]) && !isSplit)
86 | // throw new ZipException("Wrong password!", ZipExceptionConstants.WRONG_PASSWORD);
87 |
88 | zipCryptoEngine.updateKeys((byte) (result ^ zipCryptoEngine.decryptByte()));
89 | if (i+1 != InternalZipConstants.STD_DEC_HDR_SIZE)
90 | result = headerBytes[i+1];
91 | }
92 | } catch (Exception e) {
93 | throw new ZipException(e);
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'com.google.firebase.crashlytics'
5 |
6 | android {
7 | compileSdkVersion 30
8 | defaultConfig {
9 | applicationId "com.ditronic.securezipnotes"
10 | minSdkVersion 19
11 | targetSdkVersion 30
12 | versionCode 7
13 | versionName "1.2.0"
14 | multiDexEnabled true
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | // Causes a "pm clear" command after each test invocation
17 | testInstrumentationRunnerArguments clearPackageData: 'true'
18 |
19 | setProperty("archivesBaseName", applicationId + "-v" + versionCode + "(" + versionName + ")")
20 | }
21 | buildTypes {
22 | release {
23 | minifyEnabled true
24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
25 | manifestPlaceholders = [enableCrashlyticsReporting: "true"]
26 | }
27 | debug {
28 | // A different "package name" for debug builds enables a clean separation during development.
29 | applicationIdSuffix = ".dev"
30 | versionNameSuffix '-DEBUG'
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33 | manifestPlaceholders = [enableCrashlyticsReporting: "false"]
34 | }
35 | }
36 | lintOptions {
37 | abortOnError true
38 | warningsAsErrors true
39 | lintConfig file('lint.xml')
40 | }
41 | compileOptions {
42 | targetCompatibility = '1.8'
43 | sourceCompatibility = '1.8'
44 | kotlinOptions.allWarningsAsErrors = false
45 | }
46 | kotlinOptions {
47 | jvmTarget = "1.8"
48 | }
49 | testOptions {
50 | execution 'ANDROIDX_TEST_ORCHESTRATOR'
51 | }
52 | packagingOptions {
53 | exclude 'META-INF/DEPENDENCIES'
54 | }
55 | viewBinding.enabled = true
56 | }
57 |
58 | dependencies {
59 | implementation fileTree(dir: 'libs', include: ['*.jar'])
60 | implementation 'androidx.appcompat:appcompat:1.2.0'
61 | //implementation 'com.android.support:multidex:1.0.3'
62 |
63 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
64 | implementation 'com.google.android.material:material:1.3.0-beta01'
65 | implementation 'androidx.cardview:cardview:1.0.0'
66 | implementation "com.jakewharton.timber:timber:4.7.1"
67 |
68 | implementation 'androidx.biometric:biometric:1.0.1'
69 | //implementation 'com.google.android.gms:play-services-ads:18.0.0'
70 | implementation 'com.google.firebase:firebase-analytics:18.0.0'
71 | implementation 'com.google.firebase:firebase-crashlytics:17.3.0'
72 |
73 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
74 |
75 | testImplementation 'junit:junit:4.13.1'
76 |
77 | androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
78 | androidTestImplementation 'androidx.test:core-ktx:1.3.1-alpha02'
79 | androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3-alpha02'
80 | androidTestImplementation 'androidx.test:rules:1.3.1-alpha02'
81 | androidTestImplementation 'androidx.test:runner:1.3.1-alpha02'
82 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha02'
83 | androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0'
84 |
85 | androidTestUtil 'androidx.test:orchestrator:1.3.0'
86 |
87 | implementation project(path: ':simplefilesync')
88 | implementation project(path: ':zip4j')
89 | implementation 'androidx.core:core-ktx:1.5.0-alpha05'
90 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
91 | }
92 | repositories {
93 | mavenCentral()
94 | }
95 |
96 | apply plugin: 'com.google.gms.google-services'
97 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/Zip64EndCentralDirRecord.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | public class Zip64EndCentralDirRecord {
20 |
21 | private long signature;
22 |
23 | private long sizeOfZip64EndCentralDirRec;
24 |
25 | private int versionMadeBy;
26 |
27 | private int versionNeededToExtract;
28 |
29 | private int noOfThisDisk;
30 |
31 | private int noOfThisDiskStartOfCentralDir;
32 |
33 | private long totNoOfEntriesInCentralDirOnThisDisk;
34 |
35 | private long totNoOfEntriesInCentralDir;
36 |
37 | private long sizeOfCentralDir;
38 |
39 | private long offsetStartCenDirWRTStartDiskNo;
40 |
41 | private byte[] extensibleDataSector;
42 |
43 | public long getSignature() {
44 | return signature;
45 | }
46 |
47 | public void setSignature(long signature) {
48 | this.signature = signature;
49 | }
50 |
51 | public long getSizeOfZip64EndCentralDirRec() {
52 | return sizeOfZip64EndCentralDirRec;
53 | }
54 |
55 | public void setSizeOfZip64EndCentralDirRec(long sizeOfZip64EndCentralDirRec) {
56 | this.sizeOfZip64EndCentralDirRec = sizeOfZip64EndCentralDirRec;
57 | }
58 |
59 | public int getVersionMadeBy() {
60 | return versionMadeBy;
61 | }
62 |
63 | public void setVersionMadeBy(int versionMadeBy) {
64 | this.versionMadeBy = versionMadeBy;
65 | }
66 |
67 | public int getVersionNeededToExtract() {
68 | return versionNeededToExtract;
69 | }
70 |
71 | public void setVersionNeededToExtract(int versionNeededToExtract) {
72 | this.versionNeededToExtract = versionNeededToExtract;
73 | }
74 |
75 | public int getNoOfThisDisk() {
76 | return noOfThisDisk;
77 | }
78 |
79 | public void setNoOfThisDisk(int noOfThisDisk) {
80 | this.noOfThisDisk = noOfThisDisk;
81 | }
82 |
83 | public int getNoOfThisDiskStartOfCentralDir() {
84 | return noOfThisDiskStartOfCentralDir;
85 | }
86 |
87 | public void setNoOfThisDiskStartOfCentralDir(int noOfThisDiskStartOfCentralDir) {
88 | this.noOfThisDiskStartOfCentralDir = noOfThisDiskStartOfCentralDir;
89 | }
90 |
91 | public long getTotNoOfEntriesInCentralDirOnThisDisk() {
92 | return totNoOfEntriesInCentralDirOnThisDisk;
93 | }
94 |
95 | public void setTotNoOfEntriesInCentralDirOnThisDisk(
96 | long totNoOfEntriesInCentralDirOnThisDisk) {
97 | this.totNoOfEntriesInCentralDirOnThisDisk = totNoOfEntriesInCentralDirOnThisDisk;
98 | }
99 |
100 | public long getTotNoOfEntriesInCentralDir() {
101 | return totNoOfEntriesInCentralDir;
102 | }
103 |
104 | public void setTotNoOfEntriesInCentralDir(long totNoOfEntriesInCentralDir) {
105 | this.totNoOfEntriesInCentralDir = totNoOfEntriesInCentralDir;
106 | }
107 |
108 | public long getSizeOfCentralDir() {
109 | return sizeOfCentralDir;
110 | }
111 |
112 | public void setSizeOfCentralDir(long sizeOfCentralDir) {
113 | this.sizeOfCentralDir = sizeOfCentralDir;
114 | }
115 |
116 | public long getOffsetStartCenDirWRTStartDiskNo() {
117 | return offsetStartCenDirWRTStartDiskNo;
118 | }
119 |
120 | public void setOffsetStartCenDirWRTStartDiskNo(
121 | long offsetStartCenDirWRTStartDiskNo) {
122 | this.offsetStartCenDirWRTStartDiskNo = offsetStartCenDirWRTStartDiskNo;
123 | }
124 |
125 | public byte[] getExtensibleDataSector() {
126 | return extensibleDataSector;
127 | }
128 |
129 | public void setExtensibleDataSector(byte[] extensibleDataSector) {
130 | this.extensibleDataSector = extensibleDataSector;
131 | }
132 |
133 |
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/noteedit/NoteEditUI.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.noteedit
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.view.ActionMode
6 | import android.view.Menu
7 | import android.view.MenuItem
8 | import android.view.WindowManager
9 | import android.view.inputmethod.InputMethodManager
10 | import kotlinx.android.synthetic.main.activity_note_edit.*
11 |
12 |
13 | fun NoteEditActivity.applyEditMode(enable: Boolean) {
14 | model.editMode = enable
15 | val editTextTitle = edit_text_title
16 | val editTextMain = edit_text_main
17 |
18 | // Rather simple procedure for title edit text
19 | editTextTitle.isCursorVisible = model.editMode
20 | editTextTitle.isClickable = model.editMode
21 | editTextTitle.isFocusable = model.editMode
22 | editTextTitle.isLongClickable = model.editMode
23 | editTextTitle.setTextIsSelectable(model.editMode)
24 | editTextTitle.isLongClickable = model.editMode
25 |
26 |
27 | // Complicated procedure for the main edit text
28 | if (Build.VERSION.SDK_INT >= NoteEditActivity.MIN_API_COPY_READ_ONLY) { // 21
29 | editTextMain.showSoftInputOnFocus = model.editMode
30 | editTextMain.isCursorVisible = model.editMode
31 | } else {
32 | editTextMain.isCursorVisible = model.editMode
33 | editTextMain.isClickable = model.editMode
34 | editTextMain.isFocusable = model.editMode
35 | editTextMain.isLongClickable = model.editMode
36 | editTextMain.setTextIsSelectable(model.editMode)
37 | editTextMain.isLongClickable = model.editMode
38 | }
39 |
40 | if (!model.editMode) {
41 | editTextMain.customSelectionActionModeCallback = CustomSelectionActionModeCallback()
42 | if (Build.VERSION.SDK_INT >= NoteEditActivity.MIN_API_COPY_READ_ONLY) { // 23
43 | editTextMain.customInsertionActionModeCallback = CustomInsertionActionModeCallback()
44 | }
45 | // Close keyboard
46 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
47 | val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
48 | imm.hideSoftInputFromWindow(editTextMain.windowToken, 0)
49 | } else {
50 | editTextMain.customSelectionActionModeCallback = null
51 | if (Build.VERSION.SDK_INT >= NoteEditActivity.MIN_API_COPY_READ_ONLY) { // 23
52 | editTextMain.customInsertionActionModeCallback = null
53 | }
54 | // Open keyboard
55 | editTextMain.requestFocus()
56 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
57 | val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
58 | imm.showSoftInput(editTextMain, InputMethodManager.SHOW_IMPLICIT)
59 | }
60 |
61 | invalidateOptionsMenu()
62 | }
63 |
64 |
65 | internal class CustomSelectionActionModeCallback : ActionMode.Callback {
66 | override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
67 | return true
68 | }
69 |
70 | override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
71 | try {
72 | val copyItem = menu.findItem(android.R.id.copy)
73 | val title = copyItem.title
74 | menu.clear() // We only want copy functionality, no paste, no cut.
75 | menu.add(0, android.R.id.copy, 0, title)
76 | } catch (ignored: Exception) {
77 | }
78 |
79 | return true
80 | }
81 |
82 | override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
83 | return false
84 | }
85 |
86 | override fun onDestroyActionMode(mode: ActionMode) {}
87 | }
88 |
89 | internal class CustomInsertionActionModeCallback : ActionMode.Callback {
90 | override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
91 | return false
92 | }
93 |
94 | override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
95 | return false
96 | }
97 |
98 | override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
99 | return false
100 | }
101 |
102 | override fun onDestroyActionMode(mode: ActionMode) {}
103 | }
104 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/unzip/UnzipUtil.java:
--------------------------------------------------------------------------------
1 | package net.lingala.zip4j.unzip;
2 |
3 | import java.io.File;
4 |
5 | import net.lingala.zip4j.exception.ZipException;
6 | import net.lingala.zip4j.model.FileHeader;
7 | import net.lingala.zip4j.model.UnzipParameters;
8 | import net.lingala.zip4j.util.InternalZipConstants;
9 | import net.lingala.zip4j.util.Zip4jUtil;
10 |
11 | public class UnzipUtil {
12 |
13 | public static void applyFileAttributes(FileHeader fileHeader, File file) throws ZipException {
14 | applyFileAttributes(fileHeader, file, null);
15 | }
16 |
17 | public static void applyFileAttributes(FileHeader fileHeader, File file,
18 | UnzipParameters unzipParameters) throws ZipException{
19 |
20 | if (fileHeader == null) {
21 | throw new ZipException("cannot set file properties: file header is null");
22 | }
23 |
24 | if (file == null) {
25 | throw new ZipException("cannot set file properties: output file is null");
26 | }
27 |
28 | if (!Zip4jUtil.checkFileExists(file)) {
29 | throw new ZipException("cannot set file properties: file doesnot exist");
30 | }
31 |
32 | if (unzipParameters == null || !unzipParameters.isIgnoreDateTimeAttributes()) {
33 | setFileLastModifiedTime(fileHeader, file);
34 | }
35 |
36 | if (unzipParameters == null) {
37 | setFileAttributes(fileHeader, file, true, true, true, true);
38 | } else {
39 | if (unzipParameters.isIgnoreAllFileAttributes()) {
40 | setFileAttributes(fileHeader, file, false, false, false, false);
41 | } else {
42 | setFileAttributes(fileHeader, file, !unzipParameters.isIgnoreReadOnlyFileAttribute(),
43 | !unzipParameters.isIgnoreHiddenFileAttribute(),
44 | !unzipParameters.isIgnoreArchiveFileAttribute(),
45 | !unzipParameters.isIgnoreSystemFileAttribute());
46 | }
47 | }
48 | }
49 |
50 | private static void setFileAttributes(FileHeader fileHeader, File file, boolean setReadOnly,
51 | boolean setHidden, boolean setArchive, boolean setSystem) throws ZipException {
52 | if (fileHeader == null) {
53 | throw new ZipException("invalid file header. cannot set file attributes");
54 | }
55 |
56 | byte[] externalAttrbs = fileHeader.getExternalFileAttr();
57 | if (externalAttrbs == null) {
58 | return;
59 | }
60 |
61 | int atrrib = externalAttrbs[0];
62 | switch (atrrib) {
63 | case InternalZipConstants.FILE_MODE_READ_ONLY:
64 | if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
65 | break;
66 | case InternalZipConstants.FILE_MODE_HIDDEN:
67 | case InternalZipConstants.FOLDER_MODE_HIDDEN:
68 | if (setHidden) Zip4jUtil.setFileHidden(file);
69 | break;
70 | case InternalZipConstants.FILE_MODE_ARCHIVE:
71 | case InternalZipConstants.FOLDER_MODE_ARCHIVE:
72 | if (setArchive) Zip4jUtil.setFileArchive(file);
73 | break;
74 | case InternalZipConstants.FILE_MODE_READ_ONLY_HIDDEN:
75 | if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
76 | if (setHidden) Zip4jUtil.setFileHidden(file);
77 | break;
78 | case InternalZipConstants.FILE_MODE_READ_ONLY_ARCHIVE:
79 | if (setArchive) Zip4jUtil.setFileArchive(file);
80 | if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
81 | break;
82 | case InternalZipConstants.FILE_MODE_HIDDEN_ARCHIVE:
83 | case InternalZipConstants.FOLDER_MODE_HIDDEN_ARCHIVE:
84 | if (setArchive) Zip4jUtil.setFileArchive(file);
85 | if (setHidden) Zip4jUtil.setFileHidden(file);
86 | break;
87 | case InternalZipConstants.FILE_MODE_READ_ONLY_HIDDEN_ARCHIVE:
88 | if (setArchive) Zip4jUtil.setFileArchive(file);
89 | if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
90 | if (setHidden) Zip4jUtil.setFileHidden(file);
91 | break;
92 | case InternalZipConstants.FILE_MODE_SYSTEM:
93 | if (setReadOnly) Zip4jUtil.setFileReadOnly(file);
94 | if (setHidden) Zip4jUtil.setFileHidden(file);
95 | if (setSystem) Zip4jUtil.setFileSystemMode(file);
96 | break;
97 | default:
98 | //do nothing
99 | break;
100 | }
101 | }
102 |
103 | private static void setFileLastModifiedTime(FileHeader fileHeader, File file) throws ZipException {
104 | if (fileHeader.getLastModFileTime() <= 0) {
105 | return;
106 | }
107 |
108 | if (file.exists()) {
109 | file.setLastModified(Zip4jUtil.dosToJavaTme(fileHeader.getLastModFileTime()));
110 | }
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ditronic/securezipnotes/tests/SetupTests.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.tests
2 |
3 | import androidx.test.espresso.assertion.ViewAssertions
4 | import androidx.test.espresso.matcher.ViewMatchers
5 | import androidx.test.espresso.matcher.ViewMatchers.assertThat
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import androidx.test.filters.LargeTest
8 | import com.ditronic.securezipnotes.robotpattern.*
9 | import com.ditronic.securezipnotes.testutils.pressBack
10 | import org.hamcrest.Description
11 | import org.hamcrest.Matcher
12 | import org.hamcrest.Matchers
13 | import org.hamcrest.Matchers.containsString
14 | import org.hamcrest.TypeSafeMatcher
15 | import org.junit.Assert.assertEquals
16 | import org.junit.Test
17 | import org.junit.runner.RunWith
18 | import java.text.SimpleDateFormat
19 |
20 |
21 | @RunWith(AndroidJUnit4::class)
22 | @LargeTest
23 | class SetupTests {
24 |
25 | companion object {
26 | private const val SECRET_NOTE = "My secret note"
27 |
28 | private fun matchesRandomPassword(): Matcher = object : TypeSafeMatcher() {
29 | override fun describeTo(description: Description?) = Unit
30 | override fun matchesSafely(password: String): Boolean {
31 | if (password.length != 20) {
32 | return false
33 | }
34 | return true
35 | }
36 | }
37 | }
38 |
39 | @Test
40 | fun createNewPassword() {
41 | precondition_cleanStart()
42 |
43 | init_createNewZipFile()
44 |
45 | init_genRandomPassword()
46 | init_typeNewPassword(TESTPASSWORD)
47 | init_confirmNewPassword(TESTPASSWORD)
48 |
49 | noteEdit_typeText(SECRET_NOTE)
50 | noteEdit_assertState("Note 1", SECRET_NOTE, editMode = true)
51 | pressBack()
52 |
53 | main_assertListState(listOf("Note 1"))
54 | val noteEntry = main_extractEntryList()[0]
55 | assertEquals("Size: " + SECRET_NOTE.length, noteEntry.size)
56 | assertThat(noteEntry.modDate, containsString(SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Calendar.getInstance().timeInMillis)))
57 |
58 | main_clickNote("Note 1")
59 | noteEdit_assertState("Note 1", SECRET_NOTE, editMode = false)
60 | pressBack()
61 |
62 | // Back to MainActivity, assert that nothing changed
63 | main_assertListState(listOf("Note 1"))
64 | val newNoteEntry = main_extractEntryList()[0]
65 | assertEquals(noteEntry, newNoteEntry)
66 | }
67 |
68 | @Test
69 | fun passwordTooShort() {
70 | precondition_cleanStart()
71 |
72 | init_createNewZipFile()
73 | init_typeNewPassword("")
74 | init_newPassword_assertErrorText("Minimum length: 8 characters")
75 | init_typeNewPassword("sfse")
76 | init_newPassword_assertErrorText("Minimum length: 8 characters")
77 | init_typeNewPassword(TESTPASSWORD)
78 | Thread.sleep(2000)
79 | init_confirmNewPassword(TESTPASSWORD)
80 | noteEdit_assertState("Note 1", "", editMode = true)
81 | }
82 |
83 | @Test
84 | fun passwordMismatch() {
85 | precondition_cleanStart()
86 |
87 | init_createNewZipFile()
88 | init_typeNewPassword(TESTPASSWORD)
89 | init_confirmNewPassword(TESTPASSWORD + "mismatch")
90 | init_confirmPassword_assertErrorText("Passwords do not match")
91 | pressBack()
92 | pressBack()
93 | init_typeNewPassword("lalalalalala")
94 | init_confirmNewPassword("lalalalalala mismatch")
95 | init_confirmPassword_assertErrorText("Passwords do not match")
96 | init_confirmNewPassword("lalalalalala")
97 | noteEdit_assertState("Note 1", "", editMode = true)
98 | }
99 |
100 |
101 |
102 | @Test
103 | fun generateRandomPassword() {
104 | precondition_cleanStart()
105 |
106 | init_createNewZipFile()
107 | init_onViewPassword().check(ViewAssertions.matches(ViewMatchers.withText(matchesRandomPassword())))
108 | init_typeNewPassword("")
109 | init_onViewPassword().check(ViewAssertions.matches(ViewMatchers.withText(Matchers.isEmptyString())))
110 | init_genRandomPassword()
111 | init_onViewPassword().check(ViewAssertions.matches(ViewMatchers.withText(matchesRandomPassword())))
112 | init_chooseNewPassword()
113 | init_confirmNewPassword("")
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/crypto/StandardEncrypter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.crypto;
18 |
19 | import java.util.Random;
20 |
21 | import net.lingala.zip4j.crypto.engine.ZipCryptoEngine;
22 | import net.lingala.zip4j.exception.ZipException;
23 | import net.lingala.zip4j.util.InternalZipConstants;
24 |
25 | public class StandardEncrypter implements IEncrypter {
26 |
27 | private ZipCryptoEngine zipCryptoEngine;
28 | private byte[] headerBytes;
29 |
30 | public StandardEncrypter(char[] password, int crc) throws ZipException {
31 | if (password == null || password.length <= 0) {
32 | throw new ZipException("input password is null or empty in standard encrpyter constructor");
33 | }
34 |
35 | this.zipCryptoEngine = new ZipCryptoEngine();
36 |
37 | this.headerBytes = new byte[InternalZipConstants.STD_DEC_HDR_SIZE];
38 | init(password, crc);
39 | }
40 |
41 | private void init(char[] password, int crc) throws ZipException {
42 | if (password == null || password.length <= 0) {
43 | throw new ZipException("input password is null or empty, cannot initialize standard encrypter");
44 | }
45 | zipCryptoEngine.initKeys(password);
46 | headerBytes = generateRandomBytes(InternalZipConstants.STD_DEC_HDR_SIZE);
47 | // Initialize again since the generated bytes were encrypted.
48 | zipCryptoEngine.initKeys(password);
49 |
50 | headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 1] = (byte)((crc >>> 24));
51 | headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 2] = (byte)((crc >>> 16));
52 |
53 | if (headerBytes.length < InternalZipConstants.STD_DEC_HDR_SIZE) {
54 | throw new ZipException("invalid header bytes generated, cannot perform standard encryption");
55 | }
56 |
57 | encryptData(headerBytes);
58 | }
59 |
60 | public int encryptData(byte[] buff) throws ZipException {
61 | if (buff == null) {
62 | throw new NullPointerException();
63 | }
64 | return encryptData(buff, 0, buff.length);
65 | }
66 |
67 | public int encryptData(byte[] buff, int start, int len) throws ZipException {
68 |
69 | if (len < 0) {
70 | throw new ZipException("invalid length specified to decrpyt data");
71 | }
72 |
73 | try {
74 | for (int i = start; i < start + len; i++) {
75 | buff[i] = encryptByte(buff[i]);
76 | }
77 | return len;
78 | } catch (Exception e) {
79 | throw new ZipException(e);
80 | }
81 | }
82 |
83 | protected byte encryptByte(byte val) {
84 | byte temp_val = (byte) (val ^ zipCryptoEngine.decryptByte() & 0xff);
85 | zipCryptoEngine.updateKeys(val);
86 | return temp_val;
87 | }
88 |
89 | protected byte[] generateRandomBytes(int size) throws ZipException {
90 |
91 | if (size <= 0) {
92 | throw new ZipException("size is either 0 or less than 0, cannot generate header for standard encryptor");
93 | }
94 |
95 | byte[] buff = new byte[size];
96 |
97 | Random rand = new Random();
98 |
99 | for (int i = 0; i < buff.length; i ++) {
100 | // Encrypted to get less predictability for poorly implemented
101 | // rand functions.
102 | buff[i] = encryptByte((byte) rand.nextInt(256));
103 | }
104 |
105 | // buff[0] = (byte)87;
106 | // buff[1] = (byte)176;
107 | // buff[2] = (byte)-49;
108 | // buff[3] = (byte)-43;
109 | // buff[4] = (byte)93;
110 | // buff[5] = (byte)-204;
111 | // buff[6] = (byte)-105;
112 | // buff[7] = (byte)213;
113 | // buff[8] = (byte)-80;
114 | // buff[9] = (byte)-8;
115 | // buff[10] = (byte)21;
116 | // buff[11] = (byte)242;
117 |
118 | // for( int j=0; j<2; j++ ) {
119 | // Random rand = new Random();
120 | // int i = rand.nextInt();
121 | // buff[0+j*4] = (byte)(i>>24);
122 | // buff[1+j*4] = (byte)(i>>16);
123 | // buff[2+j*4] = (byte)(i>>8);
124 | // buff[3+j*4] = (byte)i;
125 | // }
126 | return buff;
127 | }
128 |
129 | public byte[] getHeaderBytes() {
130 | return headerBytes;
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ditronic/securezipnotes/testutils/TestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.testutils
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.view.InputDevice
7 | import android.view.MotionEvent
8 | import android.widget.Button
9 | import androidx.annotation.IdRes
10 | import androidx.appcompat.app.AlertDialog
11 | import androidx.fragment.app.DialogFragment
12 | import androidx.test.espresso.Espresso
13 | import androidx.test.espresso.ViewAction
14 | import androidx.test.espresso.action.*
15 | import androidx.test.espresso.assertion.ViewAssertions
16 | import androidx.test.espresso.matcher.RootMatchers
17 | import androidx.test.espresso.matcher.ViewMatchers
18 | import androidx.test.platform.app.InstrumentationRegistry
19 | import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
20 | import androidx.test.runner.lifecycle.Stage
21 | import com.ditronic.securezipnotes.BuildConfig
22 | import org.hamcrest.CoreMatchers
23 | import java.io.File
24 |
25 | fun targetContext() : Context {
26 | return InstrumentationRegistry.getInstrumentation().targetContext
27 | }
28 |
29 | fun getResourceString(@IdRes resId: Int): String {
30 | return targetContext() .getString(resId)
31 | }
32 |
33 | fun assertToast(toastMessage : String) {
34 | val ac = getCurrentActivity()
35 | //Espresso.onView(ViewMatchers.withText(toastMessage)).inRoot(RootMatchers.withDecorView(CoreMatchers.not(CoreMatchers.`is`(ac.window.getDecorView())))).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
36 | Espresso.onView(ViewMatchers.withText(toastMessage)).inRoot(RootMatchers.withDecorView(CoreMatchers.not(ac.window.getDecorView()))).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
37 | }
38 |
39 | fun pressBack() {
40 | Espresso.onView(ViewMatchers.isRoot()).perform(ViewActions.pressBack())
41 | }
42 |
43 | fun click_dialogOK() {
44 | Espresso.onView(ViewMatchers.withText("OK")).inRoot(RootMatchers.isDialog()).perform(ViewActions.click())
45 | }
46 |
47 | fun clickBottomCenter(): ViewAction {
48 | return ViewActions.actionWithAssertions(
49 | GeneralClickAction(
50 | Tap.SINGLE,
51 | GeneralLocation.BOTTOM_CENTER,
52 | Press.FINGER,
53 | InputDevice.SOURCE_UNKNOWN,
54 | MotionEvent.BUTTON_PRIMARY))
55 | }
56 |
57 |
58 | fun clearLocalFilesDir() {
59 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
60 | val root = appContext.filesDir
61 | val files = root.listFiles()
62 | files?.forEach {
63 | println("Remove file prior to UI test: " + it.absolutePath)
64 | if (it.delete()) {
65 | throw RuntimeException("Failed to delete from filesDir")
66 | }
67 | }
68 | }
69 |
70 | fun removeAllPrefs() {
71 | val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
72 | val root = targetContext.filesDir.parentFile
73 | val sharedPrefs = File(root, "shared_prefs").list()
74 | if (sharedPrefs == null) {
75 | println("Failed to list shared preference files")
76 | return
77 | }
78 | sharedPrefs.forEach { fileName ->
79 | targetContext.getSharedPreferences(fileName.replace(".xml", ""), Context.MODE_PRIVATE).edit().clear().apply()
80 | }
81 | }
82 |
83 | fun getCurrentActivity(): Activity {
84 | lateinit var currentActivity: Activity
85 | InstrumentationRegistry.getInstrumentation().runOnMainSync {
86 | run {
87 | currentActivity = getCurrentActivityMainThread()
88 | }
89 | }
90 | return currentActivity
91 | }
92 |
93 | fun getCurrentActivityMainThread(): Activity {
94 | val currentActivity =
95 | ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
96 | .elementAt(0)
97 | return currentActivity
98 | }
99 |
100 | internal fun launchActivity(activityClass : Class) {
101 | val intent = Intent(Intent.ACTION_MAIN)
102 | intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
103 | intent.setClassName(BuildConfig.APPLICATION_ID, activityClass.name)
104 | InstrumentationRegistry.getInstrumentation().startActivitySync(intent)
105 | Espresso.onIdle()
106 | }
107 |
108 | fun clickDialogOkWithoutEspresso(dialog: DialogFragment) {
109 | val positiveButton = (dialog.dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
110 | clickButtonWithoutEspresso(button = positiveButton)
111 | }
112 |
113 | fun clickButtonWithoutEspresso(button: Button) {
114 | InstrumentationRegistry.getInstrumentation().runOnMainSync {
115 | button.performClick()
116 | }
117 | Espresso.onIdle()
118 | }
119 |
--------------------------------------------------------------------------------
/simplefilesync/README.md:
--------------------------------------------------------------------------------
1 | # Deprecation Notice
2 |
3 | I deprecated this library because it does not reflect good Android-practices.
4 | Instead, I recommend to use Kotlin-Coroutines and ViewModels.
5 | This helps to prevent numerous memory leaks and lifecycle bugs.
6 | Or alternatively, I recommend to abandon native app-development and switch to cross-platform frameworks like https://capacitorjs.com/.
7 |
8 | ____
9 |
10 | # Simple File Sync
11 |
12 | ``SimpleFileSync`` is an Android library that enables to easily synchronize individual files with Dropbox.
13 |
14 | ``SimpleFileSync`` provides synchronization in both directions, that is: It either uploads or downloads files.
15 | The direction depends on the timestamps.
16 | Moreover, ``SimpleFileSync`` compares a hash of the local file with the remote file to avoid unnecessary uploads or downloads.
17 |
18 |
19 | ``SimpleFileSync`` is a small wrapper on top of the following official client libraries:
20 |
21 | - Dropbox Core SDK: `com.dropbox.core:dropbox-core-sdk`.
22 | The Dropbox Core SDK is open-sourced at https://github.com/dropbox/dropbox-sdk-java.
23 |
24 | ## Dropbox Integration
25 | Integrating ``SimpleFileSync`` into your app involves the following steps:
26 |
27 | 1. Copy the `simplefilesync` folder into your project.
28 | Then add a gradle dependency to your app-level `build.gradle`:
29 | ```Groovy
30 | dependencies {
31 | ....
32 | implementation project(path: ':simplefilesync')
33 | }
34 | ```
35 | Furthermore, add `simplefilesync` to your root `settings.gradle`:
36 | ```Groovy
37 | include ':app', ':simplefilesync'
38 | ```
39 |
40 | 2. Add the following to your `AndroidManifest.xml`:
41 | ```XML
42 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | ```
54 |
55 | 3. Initiate the oauth2 flow at an appropriate place in your code, e.g. when the user clicks a button:
56 | ```Java
57 | DropboxFileSync.launchInitialOauthActivity(context);
58 | ```
59 |
60 | 4. Insert the following call into the `onResume` of your activity:
61 | ```Java
62 | DropboxFileSync.onResumeFetchOAuthToken(this);
63 | ```
64 |
65 | 5. To do the actual file synchronization, instantiate an instance of `DropboxFileSync` and execute it.
66 | A good place for this might be the `onResume` of your activity:
67 | ```Java
68 | new DropboxFileSync(this, myLocalFile, myRemoteFileName,
69 | new AbstractFileSync.SSyncCallback() {
70 | @Override
71 | public void onSyncCompleted(SSyncResult res) {
72 | // TODO: Check the result code and notify the user about success or failure.
73 | // TODO: If the result is DOWNLOAD_SUCCESS, then you will probably need to copy the temporary download file to a permanent storage location.
74 | }
75 | }).execute();
76 | ```
77 |
78 | ## Dropbox Production Integration
79 |
80 | For debugging purposes, this library includes an app key that is linked to a Dropbox sample app.
81 | For a production app, you need to register a Dropbox app according to the Dropbox developer documentation: https://www.dropbox.com/developers/.
82 | After registering your Dropbox app, add your app key to `strings.xml`.
83 | Replace the `MY_APP_KEY` entries with the app key from your Dropbox developer dashboard:
84 | ```XML
85 | MY_APP_KEY
86 | db-MY_APP_KEY
87 | ```
88 |
89 | ## Potential Issues
90 |
91 | #### Optional Auto Backup Configuration
92 | It might be a good idea to disable the Android Auto Backup feature.
93 | This can be done by setting `android:allowBackup="false"` in your application `AndroidManifest.xml`.
94 | Alternatively, you might disable Auto Backup for a selected folder where your local sync files are stored.
95 | Otherwise, the Auto Backup might mess up the synchronization with Dropbox.
96 |
97 |
98 | #### Build Failure about META_INF/DEPENDENCIES
99 | If you get a message like `More than one file was found with OS independent path 'META-INF/DEPENDENCIES'`,
100 | then you should be able to fix it by adding the following entry to your app-level `build.gradle`:
101 | ```
102 | android {
103 | ....
104 | packagingOptions {
105 | exclude 'META-INF/DEPENDENCIES'
106 | }
107 | }
108 | ```
109 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/progress/ProgressMonitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.progress;
18 |
19 | import net.lingala.zip4j.exception.ZipException;
20 |
21 | /**
22 | * If Zip4j is set to run in thread mode, this class helps retrieve current progress
23 | *
24 | */
25 | public class ProgressMonitor {
26 |
27 | private int state;
28 | private long totalWork;
29 | private long workCompleted;
30 | private int percentDone;
31 | private int currentOperation;
32 | private String fileName;
33 | private int result;
34 | private Throwable exception;
35 | private boolean cancelAllTasks;
36 | private boolean pause;
37 |
38 | //Progress monitor States
39 | public static final int STATE_READY = 0;
40 | public static final int STATE_BUSY = 1;
41 |
42 | //Progress monitor result codes
43 | public static final int RESULT_SUCCESS = 0;
44 | public static final int RESULT_WORKING = 1;
45 | public static final int RESULT_ERROR = 2;
46 | public static final int RESULT_CANCELLED = 3;
47 |
48 | //Operation Types
49 | public static final int OPERATION_NONE = -1;
50 | public static final int OPERATION_ADD = 0;
51 | public static final int OPERATION_EXTRACT = 1;
52 | public static final int OPERATION_REMOVE = 2;
53 | public static final int OPERATION_CALC_CRC = 3;
54 | public static final int OPERATION_MERGE = 4;
55 |
56 | public ProgressMonitor() {
57 | reset();
58 | percentDone = 0;
59 | }
60 |
61 | public int getState() {
62 | return state;
63 | }
64 |
65 | public void setState(int state) {
66 | this.state = state;
67 | }
68 |
69 | public long getTotalWork() {
70 | return totalWork;
71 | }
72 |
73 | public void setTotalWork(long totalWork) {
74 | this.totalWork = totalWork;
75 | }
76 |
77 | public long getWorkCompleted() {
78 | return workCompleted;
79 | }
80 |
81 | public void updateWorkCompleted(long workCompleted) {
82 | this.workCompleted += workCompleted;
83 |
84 | if (totalWork > 0) {
85 | percentDone = (int)((this.workCompleted*100/totalWork));
86 | if (percentDone > 100) {
87 | percentDone = 100;
88 | }
89 | }
90 | while (pause) {
91 | try {
92 | Thread.sleep(150);
93 | } catch (InterruptedException e) {
94 | //Do nothing
95 | }
96 | }
97 | }
98 |
99 | public int getPercentDone() {
100 | return percentDone;
101 | }
102 |
103 | public void setPercentDone(int percentDone) {
104 | this.percentDone = percentDone;
105 | }
106 |
107 | public int getResult() {
108 | return result;
109 | }
110 |
111 | public void setResult(int result) {
112 | this.result = result;
113 | }
114 |
115 | public String getFileName() {
116 | return fileName;
117 | }
118 |
119 | public void setFileName(String fileName) {
120 | this.fileName = fileName;
121 | }
122 |
123 | public int getCurrentOperation() {
124 | return currentOperation;
125 | }
126 |
127 | public void setCurrentOperation(int currentOperation) {
128 | this.currentOperation = currentOperation;
129 | }
130 |
131 | public Throwable getException() {
132 | return exception;
133 | }
134 |
135 | public void setException(Throwable exception) {
136 | this.exception = exception;
137 | }
138 |
139 | public void endProgressMonitorSuccess() throws ZipException {
140 | reset();
141 | result = ProgressMonitor.RESULT_SUCCESS;
142 | }
143 |
144 | public void endProgressMonitorError(Throwable e) throws ZipException {
145 | reset();
146 | result = ProgressMonitor.RESULT_ERROR;
147 | exception = e;
148 | }
149 |
150 | public void reset() {
151 | currentOperation = OPERATION_NONE;
152 | state = STATE_READY;
153 | fileName = null;
154 | totalWork = 0;
155 | workCompleted = 0;
156 | percentDone = 0;
157 | }
158 |
159 | public void fullReset() {
160 | reset();
161 | exception = null;
162 | result = RESULT_SUCCESS;
163 | }
164 |
165 | public boolean isCancelAllTasks() {
166 | return cancelAllTasks;
167 | }
168 |
169 | public void cancelAllTasks() {
170 | this.cancelAllTasks = true;
171 | }
172 |
173 | public boolean isPause() {
174 | return pause;
175 | }
176 |
177 | public void setPause(boolean pause) {
178 | this.pause = pause;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/zip4j/src/main/java/net/lingala/zip4j/model/ZipModel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Srikanth Reddy Lingala
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing,
11 | * software distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.lingala.zip4j.model;
18 |
19 | import java.util.List;
20 |
21 | public class ZipModel implements Cloneable {
22 |
23 | private List localFileHeaderList;
24 |
25 | private List dataDescriptorList;
26 |
27 | private ArchiveExtraDataRecord archiveExtraDataRecord;
28 |
29 | private CentralDirectory centralDirectory;
30 |
31 | private EndCentralDirRecord endCentralDirRecord;
32 |
33 | private Zip64EndCentralDirLocator zip64EndCentralDirLocator;
34 |
35 | private Zip64EndCentralDirRecord zip64EndCentralDirRecord;
36 |
37 | private boolean splitArchive;
38 |
39 | private long splitLength;
40 |
41 | private String zipFile;
42 |
43 | private boolean isZip64Format;
44 |
45 | private boolean isNestedZipFile;
46 |
47 | private long start;
48 |
49 | private long end;
50 |
51 | private String fileNameCharset;
52 |
53 | public ZipModel() {
54 | splitLength = -1;
55 | }
56 |
57 | public List getLocalFileHeaderList() {
58 | return localFileHeaderList;
59 | }
60 |
61 | public void setLocalFileHeaderList(List localFileHeaderList) {
62 | this.localFileHeaderList = localFileHeaderList;
63 | }
64 |
65 | public List getDataDescriptorList() {
66 | return dataDescriptorList;
67 | }
68 |
69 | public void setDataDescriptorList(List dataDescriptorList) {
70 | this.dataDescriptorList = dataDescriptorList;
71 | }
72 |
73 | public CentralDirectory getCentralDirectory() {
74 | return centralDirectory;
75 | }
76 |
77 | public void setCentralDirectory(CentralDirectory centralDirectory) {
78 | this.centralDirectory = centralDirectory;
79 | }
80 |
81 | public EndCentralDirRecord getEndCentralDirRecord() {
82 | return endCentralDirRecord;
83 | }
84 |
85 | public void setEndCentralDirRecord(EndCentralDirRecord endCentralDirRecord) {
86 | this.endCentralDirRecord = endCentralDirRecord;
87 | }
88 |
89 | public ArchiveExtraDataRecord getArchiveExtraDataRecord() {
90 | return archiveExtraDataRecord;
91 | }
92 |
93 | public void setArchiveExtraDataRecord(
94 | ArchiveExtraDataRecord archiveExtraDataRecord) {
95 | this.archiveExtraDataRecord = archiveExtraDataRecord;
96 | }
97 |
98 | public boolean isSplitArchive() {
99 | return splitArchive;
100 | }
101 |
102 | public void setSplitArchive(boolean splitArchive) {
103 | this.splitArchive = splitArchive;
104 | }
105 |
106 | public String getZipFile() {
107 | return zipFile;
108 | }
109 |
110 | public void setZipFile(String zipFile) {
111 | this.zipFile = zipFile;
112 | }
113 |
114 | public Zip64EndCentralDirLocator getZip64EndCentralDirLocator() {
115 | return zip64EndCentralDirLocator;
116 | }
117 |
118 | public void setZip64EndCentralDirLocator(
119 | Zip64EndCentralDirLocator zip64EndCentralDirLocator) {
120 | this.zip64EndCentralDirLocator = zip64EndCentralDirLocator;
121 | }
122 |
123 | public Zip64EndCentralDirRecord getZip64EndCentralDirRecord() {
124 | return zip64EndCentralDirRecord;
125 | }
126 |
127 | public void setZip64EndCentralDirRecord(
128 | Zip64EndCentralDirRecord zip64EndCentralDirRecord) {
129 | this.zip64EndCentralDirRecord = zip64EndCentralDirRecord;
130 | }
131 |
132 | public boolean isZip64Format() {
133 | return isZip64Format;
134 | }
135 |
136 | public void setZip64Format(boolean isZip64Format) {
137 | this.isZip64Format = isZip64Format;
138 | }
139 |
140 | public boolean isNestedZipFile() {
141 | return isNestedZipFile;
142 | }
143 |
144 | public void setNestedZipFile(boolean isNestedZipFile) {
145 | this.isNestedZipFile = isNestedZipFile;
146 | }
147 |
148 | public long getStart() {
149 | return start;
150 | }
151 |
152 | public void setStart(long start) {
153 | this.start = start;
154 | }
155 |
156 | public long getEnd() {
157 | return end;
158 | }
159 |
160 | public void setEnd(long end) {
161 | this.end = end;
162 | }
163 |
164 | public long getSplitLength() {
165 | return splitLength;
166 | }
167 |
168 | public void setSplitLength(long splitLength) {
169 | this.splitLength = splitLength;
170 | }
171 |
172 | public Object clone() throws CloneNotSupportedException {
173 | return super.clone();
174 | }
175 |
176 | public String getFileNameCharset() {
177 | return fileNameCharset;
178 | }
179 |
180 | public void setFileNameCharset(String fileNameCharset) {
181 | this.fileNameCharset = fileNameCharset;
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/password/PwManager.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.password
2 |
3 | import android.os.Build
4 | import android.widget.Toast
5 | import androidx.fragment.app.FragmentActivity
6 | import com.ditronic.securezipnotes.dialogs.PwDialog
7 | import com.ditronic.securezipnotes.util.Boast
8 | import com.ditronic.securezipnotes.zip.CryptoZip
9 | import net.lingala.zip4j.io.ZipInputStream
10 | import net.lingala.zip4j.model.FileHeader
11 | import timber.log.Timber
12 |
13 | sealed class PwResult {
14 | class Success(val inputStream: ZipInputStream?,
15 | val password: String): PwResult()
16 | object Failure : PwResult()
17 | }
18 |
19 | class PwRequest(
20 | val fileHeader: FileHeader,
21 | val continuation: (res: PwResult.Success) -> Unit
22 | )
23 |
24 | object PwManager {
25 |
26 | private var cachedPw: String? = null
27 |
28 | val cachedPassword
29 | get() = cachedPw
30 |
31 |
32 | private fun onRetrievedPassword(ac: FragmentActivity, pwRequest: PwRequest, pw: String?) {
33 | if (pw == null) {
34 | showPasswordDialog(ac, pwRequest = pwRequest)
35 | return
36 | }
37 | val zipStream = CryptoZip.instance(ac).isPasswordValid(pwRequest.fileHeader, pw)
38 | if (zipStream != null) {
39 | cachedPw = pw // Assign password before running any callback!
40 | pwRequest.continuation(PwResult.Success(inputStream = zipStream, password = pw)) // Password valid, run success callback.
41 | } else {
42 | Timber.d("Outdated password, invalidate preferences and show password dialog")
43 | cachedPw = null
44 | clearPrivatePrefs(ac)
45 | // Ask the user for the right password, which runs the callback later on.
46 | showPasswordDialog(ac, pwRequest = pwRequest)
47 | }
48 | }
49 |
50 |
51 | fun retrievePasswordAsync(ac: FragmentActivity, fileHeader: FileHeader, continuation: (res: PwResult.Success) -> Unit) {
52 | val pwRequest = PwRequest(fileHeader = fileHeader, continuation = continuation)
53 | val cachedPassword = cachedPassword
54 | if (cachedPassword != null) {
55 | onRetrievedPassword(ac, pwRequest = pwRequest, pw = cachedPassword) // Synchronous case: Password already present
56 | return
57 | }
58 |
59 | if (Build.VERSION.SDK_INT < 23) {
60 | val pw = getOldApiPw(ac)
61 | onRetrievedPassword(ac, pwRequest = pwRequest, pw = pw) // Synchronous case: Old API
62 | return
63 | }
64 |
65 | val cipherToUnlock = initDecryptCipher(ac)
66 | if (cipherToUnlock == null) {
67 | onRetrievedPassword(ac, pwRequest = pwRequest, pw = null) // Synchronous case: Cipher failure
68 | return
69 | }
70 |
71 | unlockCipherWithBiometricPrompt(ac, cipherToUnlock) { unlockedCipher ->
72 | val pw = if (unlockedCipher != null) {
73 | decryptPassword(ac, unlockedCipher)
74 | } else {
75 | null
76 | }
77 | onRetrievedPassword(ac, pwRequest = pwRequest, pw = pw) // Asynchronous case
78 | }
79 | }
80 |
81 |
82 | private fun showPasswordDialog(ac: FragmentActivity, pwRequest: PwRequest) {
83 | PwDialog().show(activity = ac, state = pwRequest)
84 | }
85 |
86 | private enum class SyncMode {
87 | SYNC, ASYNC
88 | }
89 |
90 | fun saveUserProvidedPassword(ac: FragmentActivity, res: PwResult.Success, cb: (res: PwResult.Success) -> Unit) {
91 | val syncMode = savePasswordInternal(ac, res, cb)
92 | if (syncMode == SyncMode.SYNC) {
93 | cb(res)
94 | }
95 | }
96 |
97 |
98 | private fun savePasswordInternal(ac: FragmentActivity, res: PwResult.Success, cb: (res: PwResult.Success) -> Unit): SyncMode {
99 |
100 | cachedPw = res.password // Assign password before running any callback!
101 |
102 | // The salt must be different for each file since this ZIP format uses counter mode with a constant IV!
103 | // Therefore we cannot simply store a key that is derived via PBKDF2. Instead, we encrypt the password via KeyStore.
104 |
105 | if (Build.VERSION.SDK_INT < 23) {
106 | saveLowAPIPw(res.password, ac)
107 | return SyncMode.SYNC
108 | }
109 |
110 | val secretKey = tryGenerateKeyStoreKey() ?: return SyncMode.SYNC
111 | val cipherToUnlock = initEncryptCipher(secretKey) ?: return SyncMode.SYNC
112 |
113 | // Unlock the freshly created key in order to encrypt the password.
114 | unlockCipherWithBiometricPrompt(ac, cipherToUnlock) { unlockedCipher ->
115 | val success = finalizePwEncryption(ac, res.password, unlockedCipher)
116 | if (success) {
117 | Boast.makeText(ac, "Password configured successfully", Toast.LENGTH_LONG).show()
118 | } else {
119 | Boast.makeText(ac, "Failed to encrypt the password", Toast.LENGTH_LONG).show()
120 | }
121 | cb(res) // Callback must be executed regardless of success or failure.
122 | }
123 | return SyncMode.ASYNC // Asynchronous case
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ditronic/securezipnotes/zip/NotesImport.kt:
--------------------------------------------------------------------------------
1 | package com.ditronic.securezipnotes.zip
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import androidx.appcompat.app.AlertDialog
6 | import com.ditronic.simplefilesync.util.FilesUtil
7 | import net.lingala.zip4j.core.ZipFile
8 | import net.lingala.zip4j.model.FileHeader
9 | import net.lingala.zip4j.util.Zip4jConstants
10 | import timber.log.Timber
11 | import java.io.File
12 | import java.io.FileNotFoundException
13 | import java.io.IOException
14 | import java.io.InputStream
15 |
16 |
17 | object NotesImport {
18 |
19 | private val TAG = NotesImport::class.java.name
20 |
21 | private fun alertDialog(cx: Context, message: String) {
22 | AlertDialog.Builder(cx)
23 | .setMessage(message)
24 | .setPositiveButton(android.R.string.ok) { _, _ -> }.show()
25 | }
26 |
27 | private fun validateCompressionMethod(cx: Context, fileHeader: FileHeader) : Boolean {
28 | val aesExtraDataRecord = fileHeader.aesExtraDataRecord
29 | if (aesExtraDataRecord == null) {
30 | alertDialog(cx, "Import failed. Could not find AES data record.")
31 | return false
32 | }
33 | val compMethod = aesExtraDataRecord.compressionMethod
34 | if (compMethod == Zip4jConstants.COMP_STORE) {
35 | return true
36 | } else if (compMethod == Zip4jConstants.COMP_DEFLATE) {
37 | return true
38 | } else if (compMethod == 12) {
39 | alertDialog(cx, "Import failed: This app does not support BZIP2 compression.")
40 | return false
41 | } else if (compMethod == 9) {
42 | alertDialog(cx, "Import failed: This app does not support DEFLATE64 compression.")
43 | return false
44 | } else if (compMethod == 14) {
45 | alertDialog(cx, "Import failed: This app does not support LZMA compression.")
46 | return false
47 | } else if (compMethod == 98) {
48 | alertDialog(cx, "Import failed: This app does not support PPMD compression.")
49 | return false
50 | } else {
51 | alertDialog(cx, "Import failed: Unsupported compression method (" + compMethod + ").")
52 | return false
53 | }
54 | }
55 |
56 | private fun isValidAesZipFile(cx: Context, tmpFile: File): Boolean {
57 | val tmpZipFile: ZipFile
58 | try {
59 | tmpZipFile = ZipFile(tmpFile.path)
60 | tmpZipFile.readZipInfo()
61 | } catch (e: Exception) {
62 | Timber.e(e)
63 | alertDialog(cx, "Import failed. Probably this is not a valid Zip file.")
64 | return false
65 | }
66 |
67 | val fileHeaders = tmpZipFile.fileHeadersFast
68 |
69 | fileHeaders.forEach {
70 | if (it.isDirectory) {
71 | alertDialog(cx, "Import failed. Zip files with pure directory entries are not supported.")
72 | return false
73 | }
74 | }
75 | fileHeaders.forEach {
76 | if (!it.isEncrypted) {
77 | alertDialog(cx, "Import failed. Zip files with non-encrypted entries are not supported.")
78 | return false
79 | }
80 | }
81 | fileHeaders.forEach {
82 | if (it.encryptionMethod != Zip4jConstants.ENC_METHOD_AES) {
83 | alertDialog(cx, "Unsupported encryption algorithm. This app only supports Zip files with AES encryption.")
84 | return false
85 | }
86 | }
87 | fileHeaders.forEach {
88 | if (it.fileName.isEmpty()) {
89 | alertDialog(cx, "File names must not have zero lengths.")
90 | return false
91 | }
92 | }
93 | fileHeaders.forEach {
94 | if (!validateCompressionMethod(cx, it)) {
95 | return false
96 | }
97 | }
98 | return true
99 | }
100 |
101 | fun importFromFile(cx: Context, tmpFile: File, successMessage: String) {
102 | if (!isValidAesZipFile(cx, tmpFile)) {
103 | tmpFile.delete()
104 | return
105 | }
106 |
107 | try {
108 | FilesUtil.copyFile(tmpFile, CryptoZip.getMainFilePath(cx))
109 | } catch (e: IOException) {
110 | throw RuntimeException(e)
111 | }
112 |
113 | tmpFile.delete()
114 | CryptoZip.resetCryptoZip(cx) // Refresh data after an import
115 | alertDialog(cx, successMessage)
116 | }
117 |
118 | fun importFromUri(cx: Context, importUri: Uri?) {
119 | if (importUri == null) {
120 | alertDialog(cx, "Failed to select a file")
121 | return
122 | }
123 |
124 | val `is`: InputStream?
125 | try {
126 | `is` = cx.contentResolver.openInputStream(importUri)
127 | } catch (e: FileNotFoundException) {
128 | alertDialog(cx, "Failed to find file.")
129 | return
130 | }
131 |
132 | if (`is` == null) {
133 | alertDialog(cx, "Failed to open file.")
134 | return
135 | }
136 | val tmpFile = FilesUtil.streamToTmpFile(`is`)
137 | importFromFile(cx, tmpFile, "Successfully imported zip notes.")
138 | }
139 | }
140 |
--------------------------------------------------------------------------------