Compare commits
268 Commits
2.6
...
2.7-androi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db967a1a16 | ||
|
|
f83896d89d | ||
|
|
683c0165e6 | ||
|
|
3853b6f180 | ||
|
|
bff3577ada | ||
|
|
4c48bfb1af | ||
|
|
a8263b276c | ||
|
|
11e32abb90 | ||
|
|
b6e6dce9f0 | ||
|
|
ada1c7f6ae | ||
|
|
1cbb584011 | ||
|
|
f9289a3e9e | ||
|
|
9a37253461 | ||
|
|
5a4456bf46 | ||
|
|
1d06dd65ed | ||
|
|
ed6c32811c | ||
|
|
9a564ff35e | ||
|
|
4909479b0f | ||
|
|
434a7cc280 | ||
|
|
50a48ae092 | ||
|
|
c3017069b1 | ||
|
|
c7425be681 | ||
|
|
249a1975cd | ||
|
|
190a241a25 | ||
|
|
aef8422102 | ||
|
|
c2aafd8602 | ||
|
|
8e41cba7ac | ||
|
|
9d29775b14 | ||
|
|
55bd9382bc | ||
|
|
687923da32 | ||
|
|
66e893fd83 | ||
|
|
cc5c45e3aa | ||
|
|
d472d975ce | ||
|
|
7a97a0b0c8 | ||
|
|
02aed778bc | ||
|
|
b748e607ad | ||
|
|
c801ff546a | ||
|
|
17185391ce | ||
|
|
4579095afc | ||
|
|
788d85178d | ||
|
|
af4d7c4bc9 | ||
|
|
a4bbfdf850 | ||
|
|
cef3d470bd | ||
|
|
0d37c45dbe | ||
|
|
e568b5a9da | ||
|
|
e7ac8661f9 | ||
|
|
882de547d0 | ||
|
|
6957d46ef9 | ||
|
|
3a9a518cb1 | ||
|
|
0900aff93a | ||
|
|
3974b70a83 | ||
|
|
498b7caecb | ||
|
|
0b26260124 | ||
|
|
bc0ffbd552 | ||
|
|
c08d3a0e8b | ||
|
|
5501f1f97d | ||
|
|
073ef4f439 | ||
|
|
a7f82d3148 | ||
|
|
831b475b28 | ||
|
|
728a4486d3 | ||
|
|
5035c52846 | ||
|
|
a0447298d3 | ||
|
|
0b044ab9a4 | ||
|
|
3b24e1d1b8 | ||
|
|
cc82e52c33 | ||
|
|
faf59875bf | ||
|
|
e12e14ef03 | ||
|
|
f41f07f0ae | ||
|
|
1cfc199541 | ||
|
|
c43cc73ad5 | ||
|
|
1bd61759bf | ||
|
|
cbf277c493 | ||
|
|
87b7afd587 | ||
|
|
2db0bb35d5 | ||
|
|
c51262ccc2 | ||
|
|
1b703515dd | ||
|
|
0aa7baf59e | ||
|
|
8bdf1755b7 | ||
|
|
bda1ac3bd4 | ||
|
|
8d7c351912 | ||
|
|
38a357cb28 | ||
|
|
f0d523fb35 | ||
|
|
1cb720da32 | ||
|
|
1031414ba2 | ||
|
|
cb74b1f3fc | ||
|
|
82e2d0b5ac | ||
|
|
33f2e0edda | ||
|
|
9c8566b537 | ||
|
|
10698284d2 | ||
|
|
11185725d1 | ||
|
|
71f1b3c130 | ||
|
|
dc19806e02 | ||
|
|
94ac8b1460 | ||
|
|
40a807c6af | ||
|
|
c115e9149c | ||
|
|
7123e97ef9 | ||
|
|
37fb672133 | ||
|
|
2771125eb5 | ||
|
|
a16cb19311 | ||
|
|
8f8920b91f | ||
|
|
1d913b7f78 | ||
|
|
5fe9106f6d | ||
|
|
8d9a3e0ab0 | ||
|
|
344771dbdf | ||
|
|
d38dba7272 | ||
|
|
409f005eec | ||
|
|
df7903e146 | ||
|
|
49edaef79d | ||
|
|
fcbed9ef01 | ||
|
|
3edb414d23 | ||
|
|
d779c21cc1 | ||
|
|
8d32bc56ae | ||
|
|
9d03ed06c3 | ||
|
|
ee290e5c14 | ||
|
|
789761b177 | ||
|
|
cd0876d58a | ||
|
|
bfae4da56c | ||
|
|
f342ed5940 | ||
|
|
c7373fee28 | ||
|
|
44b2955652 | ||
|
|
8a4af69008 | ||
|
|
6650382e19 | ||
|
|
a1f5e0ba1c | ||
|
|
035bb6b285 | ||
|
|
c0107fb90e | ||
|
|
138be9d14c | ||
|
|
61f474217b | ||
|
|
d31c5eed0a | ||
|
|
5060de689b | ||
|
|
b95424ddf3 | ||
|
|
e40a442a30 | ||
|
|
b5134a9faf | ||
|
|
a791d449ce | ||
|
|
43e1a9d539 | ||
|
|
e91f80d10e | ||
|
|
9db855c7fb | ||
|
|
2dc3636b26 | ||
|
|
4d9df012f6 | ||
|
|
ff9d0d75ef | ||
|
|
4e160b3b33 | ||
|
|
5048acc9f9 | ||
|
|
1841541bc4 | ||
|
|
11d9af3844 | ||
|
|
e30b618241 | ||
|
|
966327571d | ||
|
|
303d50c197 | ||
|
|
bcdfdec211 | ||
|
|
fb769d2ac5 | ||
|
|
f8043ae16d | ||
|
|
7150f2f5c5 | ||
|
|
81bd2e3065 | ||
|
|
78c9618807 | ||
|
|
bed8939b8a | ||
|
|
9443d93500 | ||
|
|
877eba66be | ||
|
|
3af8aba40c | ||
|
|
7ece02c73d | ||
|
|
ebbd2b3ac4 | ||
|
|
a85eff4277 | ||
|
|
98f1c776be | ||
|
|
6b554c67ed | ||
|
|
f2ae35080d | ||
|
|
0ff6c93a95 | ||
|
|
9147600b97 | ||
|
|
fafe56166e | ||
|
|
0a024b2594 | ||
|
|
b4c2a393f1 | ||
|
|
39dcef46d2 | ||
|
|
d6a88583f5 | ||
|
|
1c17b84dcf | ||
|
|
cecaf1b5cc | ||
|
|
888338e107 | ||
|
|
32055abf29 | ||
|
|
0f72dffaf1 | ||
|
|
5d1be43b65 | ||
|
|
dc7089c38c | ||
|
|
34540f0844 | ||
|
|
e818713484 | ||
|
|
6e2289994c | ||
|
|
05a9ba46d0 | ||
|
|
70bb30ba0c | ||
|
|
444d7e9b35 | ||
|
|
47164c7a92 | ||
|
|
ad00ceb4ce | ||
|
|
473e3ca11f | ||
|
|
35c0431cec | ||
|
|
70c784db83 | ||
|
|
d448099a2d | ||
|
|
e3a7ea57e0 | ||
|
|
fa6133200e | ||
|
|
dfa67bdca9 | ||
|
|
8c9c4ef7b2 | ||
|
|
1adb18a7e7 | ||
|
|
f50fdb7777 | ||
|
|
33bf2c93d0 | ||
|
|
f2abcc9e43 | ||
|
|
5ef69aa045 | ||
|
|
1c0f274868 | ||
|
|
1f592f50a9 | ||
|
|
30fdb54e94 | ||
|
|
4f552be5a9 | ||
|
|
1439df9f9a | ||
|
|
e676a0e258 | ||
|
|
895df6377d | ||
|
|
3d46f60ff4 | ||
|
|
44d8ab6e53 | ||
|
|
cd70009c2c | ||
|
|
4261160902 | ||
|
|
ced7aef5d7 | ||
|
|
63100913c5 | ||
|
|
6904d4c427 | ||
|
|
4271d77225 | ||
|
|
6811773e54 | ||
|
|
060ce61030 | ||
|
|
9a5e9ced31 | ||
|
|
568401a612 | ||
|
|
92a3a0ccbd | ||
|
|
ba24c2be34 | ||
|
|
019cefd3fb | ||
|
|
eef82f7ed4 | ||
|
|
2dfe0f78b0 | ||
|
|
627144b583 | ||
|
|
fad0f5e5dd | ||
|
|
8562338b62 | ||
|
|
17de69834e | ||
|
|
aeeab7dbf6 | ||
|
|
ce60ba6c9f | ||
|
|
d22f93e564 | ||
|
|
6f4f6b8d1e | ||
|
|
6fa8ee53cd | ||
|
|
23af56c150 | ||
|
|
91828cbad7 | ||
|
|
40d2788ae0 | ||
|
|
21a3a28980 | ||
|
|
f5c7bee58f | ||
|
|
e364f5159b | ||
|
|
74f9f1ca00 | ||
|
|
328d38ac19 | ||
|
|
7735d82c7b | ||
|
|
1e7c200865 | ||
|
|
724b357dd8 | ||
|
|
a85efc5736 | ||
|
|
9eb58119ea | ||
|
|
77b4ed2cfd | ||
|
|
011416690a | ||
|
|
53eb5c8a73 | ||
|
|
2f99855cd4 | ||
|
|
18eaeec1de | ||
|
|
5ee700c9b9 | ||
|
|
a8949ca07e | ||
|
|
0a42579d9e | ||
|
|
f2f8747126 | ||
|
|
f83cdacab8 | ||
|
|
4f708809e5 | ||
|
|
98aeb02d32 | ||
|
|
2bbaeccd05 | ||
|
|
91e0a04e66 | ||
|
|
661fc523ad | ||
|
|
b9cbaf7343 | ||
|
|
e451308fdc | ||
|
|
1b51c5efa4 | ||
|
|
a8776eec58 | ||
|
|
d9cdb7ef83 | ||
|
|
28c7a64bd2 | ||
|
|
d7193f7753 | ||
|
|
f5c7d11f0e | ||
|
|
c0ba96daa2 | ||
|
|
b374d9e04a |
30
.gitignore
vendored
30
.gitignore
vendored
@@ -13,35 +13,13 @@ xcuserdata/
|
|||||||
DerivedData/
|
DerivedData/
|
||||||
|
|
||||||
# Generated
|
# Generated
|
||||||
/platform-independent/cli-c/VERSION
|
|
||||||
/platform-independent/cli-c/mpw-*.tar.gz
|
|
||||||
/platform-darwin/Resources/Media/Images.xcassets/
|
/platform-darwin/Resources/Media/Images.xcassets/
|
||||||
|
/platform-darwin/Podfile.lock
|
||||||
# Media
|
/platform-darwin/Pods/
|
||||||
public/Press/Background.png
|
|
||||||
public/Press/Front-Page.png
|
|
||||||
public/Press/MasterPassword_PressKit/MasterPassword_pressrelease_*.pdf
|
|
||||||
|
|
||||||
# Gradle
|
# Gradle
|
||||||
build
|
build
|
||||||
!/build
|
|
||||||
.gradle
|
.gradle
|
||||||
local.properties
|
local.properties
|
||||||
|
/gradle/builds
|
||||||
# Maven
|
/platform-android/.externalNativeBuild
|
||||||
target
|
|
||||||
dependency-reduced-pom.xml
|
|
||||||
|
|
||||||
# C
|
|
||||||
core/c/*.o
|
|
||||||
core/c/lib/*/.unpacked
|
|
||||||
core/c/lib/*/.patched
|
|
||||||
core/c/lib/*/src
|
|
||||||
core/c/lib/include
|
|
||||||
platform-independent/cli-c/cli/*.o
|
|
||||||
platform-independent/cli-c/mpw-*.tar.gz
|
|
||||||
platform-independent/cli-c/mpw-*.tar.gz.sig
|
|
||||||
platform-independent/cli-c/mpw
|
|
||||||
platform-independent/cli-c/mpw-bench
|
|
||||||
platform-independent/cli-c/mpw-tests
|
|
||||||
platform-independent/cli-c/VERSION
|
|
||||||
|
|||||||
17
.gitlab-ci.yml
Normal file
17
.gitlab-ci.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
variables:
|
||||||
|
GIT_DEPTH: 3
|
||||||
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
|
|
||||||
|
build_project:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- "( brew bundle )"
|
||||||
|
- "( ./lib/bin/build_libsodium-macos )"
|
||||||
|
- "( cd ./platform-independent/c/cli && ./clean && targets=all ./build && ./mpw-tests && ./mpw-cli-tests )"
|
||||||
|
- "( cd ./gradle && ./gradlew --info clean test )"
|
||||||
|
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator clean build )"
|
||||||
|
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' clean build )"
|
||||||
|
tags:
|
||||||
|
- brew
|
||||||
|
- java
|
||||||
|
- xcode_9
|
||||||
16
.gitmodules
vendored
16
.gitmodules
vendored
@@ -17,11 +17,17 @@
|
|||||||
path = platform-darwin/External/jrswizzle
|
path = platform-darwin/External/jrswizzle
|
||||||
url = git://github.com/jonmarimba/jrswizzle.git
|
url = git://github.com/jonmarimba/jrswizzle.git
|
||||||
[submodule "MasterPassword/Web/js/mpw-js"]
|
[submodule "MasterPassword/Web/js/mpw-js"]
|
||||||
path = platform-independent/web-js/js/mpw-js
|
path = platform-independent/web/js/mpw-js
|
||||||
url = https://github.com/tmthrgd/mpw-js.git
|
url = https://github.com/tmthrgd/mpw-js.git
|
||||||
[submodule "platform-darwin/External/libsodium"]
|
[submodule "lib/libsodium"]
|
||||||
path = platform-darwin/External/libsodium
|
path = lib/libsodium
|
||||||
url = https://github.com/jedisct1/libsodium.git
|
url = https://github.com/jedisct1/libsodium.git
|
||||||
[submodule "platform-darwin/External/libjson-c"]
|
[submodule "lib/libjson-c"]
|
||||||
path = platform-darwin/External/libjson-c
|
path = lib/libjson-c
|
||||||
url = https://github.com/json-c/json-c.git
|
url = https://github.com/json-c/json-c.git
|
||||||
|
[submodule "public/site"]
|
||||||
|
path = public/site
|
||||||
|
url = https://github.com/Lyndir/MasterPassword.git
|
||||||
|
branch = gh-pages
|
||||||
|
shallow = true
|
||||||
|
update = none
|
||||||
|
|||||||
10
.travis.yml
10
.travis.yml
@@ -1,10 +0,0 @@
|
|||||||
language: objective-c
|
|
||||||
osx_image: xcode8.3
|
|
||||||
env: TERM=dumb SHLVL=0
|
|
||||||
git:
|
|
||||||
submodules: true
|
|
||||||
script:
|
|
||||||
- "( brew install libsodium )"
|
|
||||||
- "( cd ./platform-independent/cli-c && ./clean && targets='mpw mpw-bench mpw-tests' ./build && ./mpw-tests )"
|
|
||||||
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator )"
|
|
||||||
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' )"
|
|
||||||
5
Brewfile
Normal file
5
Brewfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
brew "libsodium"
|
||||||
|
brew "json-c"
|
||||||
|
|
||||||
|
brew "automake"
|
||||||
|
brew "autoconf"
|
||||||
61
README.md
61
README.md
@@ -1,11 +1,4 @@
|
|||||||
[](https://travis-ci.org/Lyndir/MasterPassword)
|
# [Master Password •••|](http://masterpassword.app)
|
||||||
[](https://gitter.im/lyndir/MasterPassword?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [Master Password •••|](http://masterpasswordapp.com)
|
|
||||||
|
|
||||||
Master Password is a completely new way of thinking about passwords.
|
Master Password is a completely new way of thinking about passwords.
|
||||||
|
|
||||||
@@ -13,9 +6,9 @@ It consists of an algorithm that implements the core idea and applications for v
|
|||||||
|
|
||||||
To skip the intro and go straight to the information on how to use the code, [click here](#source-code).
|
To skip the intro and go straight to the information on how to use the code, [click here](#source-code).
|
||||||
|
|
||||||
Master Password is available for [📲 iOS](https://itunes.apple.com/app/id510296984), [🖥 macOS](https://ssl.masterpasswordapp.com/masterpassword-mac.zip), [📲 Android](https://ssl.masterpasswordapp.com/masterpassword-android.apk), [🖥 Desktop](https://ssl.masterpasswordapp.com/masterpassword-gui.jar), and [⌨ Console](https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz).
|
Master Password is available for [📲 iOS](https://itunes.apple.com/app/id510296984), [🖥 macOS](https://masterpassword.app/masterpassword-mac.zip), [📲 Android](https://masterpassword.app/masterpassword-android.apk), [🖥 Desktop](https://masterpassword.app/masterpassword-gui.jar), and [⌨ Console](https://masterpassword.app/masterpassword-cli.tar.gz).
|
||||||
|
|
||||||
Master Password is also available from the following package managers: [macOS: Homebrew](https://brew.sh/) (`brew install mpw`).
|
Master Password is also available from: [macOS: Homebrew](https://brew.sh/) (`brew install mpw`) and [Docker](https://www.docker.com/) (`docker run -ti registry.gitlab.com/masterpassword/masterpassword`).
|
||||||
Get in touch if you are interested in adding Master Password to any other package managers.
|
Get in touch if you are interested in adding Master Password to any other package managers.
|
||||||
|
|
||||||
There are many reasons for using Master Password instead of an ordinary password manager, read below for the details, but if you want my personal favourites, they would be:
|
There are many reasons for using Master Password instead of an ordinary password manager, read below for the details, but if you want my personal favourites, they would be:
|
||||||
@@ -90,7 +83,7 @@ Master Password is *not* a password manager. It does not store your website pas
|
|||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
The details of how Master Password works [are available here](http://masterpasswordapp.com/algorithm.html).
|
The details of how Master Password works [are available here](https://masterpassword.app/masterpassword-algorithm.pdf).
|
||||||
|
|
||||||
In short:
|
In short:
|
||||||
|
|
||||||
@@ -136,7 +129,7 @@ Please don't hesitate to [get in touch](#support), we're more than happy to answ
|
|||||||
|
|
||||||
# Source Code
|
# Source Code
|
||||||
|
|
||||||
Master Password's algorithm is [documented](http://masterpasswordapp.com/algorithm.html) and its implementation is Free Software (GPLv3).
|
Master Password's algorithm is [documented](https://masterpassword.app/masterpassword-algorithm.pdf) and its implementation is [Free Software (GPLv3)](LICENSE).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -194,49 +187,7 @@ Note that in order to build the Android application, you will need to have the A
|
|||||||
|
|
||||||
Go into the `platform-independent/cli-c` directory and run `./build`. The native command-line client will then be built.
|
Go into the `platform-independent/cli-c` directory and run `./build`. The native command-line client will then be built.
|
||||||
|
|
||||||
When the build completes, you will have an `mpw` binary you can use. You can copy it into your `PATH` or use the `./install` script to help you do so.
|
For detailed instructions, see [the native CLI instructions](platform-independent/cli-c/README.md).
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
./build && sudo ./install
|
|
||||||
mpw -h
|
|
||||||
|
|
||||||
Normally, this is all you need to do, however note that there are a few dependencies that need to be met, depending on which targets you are building (by default, only the `mpw` target is built):
|
|
||||||
|
|
||||||
- `mpw`
|
|
||||||
|
|
||||||
The C implementation depends either on `libsodium` or Tarsnap's `scrypt` and `openssl-dev`.
|
|
||||||
|
|
||||||
We recommend you install `libsodium`. If `libsodium` is not installed when `./build` is executed, the script will try to download and statically link Tarsnap's `scrypt` instead. Tarsnap's `scrypt` depends on you having `openssl-dev` installed.
|
|
||||||
|
|
||||||
If you have `mpw_color` enabled (it is enabled by default), the build also depends on `ncurses-dev` to communicate with the terminal.
|
|
||||||
|
|
||||||
- `mpw-bench`
|
|
||||||
|
|
||||||
This tool compares the performance of a few cryptographic algorithms, including bcrypt. The `./build` script will try to automatically download and statically link `bcrypt`.
|
|
||||||
|
|
||||||
- `mpw-tests`
|
|
||||||
|
|
||||||
This tool runs a suite of tests to ensure the correct passwords are being generated by the algorithm under various circumstances. The test suite is declared in `mpw-tests.xml` which needs to exist in the current working directory when running the tool. In addition, `libxml2` is used to parse the file, so this target depends on you having it installed when running `./build`.
|
|
||||||
|
|
||||||
|
|
||||||
Finally, there are a few different ways you can modify the build process.
|
|
||||||
|
|
||||||
To build additional targets, set the `targets` environment variable:
|
|
||||||
|
|
||||||
targets='mpw mpw-tests' ./build
|
|
||||||
|
|
||||||
To pass additional compiler arguments, eg. add a library search path, pass them as arguments to the script:
|
|
||||||
|
|
||||||
./build -L/usr/local/lib
|
|
||||||
|
|
||||||
There are a few toggleable features, to change them, pass them as environment variables:
|
|
||||||
|
|
||||||
mpw_color=0 ./build
|
|
||||||
|
|
||||||
Currently, there is only one toggleable feature:
|
|
||||||
|
|
||||||
- `mpw_color`: [default: 1] Colorized Identicon, depends on `ncurses-dev`.
|
|
||||||
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
home=http://www.openwall.com/crypt/
|
|
||||||
pkg=http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz
|
|
||||||
pkg_sha256=83fa01fca6996fe8d882b7f8e9ba0305a5664936100b01481ea3c6a8ce8d72fd
|
|
||||||
patches=(arm)
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
--- x86.S 2014-11-21 09:09:58.000000000 -0500
|
|
||||||
+++ x86.S 2014-11-21 09:11:01.000000000 -0500
|
|
||||||
@@ -199,5 +199,9 @@
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__ELF__) && defined(__linux__)
|
|
||||||
+#if defined(__arm__)
|
|
||||||
+.section .note.GNU-stack,"",%progbits
|
|
||||||
+#else
|
|
||||||
.section .note.GNU-stack,"",@progbits
|
|
||||||
#endif
|
|
||||||
+#endif
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
home=http://www.tarsnap.com/scrypt.html
|
|
||||||
git=https://github.com/Tarsnap/scrypt.git
|
|
||||||
pkg=https://www.tarsnap.com/scrypt/scrypt-1.2.1.tgz
|
|
||||||
pkg_sha256=4621f5e7da2f802e20850436219370092e9fcda93bd598f6d4236cce33f4c577
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
diff -ruN /Users/lhunath/.src/scrypt/Makefile ./Makefile
|
|
||||||
--- /Users/lhunath/.src/scrypt/Makefile 2014-05-02 11:28:58.000000000 -0400
|
|
||||||
+++ ./Makefile 2014-05-02 12:07:27.000000000 -0400
|
|
||||||
@@ -2,11 +2,11 @@
|
|
||||||
VER?= nosse
|
|
||||||
SRCS= main.c
|
|
||||||
LDADD+= -lcrypto
|
|
||||||
-WARNS?= 6
|
|
||||||
+WARNS?= 0
|
|
||||||
|
|
||||||
# We have a config file for FreeBSD
|
|
||||||
CFLAGS += -I .
|
|
||||||
-CFLAGS += -DCONFIG_H_FILE=\"config_freebsd.h\"
|
|
||||||
+CFLAGS += -DCONFIG_H_FILE=\"config_osx.h\"
|
|
||||||
|
|
||||||
# Include all possible object files containing built scrypt code.
|
|
||||||
CLEANFILES += crypto_scrypt-ref.o
|
|
||||||
diff -ruN /Users/lhunath/.src/scrypt/lib/util/memlimit.c ./lib/util/memlimit.c
|
|
||||||
--- /Users/lhunath/.src/scrypt/lib/util/memlimit.c 2014-05-02 11:28:58.000000000 -0400
|
|
||||||
+++ ./lib/util/memlimit.c 2014-05-02 11:52:42.000000000 -0400
|
|
||||||
@@ -75,7 +75,7 @@
|
|
||||||
* have returned to us.
|
|
||||||
*/
|
|
||||||
if (usermemlen == sizeof(uint64_t))
|
|
||||||
- usermem = *(uint64_t *)usermembuf;
|
|
||||||
+ usermem = *(uint64_t *)(void *)usermembuf;
|
|
||||||
else if (usermemlen == sizeof(uint32_t))
|
|
||||||
usermem = SIZE_MAX;
|
|
||||||
else
|
|
||||||
diff -ruN /Users/lhunath/.src/scrypt/lib/util/memlimit.c ./lib/util/memlimit.c
|
|
||||||
--- /Users/lhunath/.src/scrypt/config_osx.h 1969-12-31 19:00:00.000000000 -0500
|
|
||||||
+++ config_osx.h 2014-05-02 12:06:55.000000000 -0400
|
|
||||||
@@ -0,0 +1,5 @@
|
|
||||||
+/* A default configuration for FreeBSD, used if there is no config.h. */
|
|
||||||
+
|
|
||||||
+#define HAVE_POSIX_MEMALIGN 1
|
|
||||||
+#define HAVE_SYSCTL_HW_USERMEM 1
|
|
||||||
+#define HAVE_SYS_PARAM_H 1
|
|
||||||
@@ -1,447 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#if MPW_COLOR
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <curses.h>
|
|
||||||
#include <term.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if HAS_CPERCIVA
|
|
||||||
#include <scrypt/crypto_scrypt.h>
|
|
||||||
#include <scrypt/sha256.h>
|
|
||||||
#elif HAS_SODIUM
|
|
||||||
#include "sodium.h"
|
|
||||||
#ifdef SODIUM_LIBRARY_MINIMAL
|
|
||||||
#include "crypto_stream_aes128ctr.h"
|
|
||||||
#include "crypto_kdf_blake2b.h"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "mpw-util.h"
|
|
||||||
|
|
||||||
int mpw_verbosity = inf_level;
|
|
||||||
|
|
||||||
bool mpw_push_buf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) {
|
|
||||||
|
|
||||||
if (!buffer || !bufferSize || !pushBuffer || !pushSize)
|
|
||||||
return false;
|
|
||||||
if (*bufferSize == ERR)
|
|
||||||
// The buffer was marked as broken, it is missing a previous push. Abort to avoid corrupt content.
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!mpw_realloc( buffer, bufferSize, pushSize )) {
|
|
||||||
// realloc failed, we can't push. Mark the buffer as broken.
|
|
||||||
mpw_free( *buffer, *bufferSize );
|
|
||||||
*bufferSize = (size_t)ERR;
|
|
||||||
*buffer = NULL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *bufferOffset = *buffer + *bufferSize - pushSize;
|
|
||||||
memcpy( bufferOffset, pushBuffer, pushSize );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpw_push_string(uint8_t **const buffer, size_t *const bufferSize, const char *pushString) {
|
|
||||||
|
|
||||||
return pushString && mpw_push_buf( buffer, bufferSize, pushString, strlen( pushString ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpw_string_push(char **const string, const char *pushString) {
|
|
||||||
|
|
||||||
if (!*string)
|
|
||||||
*string = calloc( 1, sizeof( char ) );
|
|
||||||
|
|
||||||
size_t stringLength = strlen( *string );
|
|
||||||
return pushString && mpw_push_buf( (uint8_t **const)string, &stringLength, pushString, strlen( pushString ) + 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpw_string_pushf(char **const string, const char *pushFormat, ...) {
|
|
||||||
|
|
||||||
va_list args;
|
|
||||||
va_start( args, pushFormat );
|
|
||||||
char *pushString = NULL;
|
|
||||||
bool success = vasprintf( &pushString, pushFormat, args ) >= 0 && mpw_string_push( string, pushString );
|
|
||||||
va_end( args );
|
|
||||||
mpw_free_string( pushString );
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpw_push_int(uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt) {
|
|
||||||
|
|
||||||
return mpw_push_buf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
bool __mpw_realloc(void **buffer, size_t *bufferSize, const size_t deltaSize) {
|
|
||||||
|
|
||||||
if (!buffer)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
void *newBuffer = realloc( *buffer, (bufferSize? *bufferSize: 0) + deltaSize );
|
|
||||||
if (!newBuffer)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*buffer = newBuffer;
|
|
||||||
if (bufferSize)
|
|
||||||
*bufferSize += deltaSize;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpw_free(const void *buffer, const size_t bufferSize) {
|
|
||||||
|
|
||||||
if (!buffer)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
memset( (void *)buffer, 0, bufferSize );
|
|
||||||
free( (void *)buffer );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpw_free_string(const char *string) {
|
|
||||||
|
|
||||||
return string && mpw_free( string, strlen( string ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t const *mpw_kdf_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
|
||||||
uint64_t N, uint32_t r, uint32_t p) {
|
|
||||||
|
|
||||||
if (!secret || !salt)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
uint8_t *key = malloc( keySize );
|
|
||||||
if (!key)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
#if HAS_CPERCIVA
|
|
||||||
if (crypto_scrypt( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) < 0) {
|
|
||||||
mpw_free( key, keySize );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#elif HAS_SODIUM
|
|
||||||
if (crypto_pwhash_scryptsalsa208sha256_ll( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) != 0) {
|
|
||||||
mpw_free( key, keySize );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#error No crypto support for mpw_scrypt.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t const *mpw_kdf_blake2b(const size_t subkeySize, const uint8_t *key, const size_t keySize,
|
|
||||||
const uint8_t *context, const size_t contextSize, const uint64_t id, const char *personal) {
|
|
||||||
|
|
||||||
if (!key || !keySize || !subkeySize) {
|
|
||||||
errno = EINVAL;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *subkey = malloc( subkeySize );
|
|
||||||
if (!subkey)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
#if HAS_SODIUM
|
|
||||||
if (keySize < crypto_generichash_blake2b_KEYBYTES_MIN || keySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
|
|
||||||
subkeySize < crypto_generichash_blake2b_KEYBYTES_MIN || subkeySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
|
|
||||||
contextSize < crypto_generichash_blake2b_BYTES_MIN || contextSize > crypto_generichash_blake2b_BYTES_MAX ||
|
|
||||||
(personal && strlen( personal ) > crypto_generichash_blake2b_PERSONALBYTES)) {
|
|
||||||
errno = EINVAL;
|
|
||||||
free( subkey );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t saltBuf[crypto_generichash_blake2b_SALTBYTES];
|
|
||||||
bzero( saltBuf, sizeof saltBuf );
|
|
||||||
if (id) {
|
|
||||||
uint64_t id_n = htonll( id );
|
|
||||||
memcpy( saltBuf, &id_n, sizeof id_n );
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t personalBuf[crypto_generichash_blake2b_PERSONALBYTES];
|
|
||||||
bzero( personalBuf, sizeof saltBuf );
|
|
||||||
if (personal && strlen( personal ))
|
|
||||||
memcpy( personalBuf, personal, strlen( personal ) );
|
|
||||||
|
|
||||||
if (crypto_generichash_blake2b_salt_personal( subkey, subkeySize, context, contextSize, key, keySize, saltBuf, personalBuf ) != 0) {
|
|
||||||
mpw_free( subkey, subkeySize );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#error No crypto support for mpw_kdf_blake2b.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return subkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t const *mpw_hash_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *message, const size_t messageSize) {
|
|
||||||
|
|
||||||
if (!key || !keySize || !message || !messageSize)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
#if HAS_CPERCIVA
|
|
||||||
uint8_t *const mac = malloc( 32 );
|
|
||||||
if (!mac)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
HMAC_SHA256_Buf( key, keySize, message, messageSize, mac );
|
|
||||||
#elif HAS_SODIUM
|
|
||||||
uint8_t *const mac = malloc( crypto_auth_hmacsha256_BYTES );
|
|
||||||
if (!mac)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
crypto_auth_hmacsha256_state state;
|
|
||||||
if (crypto_auth_hmacsha256_init( &state, key, keySize ) != 0 ||
|
|
||||||
crypto_auth_hmacsha256_update( &state, message, messageSize ) != 0 ||
|
|
||||||
crypto_auth_hmacsha256_final( &state, mac ) != 0) {
|
|
||||||
mpw_free( mac, crypto_auth_hmacsha256_BYTES );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#error No crypto support for mpw_hmac_sha256.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return mac;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t keySize, const uint8_t *buf, const size_t bufSize) {
|
|
||||||
|
|
||||||
#if HAS_SODIUM
|
|
||||||
if (!key || keySize < crypto_stream_KEYBYTES)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
uint8_t nonce[crypto_stream_NONCEBYTES];
|
|
||||||
bzero( (void *)nonce, sizeof( nonce ) );
|
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
if (encrypt) {
|
|
||||||
uint8_t *const cipherBuf = malloc( bufSize );
|
|
||||||
if (crypto_stream_aes128ctr_xor( cipherBuf, buf, bufSize, nonce, key ) != 0) {
|
|
||||||
mpw_free( cipherBuf, bufSize );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return cipherBuf;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
uint8_t *const plainBuf = malloc( bufSize );
|
|
||||||
if (crypto_stream_aes128ctr( plainBuf, bufSize, nonce, key ) != 0) {
|
|
||||||
mpw_free( plainBuf, bufSize );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
for (size_t c = 0; c < bufSize; ++c)
|
|
||||||
plainBuf[c] = buf[c] ^ plainBuf[c];
|
|
||||||
return plainBuf;
|
|
||||||
}
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#else
|
|
||||||
#error No crypto support for mpw_aes.
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t const *mpw_aes_encrypt(const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, const size_t bufSize) {
|
|
||||||
|
|
||||||
return mpw_aes( true, key, keySize, plainBuf, bufSize );
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t const *mpw_aes_decrypt(const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, const size_t bufSize) {
|
|
||||||
|
|
||||||
return mpw_aes( false, key, keySize, cipherBuf, bufSize );
|
|
||||||
}
|
|
||||||
|
|
||||||
MPKeyID mpw_id_buf(const void *buf, size_t length) {
|
|
||||||
|
|
||||||
if (!buf)
|
|
||||||
return "<unset>";
|
|
||||||
|
|
||||||
#if HAS_CPERCIVA
|
|
||||||
uint8_t hash[32];
|
|
||||||
SHA256_Buf( buf, length, hash );
|
|
||||||
#elif HAS_SODIUM
|
|
||||||
uint8_t hash[crypto_hash_sha256_BYTES];
|
|
||||||
crypto_hash_sha256( hash, buf, length );
|
|
||||||
#else
|
|
||||||
#error No crypto support for mpw_id_buf.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return mpw_hex( hash, sizeof( hash ) / sizeof( uint8_t ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpw_id_buf_equals(const char *id1, const char *id2) {
|
|
||||||
|
|
||||||
size_t size = strlen( id1 );
|
|
||||||
if (size != strlen( id2 ))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (size_t c = 0; c < size; ++c)
|
|
||||||
if (tolower( id1[c] ) != tolower( id2[c] ))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *str_str;
|
|
||||||
|
|
||||||
const char *mpw_str(const char *format, ...) {
|
|
||||||
|
|
||||||
va_list args;
|
|
||||||
va_start( args, format );
|
|
||||||
vasprintf( &str_str, format, args );
|
|
||||||
va_end( args );
|
|
||||||
|
|
||||||
return str_str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char **mpw_hex_buf;
|
|
||||||
static unsigned int mpw_hex_buf_i;
|
|
||||||
|
|
||||||
const char *mpw_hex(const void *buf, size_t length) {
|
|
||||||
|
|
||||||
// FIXME: Not thread-safe
|
|
||||||
if (!mpw_hex_buf)
|
|
||||||
mpw_hex_buf = calloc( 10, sizeof( char * ) );
|
|
||||||
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
|
|
||||||
|
|
||||||
if (mpw_realloc( &mpw_hex_buf[mpw_hex_buf_i], NULL, length * 2 + 1 ))
|
|
||||||
for (size_t kH = 0; kH < length; kH++)
|
|
||||||
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
|
|
||||||
|
|
||||||
return mpw_hex_buf[mpw_hex_buf_i];
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *mpw_hex_l(uint32_t number) {
|
|
||||||
|
|
||||||
return mpw_hex( &number, sizeof( number ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef COLOR
|
|
||||||
static int putvari;
|
|
||||||
static char *putvarc = NULL;
|
|
||||||
static int termsetup;
|
|
||||||
static int initputvar() {
|
|
||||||
if (!isatty(STDERR_FILENO))
|
|
||||||
return 0;
|
|
||||||
if (putvarc)
|
|
||||||
free( putvarc );
|
|
||||||
if (!termsetup) {
|
|
||||||
int status;
|
|
||||||
if (! (termsetup = (setupterm( NULL, STDERR_FILENO, &status ) == 0 && status == 1))) {
|
|
||||||
wrn( "Terminal doesn't support color (setupterm errno %d).\n", status );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
putvarc=(char *)calloc(256, sizeof(char));
|
|
||||||
putvari=0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
static int putvar(int c) {
|
|
||||||
putvarc[putvari++]=c;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char *mpw_identicon(const char *fullName, const char *masterPassword) {
|
|
||||||
|
|
||||||
const char *leftArm[] = { "╔", "╚", "╰", "═" };
|
|
||||||
const char *rightArm[] = { "╗", "╝", "╯", "═" };
|
|
||||||
const char *body[] = { "█", "░", "▒", "▓", "☺", "☻" };
|
|
||||||
const char *accessory[] = {
|
|
||||||
"◈", "◎", "◐", "◑", "◒", "◓", "☀", "☁", "☂", "☃", "☄", "★", "☆", "☎", "☏", "⎈", "⌂", "☘", "☢", "☣",
|
|
||||||
"☕", "⌚", "⌛", "⏰", "⚡", "⛄", "⛅", "☔", "♔", "♕", "♖", "♗", "♘", "♙", "♚", "♛", "♜", "♝", "♞", "♟",
|
|
||||||
"♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌"
|
|
||||||
};
|
|
||||||
|
|
||||||
const uint8_t *identiconSeed = mpw_hash_hmac_sha256( (const uint8_t *)masterPassword, strlen( masterPassword ),
|
|
||||||
(const uint8_t *)fullName,
|
|
||||||
strlen( fullName ) );
|
|
||||||
if (!identiconSeed)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
char *colorString, *resetString;
|
|
||||||
#ifdef COLOR
|
|
||||||
if (initputvar()) {
|
|
||||||
uint8_t colorIdentifier = (uint8_t)(identiconSeed[4] % 7 + 1);
|
|
||||||
tputs(tparm(tgetstr("AF", NULL), colorIdentifier), 1, putvar);
|
|
||||||
colorString = calloc(strlen(putvarc) + 1, sizeof(char));
|
|
||||||
strcpy(colorString, putvarc);
|
|
||||||
tputs(tgetstr("me", NULL), 1, putvar);
|
|
||||||
resetString = calloc(strlen(putvarc) + 1, sizeof(char));
|
|
||||||
strcpy(resetString, putvarc);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
colorString = calloc( 1, sizeof( char ) );
|
|
||||||
resetString = calloc( 1, sizeof( char ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
char *identicon = (char *)calloc( 256, sizeof( char ) );
|
|
||||||
snprintf( identicon, 256, "%s%s%s%s%s%s",
|
|
||||||
colorString,
|
|
||||||
leftArm[identiconSeed[0] % (sizeof( leftArm ) / sizeof( leftArm[0] ))],
|
|
||||||
body[identiconSeed[1] % (sizeof( body ) / sizeof( body[0] ))],
|
|
||||||
rightArm[identiconSeed[2] % (sizeof( rightArm ) / sizeof( rightArm[0] ))],
|
|
||||||
accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
|
|
||||||
resetString );
|
|
||||||
|
|
||||||
mpw_free( identiconSeed, 32 );
|
|
||||||
free( colorString );
|
|
||||||
free( resetString );
|
|
||||||
return identicon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the amount of bytes used by UTF-8 to encode a single character that starts with the given byte.
|
|
||||||
*/
|
|
||||||
static int mpw_utf8_sizeof(unsigned char utf8Byte) {
|
|
||||||
|
|
||||||
if (!utf8Byte)
|
|
||||||
return 0;
|
|
||||||
if ((utf8Byte & 0x80) == 0)
|
|
||||||
return 1;
|
|
||||||
if ((utf8Byte & 0xC0) != 0xC0)
|
|
||||||
return 0;
|
|
||||||
if ((utf8Byte & 0xE0) == 0xC0)
|
|
||||||
return 2;
|
|
||||||
if ((utf8Byte & 0xF0) == 0xE0)
|
|
||||||
return 3;
|
|
||||||
if ((utf8Byte & 0xF8) == 0xF0)
|
|
||||||
return 4;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t mpw_utf8_strlen(const char *utf8String) {
|
|
||||||
|
|
||||||
size_t charlen = 0;
|
|
||||||
char *remainingString = (char *)utf8String;
|
|
||||||
for (int charByteSize; (charByteSize = mpw_utf8_sizeof( (unsigned char)*remainingString )); remainingString += charByteSize)
|
|
||||||
++charlen;
|
|
||||||
|
|
||||||
return charlen;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
}
|
|
||||||
|
|
||||||
description = 'Master Password Algorithm Implementation'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile (group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.6-p10') {
|
|
||||||
exclude( module: 'joda-time' )
|
|
||||||
}
|
|
||||||
|
|
||||||
compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0'
|
|
||||||
compile group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
|
||||||
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
|
||||||
<parent>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Master Password Algorithm Implementation</name>
|
|
||||||
<description>The implementation of the Master Password algorithm</description>
|
|
||||||
|
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<!-- DEPENDENCY MANAGEMENT -->
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- PROJECT REFERENCES -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
|
||||||
<artifactId>opal-system</artifactId>
|
|
||||||
<version>1.6-p9</version>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>joda-time</groupId>
|
|
||||||
<artifactId>joda-time</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- EXTERNAL DEPENDENCIES -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.lambdaworks</groupId>
|
|
||||||
<artifactId>scrypt</artifactId>
|
|
||||||
<version>1.4.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
|
||||||
import com.lyndir.lhunath.opal.system.MessageDigests;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 2016-10-29
|
|
||||||
*/
|
|
||||||
public final class MPConstant {
|
|
||||||
|
|
||||||
/* Environment */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: default user name if one is not provided.
|
|
||||||
*/
|
|
||||||
public static final String env_userName = "MP_USERNAME";
|
|
||||||
/**
|
|
||||||
* mpw: default site type if one is not provided.
|
|
||||||
*
|
|
||||||
* @see MPSiteType#forOption(String)
|
|
||||||
*/
|
|
||||||
public static final String env_siteType = "MP_SITETYPE";
|
|
||||||
/**
|
|
||||||
* mpw: default site counter value if one is not provided.
|
|
||||||
*/
|
|
||||||
public static final String env_siteCounter = "MP_SITECOUNTER";
|
|
||||||
/**
|
|
||||||
* mpw: default path to look for run configuration files if the platform default is not desired.
|
|
||||||
*/
|
|
||||||
public static final String env_rcDir = "MP_RCDIR";
|
|
||||||
/**
|
|
||||||
* mpw: permit automatic update checks.
|
|
||||||
*/
|
|
||||||
public static final String env_checkUpdates = "MP_CHECKUPDATES";
|
|
||||||
|
|
||||||
/* Algorithm */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scrypt: CPU cost parameter.
|
|
||||||
*/
|
|
||||||
public static final int scrypt_N = 32768;
|
|
||||||
/**
|
|
||||||
* scrypt: Memory cost parameter.
|
|
||||||
*/
|
|
||||||
public static final int scrypt_r = 8;
|
|
||||||
/**
|
|
||||||
* scrypt: Parallelization parameter.
|
|
||||||
*/
|
|
||||||
public static final int scrypt_p = 2;
|
|
||||||
/**
|
|
||||||
* mpw: Master key size (byte).
|
|
||||||
*/
|
|
||||||
public static final int mpw_dkLen = 64;
|
|
||||||
/**
|
|
||||||
* mpw: Input character encoding.
|
|
||||||
*/
|
|
||||||
public static final Charset mpw_charset = Charsets.UTF_8;
|
|
||||||
/**
|
|
||||||
* mpw: Platform-agnostic byte order.
|
|
||||||
*/
|
|
||||||
public static final ByteOrder mpw_byteOrder = ByteOrder.BIG_ENDIAN;
|
|
||||||
/**
|
|
||||||
* mpw: Site digest.
|
|
||||||
*/
|
|
||||||
public static final MessageAuthenticationDigests mpw_digest = MessageAuthenticationDigests.HmacSHA256;
|
|
||||||
/**
|
|
||||||
* mpw: Key ID hash.
|
|
||||||
*/
|
|
||||||
public static final MessageDigests mpw_hash = MessageDigests.SHA256;
|
|
||||||
/**
|
|
||||||
* mpw: validity for the time-based rolling counter.
|
|
||||||
*/
|
|
||||||
public static final int mpw_counter_timeout = 5 * 60 /* s */;
|
|
||||||
|
|
||||||
|
|
||||||
public static final int MS_PER_S = 1000;
|
|
||||||
}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import java.util.*;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NonNls;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <i>07 04, 2012</i>
|
|
||||||
*
|
|
||||||
* @author lhunath
|
|
||||||
*/
|
|
||||||
public enum MPSiteType {
|
|
||||||
|
|
||||||
GeneratedMaximum( "Max", "20 characters, contains symbols.", //
|
|
||||||
ImmutableList.of( "x", "max", "maximum" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0x0 ),
|
|
||||||
|
|
||||||
GeneratedLong( "Long", "Copy-friendly, 14 characters, contains symbols.", //
|
|
||||||
ImmutableList.of( "l", "long" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
|
|
||||||
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
|
|
||||||
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
|
|
||||||
new MPTemplate( "CvcvnoCvccCvcv" ), new MPTemplate( "CvcvCvccnoCvcv" ),
|
|
||||||
new MPTemplate( "CvcvCvccCvcvno" ), new MPTemplate( "CvcvnoCvcvCvcc" ),
|
|
||||||
new MPTemplate( "CvcvCvcvnoCvcc" ), new MPTemplate( "CvcvCvcvCvccno" ),
|
|
||||||
new MPTemplate( "CvccnoCvccCvcv" ), new MPTemplate( "CvccCvccnoCvcv" ),
|
|
||||||
new MPTemplate( "CvccCvccCvcvno" ), new MPTemplate( "CvcvnoCvccCvcc" ),
|
|
||||||
new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
|
|
||||||
new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
|
|
||||||
new MPTemplate( "CvccCvcvCvccno" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0x1 ),
|
|
||||||
|
|
||||||
GeneratedMedium( "Medium", "Copy-friendly, 8 characters, contains symbols.", //
|
|
||||||
ImmutableList.of( "m", "med", "medium" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0x2 ),
|
|
||||||
|
|
||||||
GeneratedBasic( "Basic", "8 characters, no symbols.", //
|
|
||||||
ImmutableList.of( "b", "basic" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0x3 ),
|
|
||||||
|
|
||||||
GeneratedShort( "Short", "Copy-friendly, 4 characters, no symbols.", //
|
|
||||||
ImmutableList.of( "s", "short" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0x4 ),
|
|
||||||
|
|
||||||
GeneratedPIN( "PIN", "4 numbers.", //
|
|
||||||
ImmutableList.of( "i", "pin" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0x5 ),
|
|
||||||
|
|
||||||
GeneratedName( "Name", "9 letter name.", //
|
|
||||||
ImmutableList.of( "n", "name" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0xE ),
|
|
||||||
|
|
||||||
GeneratedPhrase( "Phrase", "20 character sentence.", //
|
|
||||||
ImmutableList.of( "p", "phrase" ), // NON-NLS
|
|
||||||
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
|
|
||||||
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
|
|
||||||
MPSiteTypeClass.Generated, 0xF ),
|
|
||||||
|
|
||||||
StoredPersonal( "Personal", "AES-encrypted, exportable.", //
|
|
||||||
ImmutableList.of( "personal" ), // NON-NLS
|
|
||||||
ImmutableList.<MPTemplate>of(), //
|
|
||||||
MPSiteTypeClass.Stored, 0x0, MPSiteFeature.ExportContent ),
|
|
||||||
|
|
||||||
StoredDevicePrivate( "Device", "AES-encrypted, not exported.", //
|
|
||||||
ImmutableList.of( "device" ), // NON-NLS
|
|
||||||
ImmutableList.<MPTemplate>of(), //
|
|
||||||
MPSiteTypeClass.Stored, 0x1, MPSiteFeature.DevicePrivate );
|
|
||||||
|
|
||||||
static final Logger logger = Logger.get( MPSiteType.class );
|
|
||||||
|
|
||||||
private final String shortName;
|
|
||||||
private final String description;
|
|
||||||
private final List<String> options;
|
|
||||||
private final List<MPTemplate> templates;
|
|
||||||
private final MPSiteTypeClass typeClass;
|
|
||||||
private final int typeIndex;
|
|
||||||
private final Set<MPSiteFeature> typeFeatures;
|
|
||||||
|
|
||||||
MPSiteType(final String shortName, final String description, final List<String> options, final List<MPTemplate> templates,
|
|
||||||
final MPSiteTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
|
|
||||||
|
|
||||||
this.shortName = shortName;
|
|
||||||
this.description = description;
|
|
||||||
this.options = options;
|
|
||||||
this.templates = templates;
|
|
||||||
this.typeClass = typeClass;
|
|
||||||
this.typeIndex = typeIndex;
|
|
||||||
|
|
||||||
ImmutableSet.Builder<MPSiteFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
|
||||||
for (final MPSiteFeature typeFeature : typeFeatures) {
|
|
||||||
typeFeaturesBuilder.add( typeFeature );
|
|
||||||
}
|
|
||||||
this.typeFeatures = typeFeaturesBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShortName() {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getOptions() {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPSiteTypeClass getTypeClass() {
|
|
||||||
|
|
||||||
return typeClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<MPSiteFeature> getTypeFeatures() {
|
|
||||||
|
|
||||||
return typeFeatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
|
||||||
int mask = typeIndex | typeClass.getMask();
|
|
||||||
for (final MPSiteFeature typeFeature : typeFeatures)
|
|
||||||
mask |= typeFeature.getMask();
|
|
||||||
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param option The option to select a type with. It is matched case insensitively.
|
|
||||||
*
|
|
||||||
* @return The type registered for the given option.
|
|
||||||
*/
|
|
||||||
public static MPSiteType forOption(final String option) {
|
|
||||||
|
|
||||||
for (final MPSiteType type : values())
|
|
||||||
if (type.getOptions().contains( option.toLowerCase( Locale.ROOT ) ))
|
|
||||||
return type;
|
|
||||||
|
|
||||||
throw logger.bug( "No type for option: %s", option );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name The name fromInt the type to look up. It is matched case insensitively.
|
|
||||||
*
|
|
||||||
* @return The type registered with the given name.
|
|
||||||
*/
|
|
||||||
@Contract("!null -> !null")
|
|
||||||
public static MPSiteType forName(@Nullable final String name) {
|
|
||||||
|
|
||||||
if (name == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
for (final MPSiteType type : values())
|
|
||||||
if (type.name().equalsIgnoreCase( name ))
|
|
||||||
return type;
|
|
||||||
|
|
||||||
throw logger.bug( "No type for name: %s", name );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param typeClass The class for which we look up types.
|
|
||||||
*
|
|
||||||
* @return All types that support the given class.
|
|
||||||
*/
|
|
||||||
public static ImmutableList<MPSiteType> forClass(final MPSiteTypeClass typeClass) {
|
|
||||||
|
|
||||||
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder();
|
|
||||||
for (final MPSiteType type : values())
|
|
||||||
if (type.getTypeClass() == typeClass)
|
|
||||||
types.add( type );
|
|
||||||
|
|
||||||
return types.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param type The type for which we look up types.
|
|
||||||
*
|
|
||||||
* @return The type registered with the given type.
|
|
||||||
*/
|
|
||||||
public static MPSiteType forType(final int type) {
|
|
||||||
|
|
||||||
for (final MPSiteType siteType : values())
|
|
||||||
if (siteType.getType() == type)
|
|
||||||
return siteType;
|
|
||||||
|
|
||||||
throw logger.bug( "No type: %s", type );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mask The mask for which we look up types.
|
|
||||||
*
|
|
||||||
* @return All types that support the given mask.
|
|
||||||
*/
|
|
||||||
public static ImmutableList<MPSiteType> forMask(final int mask) {
|
|
||||||
|
|
||||||
int typeMask = mask & ~0xF;
|
|
||||||
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder();
|
|
||||||
for (final MPSiteType siteType : values())
|
|
||||||
if (((siteType.getType() & ~0xF) & typeMask) != 0)
|
|
||||||
types.add( siteType );
|
|
||||||
|
|
||||||
return types.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPTemplate getTemplateAtRollingIndex(final int templateIndex) {
|
|
||||||
return templates.get( templateIndex % templates.size() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NonNls;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-02
|
|
||||||
*/
|
|
||||||
public enum MPSiteVariant {
|
|
||||||
Password( "Generate a key for authentication.", "Doesn't currently use a context.", //
|
|
||||||
ImmutableList.of( "p", "password" ), "com.lyndir.masterpassword" ), // NON-NLS
|
|
||||||
Login( "Generate a name for identification.", "Doesn't currently use a context.", //
|
|
||||||
ImmutableList.of( "l", "login" ), "com.lyndir.masterpassword.login" ), // NON-NLS
|
|
||||||
Answer( "Generate an answer to a security question.", "Empty for a universal site answer or\nthe most significant word(s) of the question.", //
|
|
||||||
ImmutableList.of( "a", "answer" ), "com.lyndir.masterpassword.answer" ); // NON-NLS
|
|
||||||
|
|
||||||
static final Logger logger = Logger.get( MPSiteType.class );
|
|
||||||
|
|
||||||
private final String description;
|
|
||||||
private final String contextDescription;
|
|
||||||
private final List<String> options;
|
|
||||||
private final String scope;
|
|
||||||
|
|
||||||
MPSiteVariant(final String description, final String contextDescription, final List<String> options, @NonNls final String scope) {
|
|
||||||
this.contextDescription = contextDescription;
|
|
||||||
|
|
||||||
this.options = options;
|
|
||||||
this.description = description;
|
|
||||||
this.scope = scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContextDescription() {
|
|
||||||
return contextDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getOptions() {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getScope() {
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param option The option to select a variant with. It is matched case insensitively.
|
|
||||||
*
|
|
||||||
* @return The variant registered for the given option.
|
|
||||||
*/
|
|
||||||
public static MPSiteVariant forOption(final String option) {
|
|
||||||
|
|
||||||
for (final MPSiteVariant variant : values())
|
|
||||||
if (variant.getOptions().contains( option.toLowerCase( Locale.ROOT ) ))
|
|
||||||
return variant;
|
|
||||||
|
|
||||||
throw logger.bug( "No variant for option: %s", option );
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param name The name fromInt the variant to look up. It is matched case insensitively.
|
|
||||||
*
|
|
||||||
* @return The variant registered with the given name.
|
|
||||||
*/
|
|
||||||
@Contract("!null -> !null")
|
|
||||||
public static MPSiteVariant forName(@Nullable final String name) {
|
|
||||||
|
|
||||||
if (name == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
for (final MPSiteVariant type : values())
|
|
||||||
if (type.name().equalsIgnoreCase( name ))
|
|
||||||
return type;
|
|
||||||
|
|
||||||
throw logger.bug( "No variant for name: %s", name );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
|
||||||
import com.lyndir.lhunath.opal.system.*;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 2014-08-30
|
|
||||||
*/
|
|
||||||
public abstract class MasterKey {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MasterKey.class );
|
|
||||||
private static boolean allowNativeByDefault = true;
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private final String fullName;
|
|
||||||
private boolean allowNative = allowNativeByDefault;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private byte[] masterKey;
|
|
||||||
|
|
||||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
|
||||||
public static MasterKey create(final String fullName, final char[] masterPassword) {
|
|
||||||
|
|
||||||
return create( Version.CURRENT, fullName, masterPassword );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
|
||||||
public static MasterKey create(final Version version, final String fullName, final char[] masterPassword) {
|
|
||||||
|
|
||||||
switch (version) {
|
|
||||||
case V0:
|
|
||||||
return new MasterKeyV0( fullName ).revalidate( masterPassword );
|
|
||||||
case V1:
|
|
||||||
return new MasterKeyV1( fullName ).revalidate( masterPassword );
|
|
||||||
case V2:
|
|
||||||
return new MasterKeyV2( fullName ).revalidate( masterPassword );
|
|
||||||
case V3:
|
|
||||||
return new MasterKeyV3( fullName ).revalidate( masterPassword );
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException( strf( "Unsupported version: %s", version ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isAllowNativeByDefault() {
|
|
||||||
return allowNativeByDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Native libraries are useful for speeding up the performance of cryptographical functions.
|
|
||||||
* Sometimes, however, we may prefer to use Java-only code.
|
|
||||||
* For instance, for auditability / trust or because the native code doesn't work on our CPU/platform.
|
|
||||||
* <p/>
|
|
||||||
* This setter affects the default setting for any newly created {@link MasterKey}s.
|
|
||||||
*
|
|
||||||
* @param allowNative false to disallow the use of native libraries.
|
|
||||||
*/
|
|
||||||
public static void setAllowNativeByDefault(final boolean allowNative) {
|
|
||||||
allowNativeByDefault = allowNative;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MasterKey(@Nonnull final String fullName) {
|
|
||||||
|
|
||||||
this.fullName = fullName;
|
|
||||||
logger.trc( "fullName: %s", fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
|
||||||
protected abstract byte[] deriveKey(char[] masterPassword);
|
|
||||||
|
|
||||||
public abstract Version getAlgorithmVersion();
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public String getFullName() {
|
|
||||||
|
|
||||||
return fullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAllowNative() {
|
|
||||||
return allowNative;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MasterKey setAllowNative(final boolean allowNative) {
|
|
||||||
this.allowNative = allowNative;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
protected byte[] getKey() {
|
|
||||||
|
|
||||||
Preconditions.checkState( isValid() );
|
|
||||||
return Preconditions.checkNotNull( masterKey );
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getKeyID() {
|
|
||||||
|
|
||||||
return idForBytes( getKey() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract String encode(@Nonnull String siteName, MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
|
|
||||||
MPSiteVariant siteVariant, @Nullable String siteContext);
|
|
||||||
|
|
||||||
public boolean isValid() {
|
|
||||||
return masterKey != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void invalidate() {
|
|
||||||
|
|
||||||
if (masterKey != null) {
|
|
||||||
Arrays.fill( masterKey, (byte) 0 );
|
|
||||||
masterKey = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
|
||||||
public MasterKey revalidate(final char[] masterPassword) {
|
|
||||||
invalidate();
|
|
||||||
|
|
||||||
logger.trc( "masterPassword: %s", new String( masterPassword ) );
|
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
masterKey = deriveKey( masterPassword );
|
|
||||||
|
|
||||||
if (masterKey == null)
|
|
||||||
logger.dbg( "masterKey calculation failed after %.2fs.", (double)(System.currentTimeMillis() - start) / MPConstant.MS_PER_S );
|
|
||||||
else
|
|
||||||
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
|
|
||||||
(double)(System.currentTimeMillis() - start) / MPConstant.MS_PER_S );
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract byte[] bytesForInt(int number);
|
|
||||||
|
|
||||||
protected abstract byte[] bytesForInt(@Nonnull UnsignedInteger number);
|
|
||||||
|
|
||||||
protected abstract byte[] idForBytes(byte[] bytes);
|
|
||||||
|
|
||||||
public enum Version {
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - does math with chars whose signedness was platform-dependent.
|
|
||||||
* - miscounted the byte-length fromInt multi-byte site names.
|
|
||||||
* - miscounted the byte-length fromInt multi-byte full names.
|
|
||||||
*/
|
|
||||||
V0,
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - miscounted the byte-length fromInt multi-byte site names.
|
|
||||||
* - miscounted the byte-length fromInt multi-byte full names.
|
|
||||||
*/
|
|
||||||
V1,
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - miscounted the byte-length fromInt multi-byte full names.
|
|
||||||
*/
|
|
||||||
V2,
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - no known issues.
|
|
||||||
*/
|
|
||||||
V3;
|
|
||||||
|
|
||||||
public static final Version CURRENT = V3;
|
|
||||||
|
|
||||||
public static Version fromInt(final int algorithmVersion) {
|
|
||||||
|
|
||||||
return values()[algorithmVersion];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int toInt() {
|
|
||||||
|
|
||||||
return ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toBundleVersion() {
|
|
||||||
switch (this) {
|
|
||||||
case V0:
|
|
||||||
return "1.0";
|
|
||||||
case V1:
|
|
||||||
return "2.0";
|
|
||||||
case V2:
|
|
||||||
return "2.1";
|
|
||||||
case V3:
|
|
||||||
return "2.2";
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException( strf( "Unsupported version: %s", this ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.primitives.Bytes;
|
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
|
||||||
import com.lambdaworks.crypto.SCrypt;
|
|
||||||
import com.lyndir.lhunath.opal.system.*;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import java.nio.*;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
|
||||||
* - V1: miscounted the byte-length fromInt multi-byte site names.
|
|
||||||
* - V0: does math with chars whose signedness was platform-dependent.
|
|
||||||
*
|
|
||||||
* @author lhunath, 2014-08-30
|
|
||||||
*/
|
|
||||||
public class MasterKeyV0 extends MasterKey {
|
|
||||||
|
|
||||||
private static final int MP_intLen = 32;
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MasterKeyV0.class );
|
|
||||||
|
|
||||||
public MasterKeyV0(final String fullName) {
|
|
||||||
super( fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Version getAlgorithmVersion() {
|
|
||||||
|
|
||||||
return Version.V0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
protected byte[] deriveKey(final char[] masterPassword) {
|
|
||||||
String fullName = getFullName();
|
|
||||||
byte[] fullNameBytes = fullName.getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
|
|
||||||
|
|
||||||
String mpKeyScope = MPSiteVariant.Password.getScope();
|
|
||||||
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MPConstant.mpw_charset ), fullNameLengthBytes, fullNameBytes );
|
|
||||||
logger.trc( "key scope: %s", mpKeyScope );
|
|
||||||
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
|
||||||
|
|
||||||
ByteBuffer mpBytesBuf = MPConstant.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
|
|
||||||
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
|
|
||||||
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
|
|
||||||
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
|
|
||||||
|
|
||||||
return scrypt( masterKeySalt, mpBytes );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
|
|
||||||
try {
|
|
||||||
if (isAllowNative())
|
|
||||||
return SCrypt.scrypt( mpBytes, masterKeySalt, MPConstant.scrypt_N, MPConstant.scrypt_r, MPConstant.scrypt_p, MPConstant.mpw_dkLen );
|
|
||||||
else
|
|
||||||
return SCrypt.scryptJ( mpBytes, masterKeySalt, MPConstant.scrypt_N, MPConstant.scrypt_r, MPConstant.scrypt_p, MPConstant.mpw_dkLen );
|
|
||||||
}
|
|
||||||
catch (final GeneralSecurityException e) {
|
|
||||||
logger.bug( e );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
Arrays.fill( mpBytes, (byte) 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
|
|
||||||
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
|
|
||||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
|
||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
|
||||||
|
|
||||||
logger.trc( "siteName: %s", siteName );
|
|
||||||
logger.trc( "siteCounter: %d", siteCounter.longValue() );
|
|
||||||
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
|
||||||
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
|
||||||
|
|
||||||
if (siteCounter.longValue() == 0)
|
|
||||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPConstant.mpw_counter_timeout * 1000)) * MPConstant.mpw_counter_timeout );
|
|
||||||
|
|
||||||
String siteScope = siteVariant.getScope();
|
|
||||||
byte[] siteNameBytes = siteName.getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
|
|
||||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
|
||||||
byte[] siteContextBytes = ((siteContext == null) || siteContext.isEmpty())? null: siteContext.getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] siteContextLengthBytes = bytesForInt( (siteContextBytes == null)? 0: siteContextBytes.length );
|
|
||||||
logger.trc( "site scope: %s, context: %s", siteScope, (siteContextBytes == null)? "<empty>": siteContext );
|
|
||||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
|
||||||
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
|
||||||
(siteContextBytes == null)? "(null)": siteContext );
|
|
||||||
|
|
||||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MPConstant.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
|
||||||
if (siteContextBytes != null)
|
|
||||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
|
|
||||||
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
|
||||||
|
|
||||||
byte[] sitePasswordSeedBytes = MPConstant.mpw_digest.of( getKey(), sitePasswordInfo );
|
|
||||||
int[] sitePasswordSeed = new int[sitePasswordSeedBytes.length];
|
|
||||||
for (int i = 0; i < sitePasswordSeedBytes.length; ++i) {
|
|
||||||
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( ByteOrder.BIG_ENDIAN );
|
|
||||||
Arrays.fill( buf.array(), (byte) ((sitePasswordSeedBytes[i] > 0)? 0x00: 0xFF) );
|
|
||||||
buf.position( 2 );
|
|
||||||
buf.put( sitePasswordSeedBytes[i] ).rewind();
|
|
||||||
sitePasswordSeed[i] = buf.getInt() & 0xFFFF;
|
|
||||||
}
|
|
||||||
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
|
|
||||||
|
|
||||||
Preconditions.checkState( sitePasswordSeed.length > 0 );
|
|
||||||
int templateIndex = sitePasswordSeed[0];
|
|
||||||
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
|
|
||||||
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
|
|
||||||
|
|
||||||
StringBuilder password = new StringBuilder( template.length() );
|
|
||||||
for (int i = 0; i < template.length(); ++i) {
|
|
||||||
int characterIndex = sitePasswordSeed[i + 1];
|
|
||||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
|
||||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
|
||||||
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
|
|
||||||
sitePasswordSeed[i + 1], passwordCharacter );
|
|
||||||
|
|
||||||
password.append( passwordCharacter );
|
|
||||||
}
|
|
||||||
|
|
||||||
return password.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] bytesForInt(final int number) {
|
|
||||||
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MPConstant.mpw_byteOrder ).putInt( number ).array();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] bytesForInt(@Nonnull final UnsignedInteger number) {
|
|
||||||
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MPConstant.mpw_byteOrder ).putInt( number.intValue() ).array();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] idForBytes(final byte[] bytes) {
|
|
||||||
return MPConstant.mpw_hash.of( bytes );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.primitives.Bytes;
|
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
|
||||||
import com.lyndir.lhunath.opal.system.*;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
|
||||||
* - V1: miscounted the byte-length fromInt multi-byte site names.
|
|
||||||
*
|
|
||||||
* @author lhunath, 2014-08-30
|
|
||||||
*/
|
|
||||||
public class MasterKeyV1 extends MasterKeyV0 {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MasterKeyV1.class );
|
|
||||||
|
|
||||||
public MasterKeyV1(final String fullName) {
|
|
||||||
super( fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Version getAlgorithmVersion() {
|
|
||||||
|
|
||||||
return Version.V1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
|
|
||||||
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
|
|
||||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
|
||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
|
||||||
|
|
||||||
logger.trc( "siteName: %s", siteName );
|
|
||||||
logger.trc( "siteCounter: %d", siteCounter.longValue() );
|
|
||||||
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
|
||||||
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
|
||||||
|
|
||||||
if (siteCounter.longValue() == 0)
|
|
||||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPConstant.mpw_counter_timeout * 1000)) * MPConstant.mpw_counter_timeout );
|
|
||||||
|
|
||||||
String siteScope = siteVariant.getScope();
|
|
||||||
byte[] siteNameBytes = siteName.getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
|
|
||||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
|
||||||
byte[] siteContextBytes = ((siteContext == null) || siteContext.isEmpty())? null: siteContext.getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] siteContextLengthBytes = bytesForInt( (siteContextBytes == null)? 0: siteContextBytes.length );
|
|
||||||
logger.trc( "site scope: %s, context: %s", siteScope, (siteContextBytes == null)? "<empty>": siteContext );
|
|
||||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
|
||||||
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
|
||||||
(siteContextBytes == null)? "(null)": siteContext );
|
|
||||||
|
|
||||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MPConstant.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
|
||||||
if (siteContextBytes != null)
|
|
||||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
|
|
||||||
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
|
||||||
|
|
||||||
byte[] sitePasswordSeed = MPConstant.mpw_digest.of( getKey(), sitePasswordInfo );
|
|
||||||
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
|
|
||||||
|
|
||||||
Preconditions.checkState( sitePasswordSeed.length > 0 );
|
|
||||||
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
|
|
||||||
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
|
|
||||||
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
|
|
||||||
|
|
||||||
StringBuilder password = new StringBuilder( template.length() );
|
|
||||||
for (int i = 0; i < template.length(); ++i) {
|
|
||||||
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
|
|
||||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
|
||||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
|
||||||
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
|
|
||||||
sitePasswordSeed[i + 1], passwordCharacter );
|
|
||||||
|
|
||||||
password.append( passwordCharacter );
|
|
||||||
}
|
|
||||||
|
|
||||||
return password.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.primitives.Bytes;
|
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
|
||||||
*
|
|
||||||
* @author lhunath, 2014-08-30
|
|
||||||
*/
|
|
||||||
public class MasterKeyV2 extends MasterKeyV1 {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MasterKeyV2.class );
|
|
||||||
|
|
||||||
public MasterKeyV2(final String fullName) {
|
|
||||||
super( fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Version getAlgorithmVersion() {
|
|
||||||
|
|
||||||
return Version.V2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
|
|
||||||
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
|
|
||||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
|
||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
|
||||||
|
|
||||||
logger.trc( "siteName: %s", siteName );
|
|
||||||
logger.trc( "siteCounter: %d", siteCounter.longValue() );
|
|
||||||
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
|
||||||
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
|
||||||
|
|
||||||
if (siteCounter.longValue() == 0)
|
|
||||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPConstant.mpw_counter_timeout * 1000)) * MPConstant.mpw_counter_timeout );
|
|
||||||
|
|
||||||
String siteScope = siteVariant.getScope();
|
|
||||||
byte[] siteNameBytes = siteName.getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
|
|
||||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
|
||||||
byte[] siteContextBytes = ((siteContext == null) || siteContext.isEmpty())? null: siteContext.getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] siteContextLengthBytes = bytesForInt( (siteContextBytes == null)? 0: siteContextBytes.length );
|
|
||||||
logger.trc( "site scope: %s, context: %s", siteScope, (siteContextBytes == null)? "<empty>": siteContext );
|
|
||||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
|
||||||
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
|
||||||
(siteContextBytes == null)? "(null)": siteContext );
|
|
||||||
|
|
||||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MPConstant.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
|
||||||
if (siteContextBytes != null)
|
|
||||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
|
|
||||||
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
|
||||||
|
|
||||||
byte[] sitePasswordSeed = MPConstant.mpw_digest.of( getKey(), sitePasswordInfo );
|
|
||||||
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
|
|
||||||
|
|
||||||
Preconditions.checkState( sitePasswordSeed.length > 0 );
|
|
||||||
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
|
|
||||||
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
|
|
||||||
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
|
|
||||||
|
|
||||||
StringBuilder password = new StringBuilder( template.length() );
|
|
||||||
for (int i = 0; i < template.length(); ++i) {
|
|
||||||
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
|
|
||||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
|
||||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
|
||||||
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
|
|
||||||
sitePasswordSeed[i + 1], passwordCharacter );
|
|
||||||
|
|
||||||
password.append( passwordCharacter );
|
|
||||||
}
|
|
||||||
|
|
||||||
return password.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Bytes;
|
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bugs:
|
|
||||||
* - no known issues.
|
|
||||||
*
|
|
||||||
* @author lhunath, 2014-08-30
|
|
||||||
*/
|
|
||||||
public class MasterKeyV3 extends MasterKeyV2 {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MasterKeyV3.class );
|
|
||||||
|
|
||||||
public MasterKeyV3(final String fullName) {
|
|
||||||
super( fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Version getAlgorithmVersion() {
|
|
||||||
|
|
||||||
return Version.V3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
protected byte[] deriveKey(final char[] masterPassword) {
|
|
||||||
byte[] fullNameBytes = getFullName().getBytes( MPConstant.mpw_charset );
|
|
||||||
byte[] fullNameLengthBytes = bytesForInt( fullNameBytes.length );
|
|
||||||
|
|
||||||
String mpKeyScope = MPSiteVariant.Password.getScope();
|
|
||||||
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MPConstant.mpw_charset ), fullNameLengthBytes, fullNameBytes );
|
|
||||||
logger.trc( "key scope: %s", mpKeyScope );
|
|
||||||
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
|
||||||
|
|
||||||
ByteBuffer mpBytesBuf = MPConstant.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
|
|
||||||
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
|
|
||||||
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
|
|
||||||
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
|
|
||||||
|
|
||||||
return scrypt( masterKeySalt, mpBytes );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
id 'net.ltgt.apt' version '0.9'
|
|
||||||
}
|
|
||||||
|
|
||||||
description = 'Master Password Site Model'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':masterpassword-algorithm')
|
|
||||||
|
|
||||||
compile group: 'joda-time', name: 'joda-time', version:'2.4'
|
|
||||||
compileOnly group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
|
||||||
apt group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
|
||||||
|
|
||||||
testCompile group: 'org.testng', name: 'testng', version:'6.8.5'
|
|
||||||
testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2'
|
|
||||||
}
|
|
||||||
test.useTestNG()
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
|
||||||
<parent>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Master Password Site Model</name>
|
|
||||||
<description>A persistence model for Master Password sites.</description>
|
|
||||||
|
|
||||||
<artifactId>masterpassword-model</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<!-- DEPENDENCY MANAGEMENT -->
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- PROJECT REFERENCES -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- EXTERNAL DEPENDENCIES -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>joda-time</groupId>
|
|
||||||
<artifactId>joda-time</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.auto.value</groupId>
|
|
||||||
<artifactId>auto-value</artifactId>
|
|
||||||
<version>1.0-rc1</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- TESTING -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.testng</groupId>
|
|
||||||
<artifactId>testng</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-classic</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-17
|
|
||||||
*/
|
|
||||||
public class IncorrectMasterPasswordException extends Exception {
|
|
||||||
|
|
||||||
private final MPUser user;
|
|
||||||
|
|
||||||
public IncorrectMasterPasswordException(final MPUser user) {
|
|
||||||
super( "Incorrect master password for user: " + user.getFullName() );
|
|
||||||
|
|
||||||
this.user = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPUser getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
|
||||||
|
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
|
||||||
import com.lyndir.masterpassword.*;
|
|
||||||
import java.util.Objects;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.joda.time.Instant;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-05
|
|
||||||
*/
|
|
||||||
public class MPSite {
|
|
||||||
|
|
||||||
public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong;
|
|
||||||
public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.valueOf( 1 );
|
|
||||||
|
|
||||||
private final MPUser user;
|
|
||||||
private MasterKey.Version algorithmVersion;
|
|
||||||
private Instant lastUsed;
|
|
||||||
private String siteName;
|
|
||||||
private MPSiteType siteType;
|
|
||||||
private UnsignedInteger siteCounter;
|
|
||||||
private int uses;
|
|
||||||
private String loginName;
|
|
||||||
|
|
||||||
public MPSite(final MPUser user, final String siteName) {
|
|
||||||
this( user, siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final UnsignedInteger siteCounter) {
|
|
||||||
this.user = user;
|
|
||||||
this.algorithmVersion = MasterKey.Version.CURRENT;
|
|
||||||
this.lastUsed = new Instant();
|
|
||||||
this.siteName = siteName;
|
|
||||||
this.siteType = siteType;
|
|
||||||
this.siteCounter = siteCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName,
|
|
||||||
final MPSiteType siteType, final UnsignedInteger siteCounter, final int uses, @Nullable final String loginName,
|
|
||||||
@Nullable final String importContent) {
|
|
||||||
this.user = user;
|
|
||||||
this.algorithmVersion = algorithmVersion;
|
|
||||||
this.lastUsed = lastUsed;
|
|
||||||
this.siteName = siteName;
|
|
||||||
this.siteType = siteType;
|
|
||||||
this.siteCounter = siteCounter;
|
|
||||||
this.uses = uses;
|
|
||||||
this.loginName = loginName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String resultFor(final MasterKey masterKey) {
|
|
||||||
return resultFor( masterKey, MPSiteVariant.Password, null );
|
|
||||||
}
|
|
||||||
|
|
||||||
public String resultFor(final MasterKey masterKey, final MPSiteVariant variant, @Nullable final String context) {
|
|
||||||
return masterKey.encode( siteName, siteType, siteCounter, variant, context );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPUser getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected String exportContent() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MasterKey.Version getAlgorithmVersion() {
|
|
||||||
return algorithmVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAlgorithmVersion(final MasterKey.Version mpVersion) {
|
|
||||||
this.algorithmVersion = mpVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getLastUsed() {
|
|
||||||
return lastUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateLastUsed() {
|
|
||||||
lastUsed = new Instant();
|
|
||||||
user.updateLastUsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSiteName() {
|
|
||||||
return siteName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSiteName(final String siteName) {
|
|
||||||
this.siteName = siteName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPSiteType getSiteType() {
|
|
||||||
return siteType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSiteType(final MPSiteType siteType) {
|
|
||||||
this.siteType = siteType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnsignedInteger getSiteCounter() {
|
|
||||||
return siteCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
|
||||||
this.siteCounter = siteCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getUses() {
|
|
||||||
return uses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUses(final int uses) {
|
|
||||||
this.uses = uses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLoginName() {
|
|
||||||
return loginName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoginName(final String loginName) {
|
|
||||||
this.loginName = loginName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(final Object obj) {
|
|
||||||
return (this == obj) || ((obj instanceof MPSite) && Objects.equals( siteName, ((MPSite) obj).siteName ));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode( siteName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return strf( "{MPSite: %s}", siteName );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.lyndir.masterpassword.MasterKey;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.joda.time.Instant;
|
|
||||||
import org.joda.time.format.DateTimeFormatter;
|
|
||||||
import org.joda.time.format.ISODateTimeFormat;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-07
|
|
||||||
*/
|
|
||||||
public class MPSiteMarshaller {
|
|
||||||
|
|
||||||
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTimeNoMillis();
|
|
||||||
|
|
||||||
private final StringBuilder export = new StringBuilder();
|
|
||||||
private ContentMode contentMode = ContentMode.PROTECTED;
|
|
||||||
private MasterKey masterKey;
|
|
||||||
|
|
||||||
public static MPSiteMarshaller marshallSafe(final MPUser user) {
|
|
||||||
MPSiteMarshaller marshaller = new MPSiteMarshaller();
|
|
||||||
marshaller.marshallHeaderForSafeContent( user );
|
|
||||||
for (final MPSite site : user.getSites())
|
|
||||||
marshaller.marshallSite( site );
|
|
||||||
|
|
||||||
return marshaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MPSiteMarshaller marshallVisible(final MPUser user, final MasterKey masterKey) {
|
|
||||||
MPSiteMarshaller marshaller = new MPSiteMarshaller();
|
|
||||||
marshaller.marshallHeaderForVisibleContentWithKey( user, masterKey );
|
|
||||||
for (final MPSite site : user.getSites())
|
|
||||||
marshaller.marshallSite( site );
|
|
||||||
|
|
||||||
return marshaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String marshallHeaderForSafeContent(final MPUser user) {
|
|
||||||
return marshallHeader( ContentMode.PROTECTED, user, null );
|
|
||||||
}
|
|
||||||
|
|
||||||
private String marshallHeaderForVisibleContentWithKey(final MPUser user, final MasterKey masterKey) {
|
|
||||||
return marshallHeader( ContentMode.VISIBLE, user, masterKey );
|
|
||||||
}
|
|
||||||
|
|
||||||
private String marshallHeader(final ContentMode contentMode, final MPUser user, @Nullable final MasterKey masterKey) {
|
|
||||||
this.contentMode = contentMode;
|
|
||||||
this.masterKey = masterKey;
|
|
||||||
|
|
||||||
StringBuilder header = new StringBuilder();
|
|
||||||
header.append( "# Master Password site export\n" );
|
|
||||||
header.append( "# " ).append( this.contentMode.description() ).append( '\n' );
|
|
||||||
header.append( "# \n" );
|
|
||||||
header.append( "##\n" );
|
|
||||||
header.append( "# Format: 1\n" );
|
|
||||||
header.append( "# Date: " ).append( rfc3339.print( new Instant() ) ).append( '\n' );
|
|
||||||
header.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
|
|
||||||
header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
|
|
||||||
header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
|
|
||||||
header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
|
|
||||||
header.append( "# Version: " ).append( MasterKey.Version.CURRENT.toBundleVersion() ).append( '\n' );
|
|
||||||
header.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' );
|
|
||||||
header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
|
|
||||||
header.append( "# Passwords: " ).append( this.contentMode.name() ).append( '\n' );
|
|
||||||
header.append( "##\n" );
|
|
||||||
header.append( "#\n" );
|
|
||||||
header.append( "# Last Times Password Login\t Site\tSite\n" );
|
|
||||||
header.append( "# used used type name\t name\tpassword\n" );
|
|
||||||
|
|
||||||
export.append( header );
|
|
||||||
return header.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String marshallSite(final MPSite site) {
|
|
||||||
String exportLine = strf( "%s %8d %8s %25s\t%25s\t%s", //
|
|
||||||
rfc3339.print( site.getLastUsed() ), // lastUsed
|
|
||||||
site.getUses(), // uses
|
|
||||||
strf( "%d:%d:%d", //
|
|
||||||
site.getSiteType().getType(), // type
|
|
||||||
site.getAlgorithmVersion().toInt(), // algorithm
|
|
||||||
site.getSiteCounter().intValue() ), // counter
|
|
||||||
ifNotNullElse( site.getLoginName(), "" ), // loginName
|
|
||||||
site.getSiteName(), // siteName
|
|
||||||
ifNotNullElse( contentMode.contentForSite( site, masterKey ), "" ) // password
|
|
||||||
);
|
|
||||||
export.append( exportLine ).append( '\n' );
|
|
||||||
|
|
||||||
return exportLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExport() {
|
|
||||||
return export.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentMode getContentMode() {
|
|
||||||
return contentMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ContentMode {
|
|
||||||
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ) {
|
|
||||||
@Override
|
|
||||||
public String contentForSite(final MPSite site, @Nullable final MasterKey masterKey) {
|
|
||||||
return site.exportContent();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
VISIBLE( "Export of site names and passwords in clear-text." ) {
|
|
||||||
@Override
|
|
||||||
public String contentForSite(final MPSite site, @Nonnull final MasterKey masterKey) {
|
|
||||||
return site.resultFor( Preconditions.checkNotNull( masterKey, "Master key is required when content mode is VISIBLE." ) );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final String description;
|
|
||||||
|
|
||||||
ContentMode(final String description) {
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String description() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract String contentForSite(MPSite site, MasterKey masterKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-07
|
|
||||||
*/
|
|
||||||
public class MPSiteResult {
|
|
||||||
|
|
||||||
private final MPSite site;
|
|
||||||
|
|
||||||
public MPSiteResult(final MPSite site) {
|
|
||||||
this.site = site;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPSite getSite() {
|
|
||||||
return site;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(final Object obj) {
|
|
||||||
return (this == obj) || ((obj instanceof MPSiteResult) && Objects.equals( site, ((MPSiteResult) obj).site ));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode( site );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return strf( "{MPSiteResult: %s}", site );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.io.CharStreams;
|
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
|
||||||
import com.lyndir.lhunath.opal.system.util.NNOperation;
|
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
|
||||||
import com.lyndir.masterpassword.MasterKey;
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
import org.joda.time.format.DateTimeFormatter;
|
|
||||||
import org.joda.time.format.ISODateTimeFormat;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-07
|
|
||||||
*/
|
|
||||||
public class MPSiteUnmarshaller {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MPSite.class );
|
|
||||||
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTimeNoMillis();
|
|
||||||
private static final Pattern[] unmarshallFormats = {
|
|
||||||
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
|
|
||||||
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
|
|
||||||
private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" );
|
|
||||||
|
|
||||||
private final int importFormat;
|
|
||||||
@SuppressWarnings({ "FieldCanBeLocal", "unused" })
|
|
||||||
private final int mpVersion;
|
|
||||||
@SuppressWarnings({ "FieldCanBeLocal", "unused" })
|
|
||||||
private final boolean clearContent;
|
|
||||||
private final MPUser user;
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static MPSiteUnmarshaller unmarshall(@Nonnull final File file)
|
|
||||||
throws IOException {
|
|
||||||
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
|
|
||||||
return unmarshall( CharStreams.readLines( reader ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static MPSiteUnmarshaller unmarshall(@Nonnull final List<String> lines) {
|
|
||||||
byte[] keyID = null;
|
|
||||||
String fullName = null;
|
|
||||||
int mpVersion = 0, importFormat = 0, avatar = 0;
|
|
||||||
boolean clearContent = false, headerStarted = false;
|
|
||||||
MPSiteType defaultType = MPSiteType.GeneratedLong;
|
|
||||||
MPSiteUnmarshaller marshaller = null;
|
|
||||||
final ImmutableList.Builder<MPSite> sites = ImmutableList.builder();
|
|
||||||
|
|
||||||
for (final String line : lines)
|
|
||||||
// Header delimitor.
|
|
||||||
if (line.startsWith( "##" ))
|
|
||||||
if (!headerStarted)
|
|
||||||
// Starts the header.
|
|
||||||
headerStarted = true;
|
|
||||||
else
|
|
||||||
// Ends the header.
|
|
||||||
marshaller = new MPSiteUnmarshaller( importFormat, mpVersion, fullName, keyID, avatar, defaultType, clearContent );
|
|
||||||
|
|
||||||
// Comment.
|
|
||||||
else if (line.startsWith( "#" )) {
|
|
||||||
if (headerStarted && (marshaller == null)) {
|
|
||||||
// In header.
|
|
||||||
Matcher headerMatcher = headerFormat.matcher( line );
|
|
||||||
if (headerMatcher.matches()) {
|
|
||||||
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
|
|
||||||
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
|
||||||
fullName = value;
|
|
||||||
else if ("Key ID".equalsIgnoreCase( name ))
|
|
||||||
keyID = CodeUtils.decodeHex( value );
|
|
||||||
else if ("Algorithm".equalsIgnoreCase( name ))
|
|
||||||
mpVersion = ConversionUtils.toIntegerNN( value );
|
|
||||||
else if ("Format".equalsIgnoreCase( name ))
|
|
||||||
importFormat = ConversionUtils.toIntegerNN( value );
|
|
||||||
else if ("Avatar".equalsIgnoreCase( name ))
|
|
||||||
avatar = ConversionUtils.toIntegerNN( value );
|
|
||||||
else if ("Passwords".equalsIgnoreCase( name ))
|
|
||||||
clearContent = "visible".equalsIgnoreCase( value );
|
|
||||||
else if ("Default Type".equalsIgnoreCase( name ))
|
|
||||||
defaultType = MPSiteType.forType( ConversionUtils.toIntegerNN( value ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No comment.
|
|
||||||
else if (marshaller != null)
|
|
||||||
ifNotNull( marshaller.unmarshallSite( line ), new NNOperation<MPSite>() {
|
|
||||||
@Override
|
|
||||||
public void apply(@Nonnull final MPSite site) {
|
|
||||||
sites.add( site );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
|
|
||||||
return Preconditions.checkNotNull( marshaller, "No full header found in import file." );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MPSiteUnmarshaller(final int importFormat, final int mpVersion, final String fullName, final byte[] keyID, final int avatar,
|
|
||||||
final MPSiteType defaultType, final boolean clearContent) {
|
|
||||||
this.importFormat = importFormat;
|
|
||||||
this.mpVersion = mpVersion;
|
|
||||||
this.clearContent = clearContent;
|
|
||||||
|
|
||||||
user = new MPUser( fullName, keyID, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public MPSite unmarshallSite(@Nonnull final String siteLine) {
|
|
||||||
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( siteLine );
|
|
||||||
if (!siteMatcher.matches())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
MPSite site;
|
|
||||||
switch (importFormat) {
|
|
||||||
case 0:
|
|
||||||
site = new MPSite( user, //
|
|
||||||
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
|
|
||||||
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
|
|
||||||
siteMatcher.group( 5 ), //
|
|
||||||
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPSite.DEFAULT_COUNTER, //
|
|
||||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
|
|
||||||
null, //
|
|
||||||
siteMatcher.group( 6 ) );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
site = new MPSite( user, //
|
|
||||||
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
|
|
||||||
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
|
|
||||||
siteMatcher.group( 7 ), //
|
|
||||||
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
|
||||||
UnsignedInteger.valueOf( siteMatcher.group( 5 ).replace( ":", "" ) ), //
|
|
||||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
|
|
||||||
siteMatcher.group( 6 ), //
|
|
||||||
siteMatcher.group( 8 ) );
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw logger.bug( "Unexpected format: %d", importFormat );
|
|
||||||
}
|
|
||||||
|
|
||||||
user.addSite( site );
|
|
||||||
return site;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPUser getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
|
||||||
import com.lyndir.masterpassword.MasterKey;
|
|
||||||
import java.util.*;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.joda.time.*;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-07
|
|
||||||
*/
|
|
||||||
public class MPUser implements Comparable<MPUser> {
|
|
||||||
|
|
||||||
private final String fullName;
|
|
||||||
private final Collection<MPSite> sites = Sets.newHashSet();
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private byte[] keyID;
|
|
||||||
private final MasterKey.Version algorithmVersion;
|
|
||||||
private int avatar;
|
|
||||||
private MPSiteType defaultType;
|
|
||||||
private ReadableInstant lastUsed;
|
|
||||||
|
|
||||||
public MPUser(final String fullName) {
|
|
||||||
this( fullName, null );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPUser(final String fullName, @Nullable final byte[] keyID) {
|
|
||||||
this( fullName, keyID, MasterKey.Version.CURRENT, 0, MPSiteType.GeneratedLong, new DateTime() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar,
|
|
||||||
final MPSiteType defaultType, final ReadableInstant lastUsed) {
|
|
||||||
this.fullName = fullName;
|
|
||||||
this.keyID = (keyID == null)? null: keyID.clone();
|
|
||||||
this.algorithmVersion = algorithmVersion;
|
|
||||||
this.avatar = avatar;
|
|
||||||
this.defaultType = defaultType;
|
|
||||||
this.lastUsed = lastUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<MPSiteResult> findSitesByName(final String query) {
|
|
||||||
ImmutableList.Builder<MPSiteResult> results = ImmutableList.builder();
|
|
||||||
for (final MPSite site : getSites())
|
|
||||||
if (site.getSiteName().startsWith( query ))
|
|
||||||
results.add( new MPSiteResult( site ) );
|
|
||||||
|
|
||||||
return results.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSite(final MPSite site) {
|
|
||||||
sites.add( site );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteSite(final MPSite site) {
|
|
||||||
sites.remove( site );
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFullName() {
|
|
||||||
return fullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasKeyID() {
|
|
||||||
return keyID != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String exportKeyID() {
|
|
||||||
return CodeUtils.encodeHex( keyID );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs an authentication attempt against the keyID for this user.
|
|
||||||
*
|
|
||||||
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
|
|
||||||
*
|
|
||||||
* @param masterPassword The password to authenticate with.
|
|
||||||
*
|
|
||||||
* @return The master key for the user if authentication was successful.
|
|
||||||
*
|
|
||||||
* @throws IncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
|
||||||
public MasterKey authenticate(final char[] masterPassword)
|
|
||||||
throws IncorrectMasterPasswordException {
|
|
||||||
MasterKey masterKey = MasterKey.create( algorithmVersion, getFullName(), masterPassword );
|
|
||||||
if ((keyID == null) || (keyID.length == 0))
|
|
||||||
keyID = masterKey.getKeyID();
|
|
||||||
else if (!Arrays.equals( masterKey.getKeyID(), keyID ))
|
|
||||||
throw new IncorrectMasterPasswordException( this );
|
|
||||||
|
|
||||||
return masterKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAvatar() {
|
|
||||||
return avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAvatar(final int avatar) {
|
|
||||||
this.avatar = avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPSiteType getDefaultType() {
|
|
||||||
return defaultType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultType(final MPSiteType defaultType) {
|
|
||||||
this.defaultType = defaultType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadableInstant getLastUsed() {
|
|
||||||
return lastUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateLastUsed() {
|
|
||||||
lastUsed = new Instant();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<MPSite> getSites() {
|
|
||||||
return sites;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(final Object obj) {
|
|
||||||
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( fullName, ((MPUser) obj).fullName ));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode( fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return strf( "{MPUser: %s}", fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(final MPUser o) {
|
|
||||||
int comparison = lastUsed.compareTo( o.lastUsed );
|
|
||||||
if (comparison == 0)
|
|
||||||
comparison = fullName.compareTo( o.fullName );
|
|
||||||
|
|
||||||
return comparison;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
|
||||||
|
|
||||||
import com.google.common.base.*;
|
|
||||||
import com.google.common.collect.*;
|
|
||||||
import com.google.common.io.CharSink;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import com.lyndir.masterpassword.MPConstant;
|
|
||||||
import java.io.*;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}.
|
|
||||||
* @author lhunath, 14-12-07
|
|
||||||
*/
|
|
||||||
public class MPUserFileManager extends MPUserManager {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MPUserFileManager.class );
|
|
||||||
private static final MPUserFileManager instance;
|
|
||||||
|
|
||||||
static {
|
|
||||||
String rcDir = System.getenv( MPConstant.env_rcDir );
|
|
||||||
if (rcDir != null)
|
|
||||||
instance = create( new File( rcDir ) );
|
|
||||||
else
|
|
||||||
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
private final File userFilesDirectory;
|
|
||||||
|
|
||||||
public static MPUserFileManager get() {
|
|
||||||
MPUserManager.instance = instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MPUserFileManager create(final File userFilesDirectory) {
|
|
||||||
return new MPUserFileManager( userFilesDirectory );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MPUserFileManager(final File userFilesDirectory) {
|
|
||||||
|
|
||||||
super( unmarshallUsers( userFilesDirectory ) );
|
|
||||||
this.userFilesDirectory = userFilesDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Iterable<MPUser> unmarshallUsers(final File userFilesDirectory) {
|
|
||||||
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
|
|
||||||
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
|
|
||||||
return ImmutableList.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
return FluentIterable.from( listUserFiles( userFilesDirectory ) ).transform( new Function<File, MPUser>() {
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public MPUser apply(@Nullable final File file) {
|
|
||||||
try {
|
|
||||||
return MPSiteUnmarshaller.unmarshall( Preconditions.checkNotNull( file ) ).getUser();
|
|
||||||
}
|
|
||||||
catch (final IOException e) {
|
|
||||||
logger.err( e, "Couldn't read user from: %s", file );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ).filter( Predicates.notNull() );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableList<File> listUserFiles(final File userFilesDirectory) {
|
|
||||||
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( new FilenameFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(final File dir, final String name) {
|
|
||||||
return name.endsWith( ".mpsites" );
|
|
||||||
}
|
|
||||||
} ), new File[0] ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addUser(final MPUser user) {
|
|
||||||
super.addUser( user );
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteUser(final MPUser user) {
|
|
||||||
super.deleteUser( user );
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the current user state to disk.
|
|
||||||
*/
|
|
||||||
public void save() {
|
|
||||||
// Save existing users.
|
|
||||||
for (final MPUser user : getUsers())
|
|
||||||
try {
|
|
||||||
new CharSink() {
|
|
||||||
@Override
|
|
||||||
public Writer openStream()
|
|
||||||
throws IOException {
|
|
||||||
File mpsitesFile = new File( userFilesDirectory, user.getFullName() + ".mpsites" );
|
|
||||||
return new OutputStreamWriter( new FileOutputStream( mpsitesFile ), Charsets.UTF_8 );
|
|
||||||
}
|
|
||||||
}.write( MPSiteMarshaller.marshallSafe( user ).getExport() );
|
|
||||||
}
|
|
||||||
catch (final IOException e) {
|
|
||||||
logger.err( e, "Unable to save sites for user: %s", user );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove deleted users.
|
|
||||||
for (final File userFile : listUserFiles( userFilesDirectory ))
|
|
||||||
if (getUserNamed( userFile.getName().replaceFirst( "\\.mpsites$", "" ) ) == null)
|
|
||||||
if (!userFile.delete())
|
|
||||||
logger.err( "Couldn't delete file: %s", userFile );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The location on the file system where the user models are stored.
|
|
||||||
*/
|
|
||||||
public File getPath() {
|
|
||||||
return userFilesDirectory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import com.google.common.collect.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-05
|
|
||||||
*/
|
|
||||||
public abstract class MPUserManager {
|
|
||||||
|
|
||||||
private final Map<String, MPUser> usersByName = Maps.newHashMap();
|
|
||||||
static MPUserManager instance;
|
|
||||||
|
|
||||||
public static MPUserManager get() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MPUserManager(final Iterable<MPUser> users) {
|
|
||||||
for (final MPUser user : users)
|
|
||||||
usersByName.put( user.getFullName(), user );
|
|
||||||
}
|
|
||||||
|
|
||||||
public SortedSet<MPUser> getUsers() {
|
|
||||||
return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MPUser getUserNamed(final String fullName) {
|
|
||||||
return usersByName.get( fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addUser(final MPUser user) {
|
|
||||||
usersByName.put( user.getFullName(), user );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteUser(final MPUser user) {
|
|
||||||
usersByName.remove( user.getFullName() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @author lhunath, 15-02-04
|
|
||||||
*/
|
|
||||||
|
|
||||||
@ParametersAreNonnullByDefault
|
|
||||||
package com.lyndir.masterpassword.model;
|
|
||||||
|
|
||||||
import javax.annotation.ParametersAreNonnullByDefault;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
}
|
|
||||||
|
|
||||||
description = 'Master Password Test Suite'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':masterpassword-algorithm')
|
|
||||||
|
|
||||||
testCompile group: 'org.testng', name: 'testng', version:'6.8.5'
|
|
||||||
testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2'
|
|
||||||
}
|
|
||||||
test.useTestNG()
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
|
||||||
<parent>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Master Password Test Suite</name>
|
|
||||||
<description>The standard test suite to ensure the Master Password algorithm is operating as it should</description>
|
|
||||||
|
|
||||||
<artifactId>masterpassword-tests</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<!-- DEPENDENCY MANAGEMENT -->
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- PROJECT REFERENCES -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- TESTING -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.testng</groupId>
|
|
||||||
<artifactId>testng</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-classic</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import static org.testng.Assert.*;
|
|
||||||
|
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import org.jetbrains.annotations.NonNls;
|
|
||||||
import org.testng.annotations.BeforeMethod;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
|
|
||||||
public class MasterKeyTest {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MasterKeyTest.class );
|
|
||||||
|
|
||||||
@NonNls
|
|
||||||
private MPTestSuite testSuite;
|
|
||||||
|
|
||||||
@BeforeMethod
|
|
||||||
public void setUp()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
testSuite = new MPTestSuite();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEncode()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
testSuite.forEach( "testEncode", new NNFunctionNN<MPTests.Case, Boolean>() {
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Boolean apply(@Nonnull final MPTests.Case testCase) {
|
|
||||||
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() );
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(),
|
|
||||||
testCase.getSiteVariant(), testCase.getSiteContext() ),
|
|
||||||
testCase.getResult(), "[testEncode] Failed test case: " + testCase );
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetUserName()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
MPTests.Case defaultCase = testSuite.getTests().getDefaultCase();
|
|
||||||
|
|
||||||
assertEquals( MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
|
|
||||||
defaultCase.getFullName(), "[testGetUserName] Failed test case: " + defaultCase );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetKeyID()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
testSuite.forEach( "testGetKeyID", new NNFunctionNN<MPTests.Case, Boolean>() {
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public Boolean apply(@Nonnull final MPTests.Case testCase) {
|
|
||||||
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
|
|
||||||
|
|
||||||
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ),
|
|
||||||
testCase.getKeyID(), "[testGetKeyID] Failed test case: " + testCase );
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInvalidate()
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
try {
|
|
||||||
MPTests.Case defaultCase = testSuite.getTests().getDefaultCase();
|
|
||||||
|
|
||||||
MasterKey masterKey = MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() );
|
|
||||||
masterKey.invalidate();
|
|
||||||
masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(),
|
|
||||||
defaultCase.getSiteVariant(), defaultCase.getSiteContext() );
|
|
||||||
|
|
||||||
fail( "[testInvalidate] Master key should have been invalidated, but was still usable." );
|
|
||||||
}
|
|
||||||
catch (final IllegalStateException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
gradle/.idea/misc.xml
generated
7
gradle/.idea/misc.xml
generated
@@ -1,9 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
<type id="jpa" />
|
||||||
|
<type id="web" />
|
||||||
|
</component>
|
||||||
<component name="MavenProjectsManager">
|
<component name="MavenProjectsManager">
|
||||||
<option name="originalFiles">
|
<option name="originalFiles">
|
||||||
<list>
|
<list>
|
||||||
<option value="$PROJECT_DIR$/../../opal/pom.xml" />
|
<option value="$PROJECT_DIR$/../../opal/pom.xml" />
|
||||||
|
<option value="$PROJECT_DIR$/../../pom.xml" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
@@ -31,7 +36,7 @@
|
|||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/classes" />
|
<output url="file://$PROJECT_DIR$/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ThriftCompiler">
|
<component name="ThriftCompiler">
|
||||||
|
|||||||
6
gradle/.idea/runConfigurations/Android.xml
generated
6
gradle/.idea/runConfigurations/Android.xml
generated
@@ -6,20 +6,18 @@
|
|||||||
<option name="PM_INSTALL_OPTIONS" value="" />
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
<option name="MODE" value="default_activity" />
|
<option name="MODE" value="default_activity" />
|
||||||
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
|
|
||||||
<option name="PREFERRED_AVD" value="" />
|
<option name="PREFERRED_AVD" value="" />
|
||||||
<option name="CLEAR_LOGCAT" value="false" />
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
|
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
|
||||||
<option name="FORCE_STOP_RUNNING_APP" value="true" />
|
<option name="FORCE_STOP_RUNNING_APP" value="true" />
|
||||||
<option name="DEBUGGER_TYPE" value="Java" />
|
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
|
||||||
<option name="USE_LAST_SELECTED_DEVICE" value="false" />
|
<option name="USE_LAST_SELECTED_DEVICE" value="false" />
|
||||||
<option name="PREFERRED_AVD" value="" />
|
<option name="PREFERRED_AVD" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Java" />
|
||||||
<Java />
|
<Java />
|
||||||
<Profilers>
|
<Profilers>
|
||||||
<option name="ENABLE_ADVANCED_PROFILING" value="false" />
|
<option name="ENABLE_ADVANCED_PROFILING" value="false" />
|
||||||
<option name="GAPID_ENABLED" value="false" />
|
|
||||||
<option name="GAPID_DISABLE_PCS" value="false" />
|
|
||||||
<option name="SUPPORT_LIB_ENABLED" value="true" />
|
<option name="SUPPORT_LIB_ENABLED" value="true" />
|
||||||
<option name="INSTRUMENTATION_ENABLED" value="true" />
|
<option name="INSTRUMENTATION_ENABLED" value="true" />
|
||||||
</Profilers>
|
</Profilers>
|
||||||
|
|||||||
16
gradle/.idea/runConfigurations/GUI.xml
generated
16
gradle/.idea/runConfigurations/GUI.xml
generated
@@ -1,16 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="GUI" type="Application" factoryName="Application">
|
|
||||||
<option name="MAIN_CLASS_NAME" value="com.lyndir.masterpassword.gui.GUI" />
|
|
||||||
<option name="VM_PARAMETERS" value="" />
|
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
|
||||||
<option name="ENABLE_SWING_INSPECTOR" value="false" />
|
|
||||||
<option name="ENV_VARIABLES" />
|
|
||||||
<option name="PASS_PARENT_ENVS" value="true" />
|
|
||||||
<module name="gui" />
|
|
||||||
<envs />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
29
gradle/.idea/runConfigurations/Tests.xml
generated
29
gradle/.idea/runConfigurations/Tests.xml
generated
@@ -1,29 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Tests" type="TestNG" factoryName="TestNG">
|
|
||||||
<module name="tests" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
|
||||||
<option name="SUITE_NAME" value="" />
|
|
||||||
<option name="PACKAGE_NAME" value="com.lyndir.masterpassword" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="" />
|
|
||||||
<option name="METHOD_NAME" value="" />
|
|
||||||
<option name="GROUP_NAME" value="" />
|
|
||||||
<option name="TEST_OBJECT" value="PACKAGE" />
|
|
||||||
<option name="VM_PARAMETERS" value="-ea" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../core/java/tests" />
|
|
||||||
<option name="OUTPUT_DIRECTORY" value="" />
|
|
||||||
<option name="ANNOTATION_TYPE" />
|
|
||||||
<option name="ENV_VARIABLES" />
|
|
||||||
<option name="PASS_PARENT_ENVS" value="true" />
|
|
||||||
<option name="TEST_SEARCH_SCOPE">
|
|
||||||
<value defaultName="singleModule" />
|
|
||||||
</option>
|
|
||||||
<option name="USE_DEFAULT_REPORTERS" value="false" />
|
|
||||||
<option name="PROPERTIES_FILE" value="" />
|
|
||||||
<envs />
|
|
||||||
<properties />
|
|
||||||
<listeners />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
2
gradle/.idea/scopes/masterpassword.xml
generated
2
gradle/.idea/scopes/masterpassword.xml
generated
@@ -1,3 +1,3 @@
|
|||||||
<component name="DependencyValidationManager">
|
<component name="DependencyValidationManager">
|
||||||
<scope name="masterpassword" pattern="com.lyndir.masterpassword.*" />
|
<scope name="masterpassword" pattern="com.lyndir.masterpassword..*" />
|
||||||
</component>
|
</component>
|
||||||
15
gradle/README.md
Normal file
15
gradle/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
To build a release distribution:
|
||||||
|
|
||||||
|
Desktop:
|
||||||
|
|
||||||
|
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle clean masterpassword-gui:shadowJar
|
||||||
|
|
||||||
|
Android:
|
||||||
|
|
||||||
|
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle clean masterpassword-android:assembleRelease
|
||||||
|
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
- At the time of writing, Android does not build with JDK 9+. As such, the above command must be ran with JAVA_HOME pointing to JDK 7-8.
|
||||||
|
- The release keystores are not included in the repository. They are maintained by Maarten Billemont (lhunath@lyndir.com).
|
||||||
@@ -1,34 +1,38 @@
|
|||||||
allprojects {
|
allprojects {
|
||||||
//apply plugin: 'findbugs'
|
apply plugin: 'findbugs'
|
||||||
|
|
||||||
group = 'com.lyndir.masterpassword'
|
group = 'com.lyndir.masterpassword'
|
||||||
version = 'GIT-SNAPSHOT'
|
version = 'GIT-SNAPSHOT'
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType( JavaCompile ) {
|
||||||
sourceCompatibility = '1.7'
|
options.encoding = 'UTF-8'
|
||||||
targetCompatibility = '1.7'
|
sourceCompatibility = '1.8'
|
||||||
|
targetCompatibility = '1.8'
|
||||||
}
|
}
|
||||||
tasks.withType(FindBugs) {
|
tasks.withType( FindBugs ) {
|
||||||
reports {
|
reports {
|
||||||
xml.enabled false
|
xml.enabled = false
|
||||||
html.enabled true
|
html.enabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath group: 'com.android.tools.build', name: 'gradle', version: '2.2.3'
|
classpath group: 'com.android.tools.build', name: 'gradle', version: '3.1.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url 'http://maven.lyndir.com' }
|
maven { url 'https://maven.lyndir.com' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
gradle/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,6 +1,5 @@
|
|||||||
#Sun Mar 26 09:11:08 EDT 2017
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
|
||||||
|
|||||||
6
gradle/gradlew
vendored
6
gradle/gradlew
vendored
@@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS=""
|
|||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
|
||||||
warn ( ) {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
die ( ) {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
@@ -155,7 +155,7 @@ if $cygwin ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Escape application args
|
||||||
save ( ) {
|
save () {
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,39 @@
|
|||||||
class Mpw < Formula
|
class Mpw < Formula
|
||||||
homepage "http://masterpasswordapp.com"
|
desc "Stateless/deterministic password and identity manager"
|
||||||
url "https://ssl.masterpasswordapp.com/mpw-2.1-cli4-0-gf6b2287.tar.gz"
|
homepage "https://masterpassword.app/"
|
||||||
sha1 "036b3d8f4bd6f0676ae16e7e9c3de65f6030874f"
|
url "https://masterpassword.app/mpw-2.6-cli-5-0-g344771db.tar.gz"
|
||||||
version "2.1-cli4"
|
version "2.6-cli-5"
|
||||||
|
sha256 "954c07b1713ecc2b30a07bead9c11e6204dd774ca67b5bdf7d2d6ad1c4eec170"
|
||||||
|
head "https://github.com/Lyndir/MasterPassword.git"
|
||||||
|
|
||||||
depends_on "automake" => :build
|
bottle do
|
||||||
depends_on "autoconf" => :build
|
cellar :any
|
||||||
depends_on "openssl"
|
sha256 "ae8b265936797778a7cde788377eed89d9eacd267755a0b1186790057a10ff3b" => :high_sierra
|
||||||
|
sha256 "b8a106c3c84ff939e928613d4a6ccf7b5234e40ebae1edf15e3cac52d8c2e5ea" => :sierra
|
||||||
resource "libscrypt" do
|
sha256 "9b58425b028a2598932474e1d0c17c13aad57e0a53ae7308c1b38404da8f3331" => :el_capitan
|
||||||
url "http://masterpasswordapp.com/libscrypt-b12b554.tar.gz"
|
|
||||||
sha1 "ee871e0f93a786c4e3622561f34565337cfdb815"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
option "without-json-c", "Disable JSON configuration support"
|
||||||
|
option "without-ncurses", "Disable colorized identicon support"
|
||||||
|
|
||||||
|
depends_on "libsodium"
|
||||||
|
depends_on "json-c" => :recommended
|
||||||
|
depends_on "ncurses" => :recommended
|
||||||
|
|
||||||
def install
|
def install
|
||||||
resource("libscrypt").stage buildpath/"lib/scrypt"
|
cd "platform-independent/cli-c" if build.head?
|
||||||
touch "lib/scrypt/.unpacked"
|
|
||||||
|
ENV["targets"] = "mpw"
|
||||||
|
ENV["mpw_json"] = build.with?("json-c") ? "1" : "0"
|
||||||
|
ENV["mpw_color"] = build.with?("ncurses") ? "1" : "0"
|
||||||
|
|
||||||
ENV["targets"] = "mpw mpw-tests"
|
|
||||||
system "./build"
|
system "./build"
|
||||||
system "./mpw-tests"
|
system "./mpw-cli-tests"
|
||||||
|
|
||||||
bin.install "mpw"
|
bin.install "mpw"
|
||||||
end
|
end
|
||||||
|
|
||||||
test do
|
test do
|
||||||
assert_equal "Jejr5[RepuSosp",
|
assert_equal "Jejr5[RepuSosp",
|
||||||
shell_output("mpw -u 'Robert Lee Mitchell' -P 'banana colored duckling' masterpasswordapp.com").strip
|
shell_output("#{bin}/mpw -q -Fnone -u 'Robert Lee Mitchell' -M 'banana colored duckling' -tlong -c1 -a3 'masterpasswordapp.com'").strip
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
|
||||||
<parent>
|
|
||||||
<groupId>com.lyndir.lhunath</groupId>
|
|
||||||
<artifactId>lyndir</artifactId>
|
|
||||||
<version>1.22</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Master Password</name>
|
|
||||||
<description>A Java implementation of the Master Password algorithm.</description>
|
|
||||||
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<modules>
|
|
||||||
<module>masterpassword-tests</module>
|
|
||||||
<module>masterpassword-algorithm</module>
|
|
||||||
<module>masterpassword-model</module>
|
|
||||||
<module>masterpassword-cli</module>
|
|
||||||
<module>masterpassword-gui</module>
|
|
||||||
</modules>
|
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>release</id>
|
|
||||||
<modules>
|
|
||||||
<module>masterpassword-android</module>
|
|
||||||
</modules>
|
|
||||||
</profile>
|
|
||||||
<profile>
|
|
||||||
<id>mod:android</id>
|
|
||||||
<modules>
|
|
||||||
<module>masterpassword-android</module>
|
|
||||||
</modules>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
|
|
||||||
<!-- REMOTE ARTIFACT REPOSITORIES -->
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>lyndir</id>
|
|
||||||
<name>Lyndir Repository</name>
|
|
||||||
<url>http://maven.lyndir.com</url>
|
|
||||||
|
|
||||||
<snapshots>
|
|
||||||
<enabled>true</enabled>
|
|
||||||
<updatePolicy>never</updatePolicy>
|
|
||||||
</snapshots>
|
|
||||||
<releases>
|
|
||||||
<enabled>true</enabled>
|
|
||||||
<updatePolicy>never</updatePolicy>
|
|
||||||
</releases>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
</project>
|
|
||||||
@@ -1,19 +1,29 @@
|
|||||||
rootProject.name = 'masterpassword'
|
rootProject.name = 'masterpassword'
|
||||||
|
|
||||||
|
def local = new Properties();
|
||||||
|
try {
|
||||||
|
local.load(file('local.properties').newDataInputStream())
|
||||||
|
} catch (FileNotFoundException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
include 'masterpassword-core'
|
||||||
|
project(':masterpassword-core').projectDir = new File( '../platform-independent/c/core' )
|
||||||
|
|
||||||
include 'masterpassword-algorithm'
|
include 'masterpassword-algorithm'
|
||||||
project(':masterpassword-algorithm').projectDir = new File( '../core/java/algorithm' )
|
project(':masterpassword-algorithm').projectDir = new File( '../platform-independent/java/algorithm' )
|
||||||
|
|
||||||
include 'masterpassword-model'
|
include 'masterpassword-model'
|
||||||
project(':masterpassword-model').projectDir = new File( '../core/java/model' )
|
project(':masterpassword-model').projectDir = new File( '../platform-independent/java/model' )
|
||||||
|
|
||||||
include 'masterpassword-tests'
|
include 'masterpassword-tests'
|
||||||
project(':masterpassword-tests').projectDir = new File( '../core/java/tests' )
|
project(':masterpassword-tests').projectDir = new File( '../platform-independent/java/tests' )
|
||||||
|
|
||||||
include 'masterpassword-cli'
|
|
||||||
project(':masterpassword-cli').projectDir = new File( '../platform-independent/cli-java' )
|
|
||||||
|
|
||||||
include 'masterpassword-gui'
|
include 'masterpassword-gui'
|
||||||
project(':masterpassword-gui').projectDir = new File( '../platform-independent/gui-java' )
|
project(':masterpassword-gui').projectDir = new File( '../platform-independent/java/gui' )
|
||||||
|
|
||||||
include 'masterpassword-android'
|
if (local.containsKey('sdk.dir')) {
|
||||||
project(':masterpassword-android').projectDir = new File( '../platform-android' )
|
include 'masterpassword-android'
|
||||||
|
project(':masterpassword-android').projectDir = new File( '../platform-android' )
|
||||||
|
} else {
|
||||||
|
logger.warn( "Skipping masterpassword-android since sdk.dir is not defined in local.properties." )
|
||||||
|
}
|
||||||
|
|||||||
322
lib/bin/build_lib
Executable file
322
lib/bin/build_lib
Executable file
@@ -0,0 +1,322 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Your build script should simply source this script, optionally override any build hooks and then invoke `build`.
|
||||||
|
# The build product should be available under `build-<platform>~/out`, under the library path.
|
||||||
|
#
|
||||||
|
# Hook lifecycle:
|
||||||
|
# - build
|
||||||
|
# - initialize
|
||||||
|
# - needs
|
||||||
|
# - clean & exit (only if script was ran with "clean" argument)
|
||||||
|
# - prepare
|
||||||
|
# - clean
|
||||||
|
# - config
|
||||||
|
# - target
|
||||||
|
# - prepare
|
||||||
|
# - configure
|
||||||
|
# - build
|
||||||
|
# - finalize
|
||||||
|
# - merge
|
||||||
|
# - clean
|
||||||
|
#
|
||||||
|
# You can override any of these hooks to provide a custom implementation or call their underscore variant to delegate to the default implementation.
|
||||||
|
# For example:
|
||||||
|
# target_prepare() { make -s distclean; }
|
||||||
|
# target_configure() { _target_configure "$@" --enable-minimal; }
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# needs <binary> ...
|
||||||
|
#
|
||||||
|
# Utility for ensuring all tools needed by the script are installed prior to starting.
|
||||||
|
needs() { _needs "$@"; }
|
||||||
|
_needs() {
|
||||||
|
local failed=0
|
||||||
|
for tool; do
|
||||||
|
hash "$tool" || { echo >&2 "Missing: $tool. Please install this tool."; (( failed++ )); }
|
||||||
|
done
|
||||||
|
|
||||||
|
return $failed
|
||||||
|
}
|
||||||
|
|
||||||
|
# initialize <prefix> <platform>
|
||||||
|
#
|
||||||
|
# The build script invokes this once prior to all other actions if the user wants a clean slate.
|
||||||
|
initialize() { _initialize "$@"; }
|
||||||
|
_initialize() {
|
||||||
|
initialize_needs "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# initialize_needs <prefix> <platform>
|
||||||
|
#
|
||||||
|
# Check if all tools needed for the default implementations are available.
|
||||||
|
#
|
||||||
|
# By default, this will check for `automake` (for aclocal) and `autoconf` (for autoreconf).
|
||||||
|
initialize_needs() { _initialize_needs "$@"; }
|
||||||
|
_initialize_needs() {
|
||||||
|
needs automake autoconf
|
||||||
|
}
|
||||||
|
|
||||||
|
# clean <prefix> <platform>
|
||||||
|
#
|
||||||
|
# Fully clean up the library code, restoring it to a pristine state.
|
||||||
|
#
|
||||||
|
# By default, this will wipe the prefix, run `make distclean` and `git clean -fdx`.
|
||||||
|
clean() { _clean "$@"; }
|
||||||
|
_clean() {
|
||||||
|
rm -rf "$prefix"
|
||||||
|
[[ ! -e Makefile ]] || make -s distclean
|
||||||
|
[[ ! -e .git ]] || git clean -fdx
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare <prefix> <platform> [ <arch> ... ]
|
||||||
|
#
|
||||||
|
# Configure the library for building the <arch>s on this machine.
|
||||||
|
# The build script invokes this once prior to building each of its targets.
|
||||||
|
# The <prefix> has been newly created.
|
||||||
|
#
|
||||||
|
# By default, this will run `autoreconf`.
|
||||||
|
prepare() { _prepare "$@"; }
|
||||||
|
_prepare() {
|
||||||
|
prepare_clean "$@"
|
||||||
|
prepare_config "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare_clean <prefix> <platform> [ <arch> ... ]
|
||||||
|
#
|
||||||
|
# Perform any necessary clean-up of the library code prior to building.
|
||||||
|
#
|
||||||
|
# By default, this will wipe and re-create the prefix.
|
||||||
|
prepare_clean() { _prepare_clean "$@"; }
|
||||||
|
_prepare_clean() {
|
||||||
|
local prefix=$1 platform=$2; shift 2
|
||||||
|
|
||||||
|
rm -rf "$prefix"
|
||||||
|
install -d "$prefix"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare_config <prefix> <platform> [ <arch> ... ]
|
||||||
|
#
|
||||||
|
# Configure the library for building the <arch>s on this machine.
|
||||||
|
#
|
||||||
|
# By default, this will run `autoreconf`.
|
||||||
|
prepare_config() { _prepare_config "$@"; }
|
||||||
|
_prepare_config() {
|
||||||
|
local prefix=$1 platform=$2; shift 2
|
||||||
|
|
||||||
|
[[ -e configure ]] || autoreconf --verbose --install --symlink 2> >(sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /')
|
||||||
|
}
|
||||||
|
|
||||||
|
# target <prefix> <platform> <arch>
|
||||||
|
#
|
||||||
|
# Build the library for the given <arch> and <platform> into the given <prefix>.
|
||||||
|
# The build script invokes this function when it's ready to build the library's code.
|
||||||
|
# Generic platform-specific environment setup has been done.
|
||||||
|
target() { _target "$@"; }
|
||||||
|
_target() {
|
||||||
|
target_prepare "$@"
|
||||||
|
target_configure "$@"
|
||||||
|
target_build "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# target_prepare <prefix> <platform> <arch>
|
||||||
|
#
|
||||||
|
# Prepare the library configuration for building the target.
|
||||||
|
#
|
||||||
|
# By default, this will run `make clean` if a Makefile is found.
|
||||||
|
target_prepare() { _target_prepare "$@"; }
|
||||||
|
_target_prepare() {
|
||||||
|
local prefix=$1 platform=$2 arch=$3; shift 3
|
||||||
|
|
||||||
|
[[ ! -e Makefile ]] || make -s clean
|
||||||
|
}
|
||||||
|
|
||||||
|
# target_configure <prefix> <platform> <arch> [ <args> ... ]
|
||||||
|
#
|
||||||
|
# Configure the library for building the target.
|
||||||
|
#
|
||||||
|
# By default, this will run `./configure --host=<host> --prefix=<prefix>/<arch> --disable-shared <args>`.
|
||||||
|
target_configure() { _target_configure "$@"; }
|
||||||
|
_target_configure() {
|
||||||
|
local prefix=$1 platform=$2 arch=$3; shift 3
|
||||||
|
|
||||||
|
case "$platform" in
|
||||||
|
'android')
|
||||||
|
host=( "$SDKROOT"/*-android* ) host=${host##*/}
|
||||||
|
|
||||||
|
set -- --with-sysroot="$SDKROOT/sysroot" "$@"
|
||||||
|
;;
|
||||||
|
'ios')
|
||||||
|
[[ $arch = *arm* ]] && host=arm || host=$arch
|
||||||
|
host+=-apple
|
||||||
|
|
||||||
|
set -- --disable-shared "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
./configure ${host:+--host="$host"} --prefix="$prefix/$arch" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# target_build <prefix> <platform> <arch>
|
||||||
|
#
|
||||||
|
# Build the library code for the target.
|
||||||
|
#
|
||||||
|
# By default, this will run `make check install`.
|
||||||
|
target_build() { _target_build "$@"; }
|
||||||
|
_target_build() {
|
||||||
|
local prefix=$1 platform=$2 arch=$3; shift 3
|
||||||
|
#make -j3 check
|
||||||
|
|
||||||
|
cores=$(getconf NPROCESSORS_ONLN 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null ||:)
|
||||||
|
make -j"${cores:-3}" install
|
||||||
|
}
|
||||||
|
|
||||||
|
# finalize <prefix> <platform> [ <arch> ... ]
|
||||||
|
#
|
||||||
|
# Prepare the final build product.
|
||||||
|
# The build script invokes this once after a successful build of all targets.
|
||||||
|
finalize() { _finalize "$@"; }
|
||||||
|
_finalize() {
|
||||||
|
finalize_merge "$@"
|
||||||
|
finalize_clean "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# finalize_merge <prefix> <platform> [ <arch> ... ]
|
||||||
|
#
|
||||||
|
# Merge all targets into a product the application can use, available at `<prefix>/out`.
|
||||||
|
#
|
||||||
|
# By default, this will copy the headers to `<prefix>/out/include`, install libraries into `<prefix>/out/lib` and mark the output product as successful.
|
||||||
|
finalize_merge() { _finalize_merge "$@"; }
|
||||||
|
_finalize_merge() {
|
||||||
|
local prefix=$1 platform=$2; shift 2
|
||||||
|
|
||||||
|
mv -f -- "$prefix/$1/include" "$prefix/out/"
|
||||||
|
|
||||||
|
mkdir -p "$prefix/out/lib"
|
||||||
|
case "$platform" in
|
||||||
|
'macos'|'ios')
|
||||||
|
for lib in "$prefix/$1/lib/"*; do
|
||||||
|
if lipo -info "$lib" >/dev/null 2>&1; then
|
||||||
|
local lib=("${lib##*/}") libs=("${@/#/$prefix/}") libs=("${libs[@]/%//lib/$lib}")
|
||||||
|
lipo -create "${libs[@]}" -output "$prefix/out/lib/$lib"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
'android')
|
||||||
|
for arch; do
|
||||||
|
local abi=$arch
|
||||||
|
case "$arch" in
|
||||||
|
'arm') abi='armeabi-v7a' ;;
|
||||||
|
'arm64') abi='arm64-v8a' ;;
|
||||||
|
esac
|
||||||
|
install -d "$prefix/out/lib/$abi"
|
||||||
|
install -p "$prefix/$arch/lib/"*.so "$prefix/out/lib/$abi"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
touch "$prefix/out/.success"
|
||||||
|
}
|
||||||
|
|
||||||
|
# finalize_clean <prefix> [ <arch> ... ]
|
||||||
|
#
|
||||||
|
# Clean up the library after a successful build (eg. housekeeping of temporary files).
|
||||||
|
#
|
||||||
|
# By default, this will run `make clean`.
|
||||||
|
finalize_clean() { _finalize_clean "$@"; }
|
||||||
|
_finalize_clean() {
|
||||||
|
[[ ! -e Makefile ]] || make -s clean
|
||||||
|
}
|
||||||
|
|
||||||
|
# build <name> [<platform>]
|
||||||
|
#
|
||||||
|
# Build the library <name> (found at ../<name>) for platform <platform> (or "host" if unspecified).
|
||||||
|
build() { _build "$@"; }
|
||||||
|
_build() {
|
||||||
|
local name=$1 platform=${2:-host}
|
||||||
|
local path="../$name"
|
||||||
|
[[ $path = /* ]] || path="${BASH_SOURCE%/*}/$path"
|
||||||
|
cd "$path"
|
||||||
|
|
||||||
|
if [[ $platform = host ]]; then
|
||||||
|
case "$(uname -s)" in
|
||||||
|
'Darwin') platform='macos' archs=( "$(uname -m)" ) ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if (( ! ${#archs[@]} )); then
|
||||||
|
case "$platform" in
|
||||||
|
'macos') archs=( 'x86_64' ) ;;
|
||||||
|
'ios') archs=( 'i386' 'x86_64' 'armv7' 'armv7s' 'arm64' ) ;;
|
||||||
|
'android') archs=( 'arm' 'arm64' 'x86' 'x86_64' ) ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
local prefix="$PWD/build-$platform~"
|
||||||
|
initialize "$prefix" "$platform"
|
||||||
|
|
||||||
|
# "clean" argument wipes the lib clean and exits. If .success exists in prefix output, skip build.
|
||||||
|
if [[ ${BASH_ARGV[@]:(-1)} = clean ]]; then
|
||||||
|
clean "$prefix" "$platform"
|
||||||
|
exit
|
||||||
|
elif [[ -e "$prefix"/out/.success ]]; then
|
||||||
|
echo >&2 "Skipping build for $platform: output product already built successfully."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare the output location and build configuration.
|
||||||
|
prepare "$prefix" "$platform" "${archs[@]}"
|
||||||
|
|
||||||
|
# Repeat the build for each individual architecture.
|
||||||
|
for arch in "${archs[@]}"; do (
|
||||||
|
|
||||||
|
# Set up a base environment for the platform.
|
||||||
|
case "$platform" in
|
||||||
|
'macos')
|
||||||
|
SDKROOT="$(xcrun --show-sdk-path --sdk macosx)"
|
||||||
|
export PATH="$(xcrun --show-sdk-platform-path --sdk macosx)/usr/bin:$PATH"
|
||||||
|
export CFLAGS="-arch $arch -flto -O2 -g -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} $CFLAGS"
|
||||||
|
export LDFLAGS="-arch $arch -flto -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} $LDFLAGS"
|
||||||
|
export CPPFLAGS="$CFLAGS $CPPFLAGS"
|
||||||
|
;;
|
||||||
|
'ios')
|
||||||
|
case "$arch" in
|
||||||
|
*'arm'*)
|
||||||
|
SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
|
||||||
|
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
|
||||||
|
export CFLAGS="-arch $arch -mthumb -fembed-bitcode -flto -O2 -g -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $CFLAGS"
|
||||||
|
export LDFLAGS="-arch $arch -mthumb -fembed-bitcode -flto -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
|
||||||
|
export CPPFLAGS="$CFLAGS $CPPFLAGS"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
SDKROOT="$(xcrun --show-sdk-path --sdk iphonesimulator)"
|
||||||
|
export PATH="$(xcrun --show-sdk-platform-path --sdk iphonesimulator)/usr/bin:$PATH"
|
||||||
|
export CFLAGS="-arch $arch -flto -O2 -g -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $CFLAGS"
|
||||||
|
export LDFLAGS="-arch $arch -flto -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
|
||||||
|
export CPPFLAGS="$CFLAGS $CPPFLAGS"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
'android')
|
||||||
|
[[ -x $ANDROID_NDK_HOME/build/ndk-build ]] || { echo >&2 "Android NDK not found. Please set ANDROID_NDK_HOME."; return 1; }
|
||||||
|
|
||||||
|
SDKROOT="$prefix/$arch/ndk"
|
||||||
|
# Platform 21 is lowest that supports x86_64
|
||||||
|
"$ANDROID_NDK_HOME/build/tools/make-standalone-toolchain.sh" --force --install-dir="$SDKROOT" --platform='android-21' --arch="$arch"
|
||||||
|
export PATH="$SDKROOT/bin:$PATH"
|
||||||
|
export CFLAGS="-O2 -g $CFLAGS"
|
||||||
|
export LDFLAGS="-avoid-version $LDFLAGS"
|
||||||
|
export CC='clang'
|
||||||
|
|
||||||
|
# For GCC:
|
||||||
|
# arm CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb" LDFLAGS="-Wl,--fix-cortex-a8"
|
||||||
|
# arm64 CFLAGS="-march=armv8-a"
|
||||||
|
# x86 CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
|
||||||
|
# x86_64 CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
target "$prefix" "$platform" "$arch"
|
||||||
|
); done
|
||||||
|
|
||||||
|
finalize "$prefix" "$platform" "${archs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
8
lib/bin/build_libjson-c-android
Executable file
8
lib/bin/build_libjson-c-android
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source "${BASH_SOURCE%/*}/build_lib"
|
||||||
|
|
||||||
|
autoreconf() {
|
||||||
|
command autoreconf -Iautoconf-archive/m4 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
build libjson-c android
|
||||||
8
lib/bin/build_libjson-c-ios
Executable file
8
lib/bin/build_libjson-c-ios
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source "${BASH_SOURCE%/*}/build_lib"
|
||||||
|
|
||||||
|
autoreconf() {
|
||||||
|
command autoreconf -Iautoconf-archive/m4 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
build libjson-c ios
|
||||||
8
lib/bin/build_libjson-c-macos
Executable file
8
lib/bin/build_libjson-c-macos
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source "${BASH_SOURCE%/*}/build_lib"
|
||||||
|
|
||||||
|
autoreconf() {
|
||||||
|
command autoreconf -Iautoconf-archive/m4 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
build libjson-c macos
|
||||||
4
lib/bin/build_libsodium-android
Executable file
4
lib/bin/build_libsodium-android
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source "${BASH_SOURCE%/*}/build_lib"
|
||||||
|
|
||||||
|
build libsodium android
|
||||||
4
lib/bin/build_libsodium-ios
Executable file
4
lib/bin/build_libsodium-ios
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source "${BASH_SOURCE%/*}/build_lib"
|
||||||
|
|
||||||
|
build libsodium ios
|
||||||
4
lib/bin/build_libsodium-macos
Executable file
4
lib/bin/build_libsodium-macos
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source "${BASH_SOURCE%/*}/build_lib"
|
||||||
|
|
||||||
|
build libsodium macos
|
||||||
1
lib/libjson-c
Submodule
1
lib/libjson-c
Submodule
Submodule lib/libjson-c added at e3752b5894
1
lib/libsodium
Submodule
1
lib/libsodium
Submodule
Submodule lib/libsodium added at cfb0f94704
24
platform-android/CMakeLists.txt
Normal file
24
platform-android/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
project( mpw-core C )
|
||||||
|
cmake_minimum_required( VERSION 3.0.0 )
|
||||||
|
|
||||||
|
add_library( mpw SHARED
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/base64.c"
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/aes.c"
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm.c"
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-types.c"
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-util.c"
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-marshal-util.c"
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-marshal.c"
|
||||||
|
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-jni.c" )
|
||||||
|
|
||||||
|
add_library( sodium SHARED IMPORTED )
|
||||||
|
set_target_properties( sodium PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out/lib/${ANDROID_ABI}/libsodium.so" )
|
||||||
|
target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out" )
|
||||||
|
target_compile_definitions( mpw PRIVATE -DMPW_SODIUM=1 )
|
||||||
|
target_link_libraries( mpw PRIVATE sodium )
|
||||||
|
|
||||||
|
add_library( json-c SHARED IMPORTED )
|
||||||
|
set_target_properties( json-c PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out/lib/${ANDROID_ABI}/libjson-c.so" )
|
||||||
|
target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out" )
|
||||||
|
target_compile_definitions( mpw PRIVATE -DMPW_JSON=1 )
|
||||||
|
target_link_libraries( mpw PRIVATE json-c )
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
To build this module, please ensure you've done the following setup:
|
|
||||||
|
|
||||||
1. Installed the Android SDK and fully downloaded the Android SDK platform 21 in it.
|
|
||||||
2. Set the environment variable ANDROID_HOME in your shell or in ~/.mavenrc to point to the root of your Android SDK install.
|
|
||||||
3. Installed the Android SDK into your Maven's local repository.
|
|
||||||
3a. Clone the maven-android-sdk-deployer available from here: https://github.com/mosabua/maven-android-sdk-deployer.git
|
|
||||||
3b. In the root of this project, run: mvn install -P 5.0
|
|
||||||
|
|
||||||
To build this module:
|
|
||||||
|
|
||||||
1. Build the parent, by going into 'MasterPassword/Java' and running: mvn clean install
|
|
||||||
2. Build this module, by going into 'MasterPassword/Java/masterpassword-android' and running: mvn clean install
|
|
||||||
3. You can then find the APK in: 'MasterPassword/Java/masterpassword-android/target'
|
|
||||||
@@ -2,45 +2,64 @@ apply plugin: 'com.android.application'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 25
|
||||||
buildToolsVersion '25.0.0'
|
buildToolsVersion '27.0.3'
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_7
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_7
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'com.lyndir.masterpassword'
|
applicationId 'com.lyndir.masterpassword'
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 20401
|
versionCode 20701
|
||||||
versionName '2.4.1'
|
versionName '2.7.1'
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path 'CMakeLists.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDirs "$projectDir/../lib/libsodium/build-android~/out/lib",
|
||||||
|
"$projectDir/../lib/libjson-c/build-android~/out/lib"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW=$(mpw masterpassword-android) gradle assembleRelease
|
// release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle masterpassword-android:assembleRelease
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
storeFile file( 'masterpassword.keystore' )
|
storeFile file( 'masterpassword.keystore' )
|
||||||
storePassword System.getenv( 'STORE_PW' )
|
storePassword System.getenv( 'STORE_PW' )
|
||||||
|
|
||||||
keyAlias 'masterpassword-android'
|
keyAlias 'masterpassword-android'
|
||||||
keyPassword System.getenv( 'KEY_PW' )
|
keyPassword System.getenv( 'KEY_PW_ANDROID' )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
if (System.getenv( 'STORE_PW' ) != null)
|
if (System.getenv( 'KEY_PW_ANDROID' ) != null)
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project( ':masterpassword-algorithm' )
|
api project( ':masterpassword-algorithm' )
|
||||||
compile project( ':masterpassword-tests' )
|
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
|
||||||
|
|
||||||
compile group: 'org.slf4j', name: 'slf4j-android', version:'1.7.13-underscore'
|
implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.13-underscore'
|
||||||
compile group: 'com.jakewharton', name: 'butterknife', version:'8.5.1'
|
implementation group: 'com.jakewharton', name: 'butterknife', version: '8.5.1'
|
||||||
annotationProcessor group: 'com.jakewharton', name: 'butterknife-compiler', version:'8.5.1'
|
annotationProcessor group: 'com.jakewharton', name: 'butterknife-compiler', version: '8.5.1'
|
||||||
compile files( 'libs/scrypt-1.4.0-native.jar' )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preBuild {
|
||||||
|
dependsOn task( type: Exec, 'buildLibSodium', {
|
||||||
|
commandLine "$projectDir/../lib/bin/build_libsodium-android"
|
||||||
|
} )
|
||||||
|
dependsOn task( type: Exec, 'buildLibJson-c', {
|
||||||
|
commandLine "$projectDir/../lib/bin/build_libjson-c-android"
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,145 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
|
||||||
<parent>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Master Password Android</name>
|
|
||||||
<description>An Android application to the Master Password algorithm</description>
|
|
||||||
|
|
||||||
<artifactId>masterpassword-android</artifactId>
|
|
||||||
<packaging>apk</packaging>
|
|
||||||
|
|
||||||
<!-- BUILD CONFIGURATION -->
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
|
||||||
<artifactId>android-maven-plugin</artifactId>
|
|
||||||
|
|
||||||
<configuration>
|
|
||||||
<zipalign>
|
|
||||||
<verbose>true</verbose>
|
|
||||||
<skip>false</skip>
|
|
||||||
</zipalign>
|
|
||||||
<sdk>
|
|
||||||
<platform>21</platform>
|
|
||||||
</sdk>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>release</id>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
|
||||||
<artifactId>android-maven-plugin</artifactId>
|
|
||||||
|
|
||||||
<configuration>
|
|
||||||
<sign>
|
|
||||||
<debug>false</debug>
|
|
||||||
</sign>
|
|
||||||
</configuration>
|
|
||||||
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>manifest-update</id>
|
|
||||||
<phase>process-resources</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>manifest-update</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<manifestVersionCodeUpdateFromVersion>true</manifestVersionCodeUpdateFromVersion>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jarsigner-plugin</artifactId>
|
|
||||||
<version>1.4</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>signing</id>
|
|
||||||
<goals>
|
|
||||||
<goal>sign</goal>
|
|
||||||
</goals>
|
|
||||||
<phase>package</phase>
|
|
||||||
<inherited>true</inherited>
|
|
||||||
<configuration>
|
|
||||||
<archiveDirectory />
|
|
||||||
<includes>
|
|
||||||
<include>target/*.apk</include>
|
|
||||||
</includes>
|
|
||||||
<keystore>release.jks</keystore>
|
|
||||||
<storepass>${env.PASSWORD}</storepass>
|
|
||||||
<keypass>${env.PASSWORD}</keypass>
|
|
||||||
<alias>masterpassword-android</alias>
|
|
||||||
<arguments>
|
|
||||||
<argument>-sigalg</argument><argument>MD5withRSA</argument>
|
|
||||||
<argument>-digestalg</argument><argument>SHA1</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
|
|
||||||
<!-- DEPENDENCY MANAGEMENT -->
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- PROJECT REFERENCES -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-tests</artifactId>
|
|
||||||
<version>GIT-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- EXTERNAL DEPENDENCIES -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.jakewharton</groupId>
|
|
||||||
<artifactId>butterknife</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-android</artifactId>
|
|
||||||
<version>1.7.13-underscore</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>android</groupId>
|
|
||||||
<artifactId>android</artifactId>
|
|
||||||
<version>5.0.1_r2</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.lambdaworks</groupId>
|
|
||||||
<artifactId>scrypt</artifactId>
|
|
||||||
<version>1.4.0-android</version>
|
|
||||||
<type>jar</type>
|
|
||||||
<classifier>native</classifier>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
import android.app.*;
|
import android.app.*;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
@@ -33,14 +33,14 @@ import android.view.WindowManager;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.Executors;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
@@ -50,14 +50,17 @@ public class EmergencyActivity extends Activity {
|
|||||||
private static final Logger logger = Logger.get( EmergencyActivity.class );
|
private static final Logger logger = Logger.get( EmergencyActivity.class );
|
||||||
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
|
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
|
||||||
private static final int PASSWORD_NOTIFICATION = 0;
|
private static final int PASSWORD_NOTIFICATION = 0;
|
||||||
public static final int CLIPBOARD_CLEAR_DELAY = 20 /* s */ * MPConstant.MS_PER_S;
|
private static final int CLIPBOARD_CLEAR_DELAY = 20 /* s */ * MPConstants.MS_PER_S;
|
||||||
|
|
||||||
private final Preferences preferences = Preferences.get( this );
|
private final Preferences preferences = Preferences.get( this );
|
||||||
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
|
||||||
private final ImmutableList<MPSiteType> allSiteTypes = ImmutableList.copyOf( MPSiteType.forClass( MPSiteTypeClass.Generated ) );
|
Executors.newSingleThreadExecutor() );
|
||||||
private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() );
|
private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf(
|
||||||
|
MPResultType.forClass( MPResultTypeClass.Template ) );
|
||||||
|
private final ImmutableList<MPAlgorithm.Version> allVersions = ImmutableList.copyOf( MPAlgorithm.Version.values() );
|
||||||
|
|
||||||
private ListenableFuture<MasterKey> masterKeyFuture;
|
@Nullable
|
||||||
|
private MPMasterKey masterKey;
|
||||||
|
|
||||||
@BindView(R.id.progressView)
|
@BindView(R.id.progressView)
|
||||||
ProgressBar progressView;
|
ProgressBar progressView;
|
||||||
@@ -71,8 +74,8 @@ public class EmergencyActivity extends Activity {
|
|||||||
@BindView(R.id.siteNameField)
|
@BindView(R.id.siteNameField)
|
||||||
EditText siteNameField;
|
EditText siteNameField;
|
||||||
|
|
||||||
@BindView(R.id.siteTypeButton)
|
@BindView(R.id.resultTypeButton)
|
||||||
Button siteTypeButton;
|
Button resultTypeButton;
|
||||||
|
|
||||||
@BindView(R.id.counterField)
|
@BindView(R.id.counterField)
|
||||||
Button siteCounterButton;
|
Button siteCounterButton;
|
||||||
@@ -97,7 +100,7 @@ public class EmergencyActivity extends Activity {
|
|||||||
|
|
||||||
private int id_userName;
|
private int id_userName;
|
||||||
private int id_masterPassword;
|
private int id_masterPassword;
|
||||||
private int id_version;
|
@Nullable
|
||||||
private String sitePassword;
|
private String sitePassword;
|
||||||
|
|
||||||
public static void start(final Context context) {
|
public static void start(final Context context) {
|
||||||
@@ -127,19 +130,19 @@ public class EmergencyActivity extends Activity {
|
|||||||
siteNameField.addTextChangedListener( new ValueChangedListener() {
|
siteNameField.addTextChangedListener( new ValueChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
void update() {
|
void update() {
|
||||||
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
|
siteCounterButton.setText( MessageFormat.format( "{0}", UnsignedInteger.ONE ) );
|
||||||
updateSitePassword();
|
updateSitePassword();
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
siteTypeButton.setOnClickListener( new View.OnClickListener() {
|
resultTypeButton.setOnClickListener( new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(final View v) {
|
public void onClick(final View v) {
|
||||||
@SuppressWarnings("SuspiciousMethodCalls")
|
@SuppressWarnings("SuspiciousMethodCalls")
|
||||||
MPSiteType siteType =
|
MPResultType resultType =
|
||||||
allSiteTypes.get( (allSiteTypes.indexOf( siteTypeButton.getTag() ) + 1) % allSiteTypes.size() );
|
allResultTypes.get( (allResultTypes.indexOf( resultTypeButton.getTag() ) + 1) % allResultTypes.size() );
|
||||||
preferences.setDefaultSiteType( siteType );
|
preferences.setDefaultResultType( resultType );
|
||||||
siteTypeButton.setTag( siteType );
|
resultTypeButton.setTag( resultType );
|
||||||
siteTypeButton.setText( siteType.getShortName() );
|
resultTypeButton.setText( resultType.getShortName() );
|
||||||
updateSitePassword();
|
updateSitePassword();
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
@@ -152,11 +155,22 @@ public class EmergencyActivity extends Activity {
|
|||||||
updateSitePassword();
|
updateSitePassword();
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
siteCounterButton.setOnLongClickListener( new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(final View v) {
|
||||||
|
if (UnsignedInteger.valueOf( siteCounterButton.getText().toString() ).equals( UnsignedInteger.ONE ))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
siteCounterButton.setText( MessageFormat.format( "{0}", UnsignedInteger.ONE ) );
|
||||||
|
updateSitePassword();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} );
|
||||||
siteVersionButton.setOnClickListener( new View.OnClickListener() {
|
siteVersionButton.setOnClickListener( new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(final View v) {
|
public void onClick(final View v) {
|
||||||
@SuppressWarnings("SuspiciousMethodCalls")
|
@SuppressWarnings("SuspiciousMethodCalls")
|
||||||
MasterKey.Version siteVersion =
|
MPAlgorithm.Version siteVersion =
|
||||||
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
|
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
|
||||||
preferences.setDefaultVersion( siteVersion );
|
preferences.setDefaultVersion( siteVersion );
|
||||||
siteVersionButton.setTag( siteVersion );
|
siteVersionButton.setTag( siteVersion );
|
||||||
@@ -175,13 +189,13 @@ public class EmergencyActivity extends Activity {
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
fullNameField.setTypeface( Res.get( this ).exo_Thin );
|
fullNameField.setTypeface( Res.get( this ).exo_Thin() );
|
||||||
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
masterPasswordField.setTypeface( Res.get( this ).sourceCodePro_ExtraLight );
|
masterPasswordField.setTypeface( Res.get( this ).sourceCodePro_ExtraLight() );
|
||||||
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
siteNameField.setTypeface( Res.get( this ).exo_Regular );
|
siteNameField.setTypeface( Res.get( this ).exo_Regular() );
|
||||||
siteNameField.setPaintFlags( siteNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
siteNameField.setPaintFlags( siteNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
sitePasswordField.setTypeface( Res.get( this ).sourceCodePro_Black );
|
sitePasswordField.setTypeface( Res.get( this ).sourceCodePro_Black() );
|
||||||
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
|
||||||
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||||
@@ -213,20 +227,20 @@ public class EmergencyActivity extends Activity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
|
// FIXME: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
|
||||||
|
|
||||||
fullNameField.setText( preferences.getFullName() );
|
fullNameField.setText( preferences.getFullName() );
|
||||||
rememberFullNameField.setChecked( preferences.isRememberFullName() );
|
rememberFullNameField.setChecked( preferences.isRememberFullName() );
|
||||||
forgetPasswordField.setChecked( preferences.isForgetPassword() );
|
forgetPasswordField.setChecked( preferences.isForgetPassword() );
|
||||||
maskPasswordField.setChecked( preferences.isMaskPassword() );
|
maskPasswordField.setChecked( preferences.isMaskPassword() );
|
||||||
sitePasswordField.setTransformationMethod( preferences.isMaskPassword()? new PasswordTransformationMethod(): null );
|
sitePasswordField.setTransformationMethod( preferences.isMaskPassword()? new PasswordTransformationMethod(): null );
|
||||||
MPSiteType defaultSiteType = preferences.getDefaultSiteType();
|
MPResultType defaultResultType = preferences.getDefaultResultType();
|
||||||
siteTypeButton.setTag( defaultSiteType );
|
resultTypeButton.setTag( defaultResultType );
|
||||||
siteTypeButton.setText( defaultSiteType.getShortName() );
|
resultTypeButton.setText( defaultResultType.getShortName() );
|
||||||
MasterKey.Version defaultVersion = preferences.getDefaultVersion();
|
MPAlgorithm.Version defaultVersion = preferences.getDefaultVersion();
|
||||||
siteVersionButton.setTag( defaultVersion );
|
siteVersionButton.setTag( defaultVersion );
|
||||||
siteVersionButton.setText( defaultVersion.name() );
|
siteVersionButton.setText( defaultVersion.name() );
|
||||||
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
|
siteCounterButton.setText( MessageFormat.format( "{0}", UnsignedInteger.ONE ) );
|
||||||
|
|
||||||
if (TextUtils.isEmpty( fullNameField.getText() ))
|
if (TextUtils.isEmpty( fullNameField.getText() ))
|
||||||
fullNameField.requestFocus();
|
fullNameField.requestFocus();
|
||||||
@@ -241,10 +255,8 @@ public class EmergencyActivity extends Activity {
|
|||||||
if (preferences.isForgetPassword()) {
|
if (preferences.isForgetPassword()) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
id_userName = id_masterPassword = 0;
|
id_userName = id_masterPassword = 0;
|
||||||
if (masterKeyFuture != null) {
|
if (masterKey != null)
|
||||||
masterKeyFuture.cancel( true );
|
masterKey = null;
|
||||||
masterKeyFuture = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
masterPasswordField.setText( "" );
|
masterPasswordField.setText( "" );
|
||||||
}
|
}
|
||||||
@@ -258,25 +270,19 @@ public class EmergencyActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void updateMasterKey() {
|
private synchronized void updateMasterKey() {
|
||||||
final String fullName = fullNameField.getText().toString();
|
String fullName = fullNameField.getText().toString();
|
||||||
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
|
char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
|
||||||
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
|
|
||||||
if ((id_userName == fullName.hashCode())
|
if ((id_userName == fullName.hashCode())
|
||||||
&& (id_masterPassword == Arrays.hashCode( masterPassword ))
|
&& (id_masterPassword == Arrays.hashCode( masterPassword )))
|
||||||
&& (id_version == version.ordinal()))
|
if (masterKey != null)
|
||||||
if ((masterKeyFuture != null) && !masterKeyFuture.isCancelled())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
id_userName = fullName.hashCode();
|
id_userName = fullName.hashCode();
|
||||||
id_masterPassword = Arrays.hashCode( masterPassword );
|
id_masterPassword = Arrays.hashCode( masterPassword );
|
||||||
id_version = version.ordinal();
|
|
||||||
|
|
||||||
if (preferences.isRememberFullName())
|
if (preferences.isRememberFullName())
|
||||||
preferences.setFullName( fullName );
|
preferences.setFullName( fullName );
|
||||||
|
|
||||||
if (masterKeyFuture != null)
|
|
||||||
masterKeyFuture.cancel( true );
|
|
||||||
|
|
||||||
if (fullName.isEmpty() || (masterPassword.length == 0)) {
|
if (fullName.isEmpty() || (masterPassword.length == 0)) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
@@ -285,43 +291,21 @@ public class EmergencyActivity extends Activity {
|
|||||||
|
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.VISIBLE );
|
progressView.setVisibility( View.VISIBLE );
|
||||||
(masterKeyFuture = executor.submit( new Callable<MasterKey>() {
|
masterKey = new MPMasterKey( fullName, masterPassword );
|
||||||
@Override
|
updateSitePassword();
|
||||||
public MasterKey call()
|
|
||||||
throws Exception {
|
|
||||||
try {
|
|
||||||
return MasterKey.create( version, fullName, masterPassword );
|
|
||||||
}
|
|
||||||
catch (final Exception e) {
|
|
||||||
sitePasswordField.setText( "" );
|
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
|
||||||
logger.err( e, "While generating master key." );
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} )).addListener( new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
runOnUiThread( new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateSitePassword();
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}, executor );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSitePassword() {
|
private void updateSitePassword() {
|
||||||
final String siteName = siteNameField.getText().toString();
|
final String siteName = siteNameField.getText().toString();
|
||||||
final MPSiteType type = (MPSiteType) siteTypeButton.getTag();
|
final MPResultType type = (MPResultType) resultTypeButton.getTag();
|
||||||
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
|
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
|
||||||
|
final MPAlgorithm.Version version = (MPAlgorithm.Version) siteVersionButton.getTag();
|
||||||
|
|
||||||
if ((masterKeyFuture == null) || siteName.isEmpty() || (type == null)) {
|
if ((masterKey == null) || siteName.isEmpty() || (type == null)) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
|
||||||
if (masterKeyFuture == null)
|
if (masterKey == null)
|
||||||
updateMasterKey();
|
updateMasterKey();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -332,7 +316,8 @@ public class EmergencyActivity extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
|
sitePassword = masterKey.siteResult( siteName, version.getAlgorithm(), counter,
|
||||||
|
MPKeyPurpose.Authentication, null, type, null );
|
||||||
|
|
||||||
runOnUiThread( new Runnable() {
|
runOnUiThread( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -342,66 +327,59 @@ public class EmergencyActivity extends Activity {
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
catch (final InterruptedException ignored) {
|
catch (final MPKeyUnavailableException ignored) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
}
|
}
|
||||||
catch (final ExecutionException e) {
|
catch (final MPAlgorithmException e) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
logger.err( e, "While generating site password." );
|
logger.err( e, "While generating site password." );
|
||||||
throw Throwables.propagate( e );
|
|
||||||
}
|
|
||||||
catch (final RuntimeException e) {
|
|
||||||
sitePasswordField.setText( "" );
|
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
|
||||||
logger.err( e, "While generating site password." );
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void integrityTests(final View view) {
|
|
||||||
if (masterKeyFuture != null) {
|
|
||||||
masterKeyFuture.cancel( true );
|
|
||||||
masterKeyFuture = null;
|
|
||||||
}
|
|
||||||
TestActivity.startNoSkip( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copySitePassword(final View view) {
|
public void copySitePassword(final View view) {
|
||||||
final String currentSitePassword = sitePassword;
|
final String currentSitePassword = sitePassword;
|
||||||
if (TextUtils.isEmpty( currentSitePassword ))
|
if (TextUtils.isEmpty( currentSitePassword ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
|
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
|
||||||
final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
|
final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
|
||||||
|
if (clipboardManager == null)
|
||||||
|
return;
|
||||||
|
|
||||||
String title = strf( "Password for %s", siteNameField.getText() );
|
String title = strf( "Password for %s", siteNameField.getText() );
|
||||||
ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
|
ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
|
||||||
clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
|
clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
|
||||||
|
|
||||||
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
|
if (notificationManager != null) {
|
||||||
.setContentText( "Paste the password into your app." )
|
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
|
||||||
.setSmallIcon( R.drawable.icon )
|
.setContentText(
|
||||||
.setAutoCancel( true );
|
"Paste the password into your app." )
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
.setSmallIcon( R.drawable.icon )
|
||||||
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
|
.setAutoCancel( true );
|
||||||
.setCategory( Notification.CATEGORY_RECOMMENDATION )
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||||
.setLocalOnly( true );
|
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
|
||||||
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
|
.setCategory( Notification.CATEGORY_RECOMMENDATION )
|
||||||
|
.setLocalOnly( true );
|
||||||
|
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
|
||||||
|
}
|
||||||
|
|
||||||
final Timer timer = new Timer();
|
final Timer timer = new Timer();
|
||||||
timer.schedule( new TimerTask() {
|
timer.schedule( new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
ClipData clip = clipboardManager.getPrimaryClip();
|
ClipData clip = clipboardManager.getPrimaryClip();
|
||||||
for (int i = 0; i < clip.getItemCount(); ++i)
|
for (int i = 0; i < clip.getItemCount(); ++i)
|
||||||
if (currentSitePassword.equals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
|
if (currentSitePassword.contentEquals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
|
||||||
clipboardManager.setPrimaryClip( EMPTY_CLIP );
|
clipboardManager.setPrimaryClip( EMPTY_CLIP );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
notificationManager.cancel( PASSWORD_NOTIFICATION );
|
|
||||||
|
if (notificationManager != null)
|
||||||
|
notificationManager.cancel( PASSWORD_NOTIFICATION );
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
}
|
}
|
||||||
}, CLIPBOARD_CLEAR_DELAY );
|
}, CLIPBOARD_CLEAR_DELAY );
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
|
//
|
||||||
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You can find a copy of the GNU General Public License in the
|
||||||
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-06-10
|
||||||
|
*/
|
||||||
|
public class MPConstants {
|
||||||
|
|
||||||
|
public static final int MS_PER_S = 1000;
|
||||||
|
}
|
||||||
@@ -22,8 +22,10 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +35,7 @@ public class MainThreadExecutor extends AbstractExecutorService {
|
|||||||
|
|
||||||
private final Handler mHandler = new Handler( Looper.getMainLooper() );
|
private final Handler mHandler = new Handler( Looper.getMainLooper() );
|
||||||
private final Set<Runnable> commands = Sets.newLinkedHashSet();
|
private final Set<Runnable> commands = Sets.newLinkedHashSet();
|
||||||
private boolean shutdown;
|
private boolean shutdown;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(final Runnable command) {
|
public void execute(final Runnable command) {
|
||||||
@@ -63,6 +65,7 @@ public class MainThreadExecutor extends AbstractExecutorService {
|
|||||||
shutdown = true;
|
shutdown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<Runnable> shutdownNow() {
|
public List<Runnable> shutdownNow() {
|
||||||
shutdown = true;
|
shutdown = true;
|
||||||
|
|||||||
@@ -32,15 +32,15 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public final class Preferences {
|
public final class Preferences {
|
||||||
|
|
||||||
private static final String PREF_TESTS_PASSED = "integrityTestsPassed";
|
private static final String PREF_TESTS_PASSED = "integrityTestsPassed";
|
||||||
private static final String PREF_NATIVE_KDF = "nativeKDF";
|
private static final String PREF_NATIVE_KDF = "nativeKDF";
|
||||||
private static final String PREF_REMEMBER_FULL_NAME = "rememberFullName";
|
private static final String PREF_REMEMBER_FULL_NAME = "rememberFullName";
|
||||||
private static final String PREF_FORGET_PASSWORD = "forgetPassword";
|
private static final String PREF_FORGET_PASSWORD = "forgetPassword";
|
||||||
private static final String PREF_MASK_PASSWORD = "maskPassword";
|
private static final String PREF_MASK_PASSWORD = "maskPassword";
|
||||||
private static final String PREF_FULL_NAME = "fullName";
|
private static final String PREF_FULL_NAME = "fullName";
|
||||||
private static final String PREF_SITE_TYPE = "siteType";
|
private static final String PREF_RESULT_TYPE = "resultType";
|
||||||
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
|
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
|
||||||
private static Preferences instance;
|
private static Preferences instance;
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -74,7 +74,7 @@ public final class Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAllowNativeKDF() {
|
public boolean isAllowNativeKDF() {
|
||||||
return prefs().getBoolean( PREF_NATIVE_KDF, MasterKey.isAllowNativeByDefault() );
|
return prefs().getBoolean( PREF_NATIVE_KDF, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setTestsPassed(final Set<String> value) {
|
public boolean setTestsPassed(final Set<String> value) {
|
||||||
@@ -86,7 +86,7 @@ public final class Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getTestsPassed() {
|
public Set<String> getTestsPassed() {
|
||||||
return prefs().getStringSet( PREF_TESTS_PASSED, ImmutableSet.<String>of() );
|
return prefs().getStringSet( PREF_TESTS_PASSED, ImmutableSet.of() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setRememberFullName(final boolean enabled) {
|
public boolean setRememberFullName(final boolean enabled) {
|
||||||
@@ -138,20 +138,21 @@ public final class Preferences {
|
|||||||
return prefs().getString( PREF_FULL_NAME, "" );
|
return prefs().getString( PREF_FULL_NAME, "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setDefaultSiteType(@Nonnull final MPSiteType value) {
|
public boolean setDefaultResultType(final MPResultType value) {
|
||||||
if (getDefaultSiteType() == value)
|
if (getDefaultResultType() == value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
prefs().edit().putInt( PREF_SITE_TYPE, value.ordinal() ).apply();
|
prefs().edit().putInt( PREF_RESULT_TYPE, value.ordinal() ).apply();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public MPSiteType getDefaultSiteType() {
|
public MPResultType getDefaultResultType() {
|
||||||
return MPSiteType.values()[prefs().getInt( PREF_SITE_TYPE, MPSiteType.GeneratedLong.ordinal() )];
|
return MPResultType.values()[
|
||||||
|
prefs().getInt( PREF_RESULT_TYPE, getDefaultVersion().getAlgorithm().mpw_default_result_type().ordinal() )];
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setDefaultVersion(@Nonnull final MasterKey.Version value) {
|
public boolean setDefaultVersion(final MPAlgorithm.Version value) {
|
||||||
if (getDefaultVersion() == value)
|
if (getDefaultVersion() == value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -160,7 +161,8 @@ public final class Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public MasterKey.Version getDefaultVersion() {
|
public MPAlgorithm.Version getDefaultVersion() {
|
||||||
return MasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MasterKey.Version.CURRENT.ordinal() )];
|
return MPAlgorithm.Version.values()[
|
||||||
|
prefs().getInt( PREF_ALGORITHM_VERSION, MPAlgorithm.Version.CURRENT.ordinal() )];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,21 +19,21 @@
|
|||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 2014-08-25
|
* @author lhunath, 2014-08-25
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("NewMethodNamingConvention")
|
||||||
public final class Res {
|
public final class Res {
|
||||||
|
|
||||||
public final Typeface sourceCodePro_Black;
|
private final Typeface sourceCodePro_Black;
|
||||||
public final Typeface sourceCodePro_ExtraLight;
|
private final Typeface sourceCodePro_ExtraLight;
|
||||||
public final Typeface exo_Bold;
|
private final Typeface exo_Bold;
|
||||||
public final Typeface exo_ExtraBold;
|
private final Typeface exo_ExtraBold;
|
||||||
public final Typeface exo_Regular;
|
private final Typeface exo_Regular;
|
||||||
public final Typeface exo_Thin;
|
private final Typeface exo_Thin;
|
||||||
|
|
||||||
private static Res res;
|
private static Res res;
|
||||||
|
|
||||||
@@ -54,4 +54,28 @@ public final class Res {
|
|||||||
exo_Regular = Typeface.createFromAsset( context.getResources().getAssets(), "Exo2.0-Regular.otf" );
|
exo_Regular = Typeface.createFromAsset( context.getResources().getAssets(), "Exo2.0-Regular.otf" );
|
||||||
exo_Thin = Typeface.createFromAsset( context.getResources().getAssets(), "Exo2.0-Thin.otf" );
|
exo_Thin = Typeface.createFromAsset( context.getResources().getAssets(), "Exo2.0-Thin.otf" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Typeface sourceCodePro_Black() {
|
||||||
|
return sourceCodePro_Black;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface sourceCodePro_ExtraLight() {
|
||||||
|
return sourceCodePro_ExtraLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface exo_Bold() {
|
||||||
|
return exo_Bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface exo_ExtraBold() {
|
||||||
|
return exo_ExtraBold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface exo_Regular() {
|
||||||
|
return exo_Regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface exo_Thin() {
|
||||||
|
return exo_Thin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
|
||||||
|
|
||||||
import android.app.*;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.*;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.*;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import com.google.common.base.*;
|
|
||||||
import com.google.common.collect.*;
|
|
||||||
import com.google.common.util.concurrent.*;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
public class TestActivity extends Activity implements MPTestSuite.Listener {
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( TestActivity.class );
|
|
||||||
|
|
||||||
private final Preferences preferences = Preferences.get( this );
|
|
||||||
private final ListeningExecutorService backgroundExecutor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
|
||||||
private final ListeningExecutorService mainExecutor = MoreExecutors.listeningDecorator( new MainThreadExecutor() );
|
|
||||||
|
|
||||||
@BindView(R.id.progressView)
|
|
||||||
ProgressBar progressView;
|
|
||||||
|
|
||||||
@BindView(R.id.statusView)
|
|
||||||
TextView statusView;
|
|
||||||
|
|
||||||
@BindView(R.id.logView)
|
|
||||||
TextView logView;
|
|
||||||
|
|
||||||
@BindView(R.id.actionButton)
|
|
||||||
Button actionButton;
|
|
||||||
|
|
||||||
@BindView(R.id.nativeKDFField)
|
|
||||||
CheckBox nativeKDFField;
|
|
||||||
|
|
||||||
private MPTestSuite testSuite;
|
|
||||||
private ListenableFuture<Boolean> testFuture;
|
|
||||||
private Runnable action;
|
|
||||||
private ImmutableSet<String> testNames;
|
|
||||||
|
|
||||||
public static void startNoSkip(final Context context) {
|
|
||||||
context.startActivity( new Intent( context, TestActivity.class ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate( savedInstanceState );
|
|
||||||
|
|
||||||
setContentView( R.layout.activity_test );
|
|
||||||
ButterKnife.bind( this );
|
|
||||||
|
|
||||||
nativeKDFField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
|
||||||
preferences.setNativeKDFEnabled( isChecked );
|
|
||||||
MasterKey.setAllowNativeByDefault( isChecked );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
|
|
||||||
try {
|
|
||||||
setStatus( 0, 0, null );
|
|
||||||
testSuite = new MPTestSuite();
|
|
||||||
testSuite.setListener( this );
|
|
||||||
testNames = FluentIterable.from( testSuite.getTests().getCases() ).transform(
|
|
||||||
new Function<MPTests.Case, String>() {
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String apply(@Nullable final MPTests.Case input) {
|
|
||||||
return (input == null)? null: input.identifier;
|
|
||||||
}
|
|
||||||
} ).filter( Predicates.notNull() ).toSet();
|
|
||||||
}
|
|
||||||
catch (final MPTestSuite.UnavailableException e) {
|
|
||||||
logger.err( e, "While loading test suite" );
|
|
||||||
setStatus( R.string.tests_unavailable, R.string.tests_btn_unavailable, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
nativeKDFField.setChecked( preferences.isAllowNativeKDF() );
|
|
||||||
|
|
||||||
if (testFuture == null)
|
|
||||||
startTestSuite();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startTestSuite() {
|
|
||||||
if (testFuture != null)
|
|
||||||
testFuture.cancel( true );
|
|
||||||
|
|
||||||
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
|
|
||||||
|
|
||||||
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
|
|
||||||
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(@Nullable final Boolean result) {
|
|
||||||
if ((result != null) && result)
|
|
||||||
setStatus( R.string.tests_passed, R.string.tests_btn_passed, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
preferences.setTestsPassed( testNames );
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
else
|
|
||||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
startTestSuite();
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(final Throwable t) {
|
|
||||||
logger.err( t, "While running test suite" );
|
|
||||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}, mainExecutor );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAction(final View v) {
|
|
||||||
if (action != null)
|
|
||||||
action.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setStatus(final int statusId, final int buttonId, @Nullable final Runnable action) {
|
|
||||||
this.action = action;
|
|
||||||
|
|
||||||
if (statusId == 0)
|
|
||||||
statusView.setText( null );
|
|
||||||
else
|
|
||||||
statusView.setText( statusId );
|
|
||||||
|
|
||||||
if (buttonId == 0)
|
|
||||||
actionButton.setText( null );
|
|
||||||
else
|
|
||||||
actionButton.setText( buttonId );
|
|
||||||
actionButton.setEnabled( action != null );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void progress(final int current, final int max, final String messageFormat, final Object... args) {
|
|
||||||
runOnUiThread( new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
logView.append( strf( "%n" + messageFormat, args ) );
|
|
||||||
|
|
||||||
progressView.setMax( max );
|
|
||||||
progressView.setProgress( current );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
android:id="@id/sitePasswordField"
|
android:id="@id/sitePasswordField"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:nextFocusForward="@+id/siteTypeButton"
|
android:nextFocusForward="@+id/resultTypeButton"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@id/siteTypeButton"
|
android:id="@id/resultTypeButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
@@ -175,12 +175,12 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:labelFor="@id/siteTypeButton"
|
android:labelFor="@id/resultTypeButton"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:textColor="@android:color/tertiary_text_dark"
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
android:text="@string/siteType_hint" />
|
android:text="@string/resultType_hint" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -253,15 +253,6 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:text="@string/btn_tests"
|
|
||||||
android:onClick="integrityTests"
|
|
||||||
android:background="@android:color/transparent" />
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="1dp"
|
android:layout_width="1dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="20dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="1dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:src="@drawable/img_stats" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
tools:max="100"
|
|
||||||
tools:progress="80"
|
|
||||||
style="?android:progressBarStyleHorizontal" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/statusView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:labelFor="@id/sitePasswordField"
|
|
||||||
android:gravity="center"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="@android:color/tertiary_text_dark"
|
|
||||||
android:text="@string/tests_testing" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/logView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="9sp"
|
|
||||||
android:textColor="@android:color/tertiary_text_dark" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/actionButton"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:enabled="false"
|
|
||||||
android:text="@string/tests_btn_testing"
|
|
||||||
android:onClick="onAction" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/nativeKDFField"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="@android:color/tertiary_text_dark"
|
|
||||||
android:text="@string/nativeKDF" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<string name="masterPassword_hint">Your master password</string>
|
<string name="masterPassword_hint">Your master password</string>
|
||||||
<string name="siteName_hint">eg. google.com</string>
|
<string name="siteName_hint">eg. google.com</string>
|
||||||
<string name="sitePassword_hint">Tap to copy</string>
|
<string name="sitePassword_hint">Tap to copy</string>
|
||||||
<string name="siteType_hint">Type</string>
|
<string name="resultType_hint">Type</string>
|
||||||
<string name="siteCounter_hint">Counter</string>
|
<string name="siteCounter_hint">Counter</string>
|
||||||
<string name="siteVersion_hint">Algorithm</string>
|
<string name="siteVersion_hint">Algorithm</string>
|
||||||
<string name="empty" />
|
<string name="empty" />
|
||||||
|
|||||||
2
platform-darwin/External/Pearl
vendored
2
platform-darwin/External/Pearl
vendored
Submodule platform-darwin/External/Pearl updated: f0a66e94e8...b713577cd6
1
platform-darwin/External/libjson-c
vendored
1
platform-darwin/External/libjson-c
vendored
Submodule platform-darwin/External/libjson-c deleted from fcad0ec015
1
platform-darwin/External/libsodium
vendored
1
platform-darwin/External/libsodium
vendored
Submodule platform-darwin/External/libsodium deleted from 4809639ae1
182
platform-darwin/MasterPassword-JNI.xcodeproj/project.pbxproj
Normal file
182
platform-darwin/MasterPassword-JNI.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 50;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
DA1554DE20B3928E00EA92C5 /* aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aes.c; sourceTree = "<group>"; };
|
||||||
|
DA1554DF20B3928E00EA92C5 /* mpw-algorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-algorithm.h"; sourceTree = "<group>"; };
|
||||||
|
DA1554E020B3928E00EA92C5 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = "<group>"; };
|
||||||
|
DA1554E120B3928E00EA92C5 /* mpw-marshal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshal.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554E220B3928E00EA92C5 /* mpw-algorithm_v2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v2.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554E320B3928E00EA92C5 /* mpw-types.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-types.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554E420B3928E00EA92C5 /* mpw-marshal-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshal-util.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554E520B3928E00EA92C5 /* mpw-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-util.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554E620B3928E00EA92C5 /* mpw-algorithm_v1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v1.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554E720B3928E00EA92C5 /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = "<group>"; };
|
||||||
|
DA1554E820B3928E00EA92C5 /* mpw-marshal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshal.h"; sourceTree = "<group>"; };
|
||||||
|
DA1554E920B3928E00EA92C5 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = "<group>"; };
|
||||||
|
DA1554EA20B3928E00EA92C5 /* mpw-algorithm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554EB20B3928E00EA92C5 /* mpw-algorithm_v0.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v0.c"; sourceTree = "<group>"; };
|
||||||
|
DA1554EC20B3928E00EA92C5 /* mpw-types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-types.h"; sourceTree = "<group>"; };
|
||||||
|
DA1554ED20B3928E00EA92C5 /* mpw-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-util.h"; sourceTree = "<group>"; };
|
||||||
|
DA1554EE20B3928E00EA92C5 /* mpw-marshal-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshal-util.h"; sourceTree = "<group>"; };
|
||||||
|
DA1554EF20B3928E00EA92C5 /* mpw-algorithm_v3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v3.c"; sourceTree = "<group>"; };
|
||||||
|
DA1F44B020BCF0C200957B45 /* mpw-jni.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-jni.h"; sourceTree = "<group>"; };
|
||||||
|
DA1F44B120BCF0C200957B45 /* mpw-jni.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-jni.c"; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
DA1554D220B3924000EA92C5 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DA1554DD20B3928E00EA92C5 /* core */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
DA1554DD20B3928E00EA92C5 /* core */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DA1554DE20B3928E00EA92C5 /* aes.c */,
|
||||||
|
DA1554E720B3928E00EA92C5 /* aes.h */,
|
||||||
|
DA1554E920B3928E00EA92C5 /* base64.c */,
|
||||||
|
DA1554E020B3928E00EA92C5 /* base64.h */,
|
||||||
|
DA1554EB20B3928E00EA92C5 /* mpw-algorithm_v0.c */,
|
||||||
|
DA1554E620B3928E00EA92C5 /* mpw-algorithm_v1.c */,
|
||||||
|
DA1554E220B3928E00EA92C5 /* mpw-algorithm_v2.c */,
|
||||||
|
DA1554EF20B3928E00EA92C5 /* mpw-algorithm_v3.c */,
|
||||||
|
DA1554EA20B3928E00EA92C5 /* mpw-algorithm.c */,
|
||||||
|
DA1554DF20B3928E00EA92C5 /* mpw-algorithm.h */,
|
||||||
|
DA1F44B120BCF0C200957B45 /* mpw-jni.c */,
|
||||||
|
DA1F44B020BCF0C200957B45 /* mpw-jni.h */,
|
||||||
|
DA1554E420B3928E00EA92C5 /* mpw-marshal-util.c */,
|
||||||
|
DA1554EE20B3928E00EA92C5 /* mpw-marshal-util.h */,
|
||||||
|
DA1554E120B3928E00EA92C5 /* mpw-marshal.c */,
|
||||||
|
DA1554E820B3928E00EA92C5 /* mpw-marshal.h */,
|
||||||
|
DA1554E320B3928E00EA92C5 /* mpw-types.c */,
|
||||||
|
DA1554EC20B3928E00EA92C5 /* mpw-types.h */,
|
||||||
|
DA1554E520B3928E00EA92C5 /* mpw-util.c */,
|
||||||
|
DA1554ED20B3928E00EA92C5 /* mpw-util.h */,
|
||||||
|
);
|
||||||
|
name = core;
|
||||||
|
path = ../platform-independent/c/core/src;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXLegacyTarget section */
|
||||||
|
DA1554D720B3924000EA92C5 /* MasterPassword-JNI */ = {
|
||||||
|
isa = PBXLegacyTarget;
|
||||||
|
buildArgumentsString = ":masterpassword-algorithm:nativeGenHeaders";
|
||||||
|
buildConfigurationList = DA1554DA20B3924000EA92C5 /* Build configuration list for PBXLegacyTarget "MasterPassword-JNI" */;
|
||||||
|
buildPhases = (
|
||||||
|
);
|
||||||
|
buildToolPath = gradle;
|
||||||
|
buildWorkingDirectory = /Users/lhunath/Documents/workspace/lyndir/MasterPassword/gradle;
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = "MasterPassword-JNI";
|
||||||
|
passBuildSettingsInEnvironment = 1;
|
||||||
|
productName = "MasterPassword-JNI";
|
||||||
|
};
|
||||||
|
/* End PBXLegacyTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
DA1554D320B3924000EA92C5 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 0930;
|
||||||
|
ORGANIZATIONNAME = "Maarten Billemont";
|
||||||
|
TargetAttributes = {
|
||||||
|
DA1554D720B3924000EA92C5 = {
|
||||||
|
CreatedOnToolsVersion = 9.3.1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = DA1554D620B3924000EA92C5 /* Build configuration list for PBXProject "MasterPassword-JNI" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
);
|
||||||
|
mainGroup = DA1554D220B3924000EA92C5;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
DA1554D720B3924000EA92C5 /* MasterPassword-JNI */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
DA1554D820B3924000EA92C5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
DA1554D920B3924000EA92C5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
DA1554DB20B3924000EA92C5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
HEADER_SEARCH_PATHS = (
|
||||||
|
"/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/include/**",
|
||||||
|
../lib/libsodium/src/libsodium/include,
|
||||||
|
);
|
||||||
|
JAVA_HOME = /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home;
|
||||||
|
OTHER_CFLAGS = (
|
||||||
|
"-DMPW_SODIUM=1",
|
||||||
|
"-DMPW_CPERCIVA=0",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
DA1554DC20B3924000EA92C5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
HEADER_SEARCH_PATHS = (
|
||||||
|
"/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/include/**",
|
||||||
|
../lib/libsodium/src/libsodium/include,
|
||||||
|
);
|
||||||
|
JAVA_HOME = /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home;
|
||||||
|
OTHER_CFLAGS = (
|
||||||
|
"-DMPW_SODIUM=1",
|
||||||
|
"-DMPW_CPERCIVA=0",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
DA1554D620B3924000EA92C5 /* Build configuration list for PBXProject "MasterPassword-JNI" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
DA1554D820B3924000EA92C5 /* Debug */,
|
||||||
|
DA1554D920B3924000EA92C5 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
DA1554DA20B3924000EA92C5 /* Build configuration list for PBXLegacyTarget "MasterPassword-JNI" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
DA1554DB20B3924000EA92C5 /* Debug */,
|
||||||
|
DA1554DC20B3924000EA92C5 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = DA1554D320B3924000EA92C5 /* Project object */;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0830"
|
LastUpgradeVersion = "0920"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0830"
|
LastUpgradeVersion = "0920"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "0920"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
|
||||||
|
BuildableName = "mpw-bench"
|
||||||
|
BlueprintName = "mpw-bench"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
|
||||||
|
BuildableName = "mpw-bench"
|
||||||
|
BlueprintName = "mpw-bench"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
|
||||||
|
BuildableName = "mpw-bench"
|
||||||
|
BlueprintName = "mpw-bench"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
|
||||||
|
BuildableName = "mpw-bench"
|
||||||
|
BlueprintName = "mpw-bench"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "0920"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
|
||||||
|
BuildableName = "mpw-cli"
|
||||||
|
BlueprintName = "mpw-cli"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
|
||||||
|
BuildableName = "mpw-cli"
|
||||||
|
BlueprintName = "mpw-cli"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
|
||||||
|
BuildableName = "mpw-cli"
|
||||||
|
BlueprintName = "mpw-cli"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
|
||||||
|
BuildableName = "mpw-cli"
|
||||||
|
BlueprintName = "mpw-cli"
|
||||||
|
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0830"
|
LastUpgradeVersion = "0920"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
language = ""
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "YES"
|
useCustomWorkingDirectory = "YES"
|
||||||
customWorkingDirectory = "/Users/lhunath/Documents/workspace/lyndir/MasterPassword/platform-independent/cli-c"
|
customWorkingDirectory = "/Users/lhunath/Documents/workspace/lyndir/MasterPassword/platform-independent/cli-c"
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:MasterPassword-JNI.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:MasterPassword-iOS.xcodeproj">
|
location = "group:MasterPassword-iOS.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : 9223372036854775807,
|
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : 9223372036854775807,
|
||||||
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : 9223372036854775807,
|
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : 9223372036854775807,
|
||||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 9223372036854775807,
|
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 9223372036854775807,
|
||||||
|
"B38C14663FCFBB7024902D2DB1D013964189DC3B" : 9223372036854775807,
|
||||||
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : 9223372036854775807,
|
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : 9223372036854775807,
|
||||||
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : 0
|
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : 0
|
||||||
},
|
},
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : "MasterPassword\/platform-darwin\/External\/uicolor-utilities\/",
|
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : "MasterPassword\/platform-darwin\/External\/uicolor-utilities\/",
|
||||||
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : "MasterPassword\/platform-darwin\/External\/Pearl\/",
|
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : "MasterPassword\/platform-darwin\/External\/Pearl\/",
|
||||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/platform-darwin\/External\/AttributedMarkdown\/",
|
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/platform-darwin\/External\/AttributedMarkdown\/",
|
||||||
|
"B38C14663FCFBB7024902D2DB1D013964189DC3B" : "MasterPassword\/platform-darwin\/External\/libjson-c\/",
|
||||||
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : "MasterPassword\/platform-darwin\/External\/libsodium\/",
|
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : "MasterPassword\/platform-darwin\/External\/libsodium\/",
|
||||||
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : "MasterPassword\/"
|
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : "MasterPassword\/"
|
||||||
},
|
},
|
||||||
@@ -70,6 +72,11 @@
|
|||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8A15A8EA0B3D0B497C4883425BC74DF995224BB3"
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8A15A8EA0B3D0B497C4883425BC74DF995224BB3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/json-c\/json-c.git",
|
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B38C14663FCFBB7024902D2DB1D013964189DC3B"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Lyndir\/MasterPassword.git",
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Lyndir\/MasterPassword.git",
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>MPGeneratedSiteEntity</key>
|
|
||||||
<dict>
|
|
||||||
<key>Login Name</key>
|
|
||||||
<array>
|
|
||||||
<string>cvccvcvcv</string>
|
|
||||||
</array>
|
|
||||||
<key>Phrase</key>
|
|
||||||
<array>
|
|
||||||
<string>cvcc cvc cvccvcv cvc</string>
|
|
||||||
<string>cvc cvccvcvcv cvcv</string>
|
|
||||||
<string>cv cvccv cvc cvcvccv</string>
|
|
||||||
</array>
|
|
||||||
<key>Maximum Security Password</key>
|
|
||||||
<array>
|
|
||||||
<string>anoxxxxxxxxxxxxxxxxx</string>
|
|
||||||
<string>axxxxxxxxxxxxxxxxxno</string>
|
|
||||||
</array>
|
|
||||||
<key>Long Password</key>
|
|
||||||
<array>
|
|
||||||
<string>CvcvnoCvcvCvcv</string>
|
|
||||||
<string>CvcvCvcvnoCvcv</string>
|
|
||||||
<string>CvcvCvcvCvcvno</string>
|
|
||||||
<string>CvccnoCvcvCvcv</string>
|
|
||||||
<string>CvccCvcvnoCvcv</string>
|
|
||||||
<string>CvccCvcvCvcvno</string>
|
|
||||||
<string>CvcvnoCvccCvcv</string>
|
|
||||||
<string>CvcvCvccnoCvcv</string>
|
|
||||||
<string>CvcvCvccCvcvno</string>
|
|
||||||
<string>CvcvnoCvcvCvcc</string>
|
|
||||||
<string>CvcvCvcvnoCvcc</string>
|
|
||||||
<string>CvcvCvcvCvccno</string>
|
|
||||||
<string>CvccnoCvccCvcv</string>
|
|
||||||
<string>CvccCvccnoCvcv</string>
|
|
||||||
<string>CvccCvccCvcvno</string>
|
|
||||||
<string>CvcvnoCvccCvcc</string>
|
|
||||||
<string>CvcvCvccnoCvcc</string>
|
|
||||||
<string>CvcvCvccCvccno</string>
|
|
||||||
<string>CvccnoCvcvCvcc</string>
|
|
||||||
<string>CvccCvcvnoCvcc</string>
|
|
||||||
<string>CvccCvcvCvccno</string>
|
|
||||||
</array>
|
|
||||||
<key>Medium Password</key>
|
|
||||||
<array>
|
|
||||||
<string>CvcnoCvc</string>
|
|
||||||
<string>CvcCvcno</string>
|
|
||||||
</array>
|
|
||||||
<key>Basic Password</key>
|
|
||||||
<array>
|
|
||||||
<string>aaanaaan</string>
|
|
||||||
<string>aannaaan</string>
|
|
||||||
<string>aaannaaa</string>
|
|
||||||
</array>
|
|
||||||
<key>Short Password</key>
|
|
||||||
<array>
|
|
||||||
<string>Cvcn</string>
|
|
||||||
</array>
|
|
||||||
<key>PIN</key>
|
|
||||||
<array>
|
|
||||||
<string>nnnn</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<key>MPCharacterClasses</key>
|
|
||||||
<dict>
|
|
||||||
<key>V</key>
|
|
||||||
<string>AEIOU</string>
|
|
||||||
<key>C</key>
|
|
||||||
<string>BCDFGHJKLMNPQRSTVWXYZ</string>
|
|
||||||
<key>v</key>
|
|
||||||
<string>aeiou</string>
|
|
||||||
<key>c</key>
|
|
||||||
<string>bcdfghjklmnpqrstvwxyz</string>
|
|
||||||
<key>A</key>
|
|
||||||
<string>AEIOUBCDFGHJKLMNPQRSTVWXYZ</string>
|
|
||||||
<key>a</key>
|
|
||||||
<string>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz</string>
|
|
||||||
<key>n</key>
|
|
||||||
<string>0123456789</string>
|
|
||||||
<key>o</key>
|
|
||||||
<string>@&%?,=[]_:-+*$#!'^~;()/.</string>
|
|
||||||
<key>x</key>
|
|
||||||
<string>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()</string>
|
|
||||||
<key> </key>
|
|
||||||
<string> </string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -66,12 +66,6 @@
|
|||||||
# readwhile command [args]
|
# readwhile command [args]
|
||||||
# Outputs the characters typed by the user into the terminal's input buffer while running the given command.
|
# Outputs the characters typed by the user into the terminal's input buffer while running the given command.
|
||||||
#
|
#
|
||||||
# pushqueue element ...
|
|
||||||
# Pushes the given arguments as elements onto the queue.
|
|
||||||
#
|
|
||||||
# popqueue
|
|
||||||
# Pops one element off the queue.
|
|
||||||
#
|
|
||||||
# log [format] [arguments...]
|
# log [format] [arguments...]
|
||||||
# Log an event at a certain importance level.
|
# Log an event at a certain importance level.
|
||||||
# The event is expressed as a printf(1) format argument.
|
# The event is expressed as a printf(1) format argument.
|
||||||
@@ -85,7 +79,7 @@
|
|||||||
# reverse [-0|-d delimitor] [elements ...] [<<< elements]
|
# reverse [-0|-d delimitor] [elements ...] [<<< elements]
|
||||||
# Reverse the order of the given elements.
|
# Reverse the order of the given elements.
|
||||||
#
|
#
|
||||||
# order [-0|-d char] [-[cC] isAscending|-n] [-t number] [elements ...] [<<< elements]
|
# order [-0|-d char] [-[cC] comparator|-n] [-t number] [elements ...] [<<< elements]
|
||||||
# Orders the elements in ascending order.
|
# Orders the elements in ascending order.
|
||||||
#
|
#
|
||||||
# mutex file
|
# mutex file
|
||||||
@@ -180,6 +174,9 @@ genToc() {
|
|||||||
# | .: GLOBAL DECLARATIONS :. |
|
# | .: GLOBAL DECLARATIONS :. |
|
||||||
# |______________________________________________________________________|
|
# |______________________________________________________________________|
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
export TMPDIR=${TMPDIR:-/tmp} TMPDIR=${TMPDIR%/} TERM=${TERM:-dumb}
|
||||||
|
|
||||||
# Variables for convenience sequences.
|
# Variables for convenience sequences.
|
||||||
bobber=( '.' 'o' 'O' 'o' )
|
bobber=( '.' 'o' 'O' 'o' )
|
||||||
spinner=( '-' \\ '|' '/' )
|
spinner=( '-' \\ '|' '/' )
|
||||||
@@ -190,8 +187,8 @@ runner=( '> >' \
|
|||||||
|
|
||||||
# Variables for terminal requests.
|
# Variables for terminal requests.
|
||||||
[[ -t 2 && $TERM != dumb ]] && {
|
[[ -t 2 && $TERM != dumb ]] && {
|
||||||
COLUMNS=$( tput cols || tput co ) # Columns in a line
|
COLUMNS=$({ tput cols || tput co;} 2>&3) # Columns in a line
|
||||||
LINES=$( tput lines || tput li ) # Lines on screen
|
LINES=$({ tput lines || tput li;} 2>&3) # Lines on screen
|
||||||
alt=$( tput smcup || tput ti ) # Start alt display
|
alt=$( tput smcup || tput ti ) # Start alt display
|
||||||
ealt=$( tput rmcup || tput te ) # End alt display
|
ealt=$( tput rmcup || tput te ) # End alt display
|
||||||
hide=$( tput civis || tput vi ) # Hide cursor
|
hide=$( tput civis || tput vi ) # Hide cursor
|
||||||
@@ -230,7 +227,7 @@ runner=( '> >' \
|
|||||||
tput eA; tput as;
|
tput eA; tput as;
|
||||||
tput ac; tput ae; } ) # Drawing characters
|
tput ac; tput ae; } ) # Drawing characters
|
||||||
back=$'\b'
|
back=$'\b'
|
||||||
} ||:
|
} 3>&2 2>/dev/null ||:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -264,7 +261,10 @@ chr() {
|
|||||||
# Outputs the decimal ASCII value of the given character.
|
# Outputs the decimal ASCII value of the given character.
|
||||||
#
|
#
|
||||||
ord() {
|
ord() {
|
||||||
printf '%d' "'$1"
|
local str=$1 s
|
||||||
|
for (( s=0; s < ${#str}; ++s )); do
|
||||||
|
printf '%d' "'${str:s:1}"
|
||||||
|
done
|
||||||
} # _____________________________________________________________________
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
@@ -277,7 +277,10 @@ ord() {
|
|||||||
# Outputs the hexadecimal ASCII value of the given character.
|
# Outputs the hexadecimal ASCII value of the given character.
|
||||||
#
|
#
|
||||||
hex() {
|
hex() {
|
||||||
printf '%x' "'$1"
|
local str=$1 s
|
||||||
|
for (( s=0; s < ${#str}; ++s )); do
|
||||||
|
printf '%02X' "'${str:s:1}"
|
||||||
|
done
|
||||||
} # _____________________________________________________________________
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
@@ -290,7 +293,10 @@ hex() {
|
|||||||
# Outputs the character that has the given hexadecimal ASCII value.
|
# Outputs the character that has the given hexadecimal ASCII value.
|
||||||
#
|
#
|
||||||
unhex() {
|
unhex() {
|
||||||
printf "\\x$1"
|
local hex=$1 h
|
||||||
|
for (( h=0; h < ${#hex}; h+=2 )); do
|
||||||
|
printf "\\x${hex:h:2}"
|
||||||
|
done
|
||||||
} # _____________________________________________________________________
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
@@ -329,6 +335,26 @@ min() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ______________________________________________________________________
|
||||||
|
# |__ si ________________________________________________________________|
|
||||||
|
#
|
||||||
|
# si number
|
||||||
|
#
|
||||||
|
# Output a human-readable version of the number using SI units.
|
||||||
|
#
|
||||||
|
si() {
|
||||||
|
local number=$1
|
||||||
|
|
||||||
|
if (( number >= 1000000000000000 )); then printf '%dM' "$((number / 1000000000000000))"
|
||||||
|
elif (( number >= 1000000000000 )); then printf '%dM' "$((number / 1000000000000))"
|
||||||
|
elif (( number >= 1000000000 )); then printf '%dM' "$((number / 1000000000))"
|
||||||
|
elif (( number >= 1000000 )); then printf '%dM' "$((number / 1000000))"
|
||||||
|
elif (( number >= 1000 )); then printf '%dk' "$((number / 1000))"
|
||||||
|
else printf '%d' "$number"; fi
|
||||||
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
# |__ totime ____________________________________________________________|
|
# |__ totime ____________________________________________________________|
|
||||||
#
|
#
|
||||||
@@ -528,6 +554,50 @@ iterate() (
|
|||||||
fi
|
fi
|
||||||
) # _____________________________________________________________________
|
) # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# _______________________________________________________________________
|
||||||
|
# |__ csvline ____________________________________________________________|
|
||||||
|
#
|
||||||
|
# csvline [-d delimiter] [-D line-delimiter]
|
||||||
|
#
|
||||||
|
# Parse a CSV record from standard input, storing the fields in the CSVLINE array.
|
||||||
|
#
|
||||||
|
# By default, a single line of input is read and parsed into comma-delimited fields.
|
||||||
|
# Fields can optionally contain double-quoted data, including field delimiters.
|
||||||
|
#
|
||||||
|
# A different field delimiter can be specified using -d. You can use -D
|
||||||
|
# to change the definition of a "record" (eg. to support NULL-delimited records).
|
||||||
|
#
|
||||||
|
csvline() {
|
||||||
|
CSVLINE=()
|
||||||
|
local line field quoted=0 delimiter=, lineDelimiter=$'\n' c
|
||||||
|
local OPTIND=1 arg
|
||||||
|
while getopts :d: arg; do
|
||||||
|
case $arg in
|
||||||
|
d) delimiter=$OPTARG ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
IFS= read -d "$lineDelimiter" -r line || return
|
||||||
|
while IFS= read -rn1 c; do
|
||||||
|
case $c in
|
||||||
|
'"')
|
||||||
|
(( quoted = !quoted ))
|
||||||
|
continue ;;
|
||||||
|
$delimiter)
|
||||||
|
if (( ! quoted )); then
|
||||||
|
CSVLINE+=( "$field" ) field=
|
||||||
|
continue
|
||||||
|
fi ;;
|
||||||
|
esac
|
||||||
|
field+=$c
|
||||||
|
done <<< "$line"
|
||||||
|
[[ $field ]] && CSVLINE+=( "$field" ) ||:
|
||||||
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
# |__ Logging ___________________________________________________________|
|
# |__ Logging ___________________________________________________________|
|
||||||
#
|
#
|
||||||
@@ -551,11 +621,11 @@ iterate() (
|
|||||||
# The closing statement also takes a format and arguments, which are displayed in the spinner.
|
# The closing statement also takes a format and arguments, which are displayed in the spinner.
|
||||||
#
|
#
|
||||||
log() {
|
log() {
|
||||||
local exitcode=$? level=${level:-inf} supported=0 end=$'\n' type=msg conMsg= logMsg= format= colorFormat= date= info= arg= args=() colorArgs=() ruler=
|
local exitcode=$? result=0 level=${level:-inf} supported=0 end=$'\n' type=msg conMsg= logMsg= format= colorFormat= date= info= arg= args=() colorArgs=() ruler=
|
||||||
|
|
||||||
# Handle options.
|
# Handle options.
|
||||||
local OPTIND=1
|
local OPTIND=1
|
||||||
while getopts :tpuPrR:d:n arg; do
|
while getopts :tpuPrR:d:nx arg; do
|
||||||
case $arg in
|
case $arg in
|
||||||
p)
|
p)
|
||||||
end='.. '
|
end='.. '
|
||||||
@@ -573,13 +643,14 @@ log() {
|
|||||||
end=$OPTARG ;;
|
end=$OPTARG ;;
|
||||||
n)
|
n)
|
||||||
end= ;;
|
end= ;;
|
||||||
t)
|
x)
|
||||||
date=$(date +"${_logDate:-%H:%M}") ;;
|
result=$exitcode ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND-1))"
|
||||||
format=$1 args=( "${@:2}" )
|
format=$1 args=( "${@:2}" )
|
||||||
(( ! ${#args[@]} )) && [[ $format ]] && { args=("$format") format=%s; local bold=; }
|
(( ! ${#args[@]} )) && [[ $format ]] && { args=("$format") format=%s; local bold=; }
|
||||||
|
date=${_logDate+$(date +"${_logDate:-%H:%M}")}
|
||||||
|
|
||||||
# Level-specific settings.
|
# Level-specific settings.
|
||||||
local logLevelColor
|
local logLevelColor
|
||||||
@@ -600,17 +671,17 @@ log() {
|
|||||||
log FTL 'Log level %s does not exist' "$level"
|
log FTL 'Log level %s does not exist' "$level"
|
||||||
exit 1 ;;
|
exit 1 ;;
|
||||||
esac
|
esac
|
||||||
(( ! supported )) && return "$exitcode"
|
(( ! supported )) && return "$result"
|
||||||
local logColor=${_logColor:+$logLevelColor}
|
local logColor=${_logColor:+$logLevelColor}
|
||||||
|
|
||||||
# Generate the log message.
|
# Generate the log message.
|
||||||
case $type in
|
case $type in
|
||||||
msg|startProgress)
|
msg|startProgress)
|
||||||
printf -v logMsg "[${date:+%s }%-3s] $format$end" ${date:+"$date"} "$level" "${args[@]}"
|
printf -v logMsg "${date:+%s }${_logLevel:+%-3s }$format$end" ${date:+"$date"} ${_logLevel:+"$level"} "${args[@]}"
|
||||||
if (( _logColor )); then
|
if (( _logColor )); then
|
||||||
colorFormat=$(sed ${reset:+-e "s/$(requote "$reset")/$reset$logColor/g"} -e "s/%[^a-z]*[a-z]/$reset$bold$logColor&$reset$logColor/g" <<< "$format")
|
colorFormat=$(sed ${reset:+-e "s/$(requote "$reset")/$reset$_logAttributes$logColor/g"} -e "s/%[^a-z]*[a-z]/$reset$_logAttributes$bold$logColor&$reset$_logAttributes$logColor/g" <<< "$format")
|
||||||
colorArgs=("${args[@]//$reset/$reset$bold$logColor}")
|
colorArgs=("${args[@]//$reset/$reset$_logAttributes$bold$logColor}")
|
||||||
printf -v conMsg "$reset[${date:+%s }$logColor$bold%-3s$reset] $logColor$colorFormat$reset$black\$$reset$end$save" ${date:+"$date"} "$level" "${colorArgs[@]}"
|
printf -v conMsg "$reset$_logAttributes${date:+%s }${_logLevel:+$logColor$bold%-3s$reset $_logAttributes}$logColor$colorFormat$reset$_logAttributes$black\$$reset$end$save" ${date:+"$date"} ${_logLevel:+"$level"} "${colorArgs[@]}"
|
||||||
else
|
else
|
||||||
conMsg=$logMsg
|
conMsg=$logMsg
|
||||||
fi
|
fi
|
||||||
@@ -619,15 +690,17 @@ log() {
|
|||||||
updateProgress)
|
updateProgress)
|
||||||
printf -v logMsg printf " [$format]" "${args[@]}"
|
printf -v logMsg printf " [$format]" "${args[@]}"
|
||||||
if (( _logColor )); then
|
if (( _logColor )); then
|
||||||
colorFormat=$(sed ${reset:+-e "s/$(requote "$reset")/$reset$logColor/g"} -e "s/%[^a-z]*[a-z]/$reset$bold$logColor&$reset$logColor/g" <<< "$format")
|
colorFormat=$(sed ${reset:+-e "s/$(requote "$reset")/$reset$_logAttributes$logColor/g"} -e "s/%[^a-z]*[a-z]/$reset$_logAttributes$bold$logColor&$reset$_logAttributes$logColor/g" <<< "$format")
|
||||||
colorArgs=("${args[@]//$reset/$reset$bold$logColor}")
|
colorArgs=("${args[@]//$reset/$reset$_logAttributes$bold$logColor}")
|
||||||
printf -v conMsg "$load$eel$blue$bold[$reset$logColor$colorFormat$reset$blue$bold]$reset$end" "${colorArgs[@]}"
|
printf -v conMsg "$load$eel$blue$bold[$reset$_logAttributes$logColor$colorFormat$reset$_logAttributes$blue$bold]$reset$end" "${colorArgs[@]}"
|
||||||
else
|
else
|
||||||
conMsg=$logMsg
|
conMsg=$logMsg
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
stopProgress)
|
stopProgress)
|
||||||
|
! kill -0 "$_logSpinner" 2>/dev/null && return
|
||||||
|
|
||||||
case $exitcode in
|
case $exitcode in
|
||||||
0) printf -v logMsg "done${format:+ ($format)}.\n" "${args[@]}"
|
0) printf -v logMsg "done${format:+ ($format)}.\n" "${args[@]}"
|
||||||
if (( _logColor )); then
|
if (( _logColor )); then
|
||||||
@@ -653,15 +726,17 @@ log() {
|
|||||||
|
|
||||||
# Create the log file.
|
# Create the log file.
|
||||||
if [[ $_logFile && ! -e $_logFile ]]; then
|
if [[ $_logFile && ! -e $_logFile ]]; then
|
||||||
[[ $_logFile = */* ]] || $_logFile=./$logFile
|
[[ $_logFile = */* ]] || _logFile=./$_logFile
|
||||||
mkdir -p "${_logFile%/*}" && touch "$_logFile"
|
mkdir -p "${_logFile%/*}" && touch "$_logFile"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stop the spinner.
|
# Stop the spinner.
|
||||||
if [[ $type = stopProgress && $_logSpinner ]]; then
|
if [[ $type = stopProgress && $_logSpinner ]]; then
|
||||||
kill "$_logSpinner"
|
{
|
||||||
wait "$_logSpinner" 2>/dev/null
|
kill "$_logSpinner" ||:
|
||||||
unset _logSpinner
|
wait "$_logSpinner" ||:
|
||||||
|
unset _logSpinner
|
||||||
|
} 2>/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Output the ruler.
|
# Output the ruler.
|
||||||
@@ -685,9 +760,10 @@ log() {
|
|||||||
while printf >&2 "$eel$blue$bold[$reset%s$reset$blue$bold]$reset\b\b\b" "${spinner[s++ % ${#spinner[@]}]}" && sleep .1
|
while printf >&2 "$eel$blue$bold[$reset%s$reset$blue$bold]$reset\b\b\b" "${spinner[s++ % ${#spinner[@]}]}" && sleep .1
|
||||||
do :; done
|
do :; done
|
||||||
} & _logSpinner=$!
|
} & _logSpinner=$!
|
||||||
|
addtrap EXIT 'level=%q _logSpinner=%q golp' "$level" "$_logSpinner"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return $exitcode
|
return $result
|
||||||
}
|
}
|
||||||
trc() { level=TRC log "$@"; }
|
trc() { level=TRC log "$@"; }
|
||||||
dbg() { level=DBG log "$@"; }
|
dbg() { level=DBG log "$@"; }
|
||||||
@@ -718,6 +794,8 @@ rrep() { level=ERR golp "$@"; }
|
|||||||
ltfp() { level=FTL golp "$@"; }
|
ltfp() { level=FTL golp "$@"; }
|
||||||
_logColor=${_logColor:-$([[ -t 2 ]] && echo 1)} _logVerbosity=2
|
_logColor=${_logColor:-$([[ -t 2 ]] && echo 1)} _logVerbosity=2
|
||||||
_logTrcColor=$grey _logDbgColor=$blue _logInfColor=$white _logWrnColor=$yellow _logErrColor=$red _logFtlColor=$bold$red
|
_logTrcColor=$grey _logDbgColor=$blue _logInfColor=$white _logWrnColor=$yellow _logErrColor=$red _logFtlColor=$bold$red
|
||||||
|
#_logDate=%H:%M # Set this to enable date output in log messages.
|
||||||
|
#_logLevel=1 # Set this to enable level output in log messages.
|
||||||
# _______________________________________________________________________
|
# _______________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
@@ -794,10 +872,10 @@ ask() {
|
|||||||
printf '%s' "$muteChar" >&$fd
|
printf '%s' "$muteChar" >&$fd
|
||||||
done
|
done
|
||||||
REPLY=$reply
|
REPLY=$reply
|
||||||
|
[[ $options && $REPLY ]] || (( silent )) && printf '\n' >&$fd
|
||||||
else
|
else
|
||||||
read -u8 -e ${options:+-n1} ${silent:+-s}
|
read -u8 -e ${options:+-n1} ${silent:+-s}
|
||||||
fi
|
fi
|
||||||
[[ $options && $REPLY ]] || (( silent )) && printf '\n' >&$fd
|
|
||||||
|
|
||||||
# Evaluate the reply.
|
# Evaluate the reply.
|
||||||
while true; do
|
while true; do
|
||||||
@@ -886,7 +964,7 @@ reverse() {
|
|||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
# |__ Order _____________________________________________________________|
|
# |__ Order _____________________________________________________________|
|
||||||
#
|
#
|
||||||
# order [-0|-d char] [-[fF] isDesired] [-[cC] isAscending|-n|-r|-t] [-T number] [-a array|elements ...] [<<< elements]
|
# order [-0|-d char] [-[fF] isDesired] [-[cC] comparator|-n|-R|-t] [-r] [-T number] [-a array|elements ...] [<<< elements]
|
||||||
#
|
#
|
||||||
# Orders the elements in ascending order.
|
# Orders the elements in ascending order.
|
||||||
# Elements are read from command arguments or standard input if no element
|
# Elements are read from command arguments or standard input if no element
|
||||||
@@ -895,21 +973,23 @@ reverse() {
|
|||||||
#
|
#
|
||||||
# By default, the elements will be ordered using lexicographic comparison.
|
# By default, the elements will be ordered using lexicographic comparison.
|
||||||
# If the -n option is given, the elements will be ordered numerically.
|
# If the -n option is given, the elements will be ordered numerically.
|
||||||
# If the -r option is given, the elements will be ordered randomly.
|
# If the -R option is given, the elements will be ordered randomly.
|
||||||
|
# If the -t option is given, the elements are ordered by file mtime.
|
||||||
# If the -f option is given, the command name following it will be used
|
# If the -f option is given, the command name following it will be used
|
||||||
# as a filter.
|
# as a filter.
|
||||||
# If the -c option is given, the command name following it will be used
|
# If the -c option is given, the command name following it will be used
|
||||||
# as a comparator.
|
# as a comparator.
|
||||||
# If the -C option is given, the bash code following it will be used
|
# If the -C option is given, the bash code following it will be used
|
||||||
# as a comparator.
|
# as a comparator.
|
||||||
# If the -t option is given, only the first number results are returned.
|
# If the -r option is given, the ordering will be reversed.
|
||||||
|
# If the -T option is given, only the first number results are returned.
|
||||||
# If the -a option is given, the elements in array are ordered instead and
|
# If the -a option is given, the elements in array are ordered instead and
|
||||||
# array is mutated to contain the result.
|
# array is mutated to contain the result.
|
||||||
# If number is 0, all results are returned.
|
# If number is 0, all results are returned.
|
||||||
#
|
#
|
||||||
# isDesired is a command name which will get one parameter. The parameter
|
# isDesired is a command name which will get one parameter. The parameter
|
||||||
# is an element which will only be included if the command exits successfully.
|
# is an element which will only be included if the command exits successfully.
|
||||||
# isAscending is a command name which will be executed for each element
|
# comparator is a command name which will be executed for each element
|
||||||
# comparison and will be passed two element arguments. The command should
|
# comparison and will be passed two element arguments. The command should
|
||||||
# succeed if the first argument is less than the second argument for the
|
# succeed if the first argument is less than the second argument for the
|
||||||
# purpose of this sort.
|
# purpose of this sort.
|
||||||
@@ -924,59 +1004,61 @@ reverse() {
|
|||||||
order() {
|
order() {
|
||||||
|
|
||||||
# Initialize the vars.
|
# Initialize the vars.
|
||||||
local delimitor=$'\n' i isDesired=true isAscending=string_ascends top=0 arrayName= array=
|
local _delimitor=$'\n' _i _j _isDesired=true _comparator=string_ascends _comparator_ascends=1 _top=0 _arrayName= _array=
|
||||||
|
|
||||||
# Parse the options.
|
# Parse the options.
|
||||||
local OPTIND=1
|
local OPTIND=1
|
||||||
while getopts :0nrd:f:F:c:C:tT:a: opt; do
|
while getopts :0nrRd:f:F:c:C:tT:a: opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
0) delimitor=$'\0' ;;
|
0) _delimitor=$'\0' ;;
|
||||||
d) delimitor=$OPTARG ;;
|
d) _delimitor=$OPTARG ;;
|
||||||
n) isAscending=number_ascends ;;
|
n) _comparator=number_ascends ;;
|
||||||
r) isAscending=random_ascends ;;
|
R) _comparator=random_ascends ;;
|
||||||
t) isAscending=mtime_ascends ;;
|
t) _comparator=mtime_ascends ;;
|
||||||
f) isDesired=$OPTARG ;;
|
f) _isDesired=$OPTARG ;;
|
||||||
F) isDesired=bash_desired bash_desired_code=$OPTARG ;;
|
F) _isDesired=bash_desired _bash_desired_code=$OPTARG ;;
|
||||||
c) isAscending=$OPTARG ;;
|
c) _comparator=$OPTARG ;;
|
||||||
C) isAscending=bash_ascends bash_ascends_code=$OPTARG ;;
|
C) _comparator=bash_ascends _bash_ascends_code=$OPTARG ;;
|
||||||
T) top=$OPTARG ;;
|
r) _comparator_ascends=0 ;;
|
||||||
a) arrayName=$OPTARG array=$arrayName[@] ;;
|
T) _top=$OPTARG ;;
|
||||||
|
a) _arrayName=$OPTARG _array=$_arrayName[@] ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND-1))"
|
||||||
|
|
||||||
# Get the elements.
|
# Get the elements.
|
||||||
local elements=() element
|
local _elements=() _element
|
||||||
if [[ $arrayName ]]; then
|
if [[ $_arrayName ]]; then
|
||||||
for element in "${!array}"; do
|
for _element in "${!_array}"; do
|
||||||
"$isDesired" "$element" && elements+=("$element")
|
"$_isDesired" "$_element" && _elements+=("$_element")
|
||||||
done
|
done
|
||||||
elif (( $# )); then
|
elif (( $# )); then
|
||||||
for element; do
|
for _element; do
|
||||||
"$isDesired" "$element" && elements+=("$element")
|
"$_isDesired" "$_element" && _elements+=("$_element")
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
while IFS= read -r -d "$delimitor" element; do
|
while IFS= read -r -d "$_delimitor" _element; do
|
||||||
"$isDesired" "$element" && elements+=("$element")
|
"$_isDesired" "$_element" && _elements+=("$_element")
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Iterate in reverse order.
|
# Iterate in reverse order.
|
||||||
for (( i = 1; i < ${#elements[@]}; ++i )); do
|
for (( _i = 1; _i < ${#_elements[@]}; ++_i )); do
|
||||||
for (( j = i; j > 0; --j )); do
|
for (( _j = _i; _j > 0; --_j )); do
|
||||||
element=${elements[j]}
|
_element=${_elements[_j]}
|
||||||
if "$isAscending" "$element" "${elements[j-1]}"; then
|
if ( (( _comparator_ascends )) && "$_comparator" "$_element" "${_elements[_j-1]}" ) ||
|
||||||
elements[j]=${elements[j-1]}
|
( (( ! _comparator_ascends )) && ! "$_comparator" "$_element" "${_elements[_j-1]}" ); then
|
||||||
elements[j-1]=$element
|
_elements[_j]=${_elements[_j-1]}
|
||||||
|
_elements[_j-1]=$_element
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
(( top )) || top=${#elements[@]}
|
(( _top )) || _top=${#_elements[@]}
|
||||||
if [[ $array ]]; then
|
if [[ $_array ]]; then
|
||||||
declare -ga "$array=($(printf '%q ' "${elements[@]:0:top}"))"
|
declare -ga "$_array=($(printf '%q ' "${_elements[@]:0:_top}"))"
|
||||||
else
|
else
|
||||||
printf "%s${delimitor:-\0}" "${elements[@]:0:top}"
|
printf "%s${_delimitor:-\0}" "${_elements[@]:0:_top}"
|
||||||
fi
|
fi
|
||||||
} # _____________________________________________________________________
|
} # _____________________________________________________________________
|
||||||
string_ascends() { [[ $1 < $2 ]]; }
|
string_ascends() { [[ $1 < $2 ]]; }
|
||||||
@@ -987,8 +1069,28 @@ exists_desired() { [[ -e $1 ]]; }
|
|||||||
line_desired() { [[ $1 ]]; }
|
line_desired() { [[ $1 ]]; }
|
||||||
code_desired() { line_desired "$1" && ! comment_desired "$1"; }
|
code_desired() { line_desired "$1" && ! comment_desired "$1"; }
|
||||||
comment_desired() { line_desired "$1" && [[ $1 = @(#|//|/\*)* ]]; }
|
comment_desired() { line_desired "$1" && [[ $1 = @(#|//|/\*)* ]]; }
|
||||||
bash_desired() { bash -c "$bash_desired_code" -- "$@"; }
|
bash_desired() { bash -c "$_bash_desired_code" -- "$@"; }
|
||||||
bash_ascends() { bash -c "$bash_ascends_code" -- "$@"; }
|
bash_ascends() { bash -c "$_bash_ascends_code" -- "$@"; }
|
||||||
|
|
||||||
|
|
||||||
|
# ______________________________________________________________________
|
||||||
|
# |__ AddTrap _____________________________________________________________|
|
||||||
|
#
|
||||||
|
# addtrap signal command-format [args...]
|
||||||
|
#
|
||||||
|
# Add a command to the current commands executed when a signal is received by the bash process.
|
||||||
|
#
|
||||||
|
# The command-format is a printf-style format for the command to execute. The optional
|
||||||
|
# args are interpolated into the command-format by bash's built-in printf.
|
||||||
|
#
|
||||||
|
addtrap() {
|
||||||
|
local signal=$1 cmd=$2; shift 2
|
||||||
|
printf -v cmd "$cmd" "$@"
|
||||||
|
|
||||||
|
read _ _ oldtrap <<< "$(trap -p "$signal")"
|
||||||
|
eval "declare oldtrap=${oldtrap% *}"
|
||||||
|
trap "$oldtrap${oldtrap:+; }$cmd" "$signal"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
@@ -1119,9 +1221,10 @@ options() {
|
|||||||
while getopts "$optstring" arg; do
|
while getopts "$optstring" arg; do
|
||||||
if [[ $arg = h && ! ${options[h]} ]]; then
|
if [[ $arg = h && ! ${options[h]} ]]; then
|
||||||
# Show usage message.
|
# Show usage message.
|
||||||
[[ -t 1 ]]; local fd=$(( $? + 1 )) optarg
|
[[ -t 1 ]] && local fd=1 || local fd=2
|
||||||
|
|
||||||
# Print out the app usage.
|
# Print out the app usage.
|
||||||
|
local optarg
|
||||||
printf " Usage: $reset$bold%s$reset" "${BASH_SOURCE[1]##*/}" >&$fd
|
printf " Usage: $reset$bold%s$reset" "${BASH_SOURCE[1]##*/}" >&$fd
|
||||||
for optchar in "${!options[@]}"; do
|
for optchar in "${!options[@]}"; do
|
||||||
[[ $optchar = *: ]] && optarg=" arg" || optarg=
|
[[ $optchar = *: ]] && optarg=" arg" || optarg=
|
||||||
@@ -1172,7 +1275,7 @@ showHelp() {
|
|||||||
(( cols = ${cols:-80} - 10 ))
|
(( cols = ${cols:-80} - 10 ))
|
||||||
|
|
||||||
# Figure out what FD to use for our messages.
|
# Figure out what FD to use for our messages.
|
||||||
[[ -t 1 ]]; local fd=$(( $? + 1 ))
|
[[ -t 1 ]] && local fd=1 || local fd=2
|
||||||
|
|
||||||
# Print out the help header.
|
# Print out the help header.
|
||||||
printf "$reset$bold\n" >&$fd
|
printf "$reset$bold\n" >&$fd
|
||||||
@@ -1214,18 +1317,17 @@ showHelp() {
|
|||||||
shquote() {
|
shquote() {
|
||||||
|
|
||||||
# Initialize the defaults.
|
# Initialize the defaults.
|
||||||
local arg escape=0 sq="'\\''" dq='\"' quotedArgs=() type=single always=0
|
local OPTIND=1 arg escape=0 sq="'\\''" dq='\"' quotedArgs=() type=single always=0
|
||||||
|
|
||||||
# Parse the options.
|
# Parse the options.
|
||||||
while [[ $1 = -* ]]; do
|
while getopts :eda arg; do
|
||||||
case $1 in
|
case $arg in
|
||||||
-e) type=escape ;;
|
e) type=escape ;;
|
||||||
-d) type=double ;;
|
d) type=double ;;
|
||||||
-a) always=1 ;;
|
a) always=1 ;;
|
||||||
--) shift; break ;;
|
|
||||||
esac
|
esac
|
||||||
shift
|
|
||||||
done
|
done
|
||||||
|
shift "$((OPTIND-1))"
|
||||||
|
|
||||||
# Print out each argument, quoting it properly.
|
# Print out each argument, quoting it properly.
|
||||||
for arg; do
|
for arg; do
|
||||||
@@ -1330,6 +1432,29 @@ shorten() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ______________________________________________________________________
|
||||||
|
# |__ CdSource ________________________________________________________________|
|
||||||
|
#
|
||||||
|
# cdsource [file]
|
||||||
|
#
|
||||||
|
# Change the current directory into the directory where the file is located, resolving symlinks.
|
||||||
|
#
|
||||||
|
cdsource() {
|
||||||
|
local source=${1:-${BASH_SOURCE[1]}}
|
||||||
|
|
||||||
|
while [[ $source ]]; do
|
||||||
|
[[ $source = */* ]] && cd "${source%/*}"
|
||||||
|
|
||||||
|
if [[ -L ${source##*/} ]]; then
|
||||||
|
source=$(readlink "${source##*/}")
|
||||||
|
else
|
||||||
|
source=
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
# |__ Up ________________________________________________________________|
|
# |__ Up ________________________________________________________________|
|
||||||
#
|
#
|
||||||
@@ -1405,7 +1530,34 @@ inArray() {
|
|||||||
|
|
||||||
# Perform the search.
|
# Perform the search.
|
||||||
for element
|
for element
|
||||||
do [[ $element = $search ]] && return 0; done
|
do [[ "$element" = "$search" ]] && return 0; done
|
||||||
|
return 1
|
||||||
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ______________________________________________________________________
|
||||||
|
# |__ IndexOf ___________________________________________________________|
|
||||||
|
#
|
||||||
|
# indexOf element array
|
||||||
|
#
|
||||||
|
# Outputs the index of the given element in the given array.
|
||||||
|
#
|
||||||
|
# element The element to search the array for.
|
||||||
|
# array This is a list of elements to search through.
|
||||||
|
#
|
||||||
|
indexOf() {
|
||||||
|
|
||||||
|
# Parse the options.
|
||||||
|
local element index=0
|
||||||
|
local search=$1; shift
|
||||||
|
|
||||||
|
# Perform the search.
|
||||||
|
for element
|
||||||
|
do
|
||||||
|
[[ $element = $search ]] && echo "$index" && return 0
|
||||||
|
let ++index
|
||||||
|
done
|
||||||
return 1
|
return 1
|
||||||
} # _____________________________________________________________________
|
} # _____________________________________________________________________
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# See https://developer.apple.com/library/ios/qa/qa1686/_index.html
|
# See https://developer.apple.com/library/ios/qa/qa1686/_index.html
|
||||||
cd "${BASH_SOURCE%/*}"
|
cd "${BASH_SOURCE%/*}"
|
||||||
source bashlib
|
source bashlib
|
||||||
|
trap 'echo >&2 "ERROR: $?: $BASH_COMMAND"' ERR
|
||||||
set -e
|
set -e
|
||||||
cd ..
|
cd ..
|
||||||
export PATH+=:/usr/local/bin
|
export PATH+=:/usr/local/bin
|
||||||
@@ -97,26 +98,18 @@ if [[ "$(latest "$ios_icon"/*)" -nt "$appiconset/Contents.json" ]] ||
|
|||||||
source=$ios_icon/$filename
|
source=$ios_icon/$filename
|
||||||
if [[ ! -e $source ]]; then
|
if [[ ! -e $source ]]; then
|
||||||
source=$mac_icon/$filename
|
source=$mac_icon/$filename
|
||||||
if [[ ! -e $source ]]; then
|
[[ -e $source ]] || ftl 'No icon for: %s' "$filename"
|
||||||
err 'No icon for: %s' "$filename"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if imageProps=$(copyImage "$source" "$appiconset/$filename"); then
|
imageProps=$(copyImage "$source" "$appiconset/$filename")
|
||||||
printf '%s{"size":"%dx%d","filename":"%s","scale":"%sx"' \
|
printf '%s{"size":"%dx%d","filename":"%s","scale":"%sx"' \
|
||||||
"$comma" "$pt" "$pt" "$filename" "$scale"
|
"$comma" "$pt" "$pt" "$filename" "$scale"
|
||||||
[[ $idiom ]] && printf ',"idiom":"%s"' "$idiom"
|
[[ $idiom ]] && printf ',"idiom":"%s"' "$idiom"
|
||||||
[[ $os ]] && printf ',"minimum-system-version":"%s"' "$os"
|
[[ $os ]] && printf ',"minimum-system-version":"%s"' "$os"
|
||||||
[[ $imageProps ]] && printf '%s' "$imageProps"
|
[[ $imageProps ]] && printf '%s' "$imageProps"
|
||||||
printf '}'
|
printf '}'
|
||||||
|
|
||||||
comma=,
|
|
||||||
else
|
|
||||||
rm "$appiconset/Contents.json"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
comma=,
|
||||||
done
|
done
|
||||||
printf '],"info":{"version":1,"author":"genassets"},"properties":{"pre-rendered":true}}\n'
|
printf '],"info":{"version":1,"author":"genassets"},"properties":{"pre-rendered":true}}\n'
|
||||||
} > "$appiconset/Contents.json"
|
} > "$appiconset/Contents.json"
|
||||||
@@ -137,20 +130,16 @@ if [[ "$(latest "$ios_launch"/*)" -nt "$launchimage/Contents.json" ]]; then
|
|||||||
esac
|
esac
|
||||||
filename="Default${os:+-$os}${subtype:+-$subtype}${scale:+@${scale}x}${idiom:+~$idiom}.png"
|
filename="Default${os:+-$os}${subtype:+-$subtype}${scale:+@${scale}x}${idiom:+~$idiom}.png"
|
||||||
|
|
||||||
if imageProps=$(copyImage "$ios_launch/$name${scale:+@${scale}x}.png" "$launchimage/$filename"); then
|
imageProps=$(copyImage "$ios_launch/$name${scale:+@${scale}x}.png" "$launchimage/$filename")
|
||||||
printf '%s{"extent":"full-screen","filename":"%s","orientation":"portrait","scale":"%sx"' \
|
printf '%s{"extent":"full-screen","filename":"%s","orientation":"portrait","scale":"%sx"' \
|
||||||
"$comma" "$filename" "${scale:-1}"
|
"$comma" "$filename" "${scale:-1}"
|
||||||
[[ $idiom ]] && printf ',"idiom":"%s"' "$idiom"
|
[[ $idiom ]] && printf ',"idiom":"%s"' "$idiom"
|
||||||
[[ $os ]] && printf ',"minimum-system-version":"%s"' "$os"
|
[[ $os ]] && printf ',"minimum-system-version":"%s"' "$os"
|
||||||
[[ $subtype ]] && printf ',"subtype":"%s"' "$subtype"
|
[[ $subtype ]] && printf ',"subtype":"%s"' "$subtype"
|
||||||
[[ $imageProps ]] && printf '%s' "$imageProps"
|
[[ $imageProps ]] && printf '%s' "$imageProps"
|
||||||
printf '}'
|
printf '}'
|
||||||
|
|
||||||
comma=,
|
comma=,
|
||||||
else
|
|
||||||
rm "$launchimage/Contents.json"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
printf '],"info":{"version":1,"author":"genassets"}}\n'
|
printf '],"info":{"version":1,"author":"genassets"}}\n'
|
||||||
} > "$launchimage/Contents.json"
|
} > "$launchimage/Contents.json"
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# This script should be in the 'Scripts' directory under the git repository's root.
|
|
||||||
cd "${BASH_SOURCE%/*}/.."
|
|
||||||
shopt -s extglob
|
|
||||||
|
|
||||||
|
|
||||||
## Submodules that need to be checked out.
|
|
||||||
dependencies=( External/{InAppSettingsKit,Pearl{,:External/jrswizzle,:External/uicolor-utilities},RHStatusItemView} )
|
|
||||||
|
|
||||||
## Custom migration.
|
|
||||||
# None yet.
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
isCheckedOut() {
|
|
||||||
local modulePath=$1
|
|
||||||
! git submodule status | grep -q "^-[^ ]* $modulePath"
|
|
||||||
}
|
|
||||||
|
|
||||||
# git submodule sync -- A bug causes this to init ALL external dependencies.
|
|
||||||
git submodule sync $(git submodule status | awk '/^ / { print $2 }')
|
|
||||||
|
|
||||||
|
|
||||||
# Check out our missing dependencies
|
|
||||||
for dependency in "${dependencies[@]}"; do
|
|
||||||
[[ $dependency = *:* ]] && root=${dependency%%:*} || root=.
|
|
||||||
path=${dependency#*:}
|
|
||||||
( cd "$root"; git submodule update --init "$path" )
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
# Update our modules
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
|
|
||||||
# Our modules may define a custom update script, if so, run it.
|
|
||||||
find !(Scripts)/ -name "${BASH_SOURCE##*/}" -exec {} \;
|
|
||||||
|
|
||||||
|
|
||||||
# Finally, for our modules that haven't got a custom update script, update them recursively.
|
|
||||||
git submodule update --recursive --rebase
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
cd "${BASH_SOURCE%/*}"
|
cd "${BASH_SOURCE%/*}"
|
||||||
source ./bashlib
|
source ./bashlib
|
||||||
|
trap 'echo >&2 "ERROR: $?: $BASH_COMMAND"' ERR
|
||||||
|
set -e
|
||||||
cd ..
|
cd ..
|
||||||
export PATH+=:/usr/libexec
|
export PATH+=:/usr/libexec
|
||||||
|
|
||||||
|
|||||||
@@ -52,49 +52,44 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
|||||||
- (NSData *)keyIDForKey:(MPMasterKey)masterKey;
|
- (NSData *)keyIDForKey:(MPMasterKey)masterKey;
|
||||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||||
|
|
||||||
- (NSString *)nameOfType:(MPSiteType)type;
|
- (NSString *)nameOfType:(MPResultType)type;
|
||||||
- (NSString *)shortNameOfType:(MPSiteType)type;
|
- (NSString *)shortNameOfType:(MPResultType)type;
|
||||||
- (NSString *)classNameOfType:(MPSiteType)type;
|
- (NSString *)classNameOfType:(MPResultType)type;
|
||||||
- (Class)classOfType:(MPSiteType)type;
|
- (Class)classOfType:(MPResultType)type;
|
||||||
- (NSArray *)allTypes;
|
- (NSArray *)allTypes;
|
||||||
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
|
- (NSArray *)allTypesStartingWith:(MPResultType)startingType;
|
||||||
- (MPSiteType)defaultType;
|
- (MPResultType)defaultType;
|
||||||
- (MPSiteType)nextType:(MPSiteType)type;
|
- (MPResultType)nextType:(MPResultType)type;
|
||||||
- (MPSiteType)previousType:(MPSiteType)type;
|
- (MPResultType)previousType:(MPResultType)type;
|
||||||
|
|
||||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
|
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
|
||||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
|
||||||
usingKey:(MPKey *)key;
|
withCounter:(MPCounterValue)counter usingKey:(MPKey *)key;
|
||||||
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
|
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
|
||||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key;
|
withCounter:(MPCounterValue)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key;
|
||||||
|
|
||||||
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
|
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
|
|
||||||
|
|
||||||
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
|
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
|
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
|
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key;
|
||||||
|
|
||||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
|
||||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
|
||||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
|
||||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey;
|
|
||||||
|
|
||||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
|
||||||
result:(void ( ^ )(NSString *result))resultBlock;
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
|
||||||
result:(void ( ^ )(NSString *result))resultBlock;
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
|
||||||
result:(void ( ^ )(NSString *result))resultBlock;
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key
|
||||||
result:(void ( ^ )(NSString *result))resultBlock;
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
|
||||||
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
|
- (void)importPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
|
||||||
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site
|
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
usingKey:(MPKey *)siteKey;
|
|
||||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
|
||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker;
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker;
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ static NSOperationQueue *_mpwQueue = nil;
|
|||||||
keyData = [NSData dataWithBytes:masterKey length:MPMasterKeySize];
|
keyData = [NSData dataWithBytes:masterKey length:MPMasterKeySize];
|
||||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
|
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
|
||||||
fullName, masterPassword, [self keyIDForKey:masterKey], -[start timeIntervalSinceNow] );
|
fullName, masterPassword, [self keyIDForKey:masterKey], -[start timeIntervalSinceNow] );
|
||||||
mpw_free( masterKey, MPMasterKeySize );
|
mpw_free( &masterKey, MPMasterKeySize );
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -145,126 +145,135 @@ static NSOperationQueue *_mpwQueue = nil;
|
|||||||
return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256];
|
return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)nameOfType:(MPSiteType)type {
|
- (NSString *)nameOfType:(MPResultType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPSiteTypeGeneratedMaximum:
|
case MPResultTypeTemplateMaximum:
|
||||||
return @"Maximum Security Password";
|
return @"Maximum Security Password";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedLong:
|
case MPResultTypeTemplateLong:
|
||||||
return @"Long Password";
|
return @"Long Password";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedMedium:
|
case MPResultTypeTemplateMedium:
|
||||||
return @"Medium Password";
|
return @"Medium Password";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedBasic:
|
case MPResultTypeTemplateBasic:
|
||||||
return @"Basic Password";
|
return @"Basic Password";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedShort:
|
case MPResultTypeTemplateShort:
|
||||||
return @"Short Password";
|
return @"Short Password";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedPIN:
|
case MPResultTypeTemplatePIN:
|
||||||
return @"PIN";
|
return @"PIN";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedName:
|
case MPResultTypeTemplateName:
|
||||||
return @"Name";
|
return @"Name";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedPhrase:
|
case MPResultTypeTemplatePhrase:
|
||||||
return @"Phrase";
|
return @"Phrase";
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal:
|
case MPResultTypeStatefulPersonal:
|
||||||
return @"Personal Password";
|
return @"Personal Password";
|
||||||
|
|
||||||
case MPSiteTypeStoredDevicePrivate:
|
case MPResultTypeStatefulDevice:
|
||||||
return @"Device Private Password";
|
return @"Device Private Password";
|
||||||
|
|
||||||
|
case MPResultTypeDeriveKey:
|
||||||
|
return @"Crypto Key";
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)shortNameOfType:(MPSiteType)type {
|
- (NSString *)shortNameOfType:(MPResultType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPSiteTypeGeneratedMaximum:
|
case MPResultTypeTemplateMaximum:
|
||||||
return @"Maximum";
|
return @"Maximum";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedLong:
|
case MPResultTypeTemplateLong:
|
||||||
return @"Long";
|
return @"Long";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedMedium:
|
case MPResultTypeTemplateMedium:
|
||||||
return @"Medium";
|
return @"Medium";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedBasic:
|
case MPResultTypeTemplateBasic:
|
||||||
return @"Basic";
|
return @"Basic";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedShort:
|
case MPResultTypeTemplateShort:
|
||||||
return @"Short";
|
return @"Short";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedPIN:
|
case MPResultTypeTemplatePIN:
|
||||||
return @"PIN";
|
return @"PIN";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedName:
|
case MPResultTypeTemplateName:
|
||||||
return @"Name";
|
return @"Name";
|
||||||
|
|
||||||
case MPSiteTypeGeneratedPhrase:
|
case MPResultTypeTemplatePhrase:
|
||||||
return @"Phrase";
|
return @"Phrase";
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal:
|
case MPResultTypeStatefulPersonal:
|
||||||
return @"Personal";
|
return @"Personal";
|
||||||
|
|
||||||
case MPSiteTypeStoredDevicePrivate:
|
case MPResultTypeStatefulDevice:
|
||||||
return @"Device";
|
return @"Device";
|
||||||
|
|
||||||
|
case MPResultTypeDeriveKey:
|
||||||
|
return @"Key";
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)classNameOfType:(MPSiteType)type {
|
- (NSString *)classNameOfType:(MPResultType)type {
|
||||||
|
|
||||||
return NSStringFromClass( [self classOfType:type] );
|
return NSStringFromClass( [self classOfType:type] );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (Class)classOfType:(MPSiteType)type {
|
- (Class)classOfType:(MPResultType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
Throw( @"No type given." );
|
Throw( @"No type given." );
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPSiteTypeGeneratedMaximum:
|
case MPResultTypeTemplateMaximum:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeGeneratedLong:
|
case MPResultTypeTemplateLong:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeGeneratedMedium:
|
case MPResultTypeTemplateMedium:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeGeneratedBasic:
|
case MPResultTypeTemplateBasic:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeGeneratedShort:
|
case MPResultTypeTemplateShort:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeGeneratedPIN:
|
case MPResultTypeTemplatePIN:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeGeneratedName:
|
case MPResultTypeTemplateName:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeGeneratedPhrase:
|
case MPResultTypeTemplatePhrase:
|
||||||
return [MPGeneratedSiteEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal:
|
case MPResultTypeStatefulPersonal:
|
||||||
return [MPStoredSiteEntity class];
|
return [MPStoredSiteEntity class];
|
||||||
|
|
||||||
case MPSiteTypeStoredDevicePrivate:
|
case MPResultTypeStatefulDevice:
|
||||||
return [MPStoredSiteEntity class];
|
return [MPStoredSiteEntity class];
|
||||||
|
|
||||||
|
case MPResultTypeDeriveKey:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
@@ -272,13 +281,13 @@ static NSOperationQueue *_mpwQueue = nil;
|
|||||||
|
|
||||||
- (NSArray *)allTypes {
|
- (NSArray *)allTypes {
|
||||||
|
|
||||||
return [self allTypesStartingWith:MPSiteTypeGeneratedPhrase];
|
return [self allTypesStartingWith:MPResultTypeTemplatePhrase];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
|
- (NSArray *)allTypesStartingWith:(MPResultType)startingType {
|
||||||
|
|
||||||
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
|
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
|
||||||
MPSiteType currentType = startingType;
|
MPResultType currentType = startingType;
|
||||||
do {
|
do {
|
||||||
[allTypes addObject:@(currentType)];
|
[allTypes addObject:@(currentType)];
|
||||||
} while ((currentType = [self nextType:currentType]) != startingType);
|
} while ((currentType = [self nextType:currentType]) != startingType);
|
||||||
@@ -286,199 +295,173 @@ static NSOperationQueue *_mpwQueue = nil;
|
|||||||
return allTypes;
|
return allTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPSiteType)defaultType {
|
- (MPResultType)defaultType {
|
||||||
|
|
||||||
return MPSiteTypeGeneratedLong;
|
return MPResultTypeTemplateLong;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPSiteType)nextType:(MPSiteType)type {
|
- (MPResultType)nextType:(MPResultType)type {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPSiteTypeGeneratedPhrase:
|
case MPResultTypeTemplatePhrase:
|
||||||
return MPSiteTypeGeneratedName;
|
return MPResultTypeTemplateName;
|
||||||
case MPSiteTypeGeneratedName:
|
case MPResultTypeTemplateName:
|
||||||
return MPSiteTypeGeneratedMaximum;
|
return MPResultTypeTemplateMaximum;
|
||||||
case MPSiteTypeGeneratedMaximum:
|
case MPResultTypeTemplateMaximum:
|
||||||
return MPSiteTypeGeneratedLong;
|
return MPResultTypeTemplateLong;
|
||||||
case MPSiteTypeGeneratedLong:
|
case MPResultTypeTemplateLong:
|
||||||
return MPSiteTypeGeneratedMedium;
|
return MPResultTypeTemplateMedium;
|
||||||
case MPSiteTypeGeneratedMedium:
|
case MPResultTypeTemplateMedium:
|
||||||
return MPSiteTypeGeneratedBasic;
|
return MPResultTypeTemplateBasic;
|
||||||
case MPSiteTypeGeneratedBasic:
|
case MPResultTypeTemplateBasic:
|
||||||
return MPSiteTypeGeneratedShort;
|
return MPResultTypeTemplateShort;
|
||||||
case MPSiteTypeGeneratedShort:
|
case MPResultTypeTemplateShort:
|
||||||
return MPSiteTypeGeneratedPIN;
|
return MPResultTypeTemplatePIN;
|
||||||
case MPSiteTypeGeneratedPIN:
|
case MPResultTypeTemplatePIN:
|
||||||
return MPSiteTypeStoredPersonal;
|
return MPResultTypeStatefulPersonal;
|
||||||
case MPSiteTypeStoredPersonal:
|
case MPResultTypeStatefulPersonal:
|
||||||
return MPSiteTypeStoredDevicePrivate;
|
return MPResultTypeStatefulDevice;
|
||||||
case MPSiteTypeStoredDevicePrivate:
|
case MPResultTypeStatefulDevice:
|
||||||
return MPSiteTypeGeneratedPhrase;
|
return MPResultTypeTemplatePhrase;
|
||||||
|
case MPResultTypeDeriveKey:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [self defaultType];
|
return [self defaultType];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPSiteType)previousType:(MPSiteType)type {
|
- (MPResultType)previousType:(MPResultType)type {
|
||||||
|
|
||||||
MPSiteType previousType = type, nextType = type;
|
MPResultType previousType = type, nextType = type;
|
||||||
while ((nextType = [self nextType:nextType]) != type)
|
while ((nextType = [self nextType:nextType]) != type)
|
||||||
previousType = nextType;
|
previousType = nextType;
|
||||||
|
|
||||||
return previousType;
|
return previousType;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplateName parameter:nil withCounter:MPCounterValueInitial
|
||||||
variant:MPKeyPurposeIdentification context:nil usingKey:key];
|
variant:MPKeyPurposeIdentification context:nil usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
|
||||||
usingKey:(MPKey *)key {
|
withCounter:(MPCounterValue)counter usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
return [self mpwResultForSiteNamed:name ofType:type parameter:nil withCounter:counter
|
||||||
variant:MPKeyPurposeAuthentication context:nil usingKey:key];
|
variant:MPKeyPurposeAuthentication context:nil usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
|
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
|
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplatePhrase parameter:nil withCounter:MPCounterValueInitial
|
||||||
variant:MPKeyPurposeRecovery context:question usingKey:key];
|
variant:MPKeyPurposeRecovery context:question usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
|
||||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
withCounter:(MPCounterValue)counter variant:(MPKeyPurpose)purpose context:(NSString *)context
|
||||||
|
usingKey:(MPKey *)key {
|
||||||
|
|
||||||
__block NSString *content = nil;
|
__block NSString *result = nil;
|
||||||
[self mpw_perform:^{
|
[self mpw_perform:^{
|
||||||
char const *contentBytes = mpw_passwordForSite( [key keyForAlgorithm:self],
|
char const *resultBytes = mpw_siteResult( [key keyForAlgorithm:self],
|
||||||
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
|
name.UTF8String, counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] );
|
||||||
if (contentBytes) {
|
if (resultBytes) {
|
||||||
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
|
result = [NSString stringWithCString:resultBytes encoding:NSUTF8StringEncoding];
|
||||||
mpw_free_string( contentBytes );
|
mpw_free_string( &resultBytes );
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return content;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
|
- (BOOL)savePassword:(NSString *)plainText toSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return nil;
|
if (!(site.type & MPResultTypeClassStateful)) {
|
||||||
}
|
wrn( @"Can only save content to site with a stateful type: %lu.", (long)site.type );
|
||||||
|
return NO;
|
||||||
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
return [self decryptContent:site.contentObject usingKey:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
|
||||||
switch (site.type) {
|
|
||||||
case MPSiteTypeGeneratedMaximum:
|
|
||||||
case MPSiteTypeGeneratedLong:
|
|
||||||
case MPSiteTypeGeneratedMedium:
|
|
||||||
case MPSiteTypeGeneratedBasic:
|
|
||||||
case MPSiteTypeGeneratedShort:
|
|
||||||
case MPSiteTypeGeneratedPIN:
|
|
||||||
case MPSiteTypeGeneratedName:
|
|
||||||
case MPSiteTypeGeneratedPhrase: {
|
|
||||||
wrn( @"Cannot save content to site with generated type %lu.", (long)site.type );
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal: {
|
|
||||||
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
|
||||||
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
|
||||||
(long)site.type, [site class] );
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
|
||||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
|
||||||
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
|
|
||||||
return NO;
|
|
||||||
|
|
||||||
((MPStoredSiteEntity *)site).contentObject = encryptedContent;
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
case MPSiteTypeStoredDevicePrivate: {
|
|
||||||
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
|
||||||
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
|
||||||
(long)site.type, [site class] );
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
|
||||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
|
||||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
|
||||||
if (!encryptedContent)
|
|
||||||
[PearlKeyChain deleteItemForQuery:siteQuery];
|
|
||||||
else
|
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
|
||||||
(__bridge id)kSecValueData : encryptedContent,
|
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
||||||
#endif
|
|
||||||
}];
|
|
||||||
((MPStoredSiteEntity *)site).contentObject = nil;
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Unsupported type: %ld", (long)site.type );
|
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
|
(long)site.type, [site class] );
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
__block NSData *state = nil;
|
||||||
|
if (plainText)
|
||||||
|
[self mpw_perform:^{
|
||||||
|
char const *stateBytes = mpw_siteState( [key keyForAlgorithm:self], site.name.UTF8String,
|
||||||
|
MPCounterValueInitial, MPKeyPurposeAuthentication, NULL, site.type, plainText.UTF8String, [self version] );
|
||||||
|
if (stateBytes) {
|
||||||
|
state = [[NSString stringWithCString:stateBytes encoding:NSUTF8StringEncoding] decodeBase64];
|
||||||
|
mpw_free_string( &stateBytes );
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
NSDictionary *siteQuery = [self queryForSite:site];
|
||||||
|
if (!state)
|
||||||
|
[PearlKeyChain deleteItemForQuery:siteQuery];
|
||||||
|
else
|
||||||
|
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
||||||
|
(__bridge id)kSecValueData: state,
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
(__bridge id)kSecAttrAccessible:
|
||||||
|
site.type & MPSiteFeatureDevicePrivate? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
||||||
|
: (__bridge id)kSecAttrAccessibleWhenUnlocked,
|
||||||
|
#endif
|
||||||
|
}];
|
||||||
|
((MPStoredSiteEntity *)site).contentObject = nil;
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return PearlAwait( ^(void (^setResult)(id)) {
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
[self resolveLoginForSite:site usingKey:key result:^(NSString *result_) {
|
||||||
setResult( result_ );
|
setResult( result_ );
|
||||||
}];
|
}];
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return PearlAwait( ^(void (^setResult)(id)) {
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
[self resolvePasswordForSite:site usingKey:key result:^(NSString *result_) {
|
||||||
setResult( result_ );
|
setResult( result_ );
|
||||||
}];
|
}];
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return PearlAwait( ^(void (^setResult)(id)) {
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
|
[self resolveAnswerForSite:site usingKey:key result:^(NSString *result_) {
|
||||||
setResult( result_ );
|
setResult( result_ );
|
||||||
}];
|
}];
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
|
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return PearlAwait( ^(void (^setResult)(id)) {
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
[self resolveAnswerForQuestion:question usingKey:key result:^(NSString *result_) {
|
||||||
setResult( result_ );
|
setResult( result_ );
|
||||||
}];
|
}];
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
NSString *name = site.name;
|
NSString *name = site.name;
|
||||||
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
|
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
|
||||||
NSString *loginName = site.loginName;
|
NSString *loginName = site.loginName;
|
||||||
id<MPAlgorithm> algorithm = nil;
|
id<MPAlgorithm> algorithm = nil;
|
||||||
if (!name.length)
|
if (!name.length)
|
||||||
err( @"Missing name." );
|
err( @"Missing name." );
|
||||||
else if (!siteKey)
|
else if (!key)
|
||||||
err( @"Missing key." );
|
err( @"Missing key." );
|
||||||
else
|
else
|
||||||
algorithm = site.algorithm;
|
algorithm = site.algorithm;
|
||||||
@@ -487,244 +470,144 @@ static NSOperationQueue *_mpwQueue = nil;
|
|||||||
resultBlock( loginName );
|
resultBlock( loginName );
|
||||||
else
|
else
|
||||||
PearlNotMainQueue( ^{
|
PearlNotMainQueue( ^{
|
||||||
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
resultBlock( [algorithm mpwLoginForSiteNamed:name usingKey:key] );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
|
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
NSString *name = site.name;
|
||||||
|
MPResultType type = site.type;
|
||||||
|
id<MPAlgorithm> algorithm = nil;
|
||||||
|
if (!site.name.length)
|
||||||
|
err( @"Missing name." );
|
||||||
|
else if (!key)
|
||||||
|
err( @"Missing key." );
|
||||||
|
else
|
||||||
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
|
||||||
switch (site.type) {
|
switch (site.type) {
|
||||||
case MPSiteTypeGeneratedMaximum:
|
case MPResultTypeTemplateMaximum:
|
||||||
case MPSiteTypeGeneratedLong:
|
case MPResultTypeTemplateLong:
|
||||||
case MPSiteTypeGeneratedMedium:
|
case MPResultTypeTemplateMedium:
|
||||||
case MPSiteTypeGeneratedBasic:
|
case MPResultTypeTemplateBasic:
|
||||||
case MPSiteTypeGeneratedShort:
|
case MPResultTypeTemplateShort:
|
||||||
case MPSiteTypeGeneratedPIN:
|
case MPResultTypeTemplatePIN:
|
||||||
case MPSiteTypeGeneratedName:
|
case MPResultTypeTemplateName:
|
||||||
case MPSiteTypeGeneratedPhrase: {
|
case MPResultTypeTemplatePhrase: {
|
||||||
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||||
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
|
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
|
||||||
(long)site.type, [site class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *name = site.name;
|
MPCounterValue counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||||
MPSiteType type = site.type;
|
|
||||||
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
|
|
||||||
id<MPAlgorithm> algorithm = nil;
|
|
||||||
if (!site.name.length)
|
|
||||||
err( @"Missing name." );
|
|
||||||
else if (!siteKey)
|
|
||||||
err( @"Missing key." );
|
|
||||||
else
|
|
||||||
algorithm = site.algorithm;
|
|
||||||
|
|
||||||
PearlNotMainQueue( ^{
|
PearlNotMainQueue( ^{
|
||||||
resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
|
resultBlock( [algorithm mpwTemplateForSiteNamed:name ofType:type withCounter:counter usingKey:key] );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal: {
|
case MPResultTypeStatefulPersonal:
|
||||||
|
case MPResultTypeStatefulDevice: {
|
||||||
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)site.type, [site class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
|
NSDictionary *siteQuery = [self queryForSite:site];
|
||||||
|
NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
||||||
|
state = state?: ((MPStoredSiteEntity *)site).contentObject;
|
||||||
|
|
||||||
PearlNotMainQueue( ^{
|
PearlNotMainQueue( ^{
|
||||||
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
resultBlock( [algorithm mpwResultForSiteNamed:name ofType:type parameter:[state encodeBase64]
|
||||||
|
withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
|
||||||
|
usingKey:key] );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPSiteTypeStoredDevicePrivate: {
|
|
||||||
NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]],
|
|
||||||
@"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type,
|
|
||||||
[site class] );
|
|
||||||
|
|
||||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
case MPResultTypeDeriveKey:
|
||||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
|
||||||
|
|
||||||
PearlNotMainQueue( ^{
|
|
||||||
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
|
||||||
} );
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
NSString *name = site.name;
|
NSString *name = site.name;
|
||||||
id<MPAlgorithm> algorithm = nil;
|
id<MPAlgorithm> algorithm = nil;
|
||||||
if (!site.name.length)
|
if (!site.name.length)
|
||||||
err( @"Missing name." );
|
err( @"Missing name." );
|
||||||
else if (!siteKey)
|
else if (!key)
|
||||||
err( @"Missing key." );
|
err( @"Missing key." );
|
||||||
else
|
else
|
||||||
algorithm = site.algorithm;
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
PearlNotMainQueue( ^{
|
PearlNotMainQueue( ^{
|
||||||
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
|
resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:nil usingKey:key] );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key
|
||||||
result:(void ( ^ )(NSString *result))resultBlock {
|
result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
|
NSAssert( [[key keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
|
||||||
@"Site does not belong to current user." );
|
@"Site does not belong to current user." );
|
||||||
NSString *name = question.site.name;
|
NSString *name = question.site.name;
|
||||||
NSString *keyword = question.keyword;
|
NSString *keyword = question.keyword;
|
||||||
id<MPAlgorithm> algorithm = nil;
|
id<MPAlgorithm> algorithm = nil;
|
||||||
if (!name.length)
|
if (!name.length)
|
||||||
err( @"Missing name." );
|
err( @"Missing name." );
|
||||||
else if (!siteKey)
|
else if (!key)
|
||||||
err( @"Missing key." );
|
err( @"Missing key." );
|
||||||
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
|
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
|
||||||
algorithm = question.site.algorithm;
|
algorithm = question.site.algorithm;
|
||||||
|
|
||||||
PearlNotMainQueue( ^{
|
PearlNotMainQueue( ^{
|
||||||
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
|
resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:keyword usingKey:key] );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
- (void)importPassword:(NSString *)cipherText protectedByKey:(MPKey *)importKey
|
||||||
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (site.type) {
|
if (cipherText && cipherText.length && site.type & MPResultTypeClassStateful) {
|
||||||
case MPSiteTypeGeneratedMaximum:
|
NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:site.type parameter:cipherText
|
||||||
case MPSiteTypeGeneratedLong:
|
withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
|
||||||
case MPSiteTypeGeneratedMedium:
|
usingKey:importKey];
|
||||||
case MPSiteTypeGeneratedBasic:
|
if (plainText)
|
||||||
case MPSiteTypeGeneratedShort:
|
[self savePassword:plainText toSite:site usingKey:key];
|
||||||
case MPSiteTypeGeneratedPIN:
|
|
||||||
case MPSiteTypeGeneratedName:
|
|
||||||
case MPSiteTypeGeneratedPhrase:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal: {
|
|
||||||
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
|
||||||
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
|
||||||
(long)site.type, [site class] );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]])
|
|
||||||
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
|
|
||||||
|
|
||||||
else {
|
|
||||||
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
|
||||||
[self importClearTextPassword:clearContent intoSite:site usingKey:siteKey];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MPSiteTypeStoredDevicePrivate:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSDictionary *)queryForSite:(MPSiteEntity *)site {
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:@{
|
||||||
switch (site.type) {
|
(__bridge id)kSecAttrService: site.type & MPSiteFeatureDevicePrivate? @"DevicePrivate": @"Private",
|
||||||
case MPSiteTypeGeneratedMaximum:
|
(__bridge id)kSecAttrAccount: site.name
|
||||||
case MPSiteTypeGeneratedLong:
|
} matches:nil];
|
||||||
case MPSiteTypeGeneratedMedium:
|
|
||||||
case MPSiteTypeGeneratedBasic:
|
|
||||||
case MPSiteTypeGeneratedShort:
|
|
||||||
case MPSiteTypeGeneratedPIN:
|
|
||||||
case MPSiteTypeGeneratedName:
|
|
||||||
case MPSiteTypeGeneratedPhrase:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal: {
|
|
||||||
[self savePassword:clearContent toSite:site usingKey:siteKey];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MPSiteTypeStoredDevicePrivate:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
|
||||||
if (!(site.type & MPSiteFeatureExportContent))
|
if (!(site.type & MPSiteFeatureExportContent))
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
NSString *result = nil;
|
NSDictionary *siteQuery = [self queryForSite:site];
|
||||||
switch (site.type) {
|
NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
||||||
case MPSiteTypeGeneratedMaximum:
|
return [state?: ((MPStoredSiteEntity *)site).contentObject encodeBase64];
|
||||||
case MPSiteTypeGeneratedLong:
|
|
||||||
case MPSiteTypeGeneratedMedium:
|
|
||||||
case MPSiteTypeGeneratedBasic:
|
|
||||||
case MPSiteTypeGeneratedShort:
|
|
||||||
case MPSiteTypeGeneratedPIN:
|
|
||||||
case MPSiteTypeGeneratedName:
|
|
||||||
case MPSiteTypeGeneratedPhrase: {
|
|
||||||
result = nil;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MPSiteTypeStoredPersonal: {
|
|
||||||
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
|
||||||
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
|
||||||
(long)site.type, [site class] );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result = [((MPStoredSiteEntity *)site).contentObject encodeBase64];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case MPSiteTypeStoredDevicePrivate: {
|
|
||||||
result = nil;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker {
|
||||||
|
|
||||||
return NO;
|
if (!(type & MPResultTypeClassTemplate))
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name {
|
|
||||||
|
|
||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
|
||||||
attributes:@{
|
|
||||||
(__bridge id)kSecAttrService: @"DevicePrivate",
|
|
||||||
(__bridge id)kSecAttrAccount: name
|
|
||||||
}
|
|
||||||
matches:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
if (!key)
|
|
||||||
return nil;
|
|
||||||
NSData *decryptedContent = nil;
|
|
||||||
if ([encryptedContent length]) {
|
|
||||||
NSData *encryptionKey = [key keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
|
||||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
|
|
||||||
}
|
|
||||||
if (!decryptedContent)
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
|
|
||||||
|
|
||||||
if (!(type & MPSiteTypeClassGenerated))
|
|
||||||
return NO;
|
return NO;
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
const char **templates = mpw_templatesForType( type, &count );
|
const char **templates = mpw_templatesForType( type, &count );
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
if (site.type & MPSiteTypeClassGenerated) {
|
if (site.type & MPResultTypeClassTemplate) {
|
||||||
// This migration requires explicit permission for types of the generated class.
|
// This migration requires explicit permission for types of the generated class.
|
||||||
site.requiresExplicitMigration = YES;
|
site.requiresExplicitMigration = YES;
|
||||||
return NO;
|
return NO;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
if (site.type & MPResultTypeClassTemplate && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
||||||
// This migration requires explicit permission for types of the generated class.
|
// This migration requires explicit permission for types of the generated class.
|
||||||
site.requiresExplicitMigration = YES;
|
site.requiresExplicitMigration = YES;
|
||||||
return NO;
|
return NO;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
if (site.type & MPSiteTypeClassGenerated &&
|
if (site.type & MPResultTypeClassTemplate &&
|
||||||
site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
||||||
// This migration requires explicit permission for types of the generated class.
|
// This migration requires explicit permission for types of the generated class.
|
||||||
site.requiresExplicitMigration = YES;
|
site.requiresExplicitMigration = YES;
|
||||||
|
|||||||
@@ -248,9 +248,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (MPSiteEntity *site in user.sites) {
|
for (MPSiteEntity *site in user.sites) {
|
||||||
if (site.type & MPSiteTypeClassStored) {
|
if (site.type & MPResultTypeClassStateful) {
|
||||||
NSString *content;
|
NSString *content;
|
||||||
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
|
while (!(content = [site.algorithm resolvePasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
|
||||||
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
|
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
|
||||||
NSString *masterPassword = nil;
|
NSString *masterPassword = nil;
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,6 @@
|
|||||||
|
|
||||||
#import "MPFixable.h"
|
#import "MPFixable.h"
|
||||||
|
|
||||||
typedef NS_ENUM( NSUInteger, MPImportResult ) {
|
|
||||||
MPImportResultSuccess,
|
|
||||||
MPImportResultCancelled,
|
|
||||||
MPImportResultInvalidPassword,
|
|
||||||
MPImportResultMalformedInput,
|
|
||||||
MPImportResultInternalError,
|
|
||||||
};
|
|
||||||
|
|
||||||
@interface MPAppDelegate_Shared(Store)
|
@interface MPAppDelegate_Shared(Store)
|
||||||
|
|
||||||
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
|
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
|
||||||
@@ -35,17 +27,20 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
|
|||||||
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock;
|
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock;
|
||||||
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
||||||
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
||||||
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock;
|
- (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock;
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
||||||
- (void)deleteAndResetStore;
|
- (void)deleteAndResetStore;
|
||||||
|
|
||||||
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
|
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
|
||||||
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
|
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
|
||||||
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type;
|
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type;
|
||||||
- (MPImportResult)importSites:(NSString *)importedSitesString
|
- (void)importSites:(NSString *)importData
|
||||||
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
|
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
|
||||||
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
|
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
|
||||||
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
|
result:(void ( ^ )(NSError *error))resultBlock;
|
||||||
|
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
||||||
|
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||||
|
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "mpw-marshall.h"
|
#import "mpw-marshal.h"
|
||||||
|
#import "mpw-util.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
||||||
@@ -132,12 +133,11 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock {
|
- (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock {
|
||||||
|
|
||||||
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
|
NSManagedObjectContext *privateManagedObjectContextIfReady = [self privateManagedObjectContextIfReady];
|
||||||
if (!privateManagedObjectContextIfReady)
|
if (!privateManagedObjectContextIfReady)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
return PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, privateManagedObjectContextIfReady, nil,
|
return PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, privateManagedObjectContextIfReady, nil,
|
||||||
^(id host, NSNotification *note) {
|
^(id host, NSNotification *note) {
|
||||||
NSMutableDictionary *affectedObjects = [NSMutableDictionary new];
|
NSMutableDictionary *affectedObjects = [NSMutableDictionary new];
|
||||||
@@ -216,7 +216,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
|
|
||||||
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||||
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
|
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
|
||||||
if ([self.mainManagedObjectContext respondsToSelector:@selector( automaticallyMergesChangesFromParent )]) // iOS 10+
|
if (@available(iOS 10.0, macOS 10.12, *))
|
||||||
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
|
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
|
||||||
else
|
else
|
||||||
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
||||||
@@ -489,7 +489,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MPSiteType type = activeUser.defaultType;
|
MPResultType type = activeUser.defaultType;
|
||||||
id<MPAlgorithm> algorithm = MPAlgorithmDefault;
|
id<MPAlgorithm> algorithm = MPAlgorithmDefault;
|
||||||
Class entityType = [algorithm classOfType:type];
|
Class entityType = [algorithm classOfType:type];
|
||||||
|
|
||||||
@@ -506,7 +506,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type {
|
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type {
|
||||||
|
|
||||||
if (site.type == type)
|
if (site.type == type)
|
||||||
return site;
|
return site;
|
||||||
@@ -539,328 +539,214 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
return site;
|
return site;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPImportResult)importSites:(NSString *)importedSitesString
|
- (void)importSites:(NSString *)importData
|
||||||
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
|
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
|
||||||
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
|
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
|
||||||
|
result:(void ( ^ )(NSError *error))resultBlock {
|
||||||
|
|
||||||
NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
|
NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
|
||||||
|
|
||||||
__block MPImportResult result = MPImportResultCancelled;
|
|
||||||
do {
|
do {
|
||||||
if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||||
result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword
|
NSError *error = [self importSites:importData askImportPassword:importPassword askUserPassword:userPassword
|
||||||
saveInContext:context];
|
saveInContext:context];
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
resultBlock( error );
|
||||||
|
} );
|
||||||
}])
|
}])
|
||||||
break;
|
break;
|
||||||
usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
|
usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
|
||||||
} while (YES);
|
} while (YES);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPImportResult)importSites:(NSString *)importedSitesString
|
- (NSError *)importSites:(NSString *)importData
|
||||||
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||||
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword
|
askUserPassword:(NSString *( ^ )(NSString *userName))askUserPassword
|
||||||
saveInContext:(NSManagedObjectContext *)context {
|
saveInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
// Compile patterns.
|
// Read metadata for the import file.
|
||||||
static NSRegularExpression *headerPattern;
|
MPMarshalInfo *info = mpw_marshal_read_info( importData.UTF8String );
|
||||||
static NSArray *sitePatterns;
|
if (info->format == MPMarshalFormatNone)
|
||||||
NSError *error = nil;
|
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||||
if (!headerPattern) {
|
@"type" : @(MPMarshalErrorFormat),
|
||||||
headerPattern = [[NSRegularExpression alloc]
|
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
|
||||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
}]), @"While importing sites." );
|
||||||
options:(NSRegularExpressionOptions)0 error:&error];
|
|
||||||
if (error) {
|
// Get master password for import file.
|
||||||
MPError( error, @"Error loading the header pattern." );
|
MPKey *importKey;
|
||||||
return MPImportResultInternalError;
|
NSString *importMasterPassword;
|
||||||
|
do {
|
||||||
|
importMasterPassword = askImportPassword( @(info->fullName) );
|
||||||
|
if (!importMasterPassword) {
|
||||||
|
inf( @"Import cancelled." );
|
||||||
|
mpw_marshal_info_free( &info );
|
||||||
|
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!sitePatterns) {
|
importKey = [[MPKey alloc] initForFullName:@(info->fullName) withMasterPassword:importMasterPassword];
|
||||||
sitePatterns = @[
|
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( info->algorithm )] encodeHex]
|
||||||
[[NSRegularExpression alloc] // Format 0
|
caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame);
|
||||||
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)"
|
|
||||||
options:(NSRegularExpressionOptions)0 error:&error],
|
|
||||||
[[NSRegularExpression alloc] // Format 1
|
|
||||||
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)"
|
|
||||||
options:(NSRegularExpressionOptions)0 error:&error]
|
|
||||||
];
|
|
||||||
if (error) {
|
|
||||||
MPError( error, @"Error loading the site patterns." );
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse import data.
|
// Parse import data.
|
||||||
inf( @"Importing sites." );
|
MPMarshalError importError = { .type = MPMarshalSuccess };
|
||||||
NSUInteger importFormat = 0;
|
MPMarshalledUser *importUser = mpw_marshal_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError );
|
||||||
__block MPUserEntity *user = nil;
|
mpw_marshal_info_free( &info );
|
||||||
NSUInteger importAvatar = NSNotFound;
|
|
||||||
NSData *importKeyID = nil;
|
@try {
|
||||||
NSString *importBundleVersion = nil, *importUserName = nil;
|
if (!importUser || importError.type != MPMarshalSuccess)
|
||||||
id<MPAlgorithm> importAlgorithm = nil;
|
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||||
MPSiteType importDefaultType = (MPSiteType)0;
|
@"type" : @(importError.type),
|
||||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
NSLocalizedDescriptionKey: @(importError.description),
|
||||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
}]), @"While importing sites." );
|
||||||
NSMutableSet *sitesToDelete = [NSMutableSet set];
|
|
||||||
NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
// Find an existing user to update.
|
||||||
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
NSError *error = nil;
|
||||||
for (NSString *importedSiteLine in importedSiteLines) {
|
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||||
if ([importedSiteLine hasPrefix:@"#"]) {
|
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", @(importUser->fullName)];
|
||||||
// Comment or header
|
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
||||||
if (!headerStarted) {
|
if (!users)
|
||||||
if ([importedSiteLine isEqualToString:@"##"])
|
return MPError( error, @"While looking for user: %@.", @(importUser->fullName) );
|
||||||
headerStarted = YES;
|
if ([users count] > 1)
|
||||||
continue;
|
return MPMakeError( @"While looking for user: %@, found more than one: %zu",
|
||||||
}
|
@(importUser->fullName), (size_t)[users count] );
|
||||||
if (headerEnded)
|
|
||||||
continue;
|
// Get master key for user.
|
||||||
if ([importedSiteLine isEqualToString:@"##"]) {
|
MPUserEntity *user = [users lastObject];
|
||||||
headerEnded = YES;
|
MPKey *userKey = importKey;
|
||||||
continue;
|
while (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) {
|
||||||
|
NSString *userMasterPassword = askUserPassword( user.name );
|
||||||
|
if (!userMasterPassword) {
|
||||||
|
inf( @"Import cancelled." );
|
||||||
|
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header
|
userKey = [[MPKey alloc] initForFullName:@(importUser->fullName) withMasterPassword:userMasterPassword];
|
||||||
if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
|
|
||||||
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
|
|
||||||
err( @"Invalid header format in line: %@", importedSiteLine );
|
|
||||||
return MPImportResultMalformedInput;
|
|
||||||
}
|
|
||||||
NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
|
||||||
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
|
||||||
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
|
|
||||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
|
|
||||||
|
|
||||||
if ([headerName isEqualToString:@"Format"]) {
|
|
||||||
importFormat = (NSUInteger)[headerValue integerValue];
|
|
||||||
if (importFormat >= [sitePatterns count]) {
|
|
||||||
err( @"Unsupported import format: %lu", (unsigned long)importFormat );
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (([headerName isEqualToString:@"User Name"] || [headerName isEqualToString:@"Full Name"]) && !importUserName) {
|
|
||||||
importUserName = headerValue;
|
|
||||||
|
|
||||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
|
||||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
|
||||||
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
|
||||||
if (!users) {
|
|
||||||
MPError( error, @"While looking for user: %@.", importUserName );
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
}
|
|
||||||
if ([users count] > 1) {
|
|
||||||
err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] );
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = [users lastObject];
|
|
||||||
dbg( @"Existing user? %@", [user debugDescription] );
|
|
||||||
}
|
|
||||||
if ([headerName isEqualToString:@"Avatar"])
|
|
||||||
importAvatar = (NSUInteger)[headerValue integerValue];
|
|
||||||
if ([headerName isEqualToString:@"Key ID"])
|
|
||||||
importKeyID = [headerValue decodeHex];
|
|
||||||
if ([headerName isEqualToString:@"Version"]) {
|
|
||||||
importBundleVersion = headerValue;
|
|
||||||
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
|
|
||||||
}
|
|
||||||
if ([headerName isEqualToString:@"Algorithm"])
|
|
||||||
importAlgorithm = MPAlgorithmForVersion( (MPAlgorithmVersion)[headerValue integerValue] );
|
|
||||||
if ([headerName isEqualToString:@"Default Type"])
|
|
||||||
importDefaultType = (MPSiteType)[headerValue integerValue];
|
|
||||||
if ([headerName isEqualToString:@"Passwords"]) {
|
|
||||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
|
||||||
clearText = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!headerEnded)
|
|
||||||
continue;
|
|
||||||
if (![importUserName length])
|
|
||||||
return MPImportResultMalformedInput;
|
|
||||||
if (![importedSiteLine length])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Site
|
|
||||||
NSRegularExpression *sitePattern = sitePatterns[importFormat];
|
|
||||||
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
|
|
||||||
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
|
|
||||||
err( @"Invalid site format in line: %@", importedSiteLine );
|
|
||||||
return MPImportResultMalformedInput;
|
|
||||||
}
|
|
||||||
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
|
||||||
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
|
||||||
NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent;
|
|
||||||
switch (importFormat) {
|
|
||||||
case 0:
|
|
||||||
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
|
|
||||||
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
|
|
||||||
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
|
|
||||||
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
|
||||||
if ([version length])
|
|
||||||
version = [version substringFromIndex:1]; // Strip the leading colon.
|
|
||||||
counter = @"";
|
|
||||||
loginName = @"";
|
|
||||||
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
|
||||||
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
|
|
||||||
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
|
|
||||||
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
|
|
||||||
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
|
||||||
if ([version length])
|
|
||||||
version = [version substringFromIndex:1]; // Strip the leading colon.
|
|
||||||
counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
|
||||||
if ([counter length])
|
|
||||||
counter = [counter substringFromIndex:1]; // Strip the leading colon.
|
|
||||||
loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
|
|
||||||
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]];
|
|
||||||
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
err( @"Unexpected import format: %lu", (unsigned long)importFormat );
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find existing site.
|
// Update or create user.
|
||||||
if (user) {
|
if (!user) {
|
||||||
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
|
user = [MPUserEntity insertNewObjectInContext:context];
|
||||||
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
|
user.name = @(importUser->fullName);
|
||||||
if (!existingSites) {
|
|
||||||
MPError( error, @"Lookup of existing sites failed for site: %@, user: %@.", siteName, user.userID );
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
}
|
|
||||||
if ([existingSites count]) {
|
|
||||||
dbg( @"Existing sites: %@", existingSites );
|
|
||||||
[sitesToDelete addObjectsFromArray:existingSites];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
|
user.algorithm = MPAlgorithmForVersion( importUser->algorithm );
|
||||||
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
|
|
||||||
lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for confirmation to import these sites and the master password of the user.
|
|
||||||
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count],
|
|
||||||
(unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] );
|
|
||||||
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count],
|
|
||||||
[sitesToDelete count] );
|
|
||||||
if (!userMasterPassword) {
|
|
||||||
inf( @"Import cancelled." );
|
|
||||||
return MPImportResultCancelled;
|
|
||||||
}
|
|
||||||
MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword];
|
|
||||||
if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID])
|
|
||||||
return MPImportResultInvalidPassword;
|
|
||||||
__block MPKey *importKey = userKey;
|
|
||||||
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
|
|
||||||
importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )];
|
|
||||||
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
|
|
||||||
return MPImportResultInvalidPassword;
|
|
||||||
|
|
||||||
// Delete existing sites.
|
|
||||||
if (sitesToDelete.count)
|
|
||||||
[sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
|
||||||
inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] );
|
|
||||||
[context deleteObject:obj];
|
|
||||||
}];
|
|
||||||
|
|
||||||
// Make sure there is a user.
|
|
||||||
if (user) {
|
|
||||||
if (importAvatar != NSNotFound)
|
|
||||||
user.avatar = importAvatar;
|
|
||||||
if (importDefaultType)
|
|
||||||
user.defaultType = importDefaultType;
|
|
||||||
dbg( @"Updating User: %@", [user debugDescription] );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
user = [MPUserEntity insertNewObjectInContext:context];
|
|
||||||
user.name = importUserName;
|
|
||||||
user.algorithm = MPAlgorithmDefault;
|
|
||||||
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
|
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
|
||||||
user.defaultType = importDefaultType?: user.algorithm.defaultType;
|
user.avatar = importUser->avatar;
|
||||||
if (importAvatar != NSNotFound)
|
user.defaultType = importUser->defaultType;
|
||||||
user.avatar = importAvatar;
|
user.lastUsed = [NSDate dateWithTimeIntervalSince1970:MAX( user.lastUsed.timeIntervalSince1970, importUser->lastUsed )];
|
||||||
dbg( @"Created User: %@", [user debugDescription] );
|
dbg( @"Importing user: %@", [user debugDescription] );
|
||||||
}
|
|
||||||
|
|
||||||
// Import new sites.
|
// Update or create sites.
|
||||||
for (NSArray *siteElements in importedSiteSites) {
|
for (size_t s = 0; s < importUser->sites_count; ++s) {
|
||||||
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
|
MPMarshalledSite *importSite = &importUser->sites[s];
|
||||||
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
|
|
||||||
MPSiteType type = (MPSiteType)[siteElements[2] integerValue];
|
|
||||||
MPAlgorithmVersion version = (MPAlgorithmVersion)[siteElements[3] integerValue];
|
|
||||||
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
|
|
||||||
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
|
|
||||||
NSString *siteName = siteElements[6];
|
|
||||||
NSString *exportContent = siteElements[7];
|
|
||||||
|
|
||||||
// Create new site.
|
// Find an existing site to update.
|
||||||
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version );
|
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
Class entityType = [algorithm classOfType:type];
|
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", @(importSite->name), user];
|
||||||
if (!entityType) {
|
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
|
||||||
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
|
if (!existingSites)
|
||||||
return MPImportResultInternalError;
|
return MPError( error, @"Lookup of existing sites failed for site: %@, user: %@", @(importSite->name), user.userID );
|
||||||
|
if ([existingSites count])
|
||||||
|
// Update existing site.
|
||||||
|
for (MPSiteEntity *site in existingSites) {
|
||||||
|
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||||
|
dbg( @"Updated site: %@", [site debugDescription] );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Create new site.
|
||||||
|
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( importSite->algorithm );
|
||||||
|
Class entityType = [algorithm classOfType:importSite->type];
|
||||||
|
if (!entityType)
|
||||||
|
return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->name), (long)importSite->type );
|
||||||
|
|
||||||
|
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
|
||||||
|
site.user = user;
|
||||||
|
|
||||||
|
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||||
|
dbg( @"Created site: %@", [site debugDescription] );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
|
|
||||||
site.name = siteName;
|
|
||||||
site.loginName = loginName;
|
|
||||||
site.user = user;
|
|
||||||
site.type = type;
|
|
||||||
site.uses = uses;
|
|
||||||
site.lastUsed = lastUsed;
|
|
||||||
site.algorithm = algorithm;
|
|
||||||
if ([exportContent length]) {
|
|
||||||
if (clearText)
|
|
||||||
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
|
|
||||||
else
|
|
||||||
[site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey];
|
|
||||||
}
|
|
||||||
if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound)
|
|
||||||
((MPGeneratedSiteEntity *)site).counter = counter;
|
|
||||||
|
|
||||||
dbg( @"Created Site: %@", [site debugDescription] );
|
if (![context saveToStore])
|
||||||
|
return MPMakeError( @"Failed saving imported changes." );
|
||||||
|
|
||||||
|
inf( @"Import completed successfully." );
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
|
||||||
|
MPSitesImportedNotificationUserKey: user
|
||||||
|
}];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
@finally {
|
||||||
|
mpw_marshal_free( &importUser );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![context saveToStore])
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
|
|
||||||
inf( @"Import completed successfully." );
|
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
|
|
||||||
MPSitesImportedNotificationUserKey: user
|
|
||||||
}];
|
|
||||||
|
|
||||||
return MPImportResultSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
|
- (void)importSite:(const MPMarshalledSite *)importSite protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site
|
||||||
|
usingKey:(MPKey *)userKey {
|
||||||
|
|
||||||
MPUserEntity *activeUser = [self activeUserForMainThread];
|
site.name = @(importSite->name);
|
||||||
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
|
if (importSite->content)
|
||||||
|
[site.algorithm importPassword:@(importSite->content) protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||||
|
site.type = importSite->type;
|
||||||
|
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||||
|
((MPGeneratedSiteEntity *)site).counter = importSite->counter;
|
||||||
|
site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
|
||||||
|
site.loginName = importSite->loginContent? @(importSite->loginContent): nil;
|
||||||
|
site.loginGenerated = importSite->loginType & MPResultTypeClassTemplate;
|
||||||
|
site.url = importSite->url? @(importSite->url): nil;
|
||||||
|
site.uses = importSite->uses;
|
||||||
|
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
|
||||||
|
}
|
||||||
|
|
||||||
MPMarshalledUser exportUser = mpw_marshall_user( activeUser.name.UTF8String,
|
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
|
||||||
[self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version );
|
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||||
exportUser.avatar = activeUser.avatar;
|
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock {
|
||||||
exportUser.defaultType = activeUser.defaultType;
|
|
||||||
exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970;
|
|
||||||
|
|
||||||
|
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
|
MPUserEntity *user = [self activeUserInContext:context];
|
||||||
|
NSString *masterPassword = askImportPassword( user.name );
|
||||||
|
|
||||||
for (MPSiteEntity *site in activeUser.sites) {
|
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
|
||||||
MPMarshalledSite exportSite = mpw_marshall_site( &exportUser,
|
MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version );
|
||||||
site.name.UTF8String, site.type, site.counter, site.algorithm.version );
|
exportUser->redacted = !revealPasswords;
|
||||||
exportSite.loginName = site.loginName.UTF8String;
|
exportUser->avatar = (unsigned int)user.avatar;
|
||||||
exportSite.url = site.url.UTF8String;
|
exportUser->defaultType = user.defaultType;
|
||||||
exportSite.uses = site.uses;
|
exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970;
|
||||||
exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
|
|
||||||
|
|
||||||
for (MPSiteQuestionEntity *siteQuestion in site.questions)
|
for (MPSiteEntity *site in user.sites) {
|
||||||
mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String );
|
MPCounterValue counter = MPCounterValueInitial;
|
||||||
}
|
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||||
|
counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||||
|
NSString *content = revealPasswords
|
||||||
|
? [site.algorithm exportPasswordForSite:site usingKey:self.key]
|
||||||
|
: [site.algorithm resolvePasswordForSite:site usingKey:self.key];
|
||||||
|
|
||||||
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser );
|
MPMarshalledSite *exportSite = mpw_marshal_site( exportUser,
|
||||||
|
site.name.UTF8String, site.type, counter, site.algorithm.version );
|
||||||
|
exportSite->content = content.UTF8String;
|
||||||
|
exportSite->loginContent = site.loginName.UTF8String;
|
||||||
|
exportSite->loginType = site.loginGenerated? MPResultTypeTemplateName: MPResultTypeStatefulPersonal;
|
||||||
|
exportSite->url = site.url.UTF8String;
|
||||||
|
exportSite->uses = (unsigned int)site.uses;
|
||||||
|
exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
|
||||||
|
|
||||||
|
for (MPSiteQuestionEntity *siteQuestion in site.questions)
|
||||||
|
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
|
||||||
|
}
|
||||||
|
|
||||||
|
char *export = NULL;
|
||||||
|
MPMarshalError exportError = (MPMarshalError){ .type= MPMarshalSuccess };
|
||||||
|
mpw_marshal_write( &export, MPMarshalFormatFlat, exportUser, &exportError );
|
||||||
|
NSString *mpsites = nil;
|
||||||
|
if (export && exportError.type == MPMarshalSuccess)
|
||||||
|
mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
|
||||||
|
mpw_free_string( &export );
|
||||||
|
|
||||||
|
resultBlock( mpsites, exportError.type == MPMarshalSuccess? nil:
|
||||||
|
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||||
|
@"type" : @(exportError.type),
|
||||||
|
NSLocalizedDescriptionKey: @(exportError.description),
|
||||||
|
}] );
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
@interface MPSiteEntity(MP)<MPFixable>
|
@interface MPSiteEntity(MP)<MPFixable>
|
||||||
|
|
||||||
@property(assign) BOOL loginGenerated;
|
@property(assign) BOOL loginGenerated;
|
||||||
@property(assign) MPSiteType type;
|
@property(assign) MPResultType type;
|
||||||
@property(readonly) NSString *typeName;
|
@property(readonly) NSString *typeName;
|
||||||
@property(readonly) NSString *typeShortName;
|
@property(readonly) NSString *typeShortName;
|
||||||
@property(readonly) NSString *typeClassName;
|
@property(readonly) NSString *typeClassName;
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
@interface MPGeneratedSiteEntity(MP)
|
@interface MPGeneratedSiteEntity(MP)
|
||||||
|
|
||||||
@property(assign) NSUInteger counter;
|
@property(assign) MPCounterValue counter;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
@property(assign) NSUInteger avatar;
|
@property(assign) NSUInteger avatar;
|
||||||
@property(assign) BOOL saveKey;
|
@property(assign) BOOL saveKey;
|
||||||
@property(assign) BOOL touchID;
|
@property(assign) BOOL touchID;
|
||||||
@property(assign) MPSiteType defaultType;
|
@property(assign) MPResultType defaultType;
|
||||||
@property(readonly) NSString *userID;
|
@property(readonly) NSString *userID;
|
||||||
@property(strong) id<MPAlgorithm> algorithm;
|
@property(strong) id<MPAlgorithm> algorithm;
|
||||||
|
|
||||||
|
|||||||
@@ -82,9 +82,9 @@
|
|||||||
return MPFixableResultNoProblems;
|
return MPFixableResultNoProblems;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPSiteType)type {
|
- (MPResultType)type {
|
||||||
|
|
||||||
return (MPSiteType)[self.type_ unsignedIntegerValue];
|
return (MPResultType)[self.type_ unsignedIntegerValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
|
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
return [self.loginGenerated_ boolValue];
|
return [self.loginGenerated_ boolValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setType:(MPSiteType)aType {
|
- (void)setType:(MPResultType)aType {
|
||||||
|
|
||||||
self.type_ = @(aType);
|
self.type_ = @(aType);
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
|
|
||||||
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
|
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
|
||||||
|
|
||||||
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
||||||
// Invalid self.type
|
// Invalid self.type
|
||||||
result = MPApplyFix( result, ^MPFixableResult {
|
result = MPApplyFix( result, ^MPFixableResult {
|
||||||
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
||||||
@@ -259,7 +259,7 @@
|
|||||||
self.type = self.user.defaultType;
|
self.type = self.user.defaultType;
|
||||||
return MPFixableResultProblemsFixed;
|
return MPFixableResultProblemsFixed;
|
||||||
} );
|
} );
|
||||||
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
||||||
// Invalid self.user.defaultType
|
// Invalid self.user.defaultType
|
||||||
result = MPApplyFix( result, ^MPFixableResult {
|
result = MPApplyFix( result, ^MPFixableResult {
|
||||||
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
||||||
// Mismatch between self.type and self.class
|
// Mismatch between self.type and self.class
|
||||||
result = MPApplyFix( result, ^MPFixableResult {
|
result = MPApplyFix( result, ^MPFixableResult {
|
||||||
for (MPSiteType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
|
for (MPResultType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
|
||||||
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
|
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
|
||||||
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
|
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
|
||||||
self.name, self.user.name, (long)self.type, self.class, (long)newType );
|
self.name, self.user.name, (long)self.type, self.class, (long)newType );
|
||||||
@@ -286,12 +286,12 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)counter {
|
- (MPCounterValue)counter {
|
||||||
|
|
||||||
return [self.counter_ unsignedIntegerValue];
|
return (MPCounterValue)[self.counter_ unsignedIntegerValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setCounter:(NSUInteger)aCounter {
|
- (void)setCounter:(MPCounterValue)aCounter {
|
||||||
|
|
||||||
self.counter_ = @(aCounter);
|
self.counter_ = @(aCounter);
|
||||||
}
|
}
|
||||||
@@ -354,12 +354,12 @@
|
|||||||
self.touchID_ = @(aTouchID);
|
self.touchID_ = @(aTouchID);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPSiteType)defaultType {
|
- (MPResultType)defaultType {
|
||||||
|
|
||||||
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
|
return (MPResultType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDefaultType:(MPSiteType)aDefaultType {
|
- (void)setDefaultType:(MPResultType)aDefaultType {
|
||||||
|
|
||||||
self.defaultType_ = @(aDefaultType);
|
self.defaultType_ = @(aDefaultType);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user