Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2033ebdc72 | ||
|
|
c3bb896f40 | ||
|
|
4f7c28563d | ||
|
|
b1985a2bf2 | ||
|
|
ee50a4d025 | ||
|
|
b26f5a82d7 | ||
|
|
c044ae79cd | ||
|
|
a261538602 | ||
|
|
18daef7808 | ||
|
|
68d1ab58b7 | ||
|
|
2b660adf00 | ||
|
|
e15d01882f | ||
|
|
23491faccc | ||
|
|
5f2e1611f1 | ||
|
|
9abacaf905 | ||
|
|
322e056661 | ||
|
|
228f8e4ed1 | ||
|
|
d6415277d0 | ||
|
|
db41a6635f | ||
|
|
096919637f | ||
|
|
434d70ebff | ||
|
|
bb8829b66f | ||
|
|
10f2c107c6 | ||
|
|
03080b9ccd | ||
|
|
b00ad53e42 | ||
|
|
99e286456e | ||
|
|
46cdf56944 | ||
|
|
9d5105a9e5 | ||
|
|
3c5cb1673a | ||
|
|
13107063df | ||
|
|
8a73baa6bc | ||
|
|
b65fedf40d | ||
|
|
04ab276d93 | ||
|
|
6d88d6bde0 | ||
|
|
4103c6e659 | ||
|
|
16004f2ffe | ||
|
|
37c0d323d9 | ||
|
|
560cb1a266 | ||
|
|
738ad197b2 | ||
|
|
cfcc5287db | ||
|
|
0b5502b673 | ||
|
|
d3e3c9d720 | ||
|
|
3c3f88d820 | ||
|
|
2e2c654ec9 | ||
|
|
d361ae2381 | ||
|
|
fcbb93762a | ||
|
|
f86210f5da | ||
|
|
e96f678236 | ||
|
|
8b9067ab4b | ||
|
|
5af383235a | ||
|
|
25b13dfb22 | ||
|
|
635692ef09 | ||
|
|
e6bab4e504 | ||
|
|
cd6b7e6051 | ||
|
|
b180202e07 | ||
|
|
f83f2af529 | ||
|
|
cf2c30cfe6 | ||
|
|
834e94ebd5 | ||
|
|
6d9be3fdfe | ||
|
|
07e55140ac | ||
|
|
fbbd08790d | ||
|
|
fcaa5d1d8c | ||
|
|
ea5be8efcb | ||
|
|
c8b4933c3d | ||
|
|
981ee171ae | ||
|
|
3ed6b93736 | ||
|
|
56a515c5ea | ||
|
|
15ac7a2dbf | ||
|
|
c5c7999753 | ||
|
|
bb58ed0169 | ||
|
|
4545a5c745 | ||
|
|
da8c7064fe | ||
|
|
d9bd604436 | ||
|
|
c99252809d | ||
|
|
d704f451a3 | ||
|
|
2c9ab5d153 | ||
|
|
d5d33da12f | ||
|
|
cbef1a611b | ||
|
|
0a1f215a1a | ||
|
|
907d2a8ca6 | ||
|
|
89f6e77f67 | ||
|
|
f2fb16a0b9 | ||
|
|
e3edd42b88 | ||
|
|
cc5d246d7d | ||
|
|
ca320de6d9 | ||
|
|
ae979d7240 | ||
|
|
eb1c443940 | ||
|
|
dadcefc9bf | ||
|
|
cdbaec9751 | ||
|
|
f48d480c77 | ||
|
|
23aae490df | ||
|
|
3fcf1131ac | ||
|
|
2bdec415e9 | ||
|
|
4c12f4af56 | ||
|
|
d91140439a | ||
|
|
a7912dd1b7 | ||
|
|
05391d893e | ||
|
|
2047422b08 | ||
|
|
e3fffc1923 | ||
|
|
9bf50569cc | ||
|
|
9ef265d9de | ||
|
|
385f347b33 | ||
|
|
4058d33202 | ||
|
|
98c5ee3425 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -40,6 +40,7 @@ core/c/lib/*/src
|
|||||||
core/c/lib/include
|
core/c/lib/include
|
||||||
platform-independent/cli-c/cli/*.o
|
platform-independent/cli-c/cli/*.o
|
||||||
platform-independent/cli-c/mpw-*.tar.gz
|
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
|
||||||
platform-independent/cli-c/mpw-bench
|
platform-independent/cli-c/mpw-bench
|
||||||
platform-independent/cli-c/mpw-tests
|
platform-independent/cli-c/mpw-tests
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -19,3 +19,9 @@
|
|||||||
[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/js/mpw-js
|
||||||
url = https://github.com/tmthrgd/mpw-js.git
|
url = https://github.com/tmthrgd/mpw-js.git
|
||||||
|
[submodule "platform-darwin/External/libsodium"]
|
||||||
|
path = platform-darwin/External/libsodium
|
||||||
|
url = https://github.com/jedisct1/libsodium.git
|
||||||
|
[submodule "platform-darwin/External/libjson-c"]
|
||||||
|
path = platform-darwin/External/libjson-c
|
||||||
|
url = https://github.com/json-c/json-c.git
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ env: TERM=dumb SHLVL=0
|
|||||||
git:
|
git:
|
||||||
submodules: true
|
submodules: true
|
||||||
script:
|
script:
|
||||||
- "( cd ./platform-independent/cli-c && ./clean && ./build && ./mpw-tests )"
|
- "( 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 iOS' -sdk iphonesimulator )"
|
||||||
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' )"
|
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' )"
|
||||||
|
|||||||
99
README.md
99
README.md
@@ -15,13 +15,23 @@ To skip the intro and go straight to the information on how to use the code, [cl
|
|||||||
|
|
||||||
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://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 also available from the following package managers: [macOS: Homebrew](https://brew.sh/). Get in touch if you are interested in adding Master Password to any other package managers.
|
Master Password is also available from the following package managers: [macOS: Homebrew](https://brew.sh/) (`brew install mpw`).
|
||||||
|
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:
|
||||||
|
|
||||||
|
- I don't need to worry about keeping backups of my countless authentication credentials.
|
||||||
|
- I don't need to worry that when I travel, I might not have access to my passwords vault.
|
||||||
|
- I don't need to trust an external party, proprietary code or a service to be online and stay online.
|
||||||
|
- If I feel at risk of my device being stolen or confiscated, I can set a fake master password, delete my user or wipe it worry-free.
|
||||||
|
|
||||||
|
We also have a [Frequently Asked Questions](#faq).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## What is a password?
|
## What is a password?
|
||||||
|
|
||||||
The "password". Somehow, passwords have become the default solution to authentication across the web. We've long since accepted this as the way things are, but let's stop to think for a moment about what passwords actually are:
|
Ah, the "password". Somehow, passwords have become the default solution to authentication across the web. We've long since accepted this as the way things are, but let's stop to think for a moment about what passwords actually are:
|
||||||
|
|
||||||
A password is a secret that is known only to the party providing a service and the party that should be allowed access to this service.
|
A password is a secret that is known only to the party providing a service and the party that should be allowed access to this service.
|
||||||
|
|
||||||
@@ -69,12 +79,12 @@ Master Password is *not* a password manager. It does not store your website pas
|
|||||||
|
|
||||||
## Benefits
|
## Benefits
|
||||||
|
|
||||||
- You don't need to come up with a secure password every time you make a new account - Master Password gives you the key for it.
|
- You don't need to think up a new strong password every time you make a new account - Master Password gives you the key for it.
|
||||||
- You don't need to try to remember a password you created two years ago for that one account - Master Password just gives you the key for it.
|
- You don't need to try remembering a password you created two years ago for that one account - Master Password just gives you the key for it.
|
||||||
- You don't need to that you can't get into that account you made at work when you come home because you don't have your work passwords with you - Master Password is always available.
|
- You don't need to worry about getting into that account you made at work after you come home because you don't have your office passwords with you - Master Password is availale everywhere, even offline.
|
||||||
- You don't need to try to keep password lists in sync or stored somewhere easily accessible - Master Password is always available.
|
- You don't need to try to keep password lists in sync or stored somewhere easily accessible - Master Password keys can be created anywhere.
|
||||||
- You don't need to worry what you'll do if your computer dies or you need to log into your bank while you're in the airport transit zone - Master Password is always available.
|
- You don't need to worry what you'll do if your computer dies or you need to log into your bank while you're in the airport transit zone - your Master Password keys are always available, even when starting empty.
|
||||||
- You don't need to worry about your password manager website getting hacked, your phone getting duplicated, somebody taking a picture of your passwords book - Master Password keeps no records.
|
- You don't need to worry about your password manager website getting hacked, your phone getting duplicated, somebody taking a picture of your passwords book - Master Password stores no secrets.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -94,6 +104,35 @@ We standardize `user-name` as your full name, `site-name` as the domain name of
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
1. If I lose my master password and need to set a new one, will I need to change all of my site passwords?
|
||||||
|
|
||||||
|
Yes. If your master password is compromised, it is only sensible for you to change all of your site passwords. Just like if you lose the keys in your pocket, you'll have to change all the locks they open. Master Password effectively enforces this security practice.
|
||||||
|
|
||||||
|
2. But what if I just forget my master password or I just want to change it to something else?
|
||||||
|
|
||||||
|
Sorry, still yes. Your master password is the secret component to your Master Password identity. If it changes, your identity changes. I wholly encourage you to think very carefully about what makes for a really memorable and good master password before just diving in with something lazy. A short phrase works great, eg. `banana coloured duckling`.
|
||||||
|
|
||||||
|
3. Doesn't this mean an attacker can reverse my master password from any of my site passwords?
|
||||||
|
|
||||||
|
Technically, yes. Practically, no.
|
||||||
|
|
||||||
|
You could argue that site passwords are "breadcrumbs" of your master password, but the same argument would suggest encrypted messages are breadcrumbs to the encryption key. Encryption works because it is computationally unfeasible to "guess" the encryption key that made the encrypted message, just like Master Password works because it is computationally unfeasible to "guess" your master password that made the site password.
|
||||||
|
|
||||||
|
4. The second step is just a HMAC-SHA-256, doesn't that make the SCRYPT completely pointless?
|
||||||
|
|
||||||
|
No. They are used for different reasons and one is not weaker than the other.
|
||||||
|
|
||||||
|
HMAC-SHA-256 is much faster to compute than SCRYPT, which leads some people to think "all an attacker needs to do is brute-force the SHA and ignore the SCRYPT". The reality is that the HMAC-SHA-256 guards a 64-byte authentication key (the `master-key`) which makes the search space for brute-forcing the HMAC wildly too large to compute.
|
||||||
|
The `master-password` on the other hand, is only a simple phrase, which means its search space is much smaller. This is why it is guarded by a much tougher SCRYPT operation.
|
||||||
|
|
||||||
|
5. I have another question.
|
||||||
|
|
||||||
|
Please don't hesitate to [get in touch](#support), we're more than happy to answer all your Master Password questions. Any problems or suggestions can be reported [as GitHub issues](https://github.com/Lyndir/MasterPassword/issues).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Source Code
|
# Source Code
|
||||||
|
|
||||||
@@ -146,6 +185,10 @@ Go into the `gradle` directory and run `./gradlew build`. All Java components w
|
|||||||
- `platform-android/build/outputs/apk`:
|
- `platform-android/build/outputs/apk`:
|
||||||
contains the Android application package. Install it on your Android device.
|
contains the Android application package. Install it on your Android device.
|
||||||
|
|
||||||
|
Note that in order to build the Android application, you will need to have the Android SDK installed and either have the environment variable `ANDROID_HOME` set to its location or a `gradle/local.properties` file with its location, eg. (for Homebrew users who installed the SDK using `brew install android-sdk`):
|
||||||
|
|
||||||
|
sdk.dir=/usr/local/opt/android-sdk
|
||||||
|
|
||||||
|
|
||||||
### Native CLI
|
### Native CLI
|
||||||
|
|
||||||
@@ -158,34 +201,42 @@ For example:
|
|||||||
./build && sudo ./install
|
./build && sudo ./install
|
||||||
mpw -h
|
mpw -h
|
||||||
|
|
||||||
The build has a few dependencies you should have installed before running it:
|
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`: `openssl-dev`, `ncurses-dev` (if `mpw_color=1`)
|
- `mpw`
|
||||||
- `mpw-bench`: `openssl-dev`
|
|
||||||
- `mpw-tests`: `openssl-dev`, `libxml2`
|
|
||||||
|
|
||||||
There are a few different ways you can modify the build process:
|
The C implementation depends either on `libsodium` or Tarsnap's `scrypt` and `openssl-dev`.
|
||||||
|
|
||||||
- You can change the targets that should be built. By default, all targets are built. These are the available targets:
|
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.
|
||||||
- `mpw`: This is the standard command-line `mpw` tool which implements all Master Password features.
|
|
||||||
- `mpw-tests`: This is a tool to perform the standard tests script on the `mpw` implementation.
|
|
||||||
- `mpw-bench`: This is a tool to run a benchmark on the `mpw` implementation, comparing it to the performance of other algorithms.
|
|
||||||
- You can specify custom arguments to the compiler, pass them as arguments to the build script.
|
|
||||||
- The build process involves some optionals, they can by toggled from their default setting by passing variables:
|
|
||||||
- `mpw_color`: [default: 1] Colorized Identicon, depends on
|
|
||||||
|
|
||||||
To change the targets to build, use:
|
If you have `mpw_color` enabled (it is enabled by default), the build also depends on `ncurses-dev` to communicate with the terminal.
|
||||||
|
|
||||||
targets='mpw mpw-bench' ./build
|
- `mpw-bench`
|
||||||
|
|
||||||
To add a library search path, use:
|
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
|
./build -L/usr/local/lib
|
||||||
|
|
||||||
Change an optional feature:
|
There are a few toggleable features, to change them, pass them as environment variables:
|
||||||
|
|
||||||
mpw_color=0 ./build
|
mpw_color=0 ./build
|
||||||
|
|
||||||
|
Currently, there is only one toggleable feature:
|
||||||
|
|
||||||
|
- `mpw_color`: [default: 1] Colorized Identicon, depends on `ncurses-dev`.
|
||||||
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|||||||
155
core/c/base64.c
Normal file
155
core/c/base64.c
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in
|
||||||
|
* the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* 3. All advertising materials mentioning features or use of this
|
||||||
|
* software must display the following acknowledgment:
|
||||||
|
* "This product includes software developed by the Apache Group
|
||||||
|
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||||
|
*
|
||||||
|
* 4. The names "Apache Server" and "Apache Group" must not be used to
|
||||||
|
* endorse or promote products derived from this software without
|
||||||
|
* prior written permission. For written permission, please contact
|
||||||
|
* apache@apache.org.
|
||||||
|
*
|
||||||
|
* 5. Products derived from this software may not be called "Apache"
|
||||||
|
* nor may "Apache" appear in their names without prior written
|
||||||
|
* permission of the Apache Group.
|
||||||
|
*
|
||||||
|
* 6. Redistributions of any form whatsoever must retain the following
|
||||||
|
* acknowledgment:
|
||||||
|
* "This product includes software developed by the Apache Group
|
||||||
|
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
|
||||||
|
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
|
||||||
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
* ====================================================================
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many
|
||||||
|
* individuals on behalf of the Apache Group and was originally based
|
||||||
|
* on public domain software written at the National Center for
|
||||||
|
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
|
||||||
|
* For more information on the Apache Group and the Apache HTTP server
|
||||||
|
* project, please see <http://www.apache.org/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
/* aaaack but it's fast and const should make it shared text page. */
|
||||||
|
static const unsigned char b64ToBits[256] =
|
||||||
|
{
|
||||||
|
/* ASCII table */
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
|
||||||
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
|
||||||
|
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||||
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t mpw_base64_decode_max(const char *b64Text) {
|
||||||
|
|
||||||
|
register const uint8_t *b64Cursor = (uint8_t *)b64Text;
|
||||||
|
while (b64ToBits[*(b64Cursor++)] <= 63);
|
||||||
|
int b64Size = (int)(b64Cursor - (uint8_t *)b64Text) - 1;
|
||||||
|
|
||||||
|
// Every 4 b64 chars yield 3 plain bytes => len = 3 * ceil(b64Size / 4)
|
||||||
|
return (size_t)(3 /*bytes*/ * ((b64Size + 4 /*chars*/ - 1) / 4 /*chars*/));
|
||||||
|
}
|
||||||
|
|
||||||
|
int mpw_base64_decode(uint8_t *plainBuf, const char *b64Text) {
|
||||||
|
|
||||||
|
register const uint8_t *b64Cursor = (uint8_t *)b64Text;
|
||||||
|
while (b64ToBits[*(b64Cursor++)] <= 63);
|
||||||
|
int b64Remaining = (int)(b64Cursor - (uint8_t *)b64Text) - 1;
|
||||||
|
|
||||||
|
b64Cursor = (uint8_t *)b64Text;
|
||||||
|
register uint8_t *plainCursor = plainBuf;
|
||||||
|
while (b64Remaining > 4) {
|
||||||
|
*(plainCursor++) = (b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
|
||||||
|
*(plainCursor++) = (b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
|
||||||
|
*(plainCursor++) = (b64ToBits[b64Cursor[2]] << 6 | b64ToBits[b64Cursor[3]]);
|
||||||
|
b64Cursor += 4;
|
||||||
|
b64Remaining -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note: (b64Size == 1) would be an error, so just ingore that case */
|
||||||
|
if (b64Remaining > 1)
|
||||||
|
*(plainCursor++) = (b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
|
||||||
|
if (b64Remaining > 2)
|
||||||
|
*(plainCursor++) = (b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
|
||||||
|
if (b64Remaining > 3)
|
||||||
|
*(plainCursor++) = (b64ToBits[b64Cursor[2]] << 6 | b64ToBits[b64Cursor[3]]);
|
||||||
|
|
||||||
|
return (int)(plainCursor - plainBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char basis_64[] =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
size_t mpw_base64_encode_max(size_t plainSize) {
|
||||||
|
|
||||||
|
// Every 3 plain bytes yield 4 b64 chars => len = 4 * ceil(plainSize / 3)
|
||||||
|
return 4 /*chars*/ * (plainSize + 3 /*bytes*/ - 1) / 3 /*bytes*/;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mpw_base64_encode(char *b64Text, const uint8_t *plainBuf, size_t plainSize) {
|
||||||
|
|
||||||
|
int plainCursor = 0;
|
||||||
|
char *b64Cursor = b64Text;
|
||||||
|
for (; plainCursor < plainSize - 2; plainCursor += 3) {
|
||||||
|
*b64Cursor++ = basis_64[((plainBuf[plainCursor] >> 2)) & 0x3F];
|
||||||
|
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4) |
|
||||||
|
((plainBuf[plainCursor + 1] & 0xF0) >> 4)];
|
||||||
|
*b64Cursor++ = basis_64[((plainBuf[plainCursor + 1] & 0xF) << 2) |
|
||||||
|
((plainBuf[plainCursor + 2] & 0xC0) >> 6)];
|
||||||
|
*b64Cursor++ = basis_64[plainBuf[plainCursor + 2] & 0x3F];
|
||||||
|
}
|
||||||
|
if (plainCursor < plainSize) {
|
||||||
|
*b64Cursor++ = basis_64[(plainBuf[plainCursor] >> 2) & 0x3F];
|
||||||
|
if (plainCursor == (plainSize - 1)) {
|
||||||
|
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4)];
|
||||||
|
*b64Cursor++ = '=';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4) |
|
||||||
|
((plainBuf[plainCursor + 1] & 0xF0) >> 4)];
|
||||||
|
*b64Cursor++ = basis_64[((plainBuf[plainCursor + 1] & 0xF) << 2)];
|
||||||
|
}
|
||||||
|
*b64Cursor++ = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
*b64Cursor = '\0';
|
||||||
|
return (int)(b64Cursor - b64Text);
|
||||||
|
}
|
||||||
78
core/c/base64.h
Normal file
78
core/c/base64.h
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in
|
||||||
|
* the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* 3. All advertising materials mentioning features or use of this
|
||||||
|
* software must display the following acknowledgment:
|
||||||
|
* "This product includes software developed by the Apache Group
|
||||||
|
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||||
|
*
|
||||||
|
* 4. The names "Apache Server" and "Apache Group" must not be used to
|
||||||
|
* endorse or promote products derived from this software without
|
||||||
|
* prior written permission. For written permission, please contact
|
||||||
|
* apache@apache.org.
|
||||||
|
*
|
||||||
|
* 5. Products derived from this software may not be called "Apache"
|
||||||
|
* nor may "Apache" appear in their names without prior written
|
||||||
|
* permission of the Apache Group.
|
||||||
|
*
|
||||||
|
* 6. Redistributions of any form whatsoever must retain the following
|
||||||
|
* acknowledgment:
|
||||||
|
* "This product includes software developed by the Apache Group
|
||||||
|
* for use in the Apache HTTP server project (http://www.apache.org/)."
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
|
||||||
|
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
|
||||||
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
* ====================================================================
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many
|
||||||
|
* individuals on behalf of the Apache Group and was originally based
|
||||||
|
* on public domain software written at the National Center for
|
||||||
|
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
|
||||||
|
* For more information on the Apache Group and the Apache HTTP server
|
||||||
|
* project, please see <http://www.apache.org/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The amount of bytes needed to decode the given b64Text.
|
||||||
|
*/
|
||||||
|
size_t mpw_base64_decode_max(const char *b64Text);
|
||||||
|
/** Decodes a base-64 encoded string into a plain byte buffer.
|
||||||
|
* @param plainBuf a byte buffer, size should be at least mpw_base64_decode_max(b64Text)
|
||||||
|
* @return The amount of bytes that were written to plainBuf.
|
||||||
|
*/
|
||||||
|
int mpw_base64_decode(uint8_t *plainBuf, const char *b64Text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The amount of characters needed to encode a plainBuf of the given size as base-64 (excluding the terminating NUL).
|
||||||
|
*/
|
||||||
|
size_t mpw_base64_encode_max(size_t plainSize);
|
||||||
|
/** Encodes a plain byte buffer into a base-64 encoded string.
|
||||||
|
* @param b64Text a character buffer, size should be at least mpw_base64_encode_max(plainSize) + 1
|
||||||
|
* @return The amount of characters that were written to b64Text, excluding the terminating NUL.
|
||||||
|
*/
|
||||||
|
int mpw_base64_encode(char *b64Text, const uint8_t *plainBuf, size_t plainSize);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
home=http://www.tarsnap.com/scrypt.html
|
home=http://www.tarsnap.com/scrypt.html
|
||||||
git=https://github.com/Tarsnap/scrypt.git
|
git=https://github.com/Tarsnap/scrypt.git
|
||||||
pkg=http://www.tarsnap.com/scrypt/scrypt-1.2.0.tgz
|
pkg=https://www.tarsnap.com/scrypt/scrypt-1.2.1.tgz
|
||||||
pkg_sha256=1754bc89405277c8ac14220377a4c240ddc34b1ce70882aa92cd01bfdc8569d4
|
pkg_sha256=4621f5e7da2f802e20850436219370092e9fcda93bd598f6d4236cce33f4c577
|
||||||
|
|||||||
@@ -22,48 +22,150 @@
|
|||||||
#include "mpw-algorithm_v2.c"
|
#include "mpw-algorithm_v2.c"
|
||||||
#include "mpw-algorithm_v3.c"
|
#include "mpw-algorithm_v3.c"
|
||||||
|
|
||||||
#define MP_N 32768
|
MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
|
||||||
#define MP_r 8
|
|
||||||
#define MP_p 2
|
|
||||||
#define MP_hash PearlHashSHA256
|
|
||||||
|
|
||||||
const uint8_t *mpw_masterKeyForUser(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
|
|
||||||
|
|
||||||
|
trc( "-- mpw_masterKey (algorithm: %u)\n", algorithmVersion );
|
||||||
|
trc( "fullName: %s\n", fullName );
|
||||||
|
trc( "masterPassword.id: %s\n", mpw_id_buf( masterPassword, strlen( masterPassword ) ) );
|
||||||
if (!fullName || !masterPassword)
|
if (!fullName || !masterPassword)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
switch (algorithmVersion) {
|
switch (algorithmVersion) {
|
||||||
case MPAlgorithmVersion0:
|
case MPAlgorithmVersion0:
|
||||||
return mpw_masterKeyForUser_v0( fullName, masterPassword );
|
return mpw_masterKey_v0( fullName, masterPassword );
|
||||||
case MPAlgorithmVersion1:
|
case MPAlgorithmVersion1:
|
||||||
return mpw_masterKeyForUser_v1( fullName, masterPassword );
|
return mpw_masterKey_v1( fullName, masterPassword );
|
||||||
case MPAlgorithmVersion2:
|
case MPAlgorithmVersion2:
|
||||||
return mpw_masterKeyForUser_v2( fullName, masterPassword );
|
return mpw_masterKey_v2( fullName, masterPassword );
|
||||||
case MPAlgorithmVersion3:
|
case MPAlgorithmVersion3:
|
||||||
return mpw_masterKeyForUser_v3( fullName, masterPassword );
|
return mpw_masterKey_v3( fullName, masterPassword );
|
||||||
default:
|
default:
|
||||||
ftl( "Unsupported version: %d", algorithmVersion );
|
err( "Unsupported version: %d\n", algorithmVersion );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *mpw_passwordForSite(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
MPSiteKey mpw_siteKey(
|
||||||
const MPSiteVariant siteVariant, const char *siteContext, const MPAlgorithmVersion algorithmVersion) {
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion) {
|
||||||
|
|
||||||
|
trc( "-- mpw_siteKey (algorithm: %u)\n", algorithmVersion );
|
||||||
|
trc( "siteName: %s\n", siteName );
|
||||||
|
trc( "siteCounter: %d\n", siteCounter );
|
||||||
|
trc( "keyPurpose: %d (%s)\n", keyPurpose, mpw_nameForPurpose( keyPurpose ) );
|
||||||
|
trc( "keyContext: %s\n", keyContext );
|
||||||
if (!masterKey || !siteName)
|
if (!masterKey || !siteName)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
switch (algorithmVersion) {
|
switch (algorithmVersion) {
|
||||||
case MPAlgorithmVersion0:
|
case MPAlgorithmVersion0:
|
||||||
return mpw_passwordForSite_v0( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
case MPAlgorithmVersion1:
|
case MPAlgorithmVersion1:
|
||||||
return mpw_passwordForSite_v1( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
return mpw_siteKey_v1( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
case MPAlgorithmVersion2:
|
case MPAlgorithmVersion2:
|
||||||
return mpw_passwordForSite_v2( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
case MPAlgorithmVersion3:
|
case MPAlgorithmVersion3:
|
||||||
return mpw_passwordForSite_v3( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
return mpw_siteKey_v3( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
default:
|
default:
|
||||||
ftl( "Unsupported version: %d", algorithmVersion );
|
err( "Unsupported version: %d\n", algorithmVersion );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mpw_siteResult(
|
||||||
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||||
|
const MPResultType resultType, const char *resultParam,
|
||||||
|
const MPAlgorithmVersion algorithmVersion) {
|
||||||
|
|
||||||
|
MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
|
if (!siteKey)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
trc( "-- mpw_siteResult (algorithm: %u)\n", algorithmVersion );
|
||||||
|
trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) );
|
||||||
|
trc( "resultParam: %s\n", resultParam );
|
||||||
|
|
||||||
|
char *sitePassword = NULL;
|
||||||
|
if (resultType & MPResultTypeClassTemplate) {
|
||||||
|
switch (algorithmVersion) {
|
||||||
|
case MPAlgorithmVersion0:
|
||||||
|
return mpw_sitePasswordFromTemplate_v0( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion1:
|
||||||
|
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion2:
|
||||||
|
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion3:
|
||||||
|
return mpw_sitePasswordFromTemplate_v3( masterKey, siteKey, resultType, resultParam );
|
||||||
|
default:
|
||||||
|
err( "Unsupported version: %d\n", algorithmVersion );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (resultType & MPResultTypeClassState) {
|
||||||
|
switch (algorithmVersion) {
|
||||||
|
case MPAlgorithmVersion0:
|
||||||
|
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion1:
|
||||||
|
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion2:
|
||||||
|
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion3:
|
||||||
|
return mpw_sitePasswordFromCrypt_v3( masterKey, siteKey, resultType, resultParam );
|
||||||
|
default:
|
||||||
|
err( "Unsupported version: %d\n", algorithmVersion );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (resultType & MPResultTypeClassDerive) {
|
||||||
|
switch (algorithmVersion) {
|
||||||
|
case MPAlgorithmVersion0:
|
||||||
|
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion1:
|
||||||
|
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion2:
|
||||||
|
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
|
||||||
|
case MPAlgorithmVersion3:
|
||||||
|
return mpw_sitePasswordFromDerive_v3( masterKey, siteKey, resultType, resultParam );
|
||||||
|
default:
|
||||||
|
err( "Unsupported version: %d\n", algorithmVersion );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err( "Unsupported password type: %d\n", resultType );
|
||||||
|
}
|
||||||
|
|
||||||
|
return sitePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mpw_siteState(
|
||||||
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||||
|
const MPResultType resultType, const char *state,
|
||||||
|
const MPAlgorithmVersion algorithmVersion) {
|
||||||
|
|
||||||
|
MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
|
if (!siteKey)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
trc( "-- mpw_siteState (algorithm: %u)\n", algorithmVersion );
|
||||||
|
trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) );
|
||||||
|
trc( "state: %s\n", state );
|
||||||
|
if (!masterKey || !state)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch (algorithmVersion) {
|
||||||
|
case MPAlgorithmVersion0:
|
||||||
|
return mpw_siteState_v0( masterKey, siteKey, resultType, state );
|
||||||
|
case MPAlgorithmVersion1:
|
||||||
|
return mpw_siteState_v1( masterKey, siteKey, resultType, state );
|
||||||
|
case MPAlgorithmVersion2:
|
||||||
|
return mpw_siteState_v2( masterKey, siteKey, resultType, state );
|
||||||
|
case MPAlgorithmVersion3:
|
||||||
|
return mpw_siteState_v3( masterKey, siteKey, resultType, state );
|
||||||
|
default:
|
||||||
|
err( "Unsupported version: %d\n", algorithmVersion );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
// NOTE: mpw is currently NOT thread-safe.
|
// NOTE: mpw is currently NOT thread-safe.
|
||||||
#include "mpw-types.h"
|
#include "mpw-types.h"
|
||||||
|
|
||||||
typedef enum(unsigned int, MPAlgorithmVersion) {
|
#ifndef _MPW_ALGORITHM_H
|
||||||
|
#define _MPW_ALGORITHM_H
|
||||||
|
|
||||||
|
typedef enum( unsigned int, MPAlgorithmVersion ) {
|
||||||
/** V0 did math with chars whose signedness was platform-dependent. */
|
/** V0 did math with chars whose signedness was platform-dependent. */
|
||||||
MPAlgorithmVersion0,
|
MPAlgorithmVersion0,
|
||||||
/** V1 miscounted the byte-length of multi-byte site names. */
|
/** V1 miscounted the byte-length of multi-byte site names. */
|
||||||
@@ -28,16 +31,37 @@ typedef enum(unsigned int, MPAlgorithmVersion) {
|
|||||||
MPAlgorithmVersion2,
|
MPAlgorithmVersion2,
|
||||||
/** V3 is the current version. */
|
/** V3 is the current version. */
|
||||||
MPAlgorithmVersion3,
|
MPAlgorithmVersion3,
|
||||||
|
|
||||||
|
MPAlgorithmVersionCurrent = MPAlgorithmVersion3,
|
||||||
|
MPAlgorithmVersionFirst = MPAlgorithmVersion0,
|
||||||
|
MPAlgorithmVersionLast = MPAlgorithmVersion3,
|
||||||
};
|
};
|
||||||
#define MPAlgorithmVersionCurrent MPAlgorithmVersion3
|
|
||||||
|
|
||||||
/** Derive the master key for a user based on their name and master password.
|
/** Derive the master key for a user based on their name and master password.
|
||||||
* @return A new MP_dkLen-byte allocated buffer or NULL if an allocation error occurred. */
|
* @return A new MPMasterKeySize-byte allocated buffer or NULL if an error occurred. */
|
||||||
const uint8_t *mpw_masterKeyForUser(
|
MPMasterKey mpw_masterKey(
|
||||||
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
|
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
|
||||||
|
|
||||||
/** Encode a password for the site from the given master key and site parameters.
|
/** Derive the site key for a user's site from the given master key and site parameters.
|
||||||
* @return A newly allocated string or NULL if an allocation error occurred. */
|
* @return A new MPSiteKeySize-byte allocated buffer or NULL if an error occurred. */
|
||||||
const char *mpw_passwordForSite(
|
MPSiteKey mpw_siteKey(
|
||||||
const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
const MPSiteVariant siteVariant, const char *siteContext, const MPAlgorithmVersion algorithmVersion);
|
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion);
|
||||||
|
|
||||||
|
/** Encode a password for the site from the given site key.
|
||||||
|
* @return A newly allocated string or NULL if an error occurred. */
|
||||||
|
const char *mpw_siteResult(
|
||||||
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||||
|
const MPResultType resultType, const char *resultParam,
|
||||||
|
const MPAlgorithmVersion algorithmVersion);
|
||||||
|
|
||||||
|
/** Perform symmetric encryption on a secret token's plainText.
|
||||||
|
* @return The newly allocated cipherText of the secret token encrypted by the masterKey. */
|
||||||
|
const char *mpw_siteState(
|
||||||
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext,
|
||||||
|
const MPResultType resultType, const char *state,
|
||||||
|
const MPAlgorithmVersion algorithmVersion);
|
||||||
|
|
||||||
|
#endif // _MPW_ALGORITHM_H
|
||||||
|
|||||||
@@ -22,17 +22,18 @@
|
|||||||
|
|
||||||
#include "mpw-types.h"
|
#include "mpw-types.h"
|
||||||
#include "mpw-util.h"
|
#include "mpw-util.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
#define MP_N 32768
|
#define MP_N 32768LU
|
||||||
#define MP_r 8
|
#define MP_r 8U
|
||||||
#define MP_p 2
|
#define MP_p 2U
|
||||||
#define MP_hash PearlHashSHA256
|
|
||||||
|
|
||||||
static const char *mpw_templateForType_v0(MPSiteType type, uint16_t seedByte) {
|
// Algorithm version helpers.
|
||||||
|
static const char *mpw_templateForType_v0(MPResultType type, uint16_t seedByte) {
|
||||||
|
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
const char **templates = mpw_templatesForType( type, &count );
|
const char **templates = mpw_templatesForType( type, &count );
|
||||||
char const *template = count? templates[seedByte % count]: NULL;
|
char const *template = templates && count? templates[seedByte % count]: NULL;
|
||||||
free( templates );
|
free( templates );
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
@@ -40,101 +41,209 @@ static const char *mpw_templateForType_v0(MPSiteType type, uint16_t seedByte) {
|
|||||||
static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedByte) {
|
static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedByte) {
|
||||||
|
|
||||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||||
|
if (!classCharacters)
|
||||||
|
return '\0';
|
||||||
|
|
||||||
return classCharacters[seedByte % strlen( classCharacters )];
|
return classCharacters[seedByte % strlen( classCharacters )];
|
||||||
}
|
}
|
||||||
|
|
||||||
static const uint8_t *mpw_masterKeyForUser_v0(const char *fullName, const char *masterPassword) {
|
// Algorithm version overrides.
|
||||||
|
static MPMasterKey mpw_masterKey_v0(
|
||||||
|
const char *fullName, const char *masterPassword) {
|
||||||
|
|
||||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
const char *keyScope = mpw_scopeForPurpose( MPKeyPurposeAuthentication );
|
||||||
trc( "algorithm: v%d\n", 0 );
|
trc( "keyScope: %s\n", keyScope );
|
||||||
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
|
|
||||||
trc( "masterPassword: %s\n", masterPassword );
|
|
||||||
trc( "key scope: %s\n", mpKeyScope );
|
|
||||||
|
|
||||||
// Calculate the master key salt.
|
// Calculate the master key salt.
|
||||||
// masterKeySalt = mpKeyScope . #fullName . fullName
|
trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s\n",
|
||||||
|
keyScope, mpw_hex_l( htonl( mpw_utf8_strlen( fullName ) ) ), fullName );
|
||||||
size_t masterKeySaltSize = 0;
|
size_t masterKeySaltSize = 0;
|
||||||
uint8_t *masterKeySalt = NULL;
|
uint8_t *masterKeySalt = NULL;
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
|
mpw_push_string( &masterKeySalt, &masterKeySaltSize, keyScope );
|
||||||
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( mpw_utf8_strlen( fullName ) ) );
|
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( mpw_utf8_strlen( fullName ) ) );
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
||||||
if (!masterKeySalt) {
|
if (!masterKeySalt) {
|
||||||
ftl( "Could not allocate master key salt: %d\n", errno );
|
err( "Could not allocate master key salt: %s\n", strerror( errno ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
trc( " => masterKeySalt.id: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
||||||
|
|
||||||
// Calculate the master key.
|
// Calculate the master key.
|
||||||
// masterKey = scrypt( masterPassword, masterKeySalt )
|
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )\n", MP_N, MP_r, MP_p );
|
||||||
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
MPMasterKey masterKey = mpw_kdf_scrypt( MPMasterKeySize, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
||||||
mpw_free( masterKeySalt, masterKeySaltSize );
|
mpw_free( masterKeySalt, masterKeySaltSize );
|
||||||
if (!masterKey) {
|
if (!masterKey) {
|
||||||
ftl( "Could not allocate master key: %d\n", errno );
|
err( "Could not allocate master key: %s\n", strerror( errno ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
|
trc( " => masterKey.id: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||||
|
|
||||||
return masterKey;
|
return masterKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *mpw_passwordForSite_v0(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
static MPSiteKey mpw_siteKey_v0(
|
||||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||||
|
|
||||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
|
||||||
trc( "algorithm: v%d\n", 0 );
|
trc( "keyScope: %s\n", keyScope );
|
||||||
trc( "siteName: %s\n", siteName );
|
|
||||||
trc( "siteCounter: %d\n", siteCounter );
|
// TODO: Implement MPCounterValueTOTP
|
||||||
trc( "siteVariant: %d\n", siteVariant );
|
|
||||||
trc( "siteType: %d\n", siteType );
|
|
||||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
|
||||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
|
||||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
|
||||||
mpw_hex_l( htonl( siteCounter ) ),
|
|
||||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
|
||||||
|
|
||||||
// Calculate the site seed.
|
// Calculate the site seed.
|
||||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s\n",
|
||||||
size_t sitePasswordInfoSize = 0;
|
keyScope, mpw_hex_l( htonl( mpw_utf8_strlen( siteName ) ) ), siteName, mpw_hex_l( htonl( siteCounter ) ),
|
||||||
uint8_t *sitePasswordInfo = NULL;
|
keyContext? mpw_hex_l( htonl( mpw_utf8_strlen( keyContext ) ) ): NULL, keyContext );
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
|
size_t siteSaltSize = 0;
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteName ) ) );
|
uint8_t *siteSalt = NULL;
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
mpw_push_int( &siteSalt, &siteSaltSize, htonl( mpw_utf8_strlen( siteName ) ) );
|
||||||
if (siteContext) {
|
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteContext ) ) );
|
mpw_push_int( &siteSalt, &siteSaltSize, htonl( siteCounter ) );
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
|
if (keyContext) {
|
||||||
|
mpw_push_int( &siteSalt, &siteSaltSize, htonl( mpw_utf8_strlen( keyContext ) ) );
|
||||||
|
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
|
||||||
}
|
}
|
||||||
if (!sitePasswordInfo) {
|
if (!siteSalt || !siteSaltSize) {
|
||||||
ftl( "Could not allocate site seed info: %d\n", errno );
|
err( "Could not allocate site salt: %s\n", strerror( errno ) );
|
||||||
|
mpw_free( siteSalt, siteSaltSize );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
|
trc( " => siteSalt.id: %s\n", mpw_id_buf( siteSalt, siteSaltSize ) );
|
||||||
|
|
||||||
const char *sitePasswordSeed = (const char *)mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
|
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )\n",
|
||||||
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
|
mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||||
if (!sitePasswordSeed) {
|
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
|
||||||
ftl( "Could not allocate site seed: %d\n", errno );
|
mpw_free( siteSalt, siteSaltSize );
|
||||||
|
if (!siteKey) {
|
||||||
|
err( "Could not derive site key: %s\n", strerror( errno ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
|
trc( " => siteKey.id: %s\n", mpw_id_buf( siteKey, MPSiteKeySize ) );
|
||||||
|
|
||||||
|
return siteKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mpw_sitePasswordFromTemplate_v0(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
|
|
||||||
// Determine the template.
|
// Determine the template.
|
||||||
const char *template = mpw_templateForType_v0( siteType, htons( sitePasswordSeed[0] ) );
|
const char *_siteKey = (const char *)siteKey;
|
||||||
trc( "type %d, template: %s\n", siteType, template );
|
const char *template = mpw_templateForType_v0( resultType, htons( _siteKey[0] ) );
|
||||||
if (strlen( template ) > 32) {
|
trc( "template: %u => %s\n", htons( _siteKey[0] ), template );
|
||||||
ftl( "Template too long for password seed: %lu", strlen( template ) );
|
if (!template)
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
return NULL;
|
||||||
|
if (strlen( template ) > MPSiteKeySize) {
|
||||||
|
err( "Template too long for password seed: %zu\n", strlen( template ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the password from the seed using the template.
|
// Encode the password from the seed using the template.
|
||||||
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
char *sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
||||||
for (size_t c = 0; c < strlen( template ); ++c) {
|
for (size_t c = 0; c < strlen( template ); ++c) {
|
||||||
sitePassword[c] = mpw_characterFromClass_v0( template[c], htons( sitePasswordSeed[c + 1] ) );
|
sitePassword[c] = mpw_characterFromClass_v0( template[c], htons( _siteKey[c + 1] ) );
|
||||||
trc( "class %c, index %u (0x%02X) -> character: %c\n",
|
trc( " - class: %c, index: %5u (0x%02hX) => character: %c\n",
|
||||||
template[c], htons( sitePasswordSeed[c + 1] ), htons( sitePasswordSeed[c + 1] ), sitePassword[c] );
|
template[c], htons( _siteKey[c + 1] ), htons( _siteKey[c + 1] ), sitePassword[c] );
|
||||||
}
|
}
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
trc( " => password: %s\n", sitePassword );
|
||||||
|
|
||||||
return sitePassword;
|
return sitePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *mpw_sitePasswordFromCrypt_v0(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
|
||||||
|
|
||||||
|
if (!cipherText) {
|
||||||
|
err( "Missing encrypted state.\n" );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64-decode
|
||||||
|
uint8_t *cipherBuf = calloc( 1, mpw_base64_decode_max( cipherText ) );
|
||||||
|
size_t bufSize = (size_t)mpw_base64_decode( cipherBuf, cipherText );
|
||||||
|
if ((int)bufSize < 0) {
|
||||||
|
err( "Base64 decoding error." );
|
||||||
|
mpw_free( cipherBuf, mpw_base64_decode_max( cipherText ) );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
trc( "b64 decoded: %zu bytes = %s\n", bufSize, mpw_hex( cipherBuf, bufSize ) );
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
const uint8_t *plainBytes = mpw_aes_decrypt( masterKey, MPMasterKeySize, cipherBuf, bufSize );
|
||||||
|
const char *plainText = strndup( (char *)plainBytes, bufSize );
|
||||||
|
mpw_free( plainBytes, bufSize );
|
||||||
|
if (!plainText)
|
||||||
|
err( "AES decryption error: %s\n", strerror( errno ) );
|
||||||
|
trc( "decrypted -> plainText: %s = %s\n", plainText, mpw_hex( plainText, sizeof( plainText ) ) );
|
||||||
|
mpw_free( cipherBuf, bufSize );
|
||||||
|
|
||||||
|
return plainText;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mpw_sitePasswordFromDerive_v0(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
|
|
||||||
|
switch (resultType) {
|
||||||
|
case MPResultTypeDeriveKey: {
|
||||||
|
if (!resultParam) {
|
||||||
|
err( "Missing key size parameter.\n" );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
int resultParamInt = atoi( resultParam );
|
||||||
|
if (resultParamInt < 128 || resultParamInt > 512 || resultParamInt % 8 != 0) {
|
||||||
|
err( "Parameter is not a valid key size (should be 128 - 512): %s\n", resultParam );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
uint16_t keySize = (uint16_t)(resultParamInt / 8);
|
||||||
|
trc( "keySize: %u\n", keySize );
|
||||||
|
|
||||||
|
// Derive key
|
||||||
|
const uint8_t *resultKey = mpw_kdf_blake2b( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL );
|
||||||
|
if (!resultKey) {
|
||||||
|
err( "Could not derive result key: %s\n", strerror( errno ) );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64-encode
|
||||||
|
size_t b64Max = mpw_base64_encode_max( keySize );
|
||||||
|
char *sitePassword = calloc( 1, b64Max + 1 );
|
||||||
|
if (mpw_base64_encode( sitePassword, resultKey, keySize ) < 0) {
|
||||||
|
err( "Base64 encoding error." );
|
||||||
|
mpw_free_string( sitePassword );
|
||||||
|
sitePassword = NULL;
|
||||||
|
}
|
||||||
|
trc( "b64 encoded -> key.id: %s\n", mpw_id_buf( sitePassword, strlen( sitePassword ) ) );
|
||||||
|
|
||||||
|
return sitePassword;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err( "Unsupported derived password type: %d\n", resultType );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mpw_siteState_v0(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *plainText) {
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
size_t bufSize = strlen( plainText );
|
||||||
|
const uint8_t *cipherBuf = mpw_aes_encrypt( masterKey, MPMasterKeySize, (const uint8_t *)plainText, bufSize );
|
||||||
|
if (!cipherBuf) {
|
||||||
|
err( "AES encryption error: %s\n", strerror( errno ) );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
trc( "cipherBuf: %zu bytes = %s\n", bufSize, mpw_hex( cipherBuf, bufSize ) );
|
||||||
|
|
||||||
|
// Base64-encode
|
||||||
|
size_t b64Max = mpw_base64_encode_max( bufSize );
|
||||||
|
char *cipherText = calloc( 1, b64Max + 1 );
|
||||||
|
if (mpw_base64_encode( cipherText, cipherBuf, bufSize ) < 0) {
|
||||||
|
err( "Base64 encoding error." );
|
||||||
|
mpw_free_string( cipherText );
|
||||||
|
cipherText = NULL;
|
||||||
|
}
|
||||||
|
trc( "b64 encoded -> cipherText: %s = %s\n", cipherText, mpw_hex( cipherText, sizeof( cipherText ) ) );
|
||||||
|
mpw_free( cipherBuf, bufSize );
|
||||||
|
|
||||||
|
return cipherText;
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,103 +23,76 @@
|
|||||||
#include "mpw-types.h"
|
#include "mpw-types.h"
|
||||||
#include "mpw-util.h"
|
#include "mpw-util.h"
|
||||||
|
|
||||||
#define MP_N 32768
|
#define MP_N 32768LU
|
||||||
#define MP_r 8
|
#define MP_r 8U
|
||||||
#define MP_p 2
|
#define MP_p 2U
|
||||||
#define MP_hash PearlHashSHA256
|
|
||||||
|
|
||||||
static const uint8_t *mpw_masterKeyForUser_v1(const char *fullName, const char *masterPassword) {
|
// Inherited functions.
|
||||||
|
MPMasterKey mpw_masterKey_v0(
|
||||||
|
const char *fullName, const char *masterPassword);
|
||||||
|
MPSiteKey mpw_siteKey_v0(
|
||||||
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext);
|
||||||
|
const char *mpw_sitePasswordFromCrypt_v0(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText);
|
||||||
|
const char *mpw_sitePasswordFromDerive_v0(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
|
||||||
|
const char *mpw_siteState_v0(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state);
|
||||||
|
|
||||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
// Algorithm version overrides.
|
||||||
trc( "algorithm: v%d\n", 1 );
|
static MPMasterKey mpw_masterKey_v1(
|
||||||
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
|
const char *fullName, const char *masterPassword) {
|
||||||
trc( "masterPassword: %s\n", masterPassword );
|
|
||||||
trc( "key scope: %s\n", mpKeyScope );
|
|
||||||
|
|
||||||
// Calculate the master key salt.
|
return mpw_masterKey_v0( fullName, masterPassword );
|
||||||
// masterKeySalt = mpKeyScope . #fullName . fullName
|
|
||||||
size_t masterKeySaltSize = 0;
|
|
||||||
uint8_t *masterKeySalt = NULL;
|
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
|
|
||||||
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( mpw_utf8_strlen( fullName ) ) );
|
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
|
||||||
if (!masterKeySalt) {
|
|
||||||
ftl( "Could not allocate master key salt: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
|
||||||
|
|
||||||
// Calculate the master key.
|
|
||||||
// masterKey = scrypt( masterPassword, masterKeySalt )
|
|
||||||
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
|
||||||
mpw_free( masterKeySalt, masterKeySaltSize );
|
|
||||||
if (!masterKey) {
|
|
||||||
ftl( "Could not allocate master key: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
|
|
||||||
|
|
||||||
return masterKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *mpw_passwordForSite_v1(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
static MPSiteKey mpw_siteKey_v1(
|
||||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||||
|
|
||||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
trc( "algorithm: v%d\n", 1 );
|
}
|
||||||
trc( "siteName: %s\n", siteName );
|
|
||||||
trc( "siteCounter: %d\n", siteCounter );
|
|
||||||
trc( "siteVariant: %d\n", siteVariant );
|
|
||||||
trc( "siteType: %d\n", siteType );
|
|
||||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
|
||||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
|
||||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
|
||||||
mpw_hex_l( htonl( siteCounter ) ),
|
|
||||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
|
||||||
|
|
||||||
// Calculate the site seed.
|
static const char *mpw_sitePasswordFromTemplate_v1(
|
||||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
size_t sitePasswordInfoSize = 0;
|
|
||||||
uint8_t *sitePasswordInfo = NULL;
|
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
|
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteName ) ) );
|
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
|
||||||
if (siteContext) {
|
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteContext ) ) );
|
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
|
|
||||||
}
|
|
||||||
if (!sitePasswordInfo) {
|
|
||||||
ftl( "Could not allocate site seed info: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
|
|
||||||
|
|
||||||
const uint8_t *sitePasswordSeed = mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
|
|
||||||
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
|
|
||||||
if (!sitePasswordSeed) {
|
|
||||||
ftl( "Could not allocate site seed: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
|
|
||||||
|
|
||||||
// Determine the template.
|
// Determine the template.
|
||||||
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
|
const char *template = mpw_templateForType( resultType, siteKey[0] );
|
||||||
trc( "type %d, template: %s\n", siteType, template );
|
trc( "template: %u => %s\n", siteKey[0], template );
|
||||||
if (strlen( template ) > 32) {
|
if (!template)
|
||||||
ftl( "Template too long for password seed: %lu", strlen( template ) );
|
return NULL;
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
if (strlen( template ) > MPSiteKeySize) {
|
||||||
|
err( "Template too long for password seed: %zu\n", strlen( template ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the password from the seed using the template.
|
// Encode the password from the seed using the template.
|
||||||
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
||||||
for (size_t c = 0; c < strlen( template ); ++c) {
|
for (size_t c = 0; c < strlen( template ); ++c) {
|
||||||
sitePassword[c] = mpw_characterFromClass( template[c], sitePasswordSeed[c + 1] );
|
sitePassword[c] = mpw_characterFromClass( template[c], siteKey[c + 1] );
|
||||||
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
|
trc( " - class: %c, index: %3u (0x%02hhX) => character: %c\n",
|
||||||
sitePassword[c] );
|
template[c], siteKey[c + 1], siteKey[c + 1], sitePassword[c] );
|
||||||
}
|
}
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
trc( " => password: %s\n", sitePassword );
|
||||||
|
|
||||||
return sitePassword;
|
return sitePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *mpw_sitePasswordFromCrypt_v1(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
|
||||||
|
|
||||||
|
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, cipherText );
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mpw_sitePasswordFromDerive_v1(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
|
|
||||||
|
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mpw_siteState_v1(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state) {
|
||||||
|
|
||||||
|
return mpw_siteState_v0( masterKey, siteKey, resultType, state );
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,103 +23,92 @@
|
|||||||
#include "mpw-types.h"
|
#include "mpw-types.h"
|
||||||
#include "mpw-util.h"
|
#include "mpw-util.h"
|
||||||
|
|
||||||
#define MP_N 32768
|
#define MP_N 32768LU
|
||||||
#define MP_r 8
|
#define MP_r 8U
|
||||||
#define MP_p 2
|
#define MP_p 2U
|
||||||
#define MP_hash PearlHashSHA256
|
|
||||||
|
|
||||||
static const uint8_t *mpw_masterKeyForUser_v2(const char *fullName, const char *masterPassword) {
|
// Inherited functions.
|
||||||
|
MPMasterKey mpw_masterKey_v1(
|
||||||
|
const char *fullName, const char *masterPassword);
|
||||||
|
const char *mpw_sitePasswordFromTemplate_v1(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
|
||||||
|
const char *mpw_sitePasswordFromCrypt_v1(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText);
|
||||||
|
const char *mpw_sitePasswordFromDerive_v1(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
|
||||||
|
const char *mpw_siteState_v1(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state);
|
||||||
|
|
||||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
// Algorithm version overrides.
|
||||||
trc( "algorithm: v%d\n", 2 );
|
static MPMasterKey mpw_masterKey_v2(
|
||||||
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
|
const char *fullName, const char *masterPassword) {
|
||||||
trc( "masterPassword: %s\n", masterPassword );
|
|
||||||
trc( "key scope: %s\n", mpKeyScope );
|
|
||||||
|
|
||||||
// Calculate the master key salt.
|
return mpw_masterKey_v1( fullName, masterPassword );
|
||||||
// masterKeySalt = mpKeyScope . #fullName . fullName
|
|
||||||
size_t masterKeySaltSize = 0;
|
|
||||||
uint8_t *masterKeySalt = NULL;
|
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
|
|
||||||
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( mpw_utf8_strlen( fullName ) ) );
|
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
|
||||||
if (!masterKeySalt) {
|
|
||||||
ftl( "Could not allocate master key salt: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
|
||||||
|
|
||||||
// Calculate the master key.
|
|
||||||
// masterKey = scrypt( masterPassword, masterKeySalt )
|
|
||||||
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
|
||||||
mpw_free( masterKeySalt, masterKeySaltSize );
|
|
||||||
if (!masterKey) {
|
|
||||||
ftl( "Could not allocate master key: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
|
|
||||||
|
|
||||||
return masterKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *mpw_passwordForSite_v2(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
static MPSiteKey mpw_siteKey_v2(
|
||||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||||
|
|
||||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
|
||||||
trc( "algorithm: v%d\n", 2 );
|
trc( "keyScope: %s\n", keyScope );
|
||||||
trc( "siteName: %s\n", siteName );
|
|
||||||
trc( "siteCounter: %d\n", siteCounter );
|
// TODO: Implement MPCounterValueTOTP
|
||||||
trc( "siteVariant: %d\n", siteVariant );
|
|
||||||
trc( "siteType: %d\n", siteType );
|
|
||||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
|
||||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
|
||||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
|
||||||
mpw_hex_l( htonl( siteCounter ) ),
|
|
||||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
|
||||||
|
|
||||||
// Calculate the site seed.
|
// Calculate the site seed.
|
||||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s\n",
|
||||||
size_t sitePasswordInfoSize = 0;
|
keyScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName, mpw_hex_l( htonl( siteCounter ) ),
|
||||||
uint8_t *sitePasswordInfo = NULL;
|
keyContext? mpw_hex_l( htonl( strlen( keyContext ) ) ): NULL, keyContext );
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
|
size_t siteSaltSize = 0;
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteName ) ) );
|
uint8_t *siteSalt = NULL;
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
mpw_push_int( &siteSalt, &siteSaltSize, htonl( strlen( siteName ) ) );
|
||||||
if (siteContext) {
|
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteContext ) ) );
|
mpw_push_int( &siteSalt, &siteSaltSize, htonl( siteCounter ) );
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
|
if (keyContext) {
|
||||||
|
mpw_push_int( &siteSalt, &siteSaltSize, htonl( strlen( keyContext ) ) );
|
||||||
|
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
|
||||||
}
|
}
|
||||||
if (!sitePasswordInfo) {
|
if (!siteSalt || !siteSaltSize) {
|
||||||
ftl( "Could not allocate site seed info: %d\n", errno );
|
err( "Could not allocate site salt: %s\n", strerror( errno ) );
|
||||||
|
mpw_free( siteSalt, siteSaltSize );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
|
trc( " => siteSalt.id: %s\n", mpw_id_buf( siteSalt, siteSaltSize ) );
|
||||||
|
|
||||||
const uint8_t *sitePasswordSeed = mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
|
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )\n",
|
||||||
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
|
mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||||
if (!sitePasswordSeed) {
|
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
|
||||||
ftl( "Could not allocate site seed: %d\n", errno );
|
mpw_free( siteSalt, siteSaltSize );
|
||||||
|
if (!siteKey) {
|
||||||
|
err( "Could not allocate site key: %s\n", strerror( errno ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
|
trc( " => siteKey.id: %s\n", mpw_id_buf( siteKey, MPSiteKeySize ) );
|
||||||
|
|
||||||
// Determine the template.
|
return siteKey;
|
||||||
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
|
}
|
||||||
trc( "type %d, template: %s\n", siteType, template );
|
|
||||||
if (strlen( template ) > 32) {
|
static const char *mpw_sitePasswordFromTemplate_v2(
|
||||||
ftl( "Template too long for password seed: %lu", strlen( template ) );
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
|
||||||
return NULL;
|
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the password from the seed using the template.
|
static const char *mpw_sitePasswordFromCrypt_v2(
|
||||||
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
|
||||||
for (size_t c = 0; c < strlen( template ); ++c) {
|
|
||||||
sitePassword[c] = mpw_characterFromClass( template[c], sitePasswordSeed[c + 1] );
|
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, cipherText );
|
||||||
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
|
}
|
||||||
sitePassword[c] );
|
|
||||||
}
|
static const char *mpw_sitePasswordFromDerive_v2(
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
|
|
||||||
return sitePassword;
|
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mpw_siteState_v2(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state) {
|
||||||
|
|
||||||
|
return mpw_siteState_v1( masterKey, siteKey, resultType, state );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,103 +23,84 @@
|
|||||||
#include "mpw-types.h"
|
#include "mpw-types.h"
|
||||||
#include "mpw-util.h"
|
#include "mpw-util.h"
|
||||||
|
|
||||||
#define MP_N 32768
|
#define MP_N 32768LU
|
||||||
#define MP_r 8
|
#define MP_r 8U
|
||||||
#define MP_p 2
|
#define MP_p 2U
|
||||||
#define MP_hash PearlHashSHA256
|
|
||||||
|
|
||||||
static const uint8_t *mpw_masterKeyForUser_v3(const char *fullName, const char *masterPassword) {
|
// Inherited functions.
|
||||||
|
MPSiteKey mpw_siteKey_v2(
|
||||||
|
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext);
|
||||||
|
const char *mpw_sitePasswordFromTemplate_v2(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
|
||||||
|
const char *mpw_sitePasswordFromCrypt_v2(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText);
|
||||||
|
const char *mpw_sitePasswordFromDerive_v2(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
|
||||||
|
const char *mpw_siteState_v2(
|
||||||
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state);
|
||||||
|
|
||||||
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
|
// Algorithm version overrides.
|
||||||
trc( "algorithm: v%d\n", 3 );
|
static MPMasterKey mpw_masterKey_v3(
|
||||||
trc( "fullName: %s (%zu)\n", fullName, strlen( fullName ) );
|
const char *fullName, const char *masterPassword) {
|
||||||
trc( "masterPassword: %s\n", masterPassword );
|
|
||||||
trc( "key scope: %s\n", mpKeyScope );
|
const char *keyScope = mpw_scopeForPurpose( MPKeyPurposeAuthentication );
|
||||||
|
trc( "keyScope: %s\n", keyScope );
|
||||||
|
|
||||||
// Calculate the master key salt.
|
// Calculate the master key salt.
|
||||||
// masterKeySalt = mpKeyScope . #fullName . fullName
|
trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s\n",
|
||||||
|
keyScope, mpw_hex_l( htonl( strlen( fullName ) ) ), fullName );
|
||||||
size_t masterKeySaltSize = 0;
|
size_t masterKeySaltSize = 0;
|
||||||
uint8_t *masterKeySalt = NULL;
|
uint8_t *masterKeySalt = NULL;
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
|
mpw_push_string( &masterKeySalt, &masterKeySaltSize, keyScope );
|
||||||
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( strlen( fullName ) ) );
|
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( strlen( fullName ) ) );
|
||||||
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
||||||
if (!masterKeySalt) {
|
if (!masterKeySalt) {
|
||||||
ftl( "Could not allocate master key salt: %d\n", errno );
|
err( "Could not allocate master key salt: %s\n", strerror( errno ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
trc( " => masterKeySalt.id: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
||||||
|
|
||||||
// Calculate the master key.
|
// Calculate the master key.
|
||||||
// masterKey = scrypt( masterPassword, masterKeySalt )
|
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )\n", MP_N, MP_r, MP_p );
|
||||||
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
MPMasterKey masterKey = mpw_kdf_scrypt( MPMasterKeySize, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
|
||||||
mpw_free( masterKeySalt, masterKeySaltSize );
|
mpw_free( masterKeySalt, masterKeySaltSize );
|
||||||
if (!masterKey) {
|
if (!masterKey) {
|
||||||
ftl( "Could not allocate master key: %d\n", errno );
|
err( "Could not allocate master key: %s\n", strerror( errno ) );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
|
trc( " => masterKey.id: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||||
|
|
||||||
return masterKey;
|
return masterKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *mpw_passwordForSite_v3(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
static MPSiteKey mpw_siteKey_v3(
|
||||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
const MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||||
|
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||||
|
|
||||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
trc( "algorithm: v%d\n", 3 );
|
}
|
||||||
trc( "siteName: %s\n", siteName );
|
|
||||||
trc( "siteCounter: %d\n", siteCounter );
|
static const char *mpw_sitePasswordFromTemplate_v3(
|
||||||
trc( "siteVariant: %d\n", siteVariant );
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
trc( "siteType: %d\n", siteType );
|
|
||||||
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
|
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
|
||||||
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
|
}
|
||||||
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
|
|
||||||
mpw_hex_l( htonl( siteCounter ) ),
|
static const char *mpw_sitePasswordFromCrypt_v3(
|
||||||
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
|
||||||
|
|
||||||
// Calculate the site seed.
|
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, cipherText );
|
||||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
}
|
||||||
size_t sitePasswordInfoSize = 0;
|
|
||||||
uint8_t *sitePasswordInfo = NULL;
|
static const char *mpw_sitePasswordFromDerive_v3(
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteName ) ) );
|
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
}
|
||||||
if (siteContext) {
|
|
||||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteContext ) ) );
|
static const char *mpw_siteState_v3(
|
||||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
|
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state) {
|
||||||
}
|
|
||||||
if (!sitePasswordInfo) {
|
return mpw_siteState_v2( masterKey, siteKey, resultType, state );
|
||||||
ftl( "Could not allocate site seed info: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
|
|
||||||
|
|
||||||
const uint8_t *sitePasswordSeed = mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
|
|
||||||
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
|
|
||||||
if (!sitePasswordSeed) {
|
|
||||||
ftl( "Could not allocate site seed: %d\n", errno );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
|
|
||||||
|
|
||||||
// Determine the template.
|
|
||||||
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
|
|
||||||
trc( "type %d, template: %s\n", siteType, template );
|
|
||||||
if (strlen( template ) > 32) {
|
|
||||||
ftl( "Template too long for password seed: %lu", strlen( template ) );
|
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode the password from the seed using the template.
|
|
||||||
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
|
|
||||||
for (size_t c = 0; c < strlen( template ); ++c) {
|
|
||||||
sitePassword[c] = mpw_characterFromClass( template[c], sitePasswordSeed[c + 1] );
|
|
||||||
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
|
|
||||||
sitePassword[c] );
|
|
||||||
}
|
|
||||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
|
||||||
|
|
||||||
return sitePassword;
|
|
||||||
}
|
}
|
||||||
|
|||||||
114
core/c/mpw-marshall-util.c
Normal file
114
core/c/mpw-marshall-util.c
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// 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 "mpw-marshall-util.h"
|
||||||
|
#include "mpw-util.h"
|
||||||
|
|
||||||
|
char *mpw_get_token(char **in, char *eol, char *delim) {
|
||||||
|
|
||||||
|
// Skip leading spaces.
|
||||||
|
for (; **in == ' '; ++*in);
|
||||||
|
|
||||||
|
// Find characters up to the first delim.
|
||||||
|
size_t len = strcspn( *in, delim );
|
||||||
|
char *token = len && len <= (size_t)(eol - *in)? strndup( *in, len ): NULL;
|
||||||
|
|
||||||
|
// Advance past the delimitor.
|
||||||
|
*in = min( eol, *in + len + 1 );
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t mpw_mktime(
|
||||||
|
const char *time) {
|
||||||
|
|
||||||
|
struct tm tm = { .tm_isdst = -1, .tm_gmtoff = 0 };
|
||||||
|
if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ",
|
||||||
|
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
|
||||||
|
&tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) {
|
||||||
|
tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900
|
||||||
|
tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1
|
||||||
|
return mktime( &tm );
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *mpw_get_json_section(
|
||||||
|
json_object *obj, const char *section) {
|
||||||
|
|
||||||
|
json_object *json_value = obj;
|
||||||
|
char *sectionTokenizer = strdup( section ), *sectionToken = sectionTokenizer;
|
||||||
|
for (sectionToken = strtok( sectionToken, "." ); sectionToken; sectionToken = strtok( NULL, "." ))
|
||||||
|
if (!json_object_object_get_ex( json_value, sectionToken, &json_value ) || !json_value) {
|
||||||
|
trc( "While resolving: %s: Missing value for: %s\n", section, sectionToken );
|
||||||
|
json_value = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
free( sectionTokenizer );
|
||||||
|
|
||||||
|
return json_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mpw_get_json_string(
|
||||||
|
json_object *obj, const char *section, const char *defaultValue) {
|
||||||
|
|
||||||
|
json_object *json_value = mpw_get_json_section( obj, section );
|
||||||
|
if (!json_value)
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
return json_object_get_string( json_value );
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t mpw_get_json_int(
|
||||||
|
json_object *obj, const char *section, int32_t defaultValue) {
|
||||||
|
|
||||||
|
json_object *json_value = mpw_get_json_section( obj, section );
|
||||||
|
if (!json_value)
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
return json_object_get_int( json_value );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mpw_get_json_boolean(
|
||||||
|
json_object *obj, const char *section, bool defaultValue) {
|
||||||
|
|
||||||
|
json_object *json_value = mpw_get_json_section( obj, section );
|
||||||
|
if (!json_value)
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
return json_object_get_boolean( json_value ) == TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mpw_update_masterKey(MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
|
||||||
|
const char *fullName, const char *masterPassword) {
|
||||||
|
|
||||||
|
if (*masterKeyAlgorithm != targetKeyAlgorithm) {
|
||||||
|
mpw_free( *masterKey, MPMasterKeySize );
|
||||||
|
*masterKeyAlgorithm = targetKeyAlgorithm;
|
||||||
|
*masterKey = mpw_masterKey(
|
||||||
|
fullName, masterPassword, *masterKeyAlgorithm );
|
||||||
|
if (!*masterKey) {
|
||||||
|
err( "Couldn't derive master key for user %s, algorithm %d.\n", fullName, *masterKeyAlgorithm );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
69
core/c/mpw-marshall-util.h
Normal file
69
core/c/mpw-marshall-util.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#ifndef _MPW_MARSHALL_UTIL_H
|
||||||
|
#define _MPW_MARSHALL_UTIL_H
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <json-c/json.h>
|
||||||
|
|
||||||
|
#include "mpw-algorithm.h"
|
||||||
|
|
||||||
|
/// Type parsing.
|
||||||
|
|
||||||
|
/** Get a token from a string by searching until the first character in delim, no farther than eol.
|
||||||
|
* The input string reference is advanced beyond the token delimitor if one is found.
|
||||||
|
* @return A new string containing the token or NULL if the delim wasn't found before eol. */
|
||||||
|
char *mpw_get_token(
|
||||||
|
char **in, char *eol, char *delim);
|
||||||
|
/** Convert an RFC 3339 time string into epoch time. */
|
||||||
|
time_t mpw_mktime(
|
||||||
|
const char *time);
|
||||||
|
|
||||||
|
/// JSON parsing.
|
||||||
|
|
||||||
|
/** Search for a JSON child object in a JSON object tree.
|
||||||
|
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||||
|
* @return A new JSON object or NULL if one of the section's object keys was not found in the source object's tree. */
|
||||||
|
json_object *mpw_get_json_section(
|
||||||
|
json_object *obj, const char *section);
|
||||||
|
/** Search for a string in a JSON object tree.
|
||||||
|
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||||
|
* @return A new string or defaultValue if one of the section's object keys was not found in the source object's tree. */
|
||||||
|
const char *mpw_get_json_string(
|
||||||
|
json_object *obj, const char *section, const char *defaultValue);
|
||||||
|
/** Search for an integer in a JSON object tree.
|
||||||
|
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||||
|
* @return The integer value or defaultValue if one of the section's object keys was not found in the source object's tree. */
|
||||||
|
int32_t mpw_get_json_int(
|
||||||
|
json_object *obj, const char *section, int32_t defaultValue);
|
||||||
|
/** Search for a boolean in a JSON object tree.
|
||||||
|
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
|
||||||
|
* @return The boolean value or defaultValue if one of the section's object keys was not found in the source object's tree. */
|
||||||
|
bool mpw_get_json_boolean(
|
||||||
|
json_object *obj, const char *section, bool defaultValue);
|
||||||
|
|
||||||
|
/// mpw.
|
||||||
|
|
||||||
|
/** Calculate a master key if the target master key algorithm is different from the given master key algorithm.
|
||||||
|
* @return false if an error occurred during the derivation of the master key. */
|
||||||
|
bool mpw_update_masterKey(
|
||||||
|
MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
|
||||||
|
const char *fullName, const char *masterPassword);
|
||||||
|
|
||||||
|
#endif // _MPW_MARSHALL_UTIL_H
|
||||||
747
core/c/mpw-marshall.c
Normal file
747
core/c/mpw-marshall.c
Normal file
@@ -0,0 +1,747 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// 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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "mpw-marshall.h"
|
||||||
|
#include "mpw-util.h"
|
||||||
|
#include "mpw-marshall-util.h"
|
||||||
|
|
||||||
|
MPMarshalledUser *mpw_marshall_user(
|
||||||
|
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
|
||||||
|
|
||||||
|
MPMarshalledUser *user;
|
||||||
|
if (!fullName || !masterPassword || !(user = malloc( sizeof( MPMarshalledUser ) )))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
*user = (MPMarshalledUser){
|
||||||
|
.fullName = strdup( fullName ),
|
||||||
|
.masterPassword = strdup( masterPassword ),
|
||||||
|
.algorithm = algorithmVersion,
|
||||||
|
.redacted = true,
|
||||||
|
|
||||||
|
.avatar = 0,
|
||||||
|
.defaultType = MPResultTypeDefault,
|
||||||
|
.lastUsed = 0,
|
||||||
|
|
||||||
|
.sites_count = 0,
|
||||||
|
.sites = NULL,
|
||||||
|
};
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
MPMarshalledSite *mpw_marshall_site(
|
||||||
|
MPMarshalledUser *user, const char *siteName, const MPResultType resultType,
|
||||||
|
const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion) {
|
||||||
|
|
||||||
|
if (!siteName || !mpw_realloc( &user->sites, NULL, sizeof( MPMarshalledSite ) * ++user->sites_count ))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
MPMarshalledSite *site = &user->sites[user->sites_count - 1];
|
||||||
|
*site = (MPMarshalledSite){
|
||||||
|
.name = strdup( siteName ),
|
||||||
|
.content = NULL,
|
||||||
|
.type = resultType,
|
||||||
|
.counter = siteCounter,
|
||||||
|
.algorithm = algorithmVersion,
|
||||||
|
|
||||||
|
.loginName = NULL,
|
||||||
|
.loginGenerated = false,
|
||||||
|
|
||||||
|
.url = NULL,
|
||||||
|
.uses = 0,
|
||||||
|
.lastUsed = 0,
|
||||||
|
|
||||||
|
.questions_count = 0,
|
||||||
|
.questions = NULL,
|
||||||
|
};
|
||||||
|
return site;
|
||||||
|
};
|
||||||
|
|
||||||
|
MPMarshalledQuestion *mpw_marshal_question(
|
||||||
|
MPMarshalledSite *site, const char *keyword) {
|
||||||
|
|
||||||
|
if (!keyword || !mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count ))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
MPMarshalledQuestion *question = &site->questions[site->questions_count - 1];
|
||||||
|
*question = (MPMarshalledQuestion){
|
||||||
|
.keyword = strdup( keyword ),
|
||||||
|
};
|
||||||
|
return question;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mpw_marshal_free(
|
||||||
|
MPMarshalledUser *user) {
|
||||||
|
|
||||||
|
if (!user)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
for (size_t s = 0; s < user->sites_count; ++s) {
|
||||||
|
MPMarshalledSite *site = &user->sites[s];
|
||||||
|
success &= mpw_free_string( site->name );
|
||||||
|
for (size_t q = 0; q < site->questions_count; ++q) {
|
||||||
|
MPMarshalledQuestion *question = &site->questions[q];
|
||||||
|
success &= mpw_free_string( question->keyword );
|
||||||
|
}
|
||||||
|
success &= mpw_free( site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count );
|
||||||
|
}
|
||||||
|
success &= mpw_free( user->sites, sizeof( MPMarshalledSite ) * user->sites_count );
|
||||||
|
success &= mpw_free_string( user->fullName );
|
||||||
|
success &= mpw_free_string( user->masterPassword );
|
||||||
|
success &= mpw_free( user, sizeof( MPMarshalledUser ) );
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mpw_marshall_write_flat(
|
||||||
|
char **out, const MPMarshalledUser *user, MPMarshallError *error) {
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
|
||||||
|
if (!user->fullName || !strlen( user->fullName )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!user->masterPassword || !strlen( user->masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Missing master password." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MPMasterKey masterKey = NULL;
|
||||||
|
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mpw_string_pushf( out, "# Master Password site export\n" );
|
||||||
|
if (user->redacted)
|
||||||
|
mpw_string_pushf( out, "# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n" );
|
||||||
|
else
|
||||||
|
mpw_string_pushf( out, "# Export of site names and passwords in clear-text.\n" );
|
||||||
|
mpw_string_pushf( out, "# \n" );
|
||||||
|
mpw_string_pushf( out, "##\n" );
|
||||||
|
mpw_string_pushf( out, "# Format: %d\n", 1 );
|
||||||
|
|
||||||
|
char dateString[21];
|
||||||
|
time_t now = time( NULL );
|
||||||
|
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
|
||||||
|
mpw_string_pushf( out, "# Date: %s\n", dateString );
|
||||||
|
mpw_string_pushf( out, "# User Name: %s\n", user->fullName );
|
||||||
|
mpw_string_pushf( out, "# Full Name: %s\n", user->fullName );
|
||||||
|
mpw_string_pushf( out, "# Avatar: %u\n", user->avatar );
|
||||||
|
mpw_string_pushf( out, "# Key ID: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||||
|
mpw_string_pushf( out, "# Algorithm: %d\n", user->algorithm );
|
||||||
|
mpw_string_pushf( out, "# Default Type: %d\n", user->defaultType );
|
||||||
|
mpw_string_pushf( out, "# Passwords: %s\n", user->redacted? "PROTECTED": "VISIBLE" );
|
||||||
|
mpw_string_pushf( out, "##\n" );
|
||||||
|
mpw_string_pushf( out, "#\n" );
|
||||||
|
mpw_string_pushf( out, "# Last Times Password Login\t Site\tSite\n" );
|
||||||
|
mpw_string_pushf( out, "# used used type name\t name\tpassword\n" );
|
||||||
|
|
||||||
|
// Sites.
|
||||||
|
for (size_t s = 0; s < user->sites_count; ++s) {
|
||||||
|
MPMarshalledSite *site = &user->sites[s];
|
||||||
|
if (!site->name || !strlen( site->name ))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const char *content = NULL;
|
||||||
|
if (!user->redacted) {
|
||||||
|
// Clear Text
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (site->type & MPResultTypeClassTemplate)
|
||||||
|
content = mpw_siteResult( masterKey, site->name, site->counter,
|
||||||
|
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
|
||||||
|
}
|
||||||
|
else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
|
||||||
|
// Redacted
|
||||||
|
content = strdup( site->content );
|
||||||
|
|
||||||
|
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
|
||||||
|
mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n",
|
||||||
|
dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter,
|
||||||
|
site->loginName?: "", site->name, content?: "" );
|
||||||
|
mpw_free_string( content );
|
||||||
|
}
|
||||||
|
mpw_free( masterKey, MPMasterKeySize );
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallSuccess };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mpw_marshall_write_json(
|
||||||
|
char **out, const MPMarshalledUser *user, MPMarshallError *error) {
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
|
||||||
|
if (!user->fullName || !strlen( user->fullName )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!user->masterPassword || !strlen( user->masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Missing master password." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MPMasterKey masterKey = NULL;
|
||||||
|
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section: "export"
|
||||||
|
json_object *json_file = json_object_new_object();
|
||||||
|
json_object *json_export = json_object_new_object();
|
||||||
|
json_object_object_add( json_file, "export", json_export );
|
||||||
|
json_object_object_add( json_export, "format", json_object_new_int( 1 ) );
|
||||||
|
json_object_object_add( json_export, "redacted", json_object_new_boolean( user->redacted ) );
|
||||||
|
|
||||||
|
char dateString[21];
|
||||||
|
time_t now = time( NULL );
|
||||||
|
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
|
||||||
|
json_object_object_add( json_export, "date", json_object_new_string( dateString ) );
|
||||||
|
|
||||||
|
// Section: "user"
|
||||||
|
json_object *json_user = json_object_new_object();
|
||||||
|
json_object_object_add( json_file, "user", json_user );
|
||||||
|
json_object_object_add( json_user, "avatar", json_object_new_int( (int)user->avatar ) );
|
||||||
|
json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) );
|
||||||
|
|
||||||
|
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) ))
|
||||||
|
json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) );
|
||||||
|
json_object_object_add( json_user, "key_id", json_object_new_string( mpw_id_buf( masterKey, MPMasterKeySize ) ) );
|
||||||
|
|
||||||
|
json_object_object_add( json_user, "algorithm", json_object_new_int( (int)user->algorithm ) );
|
||||||
|
json_object_object_add( json_user, "default_type", json_object_new_int( (int)user->defaultType ) );
|
||||||
|
|
||||||
|
// Section "sites"
|
||||||
|
json_object *json_sites = json_object_new_object();
|
||||||
|
json_object_object_add( json_file, "sites", json_sites );
|
||||||
|
for (size_t s = 0; s < user->sites_count; ++s) {
|
||||||
|
MPMarshalledSite *site = &user->sites[s];
|
||||||
|
if (!site->name || !strlen( site->name ))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const char *content = NULL;
|
||||||
|
if (!user->redacted) {
|
||||||
|
// Clear Text
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (site->type & MPResultTypeClassTemplate)
|
||||||
|
content = mpw_siteResult( masterKey, site->name, site->counter,
|
||||||
|
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
|
||||||
|
}
|
||||||
|
else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
|
||||||
|
// Redacted
|
||||||
|
content = strdup( site->content );
|
||||||
|
|
||||||
|
json_object *json_site = json_object_new_object();
|
||||||
|
json_object_object_add( json_sites, site->name, json_site );
|
||||||
|
json_object_object_add( json_site, "type", json_object_new_int( (int)site->type ) );
|
||||||
|
json_object_object_add( json_site, "counter", json_object_new_int( (int)site->counter ) );
|
||||||
|
json_object_object_add( json_site, "algorithm", json_object_new_int( (int)site->algorithm ) );
|
||||||
|
if (content)
|
||||||
|
json_object_object_add( json_site, "password", json_object_new_string( content ) );
|
||||||
|
if (site->loginName)
|
||||||
|
json_object_object_add( json_site, "login_name", json_object_new_string( site->loginName ) );
|
||||||
|
json_object_object_add( json_site, "login_generated", json_object_new_boolean( site->loginGenerated ) );
|
||||||
|
|
||||||
|
json_object_object_add( json_site, "uses", json_object_new_int( (int)site->uses ) );
|
||||||
|
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
|
||||||
|
json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) );
|
||||||
|
|
||||||
|
json_object *json_site_questions = json_object_new_object();
|
||||||
|
json_object_object_add( json_site, "questions", json_site_questions );
|
||||||
|
for (size_t q = 0; q < site->questions_count; ++q) {
|
||||||
|
MPMarshalledQuestion *question = &site->questions[q];
|
||||||
|
if (!question->keyword)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
json_object *json_site_question = json_object_new_object();
|
||||||
|
json_object_object_add( json_site_questions, question->keyword, json_site_question );
|
||||||
|
|
||||||
|
if (!user->redacted) {
|
||||||
|
// Clear Text
|
||||||
|
const char *answer = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
|
||||||
|
MPKeyPurposeRecovery, question->keyword, MPResultTypeTemplatePhrase, NULL, site->algorithm );
|
||||||
|
if (answer)
|
||||||
|
json_object_object_add( json_site_question, "answer", json_object_new_string( answer ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *json_site_mpw = json_object_new_object();
|
||||||
|
json_object_object_add( json_site, "_ext_mpw", json_site_mpw );
|
||||||
|
if (site->url)
|
||||||
|
json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) );
|
||||||
|
|
||||||
|
mpw_free_string( content );
|
||||||
|
}
|
||||||
|
|
||||||
|
mpw_string_pushf( out, "%s\n", json_object_to_json_string_ext( json_file, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED ) );
|
||||||
|
mpw_free( masterKey, MPMasterKeySize );
|
||||||
|
json_object_put( json_file );
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallSuccess };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mpw_marshall_write(
|
||||||
|
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) {
|
||||||
|
|
||||||
|
switch (outFormat) {
|
||||||
|
case MPMarshallFormatFlat:
|
||||||
|
return mpw_marshall_write_flat( out, user, error );
|
||||||
|
case MPMarshallFormatJSON:
|
||||||
|
return mpw_marshall_write_json( out, user, error );
|
||||||
|
default:
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MPMarshalledUser *mpw_marshall_read_flat(
|
||||||
|
char *in, const char *masterPassword, MPMarshallError *error) {
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
|
||||||
|
|
||||||
|
// Parse import data.
|
||||||
|
MPMasterKey masterKey = NULL;
|
||||||
|
MPMarshalledUser *user = NULL;
|
||||||
|
unsigned int format = 0, avatar = 0;
|
||||||
|
char *fullName = NULL, *keyID = NULL;
|
||||||
|
MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1;
|
||||||
|
MPResultType defaultType = MPResultTypeDefault;
|
||||||
|
bool headerStarted = false, headerEnded = false, importRedacted = false;
|
||||||
|
for (char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
|
||||||
|
|
||||||
|
// Comment or header
|
||||||
|
if (*positionInLine == '#') {
|
||||||
|
++positionInLine;
|
||||||
|
|
||||||
|
if (!headerStarted) {
|
||||||
|
if (*positionInLine == '#')
|
||||||
|
// ## starts header
|
||||||
|
headerStarted = true;
|
||||||
|
// Comment before header
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (headerEnded)
|
||||||
|
// Comment after header
|
||||||
|
continue;
|
||||||
|
if (*positionInLine == '#') {
|
||||||
|
// ## ends header
|
||||||
|
headerEnded = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header
|
||||||
|
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
|
||||||
|
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
|
||||||
|
if (!headerName || !headerValue) {
|
||||||
|
error->type = MPMarshallErrorStructure;
|
||||||
|
error->description = mpw_str( "Invalid header: %s", strndup( positionInLine, (size_t)(endOfLine - positionInLine) ) );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp( headerName, "Format" ) == 0)
|
||||||
|
format = (unsigned int)atoi( headerValue );
|
||||||
|
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
|
||||||
|
fullName = strdup( headerValue );
|
||||||
|
if (strcmp( headerName, "Avatar" ) == 0)
|
||||||
|
avatar = (unsigned int)atoi( headerValue );
|
||||||
|
if (strcmp( headerName, "Key ID" ) == 0)
|
||||||
|
keyID = strdup( headerValue );
|
||||||
|
if (strcmp( headerName, "Algorithm" ) == 0) {
|
||||||
|
int value = atoi( headerValue );
|
||||||
|
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
algorithm = (MPAlgorithmVersion)value;
|
||||||
|
}
|
||||||
|
if (strcmp( headerName, "Default Type" ) == 0) {
|
||||||
|
int value = atoi( headerValue );
|
||||||
|
if (!mpw_nameForType( (MPResultType)value )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
defaultType = (MPResultType)value;
|
||||||
|
}
|
||||||
|
if (strcmp( headerName, "Passwords" ) == 0)
|
||||||
|
importRedacted = strcmp( headerValue, "VISIBLE" ) != 0;
|
||||||
|
|
||||||
|
mpw_free_string( headerName );
|
||||||
|
mpw_free_string( headerValue );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!headerEnded)
|
||||||
|
continue;
|
||||||
|
if (!fullName) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing header: Full Name" };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (positionInLine >= endOfLine)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Master password doesn't match key ID." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!(user = mpw_marshall_user( fullName, masterPassword, algorithm ))) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new user." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
user->redacted = importRedacted;
|
||||||
|
user->avatar = avatar;
|
||||||
|
user->defaultType = defaultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Site
|
||||||
|
char *siteLoginName = NULL, *siteName = NULL, *siteContent = NULL;
|
||||||
|
char *str_lastUsed = NULL, *str_uses = NULL, *str_type = NULL, *str_algorithm = NULL, *str_counter = NULL;
|
||||||
|
switch (format) {
|
||||||
|
case 0: {
|
||||||
|
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||||
|
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||||
|
char *typeAndVersion = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||||
|
if (typeAndVersion) {
|
||||||
|
str_type = strdup( strtok( typeAndVersion, ":" ) );
|
||||||
|
str_algorithm = strdup( strtok( NULL, "" ) );
|
||||||
|
mpw_free_string( typeAndVersion );
|
||||||
|
}
|
||||||
|
str_counter = strdup( "1" );
|
||||||
|
siteLoginName = NULL;
|
||||||
|
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
|
||||||
|
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||||
|
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||||
|
char *typeAndVersionAndCounter = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
|
||||||
|
if (typeAndVersionAndCounter) {
|
||||||
|
str_type = strdup( strtok( typeAndVersionAndCounter, ":" ) );
|
||||||
|
str_algorithm = strdup( strtok( NULL, ":" ) );
|
||||||
|
str_counter = strdup( strtok( NULL, "" ) );
|
||||||
|
mpw_free_string( typeAndVersionAndCounter );
|
||||||
|
}
|
||||||
|
siteLoginName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
|
||||||
|
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
|
||||||
|
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unexpected import format: %u", format ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) {
|
||||||
|
MPResultType siteType = (MPResultType)atoi( str_type );
|
||||||
|
if (!mpw_nameForType( siteType )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
long long int value = atoll( str_counter );
|
||||||
|
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
MPCounterValue siteCounter = (MPCounterValue)value;
|
||||||
|
value = atoll( str_algorithm );
|
||||||
|
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
|
||||||
|
time_t siteLastUsed = mpw_mktime( str_lastUsed );
|
||||||
|
if (!siteLastUsed) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPMarshalledSite *site = mpw_marshall_site(
|
||||||
|
user, siteName, siteType, siteCounter, siteAlgorithm );
|
||||||
|
if (!site) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new site." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
site->loginName = siteLoginName? strdup( siteLoginName ): NULL;
|
||||||
|
site->uses = (unsigned int)atoi( str_uses );
|
||||||
|
site->lastUsed = siteLastUsed;
|
||||||
|
if (siteContent && strlen( siteContent )) {
|
||||||
|
if (!user->redacted) {
|
||||||
|
// Clear Text
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
site->content = mpw_siteState( masterKey, site->name, site->counter,
|
||||||
|
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Redacted
|
||||||
|
site->content = strdup( siteContent );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error->type = MPMarshallErrorMissing;
|
||||||
|
error->description = mpw_str(
|
||||||
|
"Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
|
||||||
|
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginName, siteName );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mpw_free_string( str_lastUsed );
|
||||||
|
mpw_free_string( str_uses );
|
||||||
|
mpw_free_string( str_type );
|
||||||
|
mpw_free_string( str_algorithm );
|
||||||
|
mpw_free_string( str_counter );
|
||||||
|
mpw_free_string( siteLoginName );
|
||||||
|
mpw_free_string( siteName );
|
||||||
|
mpw_free_string( siteContent );
|
||||||
|
}
|
||||||
|
mpw_free_string( fullName );
|
||||||
|
mpw_free_string( keyID );
|
||||||
|
mpw_free( masterKey, MPMasterKeySize );
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallSuccess };
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MPMarshalledUser *mpw_marshall_read_json(
|
||||||
|
char *in, const char *masterPassword, MPMarshallError *error) {
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal };
|
||||||
|
|
||||||
|
// Parse JSON.
|
||||||
|
enum json_tokener_error json_error = json_tokener_success;
|
||||||
|
json_object *json_file = json_tokener_parse_verbose( in, &json_error );
|
||||||
|
if (!json_file || json_error != json_tokener_success) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorStructure, mpw_str( "JSON error: %s", json_tokener_error_desc( json_error ) ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse import data.
|
||||||
|
MPMasterKey masterKey = NULL;
|
||||||
|
MPAlgorithmVersion masterKeyAlgorithm = (MPAlgorithmVersion)-1;
|
||||||
|
MPMarshalledUser *user = NULL;
|
||||||
|
|
||||||
|
// Section: "export"
|
||||||
|
unsigned int fileFormat = (unsigned int)mpw_get_json_int( json_file, "export.format", 0 );
|
||||||
|
if (fileFormat < 1) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported format: %u", fileFormat ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
bool fileRedacted = mpw_get_json_boolean( json_file, "export.redacted", true );
|
||||||
|
|
||||||
|
// Section: "user"
|
||||||
|
unsigned int avatar = (unsigned int)mpw_get_json_int( json_file, "user.avatar", 0 );
|
||||||
|
const char *fullName = mpw_get_json_string( json_file, "user.full_name", NULL );
|
||||||
|
const char *str_lastUsed = mpw_get_json_string( json_file, "user.last_used", NULL );
|
||||||
|
const char *keyID = mpw_get_json_string( json_file, "user.key_id", NULL );
|
||||||
|
int32_t value = mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
|
||||||
|
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user algorithm version: %u", value ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
MPAlgorithmVersion algorithm = (MPAlgorithmVersion)value;
|
||||||
|
MPResultType defaultType = (MPResultType)mpw_get_json_int( json_file, "user.default_type", MPResultTypeDefault );
|
||||||
|
if (!mpw_nameForType( defaultType )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
time_t lastUsed = mpw_mktime( str_lastUsed );
|
||||||
|
if (!lastUsed) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!fullName || !strlen( fullName )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing value for full name." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Master password doesn't match key ID." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!(user = mpw_marshall_user( fullName, masterPassword, algorithm ))) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new user." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
user->redacted = fileRedacted;
|
||||||
|
user->avatar = avatar;
|
||||||
|
user->defaultType = defaultType;
|
||||||
|
user->lastUsed = lastUsed;
|
||||||
|
|
||||||
|
// Section "sites"
|
||||||
|
json_object_iter json_site;
|
||||||
|
json_object *json_sites = mpw_get_json_section( json_file, "sites" );
|
||||||
|
json_object_object_foreachC( json_sites, json_site ) {
|
||||||
|
const char *siteName = json_site.key;
|
||||||
|
value = mpw_get_json_int( json_site.val, "algorithm", user->algorithm );
|
||||||
|
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site algorithm version: %s: %d", siteName, value ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
|
||||||
|
MPResultType siteType = (MPResultType)mpw_get_json_int( json_site.val, "type", user->defaultType );
|
||||||
|
if (!mpw_nameForType( siteType )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
value = mpw_get_json_int( json_site.val, "counter", 1 );
|
||||||
|
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, value ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
MPCounterValue siteCounter = (MPCounterValue)value;
|
||||||
|
const char *siteContent = mpw_get_json_string( json_site.val, "password", NULL );
|
||||||
|
const char *siteLoginName = mpw_get_json_string( json_site.val, "login_name", NULL );
|
||||||
|
bool siteLoginGenerated = mpw_get_json_boolean( json_site.val, "login_generated", false );
|
||||||
|
unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 );
|
||||||
|
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
|
||||||
|
time_t siteLastUsed = mpw_mktime( str_lastUsed );
|
||||||
|
if (!siteLastUsed) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object *json_site_mpw = mpw_get_json_section( json_site.val, "_ext_mpw" );
|
||||||
|
const char *siteURL = mpw_get_json_string( json_site_mpw, "url", NULL );
|
||||||
|
|
||||||
|
MPMarshalledSite *site = mpw_marshall_site( user, siteName, siteType, siteCounter, siteAlgorithm );
|
||||||
|
if (!site) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new site." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
site->loginName = siteLoginName? strdup( siteLoginName ): NULL;
|
||||||
|
site->loginGenerated = siteLoginGenerated;
|
||||||
|
site->url = siteURL? strdup( siteURL ): NULL;
|
||||||
|
site->uses = siteUses;
|
||||||
|
site->lastUsed = siteLastUsed;
|
||||||
|
if (siteContent && strlen( siteContent )) {
|
||||||
|
if (!user->redacted) {
|
||||||
|
// Clear Text
|
||||||
|
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
site->content = mpw_siteState( masterKey, site->name, site->counter,
|
||||||
|
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Redacted
|
||||||
|
site->content = strdup( siteContent );
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_iter json_site_question;
|
||||||
|
json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" );
|
||||||
|
json_object_object_foreachC( json_site_questions, json_site_question )
|
||||||
|
mpw_marshal_question( site, json_site_question.key );
|
||||||
|
}
|
||||||
|
json_object_put( json_file );
|
||||||
|
|
||||||
|
*error = (MPMarshallError){ MPMarshallSuccess };
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPMarshalledUser *mpw_marshall_read(
|
||||||
|
char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) {
|
||||||
|
|
||||||
|
switch (inFormat) {
|
||||||
|
case MPMarshallFormatFlat:
|
||||||
|
return mpw_marshall_read_flat( in, masterPassword, error );
|
||||||
|
case MPMarshallFormatJSON:
|
||||||
|
return mpw_marshall_read_json( in, masterPassword, error );
|
||||||
|
default:
|
||||||
|
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) };
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MPMarshallFormat mpw_formatWithName(
|
||||||
|
const char *formatName) {
|
||||||
|
|
||||||
|
// Lower-case to standardize it.
|
||||||
|
size_t stdFormatNameSize = strlen( formatName );
|
||||||
|
char stdFormatName[stdFormatNameSize + 1];
|
||||||
|
for (size_t c = 0; c < stdFormatNameSize; ++c)
|
||||||
|
stdFormatName[c] = (char)tolower( formatName[c] );
|
||||||
|
stdFormatName[stdFormatNameSize] = '\0';
|
||||||
|
|
||||||
|
if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
|
||||||
|
return MPMarshallFormatFlat;
|
||||||
|
if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
|
||||||
|
return MPMarshallFormatJSON;
|
||||||
|
|
||||||
|
dbg( "Not a format name: %s\n", stdFormatName );
|
||||||
|
return (MPMarshallFormat)ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mpw_nameForFormat(
|
||||||
|
const MPMarshallFormat format) {
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case MPMarshallFormatFlat:
|
||||||
|
return "flat";
|
||||||
|
case MPMarshallFormatJSON:
|
||||||
|
return "json";
|
||||||
|
default: {
|
||||||
|
dbg( "Unknown format: %d\n", format );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mpw_marshall_format_extension(
|
||||||
|
const MPMarshallFormat format) {
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case MPMarshallFormatFlat:
|
||||||
|
return "mpsites";
|
||||||
|
case MPMarshallFormatJSON:
|
||||||
|
return "mpsites.json";
|
||||||
|
default: {
|
||||||
|
dbg( "Unknown format: %d\n", format );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
core/c/mpw-marshall.h
Normal file
128
core/c/mpw-marshall.h
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#ifndef _MPW_MARSHALL_H
|
||||||
|
#define _MPW_MARSHALL_H
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "mpw-algorithm.h"
|
||||||
|
|
||||||
|
//// Types.
|
||||||
|
|
||||||
|
typedef enum( unsigned int, MPMarshallFormat ) {
|
||||||
|
/** Generate a key for authentication. */
|
||||||
|
MPMarshallFormatFlat,
|
||||||
|
/** Generate a name for identification. */
|
||||||
|
MPMarshallFormatJSON,
|
||||||
|
|
||||||
|
MPMarshallFormatDefault = MPMarshallFormatJSON,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum( unsigned int, MPMarshallErrorType ) {
|
||||||
|
/** The marshalling operation completed successfully. */
|
||||||
|
MPMarshallSuccess,
|
||||||
|
/** An error in the structure of the marshall file interrupted marshalling. */
|
||||||
|
MPMarshallErrorStructure,
|
||||||
|
/** The marshall file uses an unsupported format version. */
|
||||||
|
MPMarshallErrorFormat,
|
||||||
|
/** A required value is missing or not specified. */
|
||||||
|
MPMarshallErrorMissing,
|
||||||
|
/** The given master password is not valid. */
|
||||||
|
MPMarshallErrorMasterPassword,
|
||||||
|
/** An illegal value was specified. */
|
||||||
|
MPMarshallErrorIllegal,
|
||||||
|
/** An internal system error interrupted marshalling. */
|
||||||
|
MPMarshallErrorInternal,
|
||||||
|
};
|
||||||
|
typedef struct MPMarshallError {
|
||||||
|
MPMarshallErrorType type;
|
||||||
|
const char *description;
|
||||||
|
} MPMarshallError;
|
||||||
|
|
||||||
|
typedef struct MPMarshalledQuestion {
|
||||||
|
const char *keyword;
|
||||||
|
} MPMarshalledQuestion;
|
||||||
|
|
||||||
|
typedef struct MPMarshalledSite {
|
||||||
|
const char *name;
|
||||||
|
const char *content;
|
||||||
|
MPResultType type;
|
||||||
|
MPCounterValue counter;
|
||||||
|
MPAlgorithmVersion algorithm;
|
||||||
|
|
||||||
|
const char *loginName;
|
||||||
|
bool loginGenerated;
|
||||||
|
|
||||||
|
const char *url;
|
||||||
|
unsigned int uses;
|
||||||
|
time_t lastUsed;
|
||||||
|
|
||||||
|
size_t questions_count;
|
||||||
|
MPMarshalledQuestion *questions;
|
||||||
|
} MPMarshalledSite;
|
||||||
|
|
||||||
|
typedef struct MPMarshalledUser {
|
||||||
|
const char *fullName;
|
||||||
|
const char *masterPassword;
|
||||||
|
MPAlgorithmVersion algorithm;
|
||||||
|
bool redacted;
|
||||||
|
|
||||||
|
unsigned int avatar;
|
||||||
|
MPResultType defaultType;
|
||||||
|
time_t lastUsed;
|
||||||
|
|
||||||
|
size_t sites_count;
|
||||||
|
MPMarshalledSite *sites;
|
||||||
|
} MPMarshalledUser;
|
||||||
|
|
||||||
|
//// Marshalling.
|
||||||
|
|
||||||
|
bool mpw_marshall_write(
|
||||||
|
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error);
|
||||||
|
MPMarshalledUser *mpw_marshall_read(
|
||||||
|
char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error);
|
||||||
|
|
||||||
|
//// Utilities.
|
||||||
|
|
||||||
|
MPMarshalledUser *mpw_marshall_user(
|
||||||
|
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
|
||||||
|
MPMarshalledSite *mpw_marshall_site(
|
||||||
|
MPMarshalledUser *user,
|
||||||
|
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
|
||||||
|
MPMarshalledQuestion *mpw_marshal_question(
|
||||||
|
MPMarshalledSite *site, const char *keyword);
|
||||||
|
bool mpw_marshal_free(
|
||||||
|
MPMarshalledUser *user);
|
||||||
|
|
||||||
|
//// Format.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The purpose represented by the given name.
|
||||||
|
*/
|
||||||
|
const MPMarshallFormat mpw_formatWithName(
|
||||||
|
const char *formatName);
|
||||||
|
/**
|
||||||
|
* @return The standard name for the given purpose.
|
||||||
|
*/
|
||||||
|
const char *mpw_nameForFormat(
|
||||||
|
const MPMarshallFormat format);
|
||||||
|
const char *mpw_marshall_format_extension(
|
||||||
|
const MPMarshallFormat format);
|
||||||
|
|
||||||
|
#endif // _MPW_MARSHALL_H
|
||||||
@@ -29,55 +29,115 @@
|
|||||||
#include "mpw-types.h"
|
#include "mpw-types.h"
|
||||||
#include "mpw-util.h"
|
#include "mpw-util.h"
|
||||||
|
|
||||||
const MPSiteType mpw_typeWithName(const char *typeName) {
|
const MPResultType mpw_typeWithName(const char *typeName) {
|
||||||
|
|
||||||
|
// Find what password type is represented by the type letter.
|
||||||
|
if (strlen( typeName ) == 1) {
|
||||||
|
if ('x' == typeName[0])
|
||||||
|
return MPResultTypeTemplateMaximum;
|
||||||
|
if ('l' == typeName[0])
|
||||||
|
return MPResultTypeTemplateLong;
|
||||||
|
if ('m' == typeName[0])
|
||||||
|
return MPResultTypeTemplateMedium;
|
||||||
|
if ('b' == typeName[0])
|
||||||
|
return MPResultTypeTemplateBasic;
|
||||||
|
if ('s' == typeName[0])
|
||||||
|
return MPResultTypeTemplateShort;
|
||||||
|
if ('i' == typeName[0])
|
||||||
|
return MPResultTypeTemplatePIN;
|
||||||
|
if ('n' == typeName[0])
|
||||||
|
return MPResultTypeTemplateName;
|
||||||
|
if ('P' == typeName[0])
|
||||||
|
return MPResultTypeStatePersonal;
|
||||||
|
if ('D' == typeName[0])
|
||||||
|
return MPResultTypeStateDevice;
|
||||||
|
if ('k' == typeName[0])
|
||||||
|
return MPResultTypeDeriveKey;
|
||||||
|
}
|
||||||
|
|
||||||
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it.
|
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it.
|
||||||
size_t stdTypeNameOffset = 0;
|
size_t stdTypeNameOffset = 0;
|
||||||
size_t stdTypeNameSize = strlen( typeName );
|
size_t stdTypeNameSize = strlen( typeName );
|
||||||
if (strstr(typeName, "Generated" ) == typeName)
|
if (strstr( typeName, "Generated" ) == typeName)
|
||||||
stdTypeNameSize -= (stdTypeNameOffset = strlen( "Generated" ));
|
stdTypeNameSize -= (stdTypeNameOffset = strlen( "Generated" ));
|
||||||
char stdTypeName[stdTypeNameSize + 1];
|
char stdTypeName[stdTypeNameSize + 1];
|
||||||
for (size_t c = 0; c < stdTypeNameSize; ++c)
|
for (size_t c = 0; c < stdTypeNameSize; ++c)
|
||||||
stdTypeName[c] = (char)tolower( typeName[c + stdTypeNameOffset] );
|
stdTypeName[c] = (char)tolower( typeName[c + stdTypeNameOffset] );
|
||||||
stdTypeName[stdTypeNameSize] = '\0';
|
stdTypeName[stdTypeNameSize] = '\0';
|
||||||
|
|
||||||
// Find what site type is represented by the type name.
|
// Find what password type is represented by the type name.
|
||||||
if (0 == strcmp( stdTypeName, "x" ) || 0 == strcmp( stdTypeName, "max" ) || 0 == strcmp( stdTypeName, "maximum" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplateMaximum ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedMaximum;
|
return MPResultTypeTemplateMaximum;
|
||||||
if (0 == strcmp( stdTypeName, "l" ) || 0 == strcmp( stdTypeName, "long" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplateLong ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedLong;
|
return MPResultTypeTemplateLong;
|
||||||
if (0 == strcmp( stdTypeName, "m" ) || 0 == strcmp( stdTypeName, "med" ) || 0 == strcmp( stdTypeName, "medium" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplateMedium ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedMedium;
|
return MPResultTypeTemplateMedium;
|
||||||
if (0 == strcmp( stdTypeName, "b" ) || 0 == strcmp( stdTypeName, "basic" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplateBasic ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedBasic;
|
return MPResultTypeTemplateBasic;
|
||||||
if (0 == strcmp( stdTypeName, "s" ) || 0 == strcmp( stdTypeName, "short" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplateShort ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedShort;
|
return MPResultTypeTemplateShort;
|
||||||
if (0 == strcmp( stdTypeName, "i" ) || 0 == strcmp( stdTypeName, "pin" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplatePIN ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedPIN;
|
return MPResultTypeTemplatePIN;
|
||||||
if (0 == strcmp( stdTypeName, "n" ) || 0 == strcmp( stdTypeName, "name" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplateName ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedName;
|
return MPResultTypeTemplateName;
|
||||||
if (0 == strcmp( stdTypeName, "p" ) || 0 == strcmp( stdTypeName, "phrase" ))
|
if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
return MPSiteTypeGeneratedPhrase;
|
return MPResultTypeTemplatePhrase;
|
||||||
|
if (strncmp( mpw_nameForType( MPResultTypeStatePersonal ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
|
return MPResultTypeStatePersonal;
|
||||||
|
if (strncmp( mpw_nameForType( MPResultTypeStateDevice ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
|
return MPResultTypeStateDevice;
|
||||||
|
if (strncmp( mpw_nameForType( MPResultTypeDeriveKey ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||||
|
return MPResultTypeDeriveKey;
|
||||||
|
|
||||||
ftl( "Not a generated type name: %s", stdTypeName );
|
dbg( "Not a generated type name: %s\n", stdTypeName );
|
||||||
return MPSiteTypeGeneratedLong;
|
return (MPResultType)ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
const char *mpw_nameForType(MPResultType resultType) {
|
||||||
|
|
||||||
if (!(type & MPSiteTypeClassGenerated)) {
|
switch (resultType) {
|
||||||
ftl( "Not a generated type: %d", type );
|
case MPResultTypeTemplateMaximum:
|
||||||
*count = 0;
|
return "maximum";
|
||||||
|
case MPResultTypeTemplateLong:
|
||||||
|
return "long";
|
||||||
|
case MPResultTypeTemplateMedium:
|
||||||
|
return "medium";
|
||||||
|
case MPResultTypeTemplateBasic:
|
||||||
|
return "basic";
|
||||||
|
case MPResultTypeTemplateShort:
|
||||||
|
return "short";
|
||||||
|
case MPResultTypeTemplatePIN:
|
||||||
|
return "pin";
|
||||||
|
case MPResultTypeTemplateName:
|
||||||
|
return "name";
|
||||||
|
case MPResultTypeTemplatePhrase:
|
||||||
|
return "phrase";
|
||||||
|
case MPResultTypeStatePersonal:
|
||||||
|
return "personal";
|
||||||
|
case MPResultTypeStateDevice:
|
||||||
|
return "device";
|
||||||
|
case MPResultTypeDeriveKey:
|
||||||
|
return "key";
|
||||||
|
default: {
|
||||||
|
dbg( "Unknown password type: %d\n", resultType );
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char **mpw_templatesForType(MPResultType type, size_t *count) {
|
||||||
|
|
||||||
|
if (!(type & MPResultTypeClassTemplate)) {
|
||||||
|
dbg( "Not a generated type: %d\n", type );
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPSiteTypeGeneratedMaximum: {
|
case MPResultTypeTemplateMaximum:
|
||||||
return mpw_alloc_array( *count, const char *,
|
return mpw_alloc_array( count, const char *,
|
||||||
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
|
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
|
||||||
}
|
case MPResultTypeTemplateLong:
|
||||||
case MPSiteTypeGeneratedLong: {
|
return mpw_alloc_array( count, const char *,
|
||||||
return mpw_alloc_array( *count, const char *,
|
|
||||||
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
|
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
|
||||||
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
|
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
|
||||||
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
|
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
|
||||||
@@ -85,83 +145,89 @@ const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
|||||||
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
|
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
|
||||||
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
|
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
|
||||||
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
|
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
|
||||||
}
|
case MPResultTypeTemplateMedium:
|
||||||
case MPSiteTypeGeneratedMedium: {
|
return mpw_alloc_array( count, const char *,
|
||||||
return mpw_alloc_array( *count, const char *,
|
|
||||||
"CvcnoCvc", "CvcCvcno" );
|
"CvcnoCvc", "CvcCvcno" );
|
||||||
}
|
case MPResultTypeTemplateBasic:
|
||||||
case MPSiteTypeGeneratedBasic: {
|
return mpw_alloc_array( count, const char *,
|
||||||
return mpw_alloc_array( *count, const char *,
|
|
||||||
"aaanaaan", "aannaaan", "aaannaaa" );
|
"aaanaaan", "aannaaan", "aaannaaa" );
|
||||||
}
|
case MPResultTypeTemplateShort:
|
||||||
case MPSiteTypeGeneratedShort: {
|
return mpw_alloc_array( count, const char *,
|
||||||
return mpw_alloc_array( *count, const char *,
|
|
||||||
"Cvcn" );
|
"Cvcn" );
|
||||||
}
|
case MPResultTypeTemplatePIN:
|
||||||
case MPSiteTypeGeneratedPIN: {
|
return mpw_alloc_array( count, const char *,
|
||||||
return mpw_alloc_array( *count, const char *,
|
|
||||||
"nnnn" );
|
"nnnn" );
|
||||||
}
|
case MPResultTypeTemplateName:
|
||||||
case MPSiteTypeGeneratedName: {
|
return mpw_alloc_array( count, const char *,
|
||||||
return mpw_alloc_array( *count, const char *,
|
|
||||||
"cvccvcvcv" );
|
"cvccvcvcv" );
|
||||||
}
|
case MPResultTypeTemplatePhrase:
|
||||||
case MPSiteTypeGeneratedPhrase: {
|
return mpw_alloc_array( count, const char *,
|
||||||
return mpw_alloc_array( *count, const char *,
|
|
||||||
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
|
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
ftl( "Unknown generated type: %d", type );
|
dbg( "Unknown generated type: %d\n", type );
|
||||||
*count = 0;
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
|
const char *mpw_templateForType(MPResultType type, uint8_t seedByte) {
|
||||||
|
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
const char **templates = mpw_templatesForType( type, &count );
|
const char **templates = mpw_templatesForType( type, &count );
|
||||||
char const *template = count? templates[seedByte % count]: NULL;
|
char const *template = templates && count? templates[seedByte % count]: NULL;
|
||||||
free( templates );
|
free( templates );
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MPSiteVariant mpw_variantWithName(const char *variantName) {
|
const MPKeyPurpose mpw_purposeWithName(const char *purposeName) {
|
||||||
|
|
||||||
// Lower-case and trim optionally leading "generated" string from typeName to standardize it.
|
// Lower-case and trim optionally leading "generated" string from typeName to standardize it.
|
||||||
size_t stdVariantNameSize = strlen( variantName );
|
size_t stdPurposeNameSize = strlen( purposeName );
|
||||||
char stdVariantName[stdVariantNameSize + 1];
|
char stdPurposeName[stdPurposeNameSize + 1];
|
||||||
for (size_t c = 0; c < stdVariantNameSize; ++c)
|
for (size_t c = 0; c < stdPurposeNameSize; ++c)
|
||||||
stdVariantName[c] = (char)tolower( variantName[c] );
|
stdPurposeName[c] = (char)tolower( purposeName[c] );
|
||||||
stdVariantName[stdVariantNameSize] = '\0';
|
stdPurposeName[stdPurposeNameSize] = '\0';
|
||||||
|
|
||||||
if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" ))
|
if (strncmp( mpw_nameForPurpose( MPKeyPurposeAuthentication ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||||
return MPSiteVariantPassword;
|
return MPKeyPurposeAuthentication;
|
||||||
if (0 == strcmp( stdVariantName, "l" ) || 0 == strcmp( stdVariantName, "login" ))
|
if (strncmp( mpw_nameForPurpose( MPKeyPurposeIdentification ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||||
return MPSiteVariantLogin;
|
return MPKeyPurposeIdentification;
|
||||||
if (0 == strcmp( stdVariantName, "a" ) || 0 == strcmp( stdVariantName, "answer" ))
|
if (strncmp( mpw_nameForPurpose( MPKeyPurposeRecovery ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||||
return MPSiteVariantAnswer;
|
return MPKeyPurposeRecovery;
|
||||||
|
|
||||||
fprintf( stderr, "Not a variant name: %s", stdVariantName );
|
dbg( "Not a purpose name: %s\n", stdPurposeName );
|
||||||
abort();
|
return (MPKeyPurpose)ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *mpw_scopeForVariant(MPSiteVariant variant) {
|
const char *mpw_nameForPurpose(MPKeyPurpose purpose) {
|
||||||
|
|
||||||
switch (variant) {
|
switch (purpose) {
|
||||||
case MPSiteVariantPassword: {
|
case MPKeyPurposeAuthentication:
|
||||||
return "com.lyndir.masterpassword";
|
return "authentication";
|
||||||
}
|
case MPKeyPurposeIdentification:
|
||||||
case MPSiteVariantLogin: {
|
return "identification";
|
||||||
return "com.lyndir.masterpassword.login";
|
case MPKeyPurposeRecovery:
|
||||||
}
|
return "recovery";
|
||||||
case MPSiteVariantAnswer: {
|
|
||||||
return "com.lyndir.masterpassword.answer";
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
fprintf( stderr, "Unknown variant: %d", variant );
|
dbg( "Unknown purpose: %d\n", purpose );
|
||||||
abort();
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mpw_scopeForPurpose(MPKeyPurpose purpose) {
|
||||||
|
|
||||||
|
switch (purpose) {
|
||||||
|
case MPKeyPurposeAuthentication:
|
||||||
|
return "com.lyndir.masterpassword";
|
||||||
|
case MPKeyPurposeIdentification:
|
||||||
|
return "com.lyndir.masterpassword.login";
|
||||||
|
case MPKeyPurposeRecovery:
|
||||||
|
return "com.lyndir.masterpassword.answer";
|
||||||
|
default: {
|
||||||
|
dbg( "Unknown purpose: %d\n", purpose );
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,8 +256,8 @@ const char *mpw_charactersInClass(char characterClass) {
|
|||||||
case ' ':
|
case ' ':
|
||||||
return " ";
|
return " ";
|
||||||
default: {
|
default: {
|
||||||
fprintf( stderr, "Unknown character class: %c", characterClass );
|
dbg( "Unknown character class: %c\n", characterClass );
|
||||||
abort();
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,5 +265,8 @@ const char *mpw_charactersInClass(char characterClass) {
|
|||||||
const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
|
const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
|
||||||
|
|
||||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||||
|
if (!classCharacters)
|
||||||
|
return '\0';
|
||||||
|
|
||||||
return classCharacters[seedByte % strlen( classCharacters )];
|
return classCharacters[seedByte % strlen( classCharacters )];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,71 +18,118 @@
|
|||||||
|
|
||||||
#ifndef _MPW_TYPES_H
|
#ifndef _MPW_TYPES_H
|
||||||
#define _MPW_TYPES_H
|
#define _MPW_TYPES_H
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#ifdef NS_ENUM
|
//#ifdef NS_ENUM
|
||||||
#define enum(_type, _name) NS_ENUM(_type, _name)
|
//#define enum(_type, _name) NS_ENUM(_type, _name)
|
||||||
#else
|
//#else
|
||||||
#define enum(_type, _name) _type _name; enum
|
#define enum(_type, _name) _type _name; enum
|
||||||
#endif
|
//#endif
|
||||||
|
|
||||||
#define MP_dkLen 64
|
|
||||||
|
|
||||||
//// Types.
|
//// Types.
|
||||||
|
|
||||||
typedef enum( unsigned int, MPSiteVariant ) {
|
#define MPMasterKeySize 64 /* bytes */
|
||||||
|
typedef const uint8_t *MPMasterKey;
|
||||||
|
#define MPSiteKeySize (256 / 8) /* bytes */ // Size of HMAC-SHA-256
|
||||||
|
typedef const uint8_t *MPSiteKey;
|
||||||
|
typedef const char *MPKeyID;
|
||||||
|
|
||||||
|
typedef enum( uint8_t, MPKeyPurpose ) {
|
||||||
/** Generate a key for authentication. */
|
/** Generate a key for authentication. */
|
||||||
MPSiteVariantPassword,
|
MPKeyPurposeAuthentication,
|
||||||
/** Generate a name for identification. */
|
/** Generate a name for identification. */
|
||||||
MPSiteVariantLogin,
|
MPKeyPurposeIdentification,
|
||||||
/** Generate an answer to a security question. */
|
/** Generate a recovery token. */
|
||||||
MPSiteVariantAnswer,
|
MPKeyPurposeRecovery,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum( unsigned int, MPSiteTypeClass ) {
|
// bit 4 - 9
|
||||||
/** Generate the password. */
|
typedef enum( uint16_t, MPResultTypeClass ) {
|
||||||
MPSiteTypeClassGenerated = 1 << 4,
|
/** Use the site key to generate a password from a template. */
|
||||||
/** Store the password. */
|
MPResultTypeClassTemplate = 1 << 4,
|
||||||
MPSiteTypeClassStored = 1 << 5,
|
/** Use the site key to encrypt and decrypt a stateful entity. */
|
||||||
|
MPResultTypeClassState = 1 << 5,
|
||||||
|
/** Use the site key to derive a site-specific object. */
|
||||||
|
MPResultTypeClassDerive = 1 << 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum( unsigned int, MPSiteFeature ) {
|
// bit 10 - 15
|
||||||
|
typedef enum( uint16_t, MPSiteFeature ) {
|
||||||
/** Export the key-protected content data. */
|
/** Export the key-protected content data. */
|
||||||
MPSiteFeatureExportContent = 1 << 10,
|
MPSiteFeatureExportContent = 1 << 10,
|
||||||
/** Never export content. */
|
/** Never export content. */
|
||||||
MPSiteFeatureDevicePrivate = 1 << 11,
|
MPSiteFeatureDevicePrivate = 1 << 11,
|
||||||
|
/** Don't use this as the primary authentication result type. */
|
||||||
|
MPSiteFeatureAlternative = 1 << 12,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum( unsigned int, MPSiteType) {
|
// bit 0-3 | MPResultTypeClass | MPSiteFeature
|
||||||
MPSiteTypeGeneratedMaximum = 0x0 | MPSiteTypeClassGenerated | 0x0,
|
typedef enum( uint32_t, MPResultType ) {
|
||||||
MPSiteTypeGeneratedLong = 0x1 | MPSiteTypeClassGenerated | 0x0,
|
/** pg^VMAUBk5x3p%HP%i4= */
|
||||||
MPSiteTypeGeneratedMedium = 0x2 | MPSiteTypeClassGenerated | 0x0,
|
MPResultTypeTemplateMaximum = 0x0 | MPResultTypeClassTemplate | 0x0,
|
||||||
MPSiteTypeGeneratedBasic = 0x4 | MPSiteTypeClassGenerated | 0x0,
|
/** BiroYena8:Kixa */
|
||||||
MPSiteTypeGeneratedShort = 0x3 | MPSiteTypeClassGenerated | 0x0,
|
MPResultTypeTemplateLong = 0x1 | MPResultTypeClassTemplate | 0x0,
|
||||||
MPSiteTypeGeneratedPIN = 0x5 | MPSiteTypeClassGenerated | 0x0,
|
/** BirSuj0- */
|
||||||
MPSiteTypeGeneratedName = 0xE | MPSiteTypeClassGenerated | 0x0,
|
MPResultTypeTemplateMedium = 0x2 | MPResultTypeClassTemplate | 0x0,
|
||||||
MPSiteTypeGeneratedPhrase = 0xF | MPSiteTypeClassGenerated | 0x0,
|
/** pO98MoD0 */
|
||||||
|
MPResultTypeTemplateBasic = 0x4 | MPResultTypeClassTemplate | 0x0,
|
||||||
|
/** Bir8 */
|
||||||
|
MPResultTypeTemplateShort = 0x3 | MPResultTypeClassTemplate | 0x0,
|
||||||
|
/** 2798 */
|
||||||
|
MPResultTypeTemplatePIN = 0x5 | MPResultTypeClassTemplate | 0x0,
|
||||||
|
/** birsujano */
|
||||||
|
MPResultTypeTemplateName = 0xE | MPResultTypeClassTemplate | 0x0,
|
||||||
|
/** bir yennoquce fefi */
|
||||||
|
MPResultTypeTemplatePhrase = 0xF | MPResultTypeClassTemplate | 0x0,
|
||||||
|
|
||||||
MPSiteTypeStoredPersonal = 0x0 | MPSiteTypeClassStored | MPSiteFeatureExportContent,
|
/** Custom saved password. */
|
||||||
MPSiteTypeStoredDevicePrivate = 0x1 | MPSiteTypeClassStored | MPSiteFeatureDevicePrivate,
|
MPResultTypeStatePersonal = 0x0 | MPResultTypeClassState | MPSiteFeatureExportContent,
|
||||||
|
/** Custom saved password that should not be exported from the device. */
|
||||||
|
MPResultTypeStateDevice = 0x1 | MPResultTypeClassState | MPSiteFeatureDevicePrivate,
|
||||||
|
|
||||||
|
/** Derive a unique binary key. */
|
||||||
|
MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative,
|
||||||
|
|
||||||
|
MPResultTypeDefault = MPResultTypeTemplateLong,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum ( uint32_t, MPCounterValue ) {
|
||||||
|
/** Use a time-based counter value, resulting in a TOTP generator. */
|
||||||
|
MPCounterValueTOTP = 0,
|
||||||
|
/** The initial value for a site's counter. */
|
||||||
|
MPCounterValueInitial = 1,
|
||||||
|
|
||||||
|
MPCounterValueDefault = MPCounterValueInitial,
|
||||||
|
MPCounterValueFirst = MPCounterValueTOTP,
|
||||||
|
MPCounterValueLast = UINT32_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
//// Type utilities.
|
//// Type utilities.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The variant represented by the given name.
|
* @return The purpose represented by the given name.
|
||||||
*/
|
*/
|
||||||
const MPSiteVariant mpw_variantWithName(const char *variantName);
|
const MPKeyPurpose mpw_purposeWithName(const char *purposeName);
|
||||||
/**
|
/**
|
||||||
* @return An internal string containing the scope identifier to apply when encoding for the given variant.
|
* @return The standard name for the given purpose.
|
||||||
*/
|
*/
|
||||||
const char *mpw_scopeForVariant(MPSiteVariant variant);
|
const char *mpw_nameForPurpose(MPKeyPurpose purpose);
|
||||||
|
/**
|
||||||
|
* @return An internal string containing the scope identifier to apply when encoding for the given purpose.
|
||||||
|
*/
|
||||||
|
const char *mpw_scopeForPurpose(MPKeyPurpose purpose);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The type represented by the given name.
|
* @return The password type represented by the given name.
|
||||||
*/
|
*/
|
||||||
const MPSiteType mpw_typeWithName(const char *typeName);
|
const MPResultType mpw_typeWithName(const char *typeName);
|
||||||
|
/**
|
||||||
|
* @return The standard name for the given password type.
|
||||||
|
*/
|
||||||
|
const char *mpw_nameForType(MPResultType resultType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return A newly allocated array of internal strings that express the templates to use for the given type.
|
* @return A newly allocated array of internal strings that express the templates to use for the given type.
|
||||||
@@ -90,12 +137,12 @@ const MPSiteType mpw_typeWithName(const char *typeName);
|
|||||||
* If an unsupported type is given, count will be 0 and will return NULL.
|
* If an unsupported type is given, count will be 0 and will return NULL.
|
||||||
* The array needs to be free'ed, the strings themselves must not be free'ed or modified.
|
* The array needs to be free'ed, the strings themselves must not be free'ed or modified.
|
||||||
*/
|
*/
|
||||||
const char **mpw_templatesForType(MPSiteType type, size_t *count);
|
const char **mpw_templatesForType(MPResultType type, size_t *count);
|
||||||
/**
|
/**
|
||||||
* @return An internal string that contains the password encoding template of the given type
|
* @return An internal string that contains the password encoding template of the given type
|
||||||
* for a seed that starts with the given byte.
|
* for a seed that starts with the given byte.
|
||||||
*/
|
*/
|
||||||
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte);
|
const char *mpw_templateForType(MPResultType type, uint8_t seedByte);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return An internal string that contains all the characters that occur in the given character class.
|
* @return An internal string that contains all the characters that occur in the given character class.
|
||||||
|
|||||||
@@ -16,66 +16,115 @@
|
|||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#ifdef COLOR
|
#if MPW_COLOR
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <curses.h>
|
#include <curses.h>
|
||||||
#include <term.h>
|
#include <term.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <scrypt/sha256.h>
|
#if HAS_CPERCIVA
|
||||||
#include <scrypt/crypto_scrypt.h>
|
#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"
|
#include "mpw-util.h"
|
||||||
|
|
||||||
void mpw_push_buf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) {
|
int mpw_verbosity = inf_level;
|
||||||
|
|
||||||
if (*bufferSize == (size_t)-1)
|
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.
|
// The buffer was marked as broken, it is missing a previous push. Abort to avoid corrupt content.
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
*bufferSize += pushSize;
|
if (!mpw_realloc( buffer, bufferSize, pushSize )) {
|
||||||
uint8_t *resizedBuffer = realloc( *buffer, *bufferSize );
|
|
||||||
if (!resizedBuffer) {
|
|
||||||
// realloc failed, we can't push. Mark the buffer as broken.
|
// realloc failed, we can't push. Mark the buffer as broken.
|
||||||
mpw_free( *buffer, *bufferSize - pushSize );
|
mpw_free( *buffer, *bufferSize );
|
||||||
*bufferSize = (size_t)-1;
|
*bufferSize = (size_t)ERR;
|
||||||
*buffer = NULL;
|
*buffer = NULL;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*buffer = resizedBuffer;
|
uint8_t *bufferOffset = *buffer + *bufferSize - pushSize;
|
||||||
uint8_t *pushDst = *buffer + *bufferSize - pushSize;
|
memcpy( bufferOffset, pushBuffer, pushSize );
|
||||||
memcpy( pushDst, pushBuffer, pushSize );
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mpw_push_string(uint8_t **buffer, size_t *const bufferSize, const char *pushString) {
|
bool mpw_push_string(uint8_t **const buffer, size_t *const bufferSize, const char *pushString) {
|
||||||
|
|
||||||
mpw_push_buf( buffer, bufferSize, pushString, strlen( pushString ) );
|
return pushString && mpw_push_buf( buffer, bufferSize, pushString, strlen( pushString ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void mpw_push_int(uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt) {
|
bool mpw_string_push(char **const string, const char *pushString) {
|
||||||
|
|
||||||
mpw_push_buf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
|
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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void mpw_free(const void *buffer, const size_t bufferSize) {
|
bool mpw_string_pushf(char **const string, const char *pushFormat, ...) {
|
||||||
|
|
||||||
if (buffer) {
|
va_list args;
|
||||||
memset( (void *)buffer, 0, bufferSize );
|
va_start( args, pushFormat );
|
||||||
free( (void *)buffer );
|
char *pushString = NULL;
|
||||||
}
|
bool success = vasprintf( &pushString, pushFormat, args ) >= 0 && mpw_string_push( string, pushString );
|
||||||
|
va_end( args );
|
||||||
|
mpw_free_string( pushString );
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mpw_free_string(const char *string) {
|
bool mpw_push_int(uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt) {
|
||||||
|
|
||||||
mpw_free( string, strlen( string ) );
|
return mpw_push_buf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
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) {
|
uint64_t N, uint32_t r, uint32_t p) {
|
||||||
|
|
||||||
if (!secret || !salt)
|
if (!secret || !salt)
|
||||||
@@ -85,48 +134,202 @@ uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_
|
|||||||
if (!key)
|
if (!key)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
#if HAS_CPERCIVA
|
||||||
if (crypto_scrypt( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) < 0) {
|
if (crypto_scrypt( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) < 0) {
|
||||||
mpw_free( key, keySize );
|
mpw_free( key, keySize );
|
||||||
return NULL;
|
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;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t const *mpw_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize) {
|
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) {
|
||||||
|
|
||||||
uint8_t *const buffer = malloc( 32 );
|
if (!key || !keySize || !subkeySize) {
|
||||||
if (!buffer)
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *subkey = malloc( subkeySize );
|
||||||
|
if (!subkey)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
HMAC_SHA256_Buf( key, keySize, salt, saltSize, buffer );
|
#if HAS_SODIUM
|
||||||
return buffer;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *mpw_id_buf(const void *buf, size_t length) {
|
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];
|
uint8_t hash[32];
|
||||||
SHA256_Buf( buf, length, hash );
|
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, 32 );
|
return mpw_hex( hash, sizeof( hash ) / sizeof( uint8_t ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
static char **mpw_hex_buf = NULL;
|
bool mpw_id_buf_equals(const char *id1, const char *id2) {
|
||||||
static unsigned int mpw_hex_buf_i = 0;
|
|
||||||
|
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) {
|
const char *mpw_hex(const void *buf, size_t length) {
|
||||||
|
|
||||||
// FIXME
|
// FIXME: Not thread-safe
|
||||||
if (!mpw_hex_buf) {
|
if (!mpw_hex_buf)
|
||||||
mpw_hex_buf = malloc( 10 * sizeof( char * ) );
|
mpw_hex_buf = calloc( 10, sizeof( char * ) );
|
||||||
for (uint8_t i = 0; i < 10; ++i)
|
|
||||||
mpw_hex_buf[i] = NULL;
|
|
||||||
}
|
|
||||||
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
|
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
|
||||||
|
|
||||||
mpw_hex_buf[mpw_hex_buf_i] = realloc( mpw_hex_buf[mpw_hex_buf_i], length * 2 + 1 );
|
if (mpw_realloc( &mpw_hex_buf[mpw_hex_buf_i], NULL, length * 2 + 1 ))
|
||||||
for (size_t kH = 0; kH < length; kH++)
|
for (size_t kH = 0; kH < length; kH++)
|
||||||
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[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];
|
return mpw_hex_buf[mpw_hex_buf_i];
|
||||||
}
|
}
|
||||||
@@ -144,10 +347,10 @@ static int initputvar() {
|
|||||||
if (!isatty(STDERR_FILENO))
|
if (!isatty(STDERR_FILENO))
|
||||||
return 0;
|
return 0;
|
||||||
if (putvarc)
|
if (putvarc)
|
||||||
free(putvarc);
|
free( putvarc );
|
||||||
if (!termsetup) {
|
if (!termsetup) {
|
||||||
int status;
|
int status;
|
||||||
if (! (termsetup = (setupterm(NULL, STDERR_FILENO, &status) == OK && status == 1))) {
|
if (! (termsetup = (setupterm( NULL, STDERR_FILENO, &status ) == 0 && status == 1))) {
|
||||||
wrn( "Terminal doesn't support color (setupterm errno %d).\n", status );
|
wrn( "Terminal doesn't support color (setupterm errno %d).\n", status );
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -174,8 +377,11 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
|
|||||||
"♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌"
|
"♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌"
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t identiconSeed[32];
|
const uint8_t *identiconSeed = mpw_hash_hmac_sha256( (const uint8_t *)masterPassword, strlen( masterPassword ),
|
||||||
HMAC_SHA256_Buf( masterPassword, strlen( masterPassword ), fullName, strlen( fullName ), identiconSeed );
|
(const uint8_t *)fullName,
|
||||||
|
strlen( fullName ) );
|
||||||
|
if (!identiconSeed)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
char *colorString, *resetString;
|
char *colorString, *resetString;
|
||||||
#ifdef COLOR
|
#ifdef COLOR
|
||||||
@@ -203,6 +409,7 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
|
|||||||
accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
|
accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
|
||||||
resetString );
|
resetString );
|
||||||
|
|
||||||
|
mpw_free( identiconSeed, 32 );
|
||||||
free( colorString );
|
free( colorString );
|
||||||
free( resetString );
|
free( resetString );
|
||||||
return identicon;
|
return identicon;
|
||||||
|
|||||||
@@ -16,99 +16,167 @@
|
|||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
|
#ifndef _MPW_UTIL_H
|
||||||
|
#define _MPW_UTIL_H
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include "mpw-types.h"
|
||||||
|
|
||||||
//// Logging.
|
//// Logging.
|
||||||
|
|
||||||
#ifndef trc
|
#ifndef trc
|
||||||
int mpw_verbosity;
|
extern int mpw_verbosity;
|
||||||
#define trc_level 3
|
#define trc_level 3
|
||||||
#define trc(...) \
|
/** Logging internal state. */
|
||||||
if (mpw_verbosity >= 3) \
|
#define trc(...) ({ \
|
||||||
fprintf( stderr, __VA_ARGS__ )
|
if (mpw_verbosity >= 3) \
|
||||||
|
fprintf( stderr, __VA_ARGS__ ); })
|
||||||
#endif
|
#endif
|
||||||
#ifndef dbg
|
#ifndef dbg
|
||||||
#define dbg_level 2
|
#define dbg_level 2
|
||||||
#define dbg(...) \
|
/** Logging state and events interesting when investigating issues. */
|
||||||
if (mpw_verbosity >= 2) \
|
#define dbg(...) ({ \
|
||||||
fprintf( stderr, __VA_ARGS__ )
|
if (mpw_verbosity >= 2) \
|
||||||
|
fprintf( stderr, __VA_ARGS__ ); })
|
||||||
#endif
|
#endif
|
||||||
#ifndef inf
|
#ifndef inf
|
||||||
#define inf_level 1
|
#define inf_level 1
|
||||||
#define inf(...) \
|
/** User messages. */
|
||||||
if (mpw_verbosity >= 1) \
|
#define inf(...) ({ \
|
||||||
fprintf( stderr, __VA_ARGS__ )
|
if (mpw_verbosity >= 1) \
|
||||||
|
fprintf( stderr, __VA_ARGS__ ); })
|
||||||
#endif
|
#endif
|
||||||
#ifndef wrn
|
#ifndef wrn
|
||||||
#define wrn_level 0
|
#define wrn_level 0
|
||||||
#define wrn(...) \
|
/** Recoverable issues and user suggestions. */
|
||||||
if (mpw_verbosity >= 0) \
|
#define wrn(...) ({ \
|
||||||
fprintf( stderr, __VA_ARGS__ )
|
if (mpw_verbosity >= 0) \
|
||||||
|
fprintf( stderr, __VA_ARGS__ ); })
|
||||||
#endif
|
#endif
|
||||||
#ifndef err
|
#ifndef err
|
||||||
#define err_level -1
|
#define err_level -1
|
||||||
#define err(...) \
|
/** Unrecoverable issues. */
|
||||||
if (mpw_verbosity >= -1) \
|
#define err(...) ({ \
|
||||||
fprintf( stderr, __VA_ARGS__ )
|
if (mpw_verbosity >= -1) \
|
||||||
|
fprintf( stderr, __VA_ARGS__ ); })
|
||||||
#endif
|
#endif
|
||||||
#ifndef ftl
|
#ifndef ftl
|
||||||
#define ftl_level -2
|
#define ftl_level -2
|
||||||
#define ftl(...) \
|
/** Issues that lead to abortion. */
|
||||||
do { \
|
#define ftl(...) ({ \
|
||||||
if (mpw_verbosity >= -2) \
|
if (mpw_verbosity >= -2) \
|
||||||
fprintf( stderr, __VA_ARGS__ ); \
|
fprintf( stderr, __VA_ARGS__ ); })
|
||||||
exit( 2 ); \
|
#endif
|
||||||
} while (0)
|
|
||||||
|
#ifndef min
|
||||||
|
#define min(a, b) ({ \
|
||||||
|
__typeof__ (a) _a = (a); \
|
||||||
|
__typeof__ (b) _b = (b); \
|
||||||
|
_a < _b ? _a : _b; })
|
||||||
|
#endif
|
||||||
|
#ifndef max
|
||||||
|
#define max(a, b) ({ \
|
||||||
|
__typeof__ (a) _a = (a); \
|
||||||
|
__typeof__ (b) _b = (b); \
|
||||||
|
_a > _b ? _a : _b; })
|
||||||
|
#endif
|
||||||
|
#ifndef ERR
|
||||||
|
#define ERR -1
|
||||||
|
#endif
|
||||||
|
#ifndef stringify
|
||||||
|
#define stringify(s) #s
|
||||||
|
#endif
|
||||||
|
#ifndef stringify_def
|
||||||
|
#define stringify_def(s) stringify(s)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//// Buffers and memory.
|
//// Buffers and memory.
|
||||||
|
|
||||||
|
/** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs. */
|
||||||
#define mpw_alloc_array(_count, _type, ...) ({ \
|
#define mpw_alloc_array(_count, _type, ...) ({ \
|
||||||
_type stackElements[] = { __VA_ARGS__ }; \
|
_type stackElements[] = { __VA_ARGS__ }; \
|
||||||
_count = sizeof( stackElements ) / sizeof( _type ); \
|
if (_count) \
|
||||||
|
*_count = sizeof( stackElements ) / sizeof( _type ); \
|
||||||
_type *allocElements = malloc( sizeof( stackElements ) ); \
|
_type *allocElements = malloc( sizeof( stackElements ) ); \
|
||||||
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
|
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
|
||||||
allocElements; \
|
allocElements; \
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
|
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
|
||||||
void mpw_push_buf(
|
bool mpw_push_buf(
|
||||||
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
|
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
|
||||||
/** Push a string onto a buffer. reallocs the given buffer and appends the given string. */
|
/** Push a string onto a buffer. reallocs the given buffer and appends the given string. */
|
||||||
void mpw_push_string(
|
bool mpw_push_string(
|
||||||
uint8_t **buffer, size_t *const bufferSize, const char *pushString);
|
uint8_t **buffer, size_t *const bufferSize, const char *pushString);
|
||||||
|
/** Push a string onto another string. reallocs the target string and appends the source string. */
|
||||||
|
bool mpw_string_push(
|
||||||
|
char **const string, const char *pushString);
|
||||||
|
bool mpw_string_pushf(
|
||||||
|
char **const string, const char *pushFormat, ...);
|
||||||
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
|
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
|
||||||
void mpw_push_int(
|
bool mpw_push_int(
|
||||||
uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt);
|
uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt);
|
||||||
|
/** Reallocate the given buffer from the given size by adding the delta size.
|
||||||
|
* On success, the buffer size pointer will be updated to the buffer's new size
|
||||||
|
* and the buffer pointer may be updated to a new memory address.
|
||||||
|
* On failure, the buffer and pointers will remain unaffected.
|
||||||
|
* @param buffer A pointer to the buffer to reallocate.
|
||||||
|
* @param bufferSize A pointer to the buffer's actual size.
|
||||||
|
* @param deltaSize The amount to increase the buffer's size by.
|
||||||
|
* @return true if successful, false if reallocation failed.
|
||||||
|
*/
|
||||||
|
#define mpw_realloc(buffer, bufferSize, deltaSize) \
|
||||||
|
__mpw_realloc( (void **)buffer, bufferSize, deltaSize )
|
||||||
|
bool __mpw_realloc(void **buffer, size_t *bufferSize, const size_t deltaSize);
|
||||||
/** Free a buffer after zero'ing its contents. */
|
/** Free a buffer after zero'ing its contents. */
|
||||||
void mpw_free(
|
bool mpw_free(
|
||||||
const void *buffer, const size_t bufferSize);
|
const void *buffer, const size_t bufferSize);
|
||||||
/** Free a string after zero'ing its contents. */
|
/** Free a string after zero'ing its contents. */
|
||||||
void mpw_free_string(
|
bool mpw_free_string(
|
||||||
const char *string);
|
const char *string);
|
||||||
|
|
||||||
//// Cryptographic functions.
|
//// Cryptographic functions.
|
||||||
|
|
||||||
/** Perform a scrypt-based key derivation on the given key using the given salt and scrypt parameters.
|
/** Derive a key from the given secret and salt using the scrypt KDF.
|
||||||
* @return A new keySize-size allocated buffer. */
|
* @return A new keySize allocated buffer containing the key. */
|
||||||
uint8_t const *mpw_scrypt(
|
uint8_t const *mpw_kdf_scrypt(
|
||||||
const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
||||||
uint64_t N, uint32_t r, uint32_t p);
|
uint64_t N, uint32_t r, uint32_t p);
|
||||||
/** Calculate a SHA256-based HMAC by encrypting the given salt with the given key.
|
/** Derive a subkey from the given key using the blake2b KDF.
|
||||||
* @return A new 32-byte allocated buffer. */
|
* @return A new keySize allocated buffer containing the key. */
|
||||||
uint8_t const *mpw_hmac_sha256(
|
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);
|
||||||
|
/** Calculate the MAC for the given message with the given key using SHA256-HMAC.
|
||||||
|
* @return A new 32-byte allocated buffer containing the MAC. */
|
||||||
|
uint8_t const *mpw_hash_hmac_sha256(
|
||||||
const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize);
|
const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize);
|
||||||
|
/** Encrypt a plainBuf with the given key using AES-128-CBC.
|
||||||
|
* @return A new bufSize allocated buffer containing the cipherBuf. */
|
||||||
|
uint8_t const *mpw_aes_encrypt(
|
||||||
|
const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, const size_t bufSize);
|
||||||
|
/** Decrypt a cipherBuf with the given key using AES-128-CBC.
|
||||||
|
* @return A new bufSize allocated buffer containing the plainBuf. */
|
||||||
|
uint8_t const *mpw_aes_decrypt(
|
||||||
|
const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, const size_t bufSize);
|
||||||
|
|
||||||
//// Visualizers.
|
//// Visualizers.
|
||||||
|
|
||||||
|
/** Compose a formatted string.
|
||||||
|
* @return A C-string in a reused buffer, do not free or store it. */
|
||||||
|
const char *mpw_str(const char *format, ...);
|
||||||
/** Encode a buffer as a string of hexadecimal characters.
|
/** Encode a buffer as a string of hexadecimal characters.
|
||||||
* @return A C-string in a reused buffer, do not free or store it. */
|
* @return A C-string in a reused buffer, do not free or store it. */
|
||||||
const char *mpw_hex(const void *buf, size_t length);
|
const char *mpw_hex(const void *buf, size_t length);
|
||||||
const char *mpw_hex_l(uint32_t number);
|
const char *mpw_hex_l(uint32_t number);
|
||||||
/** Encode a fingerprint for a buffer.
|
/** Encode a fingerprint for a buffer.
|
||||||
* @return A C-string in a reused buffer, do not free or store it. */
|
* @return A C-string in a reused buffer, do not free or store it. */
|
||||||
const char *mpw_id_buf(const void *buf, size_t length);
|
MPKeyID mpw_id_buf(const void *buf, size_t length);
|
||||||
|
/** Compare two fingerprints for equality.
|
||||||
|
* @return true if the buffers represent identical fingerprints. */
|
||||||
|
bool mpw_id_buf_equals(const char *id1, const char *id2);
|
||||||
/** Encode a visual fingerprint for a user.
|
/** Encode a visual fingerprint for a user.
|
||||||
* @return A newly allocated string. */
|
* @return A newly allocated string. */
|
||||||
const char *mpw_identicon(const char *fullName, const char *masterPassword);
|
const char *mpw_identicon(const char *fullName, const char *masterPassword);
|
||||||
@@ -117,3 +185,5 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword);
|
|||||||
|
|
||||||
/** @return The amount of display characters in the given UTF-8 string. */
|
/** @return The amount of display characters in the given UTF-8 string. */
|
||||||
const size_t mpw_utf8_strlen(const char *utf8String);
|
const size_t mpw_utf8_strlen(const char *utf8String);
|
||||||
|
|
||||||
|
#endif // _MPW_UTIL_H
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
||||||
<siteName>masterpasswordapp.com</siteName>
|
<siteName>masterpasswordapp.com</siteName>
|
||||||
<siteCounter>1</siteCounter>
|
<siteCounter>1</siteCounter>
|
||||||
<siteType>GeneratedLong</siteType>
|
<resultType>GeneratedLong</resultType>
|
||||||
<siteVariant>Password</siteVariant>
|
<keyPurpose>Authentication</keyPurpose>
|
||||||
<result><!-- abstract --></result>
|
<result><!-- abstract --></result>
|
||||||
</case>
|
</case>
|
||||||
|
|
||||||
@@ -32,45 +32,45 @@
|
|||||||
<result>LiheCuwhSerz6)</result>
|
<result>LiheCuwhSerz6)</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_loginName" parent="v3">
|
<case id="v3_loginName" parent="v3">
|
||||||
<siteVariant>Login</siteVariant>
|
<keyPurpose>Identification</keyPurpose>
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>wohzaqage</result>
|
<result>wohzaqage</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_securityAnswer" parent="v3">
|
<case id="v3_securityAnswer" parent="v3">
|
||||||
<siteVariant>Answer</siteVariant>
|
<keyPurpose>Recovery</keyPurpose>
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>xin diyjiqoja hubu</result>
|
<result>xin diyjiqoja hubu</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
|
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
|
||||||
<siteContext>question</siteContext>
|
<keyContext>question</keyContext>
|
||||||
<result>xogx tem cegyiva jab</result>
|
<result>xogx tem cegyiva jab</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_type_maximum" parent="v3">
|
<case id="v3_type_maximum" parent="v3">
|
||||||
<siteType>GeneratedMaximum</siteType>
|
<resultType>GeneratedMaximum</resultType>
|
||||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_type_medium" parent="v3">
|
<case id="v3_type_medium" parent="v3">
|
||||||
<siteType>GeneratedMedium</siteType>
|
<resultType>GeneratedMedium</resultType>
|
||||||
<result>Jej2$Quv</result>
|
<result>Jej2$Quv</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_type_basic" parent="v3">
|
<case id="v3_type_basic" parent="v3">
|
||||||
<siteType>GeneratedBasic</siteType>
|
<resultType>GeneratedBasic</resultType>
|
||||||
<result>WAo2xIg6</result>
|
<result>WAo2xIg6</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_type_short" parent="v3">
|
<case id="v3_type_short" parent="v3">
|
||||||
<siteType>GeneratedShort</siteType>
|
<resultType>GeneratedShort</resultType>
|
||||||
<result>Jej2</result>
|
<result>Jej2</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_type_pin" parent="v3">
|
<case id="v3_type_pin" parent="v3">
|
||||||
<siteType>GeneratedPIN</siteType>
|
<resultType>GeneratedPIN</resultType>
|
||||||
<result>7662</result>
|
<result>7662</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_type_name" parent="v3">
|
<case id="v3_type_name" parent="v3">
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>jejraquvo</result>
|
<result>jejraquvo</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_type_phrase" parent="v3">
|
<case id="v3_type_phrase" parent="v3">
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>jejr quv cabsibu tam</result>
|
<result>jejr quv cabsibu tam</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v3_counter_ceiling" parent="v3">
|
<case id="v3_counter_ceiling" parent="v3">
|
||||||
@@ -98,45 +98,45 @@
|
|||||||
<result>LiheCuwhSerz6)</result>
|
<result>LiheCuwhSerz6)</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_loginName" parent="v2">
|
<case id="v2_loginName" parent="v2">
|
||||||
<siteVariant>Login</siteVariant>
|
<keyPurpose>Identification</keyPurpose>
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>wohzaqage</result>
|
<result>wohzaqage</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_securityAnswer" parent="v2">
|
<case id="v2_securityAnswer" parent="v2">
|
||||||
<siteVariant>Answer</siteVariant>
|
<keyPurpose>Recovery</keyPurpose>
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>xin diyjiqoja hubu</result>
|
<result>xin diyjiqoja hubu</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
|
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
|
||||||
<siteContext>question</siteContext>
|
<keyContext>question</keyContext>
|
||||||
<result>xogx tem cegyiva jab</result>
|
<result>xogx tem cegyiva jab</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_type_maximum" parent="v2">
|
<case id="v2_type_maximum" parent="v2">
|
||||||
<siteType>GeneratedMaximum</siteType>
|
<resultType>GeneratedMaximum</resultType>
|
||||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_type_medium" parent="v2">
|
<case id="v2_type_medium" parent="v2">
|
||||||
<siteType>GeneratedMedium</siteType>
|
<resultType>GeneratedMedium</resultType>
|
||||||
<result>Jej2$Quv</result>
|
<result>Jej2$Quv</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_type_basic" parent="v2">
|
<case id="v2_type_basic" parent="v2">
|
||||||
<siteType>GeneratedBasic</siteType>
|
<resultType>GeneratedBasic</resultType>
|
||||||
<result>WAo2xIg6</result>
|
<result>WAo2xIg6</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_type_short" parent="v2">
|
<case id="v2_type_short" parent="v2">
|
||||||
<siteType>GeneratedShort</siteType>
|
<resultType>GeneratedShort</resultType>
|
||||||
<result>Jej2</result>
|
<result>Jej2</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_type_pin" parent="v2">
|
<case id="v2_type_pin" parent="v2">
|
||||||
<siteType>GeneratedPIN</siteType>
|
<resultType>GeneratedPIN</resultType>
|
||||||
<result>7662</result>
|
<result>7662</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_type_name" parent="v2">
|
<case id="v2_type_name" parent="v2">
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>jejraquvo</result>
|
<result>jejraquvo</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_type_phrase" parent="v2">
|
<case id="v2_type_phrase" parent="v2">
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>jejr quv cabsibu tam</result>
|
<result>jejr quv cabsibu tam</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v2_counter_ceiling" parent="v2">
|
<case id="v2_counter_ceiling" parent="v2">
|
||||||
@@ -164,45 +164,45 @@
|
|||||||
<result>WawiYarp2@Kodh</result>
|
<result>WawiYarp2@Kodh</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_loginName" parent="v1">
|
<case id="v1_loginName" parent="v1">
|
||||||
<siteVariant>Login</siteVariant>
|
<keyPurpose>Identification</keyPurpose>
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>wohzaqage</result>
|
<result>wohzaqage</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_securityAnswer" parent="v1">
|
<case id="v1_securityAnswer" parent="v1">
|
||||||
<siteVariant>Answer</siteVariant>
|
<keyPurpose>Recovery</keyPurpose>
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>xin diyjiqoja hubu</result>
|
<result>xin diyjiqoja hubu</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
|
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
|
||||||
<siteContext>question</siteContext>
|
<keyContext>question</keyContext>
|
||||||
<result>xogx tem cegyiva jab</result>
|
<result>xogx tem cegyiva jab</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_type_maximum" parent="v1">
|
<case id="v1_type_maximum" parent="v1">
|
||||||
<siteType>GeneratedMaximum</siteType>
|
<resultType>GeneratedMaximum</resultType>
|
||||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_type_medium" parent="v1">
|
<case id="v1_type_medium" parent="v1">
|
||||||
<siteType>GeneratedMedium</siteType>
|
<resultType>GeneratedMedium</resultType>
|
||||||
<result>Jej2$Quv</result>
|
<result>Jej2$Quv</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_type_basic" parent="v1">
|
<case id="v1_type_basic" parent="v1">
|
||||||
<siteType>GeneratedBasic</siteType>
|
<resultType>GeneratedBasic</resultType>
|
||||||
<result>WAo2xIg6</result>
|
<result>WAo2xIg6</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_type_short" parent="v1">
|
<case id="v1_type_short" parent="v1">
|
||||||
<siteType>GeneratedShort</siteType>
|
<resultType>GeneratedShort</resultType>
|
||||||
<result>Jej2</result>
|
<result>Jej2</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_type_pin" parent="v1">
|
<case id="v1_type_pin" parent="v1">
|
||||||
<siteType>GeneratedPIN</siteType>
|
<resultType>GeneratedPIN</resultType>
|
||||||
<result>7662</result>
|
<result>7662</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_type_name" parent="v1">
|
<case id="v1_type_name" parent="v1">
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>jejraquvo</result>
|
<result>jejraquvo</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_type_phrase" parent="v1">
|
<case id="v1_type_phrase" parent="v1">
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>jejr quv cabsibu tam</result>
|
<result>jejr quv cabsibu tam</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v1_counter_ceiling" parent="v1">
|
<case id="v1_counter_ceiling" parent="v1">
|
||||||
@@ -230,45 +230,45 @@
|
|||||||
<result>HahiVana2@Nole</result>
|
<result>HahiVana2@Nole</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_loginName" parent="v0">
|
<case id="v0_loginName" parent="v0">
|
||||||
<siteVariant>Login</siteVariant>
|
<keyPurpose>Identification</keyPurpose>
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>lozwajave</result>
|
<result>lozwajave</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_securityAnswer" parent="v0">
|
<case id="v0_securityAnswer" parent="v0">
|
||||||
<siteVariant>Answer</siteVariant>
|
<keyPurpose>Recovery</keyPurpose>
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>miy lirfijoja dubu</result>
|
<result>miy lirfijoja dubu</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
|
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
|
||||||
<siteContext>question</siteContext>
|
<keyContext>question</keyContext>
|
||||||
<result>movm bex gevrica jaf</result>
|
<result>movm bex gevrica jaf</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_type_maximum" parent="v0">
|
<case id="v0_type_maximum" parent="v0">
|
||||||
<siteType>GeneratedMaximum</siteType>
|
<resultType>GeneratedMaximum</resultType>
|
||||||
<result>w1!3bA3icmRAc)SS@lwl</result>
|
<result>w1!3bA3icmRAc)SS@lwl</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_type_medium" parent="v0">
|
<case id="v0_type_medium" parent="v0">
|
||||||
<siteType>GeneratedMedium</siteType>
|
<resultType>GeneratedMedium</resultType>
|
||||||
<result>Fej7]Jug</result>
|
<result>Fej7]Jug</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_type_basic" parent="v0">
|
<case id="v0_type_basic" parent="v0">
|
||||||
<siteType>GeneratedBasic</siteType>
|
<resultType>GeneratedBasic</resultType>
|
||||||
<result>wvH7irC1</result>
|
<result>wvH7irC1</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_type_short" parent="v0">
|
<case id="v0_type_short" parent="v0">
|
||||||
<siteType>GeneratedShort</siteType>
|
<resultType>GeneratedShort</resultType>
|
||||||
<result>Fej7</result>
|
<result>Fej7</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_type_pin" parent="v0">
|
<case id="v0_type_pin" parent="v0">
|
||||||
<siteType>GeneratedPIN</siteType>
|
<resultType>GeneratedPIN</resultType>
|
||||||
<result>2117</result>
|
<result>2117</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_type_name" parent="v0">
|
<case id="v0_type_name" parent="v0">
|
||||||
<siteType>GeneratedName</siteType>
|
<resultType>GeneratedName</resultType>
|
||||||
<result>fejrajugo</result>
|
<result>fejrajugo</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_type_phrase" parent="v0">
|
<case id="v0_type_phrase" parent="v0">
|
||||||
<siteType>GeneratedPhrase</siteType>
|
<resultType>GeneratedPhrase</resultType>
|
||||||
<result>fejr jug gabsibu bax</result>
|
<result>fejr jug gabsibu bax</result>
|
||||||
</case>
|
</case>
|
||||||
<case id="v0_counter_ceiling" parent="v0">
|
<case id="v0_counter_ceiling" parent="v0">
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/Users/lhunath/annex/secret/release-com.lyndir.masterpassword.jks
|
|
||||||
2
platform-darwin/External/InAppSettingsKit
vendored
2
platform-darwin/External/InAppSettingsKit
vendored
Submodule platform-darwin/External/InAppSettingsKit updated: b58b72563a...2dcb598d18
2
platform-darwin/External/Pearl
vendored
2
platform-darwin/External/Pearl
vendored
Submodule platform-darwin/External/Pearl updated: 789d5c80dc...f0a66e94e8
1
platform-darwin/External/libjson-c
vendored
Submodule
1
platform-darwin/External/libjson-c
vendored
Submodule
Submodule platform-darwin/External/libjson-c added at fcad0ec015
1
platform-darwin/External/libsodium
vendored
Submodule
1
platform-darwin/External/libsodium
vendored
Submodule
Submodule platform-darwin/External/libsodium added at 4809639ae1
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,8 @@
|
|||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "YES"
|
||||||
|
customWorkingDirectory = "/Users/lhunath/Documents/workspace/lyndir/MasterPassword/platform-independent/cli-c"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : 9223372036854775807,
|
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : 9223372036854775807,
|
||||||
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : 9223372036854775807,
|
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : 9223372036854775807,
|
||||||
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : 9223372036854775807,
|
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : 9223372036854775807,
|
||||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 9223372036854775807,
|
|
||||||
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : 9223372036854775807,
|
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : 9223372036854775807,
|
||||||
|
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 9223372036854775807,
|
||||||
|
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : 9223372036854775807,
|
||||||
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : 0
|
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : 0
|
||||||
},
|
},
|
||||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1DC75A27-0030-4493-ACE8-E1D49AB9A549",
|
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1DC75A27-0030-4493-ACE8-E1D49AB9A549",
|
||||||
@@ -20,8 +21,9 @@
|
|||||||
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : "MasterPassword\/platform-darwin\/External\/jrswizzle\/",
|
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : "MasterPassword\/platform-darwin\/External\/jrswizzle\/",
|
||||||
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : "MasterPassword\/platform-darwin\/External\/InAppSettingsKit\/",
|
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : "MasterPassword\/platform-darwin\/External\/InAppSettingsKit\/",
|
||||||
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : "MasterPassword\/platform-darwin\/External\/uicolor-utilities\/",
|
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : "MasterPassword\/platform-darwin\/External\/uicolor-utilities\/",
|
||||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/platform-darwin\/External\/AttributedMarkdown\/",
|
|
||||||
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : "MasterPassword\/platform-darwin\/External\/Pearl\/",
|
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : "MasterPassword\/platform-darwin\/External\/Pearl\/",
|
||||||
|
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/platform-darwin\/External\/AttributedMarkdown\/",
|
||||||
|
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : "MasterPassword\/platform-darwin\/External\/libsodium\/",
|
||||||
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : "MasterPassword\/"
|
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : "MasterPassword\/"
|
||||||
},
|
},
|
||||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "MasterPassword",
|
"DVTSourceControlWorkspaceBlueprintNameKey" : "MasterPassword",
|
||||||
@@ -58,6 +60,11 @@
|
|||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4DDCFFD91B41F00326AD14553BD66CFD366ABD91"
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4DDCFFD91B41F00326AD14553BD66CFD366ABD91"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/jedisct1\/libsodium.git",
|
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||||
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "81A28796384A028E6C2D47C039DB8B3E5DD6D0FC"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/jonmarimba\/jrswizzle.git",
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git:\/\/github.com\/jonmarimba\/jrswizzle.git",
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||||
|
|||||||
@@ -35,11 +35,15 @@ setSettingWithTitle() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
description=$(git describe --always --dirty --long --match '*-release')
|
case $PLATFORM_NAME in
|
||||||
version=${description%-g*}
|
macosx) platform=mac ;;
|
||||||
release=${version%%-*} build=${version##*-}
|
iphone*) platform=ios ;;
|
||||||
printf -v version '%s.%d' "$release" "$build"
|
*) ftl 'ERROR: Unknown platform: %s.' "$PLATFORM_NAME"; exit 1 ;;
|
||||||
printf -v commit '%s' "${description##*-g}"
|
esac
|
||||||
|
|
||||||
|
description=$(git describe --always --dirty --long --match "*-$platform*")
|
||||||
|
version=${description%-g*} build=${version##*-} version=${version%-$build}
|
||||||
|
version=${version//-$platform/} version=${version//-/.} commit=${description##*-g}
|
||||||
|
|
||||||
addPlistWithKey GITDescription string "$description"
|
addPlistWithKey GITDescription string "$description"
|
||||||
setPlistWithKey CFBundleVersion "$(hr "${version%%.*}" 14).${version#*.}"
|
setPlistWithKey CFBundleVersion "$(hr "${version%%.*}" 14).${version#*.}"
|
||||||
@@ -51,12 +55,14 @@ setSettingWithTitle "Copyright" "$(getPlistWithKey NSHumanReadableCopyright)"
|
|||||||
|
|
||||||
if [[ $DEPLOYMENT_LOCATION = YES ]]; then
|
if [[ $DEPLOYMENT_LOCATION = YES ]]; then
|
||||||
# This build is a release. Do some release checks.
|
# This build is a release. Do some release checks.
|
||||||
crashlyticsPlist="$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/Crashlytics.plist"
|
fabricPlist="$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/Fabric.plist"
|
||||||
passed=1
|
passed=1
|
||||||
[[ $description != *-dirty ]] || \
|
[[ $description != *-dirty ]] || \
|
||||||
{ passed=0; err 'ERROR: Cannot release a dirty version, first commit any changes.'; }
|
{ passed=0; err 'ERROR: Cannot release a dirty version, first commit any changes.'; }
|
||||||
[[ -r "$crashlyticsPlist" && $(PlistBuddy -c "Print :'API Key'" "$crashlyticsPlist" 2>/dev/null) ]] || \
|
[[ $build == 0 ]] || \
|
||||||
{ passed=0; err 'ERROR: Cannot release: Crashlytics API key is missing.'; }
|
{ passed=0; err 'ERROR: Commit is not tagged for release, first tag accordingly.'; }
|
||||||
|
[[ -r "$fabricPlist" && $(PlistBuddy -c "Print :'API Key'" "$fabricPlist" 2>/dev/null) ]] || \
|
||||||
|
{ passed=0; err 'ERROR: Cannot release: Fabric API key is missing.'; }
|
||||||
(( passed )) || \
|
(( passed )) || \
|
||||||
{ ftl "Failed to pass release checks. Fix the above errors and re-try. Aborting."; exit 1; }
|
{ ftl "Failed to pass release checks. Fix the above errors and re-try. Aborting."; exit 1; }
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPKey.h"
|
#import "MPKey.h"
|
||||||
#import "MPStoredSiteEntity.h"
|
#import "MPStoredSiteEntity+CoreDataClass.h"
|
||||||
#import "MPGeneratedSiteEntity.h"
|
#import "MPGeneratedSiteEntity+CoreDataClass.h"
|
||||||
#import "MPSiteQuestionEntity.h"
|
#import "MPSiteQuestionEntity+CoreDataClass.h"
|
||||||
#import "mpw-algorithm.h"
|
#import "mpw-algorithm.h"
|
||||||
|
|
||||||
#define MPAlgorithmDefaultVersion MPAlgorithmVersionCurrent
|
#define MPAlgorithmDefaultVersion MPAlgorithmVersionCurrent
|
||||||
@@ -49,7 +49,7 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
|||||||
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
||||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
||||||
|
|
||||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
- (NSData *)keyIDForKey:(MPMasterKey)masterKey;
|
||||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||||
|
|
||||||
- (NSString *)nameOfType:(MPSiteType)type;
|
- (NSString *)nameOfType:(MPSiteType)type;
|
||||||
@@ -58,6 +58,7 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
|||||||
- (Class)classOfType:(MPSiteType)type;
|
- (Class)classOfType:(MPSiteType)type;
|
||||||
- (NSArray *)allTypes;
|
- (NSArray *)allTypes;
|
||||||
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
|
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
|
||||||
|
- (MPSiteType)defaultType;
|
||||||
- (MPSiteType)nextType:(MPSiteType)type;
|
- (MPSiteType)nextType:(MPSiteType)type;
|
||||||
- (MPSiteType)previousType:(MPSiteType)type;
|
- (MPSiteType)previousType:(MPSiteType)type;
|
||||||
|
|
||||||
|
|||||||
@@ -24,26 +24,20 @@
|
|||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
#import "MPAppDelegate_InApp.h"
|
#import "MPAppDelegate_InApp.h"
|
||||||
#import "mpw-util.h"
|
#import "mpw-util.h"
|
||||||
#include <openssl/bn.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
|
|
||||||
/* An AMD HD 7970 calculates 2495M SHA-1 hashes per second at a cost of ~350$ per GPU */
|
/* An AMD HD 7970 calculates 2495M SHA-1 hashes per second at a cost of ~350$ per GPU */
|
||||||
#define CRACKING_PER_SECOND 2495000000UL
|
#define CRACKING_PER_SECOND 2495000000UL
|
||||||
#define CRACKING_PRICE 350
|
#define CRACKING_PRICE 350
|
||||||
|
|
||||||
NSOperationQueue *_mpwQueue = nil;
|
static NSOperationQueue *_mpwQueue = nil;
|
||||||
|
|
||||||
@implementation MPAlgorithmV0 {
|
@implementation MPAlgorithmV0
|
||||||
BN_CTX *_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)init {
|
- (id)init {
|
||||||
|
|
||||||
if (!(self = [super init]))
|
if (!(self = [super init]))
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
_ctx = BN_CTX_new();
|
|
||||||
|
|
||||||
static dispatch_once_t once = 0;
|
static dispatch_once_t once = 0;
|
||||||
dispatch_once( &once, ^{
|
dispatch_once( &once, ^{
|
||||||
_mpwQueue = [NSOperationQueue new];
|
_mpwQueue = [NSOperationQueue new];
|
||||||
@@ -54,12 +48,6 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
|
|
||||||
BN_CTX_free( _ctx );
|
|
||||||
_ctx = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (MPAlgorithmVersion)version {
|
- (MPAlgorithmVersion)version {
|
||||||
|
|
||||||
return MPAlgorithmVersion0;
|
return MPAlgorithmVersion0;
|
||||||
@@ -105,7 +93,7 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
||||||
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
|
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
|
||||||
if (!migrationSites) {
|
if (!migrationSites) {
|
||||||
err( @"While looking for sites to migrate: %@", [error fullDescription] );
|
MPError( error, @"While looking for sites to migrate." );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,21 +128,21 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
__block NSData *keyData;
|
__block NSData *keyData;
|
||||||
[self mpw_perform:^{
|
[self mpw_perform:^{
|
||||||
NSDate *start = [NSDate date];
|
NSDate *start = [NSDate date];
|
||||||
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] );
|
MPMasterKey masterKey = mpw_masterKey( fullName.UTF8String, masterPassword.UTF8String, [self version] );
|
||||||
if (masterKeyBytes) {
|
if (masterKey) {
|
||||||
keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen];
|
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 keyIDForKeyData:keyData], -[start timeIntervalSinceNow] );
|
fullName, masterPassword, [self keyIDForKey:masterKey], -[start timeIntervalSinceNow] );
|
||||||
mpw_free( masterKeyBytes, MP_dkLen );
|
mpw_free( masterKey, MPMasterKeySize );
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return keyData;
|
return keyData;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)keyIDForKeyData:(NSData *)keyData {
|
- (NSData *)keyIDForKey:(MPMasterKey)masterKey {
|
||||||
|
|
||||||
return [keyData hashWith:PearlHashSHA256];
|
return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)nameOfType:(MPSiteType)type {
|
- (NSString *)nameOfType:(MPSiteType)type {
|
||||||
@@ -298,6 +286,11 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
return allTypes;
|
return allTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (MPSiteType)defaultType {
|
||||||
|
|
||||||
|
return MPSiteTypeGeneratedLong;
|
||||||
|
}
|
||||||
|
|
||||||
- (MPSiteType)nextType:(MPSiteType)type {
|
- (MPSiteType)nextType:(MPSiteType)type {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -321,9 +314,9 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
return MPSiteTypeStoredDevicePrivate;
|
return MPSiteTypeStoredDevicePrivate;
|
||||||
case MPSiteTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return MPSiteTypeGeneratedPhrase;
|
return MPSiteTypeGeneratedPhrase;
|
||||||
default:
|
|
||||||
return MPSiteTypeGeneratedLong;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [self defaultType];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPSiteType)previousType:(MPSiteType)type {
|
- (MPSiteType)previousType:(MPSiteType)type {
|
||||||
@@ -338,28 +331,28 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
||||||
variant:MPSiteVariantLogin context:nil usingKey:key];
|
variant:MPKeyPurposeIdentification context:nil usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
usingKey:(MPKey *)key {
|
usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
||||||
variant:MPSiteVariantPassword context:nil usingKey:key];
|
variant:MPKeyPurposeAuthentication context:nil usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
|
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
|
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
|
||||||
variant:MPSiteVariantAnswer context:question usingKey:key];
|
variant:MPKeyPurposeRecovery context:question usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
__block NSString *content;
|
__block NSString *content = nil;
|
||||||
[self mpw_perform:^{
|
[self mpw_perform:^{
|
||||||
char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes,
|
char const *contentBytes = mpw_passwordForSite( [key keyForAlgorithm:self],
|
||||||
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
|
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
|
||||||
if (contentBytes) {
|
if (contentBytes) {
|
||||||
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
|
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
|
||||||
@@ -403,7 +396,7 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
encryptWithSymmetricKey:encryptionKey padding:YES];
|
||||||
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
|
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
|
||||||
@@ -419,7 +412,7 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
encryptWithSymmetricKey:encryptionKey padding:YES];
|
||||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||||
@@ -427,9 +420,9 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
[PearlKeyChain deleteItemForQuery:siteQuery];
|
[PearlKeyChain deleteItemForQuery:siteQuery];
|
||||||
else
|
else
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
||||||
(__bridge id)kSecValueData: encryptedContent,
|
(__bridge id)kSecValueData : encryptedContent,
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||||
#endif
|
#endif
|
||||||
}];
|
}];
|
||||||
((MPStoredSiteEntity *)site).contentObject = nil;
|
((MPStoredSiteEntity *)site).contentObject = nil;
|
||||||
@@ -442,58 +435,38 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
|
|
||||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
dispatch_group_enter( group );
|
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
__block NSString *result = nil;
|
setResult( result_ );
|
||||||
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
}];
|
||||||
result = result_;
|
} );
|
||||||
dispatch_group_leave( group );
|
|
||||||
}];
|
|
||||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
dispatch_group_enter( group );
|
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
__block NSString *result = nil;
|
setResult( result_ );
|
||||||
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
}];
|
||||||
result = result_;
|
} );
|
||||||
dispatch_group_leave( group );
|
|
||||||
}];
|
|
||||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
dispatch_group_enter( group );
|
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
__block NSString *result = nil;
|
setResult( result_ );
|
||||||
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
|
}];
|
||||||
result = result_;
|
} );
|
||||||
dispatch_group_leave( group );
|
|
||||||
}];
|
|
||||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
|
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
return PearlAwait( ^(void (^setResult)(id)) {
|
||||||
dispatch_group_enter( group );
|
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
||||||
__block NSString *result = nil;
|
setResult( result_ );
|
||||||
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
}];
|
||||||
result = result_;
|
} );
|
||||||
dispatch_group_leave( group );
|
|
||||||
}];
|
|
||||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
@@ -510,10 +483,12 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
else
|
else
|
||||||
algorithm = site.algorithm;
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
if (!loginGenerated || [loginName length])
|
||||||
resultBlock( loginName || !loginGenerated? loginName:
|
resultBlock( loginName );
|
||||||
[algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
else
|
||||||
} );
|
PearlNotMainQueue( ^{
|
||||||
|
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
@@ -545,9 +520,8 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
else
|
else
|
||||||
algorithm = site.algorithm;
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
PearlNotMainQueue( ^{
|
||||||
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
|
resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
|
||||||
resultBlock( result );
|
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -561,9 +535,8 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
|
|
||||||
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
|
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
PearlNotMainQueue( ^{
|
||||||
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
||||||
resultBlock( result );
|
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -575,9 +548,8 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
PearlNotMainQueue( ^{
|
||||||
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
||||||
resultBlock( result );
|
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -596,9 +568,8 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
else
|
else
|
||||||
algorithm = site.algorithm;
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
PearlNotMainQueue( ^{
|
||||||
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey];
|
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
|
||||||
resultBlock( result );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,9 +588,8 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
|
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
|
||||||
algorithm = question.site.algorithm;
|
algorithm = question.site.algorithm;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
PearlNotMainQueue( ^{
|
||||||
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
|
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
|
||||||
resultBlock( result );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -743,7 +713,7 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
return nil;
|
return nil;
|
||||||
NSData *decryptedContent = nil;
|
NSData *decryptedContent = nil;
|
||||||
if ([encryptedContent length]) {
|
if ([encryptedContent length]) {
|
||||||
NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
NSData *encryptionKey = [key keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
|
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
|
||||||
}
|
}
|
||||||
if (!decryptedContent)
|
if (!decryptedContent)
|
||||||
@@ -761,18 +731,17 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
if (!templates)
|
if (!templates)
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
BIGNUM *permutations = BN_new(), *templatePermutations = BN_new();
|
NSDecimalNumber *permutations = [NSDecimalNumber zero], *templatePermutations;
|
||||||
for (size_t t = 0; t < count; ++t) {
|
for (size_t t = 0; t < count; ++t) {
|
||||||
const char *template = templates[t];
|
const char *template = templates[t];
|
||||||
BN_one( templatePermutations );
|
templatePermutations = [NSDecimalNumber one];
|
||||||
|
|
||||||
for (NSUInteger c = 0; c < strlen( template ); ++c)
|
for (NSUInteger c = 0; c < strlen( template ); ++c)
|
||||||
BN_mul_word( templatePermutations,
|
templatePermutations = [templatePermutations decimalNumberByMultiplyingBy:
|
||||||
(BN_ULONG)strlen( mpw_charactersInClass( template[c] ) ) );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:strlen( mpw_charactersInClass( template[c] ) )]];
|
||||||
|
|
||||||
BN_add( permutations, permutations, templatePermutations );
|
permutations = [permutations decimalNumberByAdding:templatePermutations];
|
||||||
}
|
}
|
||||||
BN_free( templatePermutations );
|
|
||||||
free( templates );
|
free( templates );
|
||||||
|
|
||||||
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
|
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
|
||||||
@@ -780,114 +749,108 @@ NSOperationQueue *_mpwQueue = nil;
|
|||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker {
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker {
|
||||||
|
|
||||||
BIGNUM *permutations = BN_new();
|
NSDecimalNumber *permutations = [NSDecimalNumber one];
|
||||||
BN_one( permutations );
|
|
||||||
|
|
||||||
for (NSUInteger c = 0; c < [password length]; ++c) {
|
for (NSUInteger c = 0; c < [password length]; ++c) {
|
||||||
const char passwordCharacter = [password substringWithRange:NSMakeRange( c, 1 )].UTF8String[0];
|
const char passwordCharacter = [password substringWithRange:NSMakeRange( c, 1 )].UTF8String[0];
|
||||||
|
|
||||||
unsigned int characterEntropy = 0;
|
unsigned long characterEntropy = 0;
|
||||||
for (NSString *characterClass in @[ @"v", @"c", @"a", @"x" ]) {
|
for (NSString *characterClass in @[ @"v", @"c", @"a", @"x" ]) {
|
||||||
char const *charactersForClass = mpw_charactersInClass( characterClass.UTF8String[0] );
|
char const *charactersForClass = mpw_charactersInClass( characterClass.UTF8String[0] );
|
||||||
|
|
||||||
if (strchr( charactersForClass, passwordCharacter )) {
|
if (strchr( charactersForClass, passwordCharacter )) {
|
||||||
// Found class for password character.
|
// Found class for password character.
|
||||||
characterEntropy = (BN_ULONG)strlen( charactersForClass );
|
characterEntropy = strlen( charactersForClass );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!characterEntropy)
|
if (!characterEntropy)
|
||||||
characterEntropy = 256 /* a byte */;
|
characterEntropy = 256 /* a byte */;
|
||||||
|
|
||||||
BN_mul_word( permutations, characterEntropy );
|
permutations = [permutations decimalNumberByMultiplyingBy:
|
||||||
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:characterEntropy]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
|
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack permutations:(BIGNUM *)permutations forAttacker:(MPAttacker)attacker {
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack permutations:(NSDecimalNumber *)permutations forAttacker:(MPAttacker)attacker {
|
||||||
|
|
||||||
// Determine base seconds needed to calculate the permutations.
|
// Determine base seconds needed to calculate the permutations.
|
||||||
BIGNUM *secondsToCrack = BN_dup( permutations );
|
NSDecimalNumber *secondsToCrack = [permutations decimalNumberByDividingBy:
|
||||||
BN_div_word( secondsToCrack, CRACKING_PER_SECOND );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PER_SECOND]];
|
||||||
|
|
||||||
// Modify seconds needed by applying our hardware budget.
|
// Modify seconds needed by applying our hardware budget.
|
||||||
switch (attacker) {
|
switch (attacker) {
|
||||||
case MPAttacker1:
|
case MPAttacker1:
|
||||||
break;
|
break;
|
||||||
case MPAttacker5K:
|
case MPAttacker5K:
|
||||||
BN_mul_word( secondsToCrack, CRACKING_PRICE );
|
secondsToCrack = [secondsToCrack decimalNumberByMultiplyingBy:
|
||||||
BN_div_word( secondsToCrack, 5000 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PRICE]];
|
||||||
|
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||||
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:5000]];
|
||||||
break;
|
break;
|
||||||
case MPAttacker20M:
|
case MPAttacker20M:
|
||||||
BN_mul_word( secondsToCrack, CRACKING_PRICE );
|
secondsToCrack = [secondsToCrack decimalNumberByMultiplyingBy:
|
||||||
BN_div_word( secondsToCrack, 20000000 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PRICE]];
|
||||||
|
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||||
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:20000000]];
|
||||||
break;
|
break;
|
||||||
case MPAttacker5B:
|
case MPAttacker5B:
|
||||||
BN_mul_word( secondsToCrack, CRACKING_PRICE );
|
secondsToCrack = [secondsToCrack decimalNumberByMultiplyingBy:
|
||||||
BN_div_word( secondsToCrack, 5000 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PRICE]];
|
||||||
BN_div_word( secondsToCrack, 1000000 );
|
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||||
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:5000]];
|
||||||
|
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||||
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:1000000]];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BIGNUM *max = BN_new();
|
NSDecimalNumber *ulong_max = (id)[[NSDecimalNumber alloc] initWithUnsignedLong:ULONG_MAX];
|
||||||
BN_set_word( max, (BN_ULONG)-1 );
|
|
||||||
|
|
||||||
BIGNUM *hoursToCrack = BN_dup( secondsToCrack );
|
NSDecimalNumber *hoursToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||||
BN_div_word( hoursToCrack, 3600 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:3600L]];
|
||||||
if (BN_cmp( hoursToCrack, max ) < 0)
|
if ([hoursToCrack compare:ulong_max] == NSOrderedAscending)
|
||||||
timeToCrack->hours = BN_get_word( hoursToCrack );
|
timeToCrack->hours = (unsigned long long)[hoursToCrack doubleValue];
|
||||||
else
|
else
|
||||||
timeToCrack->hours = (BN_ULONG)-1;
|
timeToCrack->hours = ULONG_MAX;
|
||||||
|
|
||||||
BIGNUM *daysToCrack = BN_dup( hoursToCrack );
|
NSDecimalNumber *daysToCrack = [hoursToCrack decimalNumberByDividingBy:
|
||||||
BN_div_word( daysToCrack, 24 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:24L]];
|
||||||
if (BN_cmp( daysToCrack, max ) < 0)
|
if ([daysToCrack compare:ulong_max] == NSOrderedAscending)
|
||||||
timeToCrack->days = BN_get_word( daysToCrack );
|
timeToCrack->days = (unsigned long long)[daysToCrack doubleValue];
|
||||||
else
|
else
|
||||||
timeToCrack->days = (BN_ULONG)-1;
|
timeToCrack->days = ULONG_MAX;
|
||||||
|
|
||||||
BIGNUM *weeksToCrack = BN_dup( daysToCrack );
|
NSDecimalNumber *weeksToCrack = [daysToCrack decimalNumberByDividingBy:
|
||||||
BN_div_word( weeksToCrack, 7 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:7L]];
|
||||||
if (BN_cmp( weeksToCrack, max ) < 0)
|
if ([weeksToCrack compare:ulong_max] == NSOrderedAscending)
|
||||||
timeToCrack->weeks = BN_get_word( weeksToCrack );
|
timeToCrack->weeks = (unsigned long long)[weeksToCrack doubleValue];
|
||||||
else
|
else
|
||||||
timeToCrack->weeks = (BN_ULONG)-1;
|
timeToCrack->weeks = ULONG_MAX;
|
||||||
|
|
||||||
BIGNUM *monthsToCrack = BN_dup( daysToCrack );
|
NSDecimalNumber *monthsToCrack = [daysToCrack decimalNumberByDividingBy:
|
||||||
BN_div_word( monthsToCrack, 31 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:31L]];
|
||||||
if (BN_cmp( monthsToCrack, max ) < 0)
|
if ([monthsToCrack compare:ulong_max] == NSOrderedAscending)
|
||||||
timeToCrack->months = BN_get_word( monthsToCrack );
|
timeToCrack->months = (unsigned long long)[monthsToCrack doubleValue];
|
||||||
else
|
else
|
||||||
timeToCrack->months = (BN_ULONG)-1;
|
timeToCrack->months = ULONG_MAX;
|
||||||
|
|
||||||
BIGNUM *yearsToCrack = BN_dup( daysToCrack );
|
NSDecimalNumber *yearsToCrack = [daysToCrack decimalNumberByDividingBy:
|
||||||
BN_div_word( yearsToCrack, 356 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:356L]];
|
||||||
if (BN_cmp( yearsToCrack, max ) < 0)
|
if ([yearsToCrack compare:ulong_max] == NSOrderedAscending)
|
||||||
timeToCrack->years = BN_get_word( yearsToCrack );
|
timeToCrack->years = (unsigned long long)[yearsToCrack doubleValue];
|
||||||
else
|
else
|
||||||
timeToCrack->years = (BN_ULONG)-1;
|
timeToCrack->years = ULONG_MAX;
|
||||||
|
|
||||||
BIGNUM *universesToCrack = BN_dup( yearsToCrack );
|
NSDecimalNumber *universesToCrack = [yearsToCrack decimalNumberByDividingBy:
|
||||||
BN_div_word( universesToCrack, 14000 );
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:14000L]];
|
||||||
BN_div_word( universesToCrack, 1000000 );
|
universesToCrack = [universesToCrack decimalNumberByDividingBy:
|
||||||
if (BN_cmp( universesToCrack, max ) < 0)
|
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:1000000L]];
|
||||||
timeToCrack->universes = BN_get_word( universesToCrack );
|
if ([universesToCrack compare:ulong_max] == NSOrderedAscending)
|
||||||
|
timeToCrack->universes = (unsigned long long)[universesToCrack doubleValue];
|
||||||
else
|
else
|
||||||
timeToCrack->universes = (BN_ULONG)-1;
|
timeToCrack->universes = ULONG_MAX;
|
||||||
|
|
||||||
for (unsigned long error = ERR_get_error(); error; error = ERR_get_error())
|
|
||||||
err( @"bignum error: %lu", error );
|
|
||||||
|
|
||||||
BN_free( max );
|
|
||||||
BN_free( permutations );
|
|
||||||
BN_free( secondsToCrack );
|
|
||||||
BN_free( hoursToCrack );
|
|
||||||
BN_free( daysToCrack );
|
|
||||||
BN_free( weeksToCrack );
|
|
||||||
BN_free( monthsToCrack );
|
|
||||||
BN_free( yearsToCrack );
|
|
||||||
BN_free( universesToCrack );
|
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,17 +25,20 @@
|
|||||||
#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid"
|
#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid"
|
||||||
#define MPProductFuel @"com.lyndir.masterpassword.products.fuel"
|
#define MPProductFuel @"com.lyndir.masterpassword.products.fuel"
|
||||||
|
|
||||||
#define MP_FUEL_HOURLY_RATE 30.f /* Tier 1 purchases/h ~> USD/h */
|
#define MP_FUEL_HOURLY_RATE 40.f /* payment in tier 1 purchases / h (≅ USD / h) */
|
||||||
|
|
||||||
@protocol MPInAppDelegate
|
@protocol MPInAppDelegate
|
||||||
|
|
||||||
- (void)updateWithProducts:(NSArray /* SKProduct */ *)products;
|
- (void)updateWithProducts:(NSDictionary<NSString *, SKProduct *> *)products
|
||||||
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction;
|
transactions:(NSDictionary<NSString *, SKPaymentTransaction *> *)transactions;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MPAppDelegate_Shared(InApp)
|
@interface MPAppDelegate_Shared(InApp)
|
||||||
|
|
||||||
|
- (NSDictionary<NSString *, SKProduct *> *)products;
|
||||||
|
- (NSDictionary<NSString *, SKPaymentTransaction *> *)transactions;
|
||||||
|
|
||||||
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate;
|
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||||
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate;
|
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,20 @@
|
|||||||
|
|
||||||
@implementation MPAppDelegate_Shared(InApp)
|
@implementation MPAppDelegate_Shared(InApp)
|
||||||
|
|
||||||
PearlAssociatedObjectProperty( NSArray*, Products, products );
|
PearlAssociatedObjectProperty( NSDictionary*, Products, products );
|
||||||
|
|
||||||
PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObservers );
|
PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObservers );
|
||||||
|
|
||||||
|
- (NSDictionary<NSString *, SKPaymentTransaction *> *)transactions {
|
||||||
|
|
||||||
|
NSMutableDictionary<NSString *, SKPaymentTransaction *> *transactions =
|
||||||
|
[NSMutableDictionary dictionaryWithCapacity:self.paymentQueue.transactions.count];
|
||||||
|
for (SKPaymentTransaction *transaction in self.paymentQueue.transactions)
|
||||||
|
transactions[transaction.payment.productIdentifier] = transaction;
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate {
|
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate {
|
||||||
|
|
||||||
if (!self.productObservers)
|
if (!self.productObservers)
|
||||||
@@ -33,7 +44,7 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
|||||||
[self.productObservers addObject:delegate];
|
[self.productObservers addObject:delegate];
|
||||||
|
|
||||||
if (self.products)
|
if (self.products)
|
||||||
[delegate updateWithProducts:self.products];
|
[delegate updateWithProducts:self.products transactions:[self transactions]];
|
||||||
else
|
else
|
||||||
[self reloadProducts];
|
[self reloadProducts];
|
||||||
}
|
}
|
||||||
@@ -75,7 +86,7 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
|||||||
// Consumable product.
|
// Consumable product.
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
#if ADHOC || DEBUG
|
#if DEBUG
|
||||||
// All features are unlocked for beta / debug / mac versions.
|
// All features are unlocked for beta / debug / mac versions.
|
||||||
return YES;
|
return YES;
|
||||||
#else
|
#else
|
||||||
@@ -93,19 +104,40 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
|||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
if (![[MPAppDelegate_Shared get] canMakePayments]) {
|
if (![[MPAppDelegate_Shared get] canMakePayments]) {
|
||||||
[PearlAlert showAlertWithTitle:@"Store Not Set Up" message:
|
[PearlAlert showAlertWithTitle:@"App Store Not Set Up" message:
|
||||||
@"Try logging using the App Store or from Settings."
|
@"Make sure your Apple ID is set under Settings -> iTunes & App Store, "
|
||||||
|
@"you have a payment method added to the account and purchases are"
|
||||||
|
@"not disabled under General -> Restrictions."
|
||||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||||
tappedButtonBlock:nil cancelTitle:@"Thanks" otherTitles:nil];
|
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
if (buttonIndex == alert.cancelButtonIndex)
|
||||||
|
// Cancel
|
||||||
|
return;
|
||||||
|
if (buttonIndex == alert.firstOtherButtonIndex) {
|
||||||
|
// Settings
|
||||||
|
[PearlLinks openSettingsStore];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try Anyway
|
||||||
|
[self performPurchaseProductWithIdentifier:productIdentifier quantity:quantity];
|
||||||
|
} cancelTitle:@"Cancel" otherTitles:@"Settings", @"Try Anyway", nil];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (SKProduct *product in self.products)
|
[self performPurchaseProductWithIdentifier:productIdentifier quantity:quantity];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)performPurchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity {
|
||||||
|
|
||||||
|
for (SKProduct *product in [self.products allValues])
|
||||||
if ([product.productIdentifier isEqualToString:productIdentifier]) {
|
if ([product.productIdentifier isEqualToString:productIdentifier]) {
|
||||||
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
|
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
|
||||||
payment.quantity = quantity;
|
if (payment) {
|
||||||
[[self paymentQueue] addPayment:payment];
|
payment.quantity = quantity;
|
||||||
|
[[self paymentQueue] addPayment:payment];
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,15 +146,22 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
|||||||
|
|
||||||
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
||||||
|
|
||||||
inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers );
|
if ([response.invalidProductIdentifiers count])
|
||||||
self.products = response.products;
|
inf( @"Invalid products: %@", response.invalidProductIdentifiers );
|
||||||
|
|
||||||
|
NSMutableDictionary *products = [NSMutableDictionary dictionaryWithCapacity:[response.products count]];
|
||||||
|
for (SKProduct *product in response.products)
|
||||||
|
products[product.productIdentifier] = product;
|
||||||
|
self.products = products;
|
||||||
|
|
||||||
for (id<MPInAppDelegate> productObserver in self.productObservers)
|
for (id<MPInAppDelegate> productObserver in self.productObservers)
|
||||||
[productObserver updateWithProducts:self.products];
|
[productObserver updateWithProducts:self.products transactions:[self transactions]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
|
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
|
||||||
|
|
||||||
|
MPError( error, @"StoreKit request (%@) failed.", request );
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
[PearlAlert showAlertWithTitle:@"Purchase Failed" message:
|
[PearlAlert showAlertWithTitle:@"Purchase Failed" message:
|
||||||
strf( @"%@\n\n%@", error.localizedDescription,
|
strf( @"%@\n\n%@", error.localizedDescription,
|
||||||
@@ -131,7 +170,6 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
|||||||
cancelTitle:@"OK" otherTitles:nil];
|
cancelTitle:@"OK" otherTitles:nil];
|
||||||
#else
|
#else
|
||||||
#endif
|
#endif
|
||||||
err( @"StoreKit request (%@) failed: %@", request, [error fullDescription] );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)requestDidFinish:(SKRequest *)request {
|
- (void)requestDidFinish:(SKRequest *)request {
|
||||||
@@ -145,23 +183,41 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
|||||||
|
|
||||||
for (SKPaymentTransaction *transaction in transactions) {
|
for (SKPaymentTransaction *transaction in transactions) {
|
||||||
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
|
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
|
||||||
|
|
||||||
switch (transaction.transactionState) {
|
switch (transaction.transactionState) {
|
||||||
case SKPaymentTransactionStatePurchased: {
|
case SKPaymentTransactionStatePurchased: {
|
||||||
inf( @"purchased: %@", transaction.payment.productIdentifier );
|
inf( @"Purchased: %@", transaction.payment.productIdentifier );
|
||||||
|
NSMutableDictionary *attributes = [NSMutableDictionary new];
|
||||||
|
|
||||||
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
|
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
|
||||||
float currentFuel = [[MPiOSConfig get].developmentFuelRemaining floatValue];
|
float currentFuel = [[MPiOSConfig get].developmentFuelRemaining floatValue];
|
||||||
float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE;
|
float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE;
|
||||||
[MPiOSConfig get].developmentFuelRemaining = @(currentFuel + purchasedFuel);
|
[MPiOSConfig get].developmentFuelRemaining = @(currentFuel + purchasedFuel);
|
||||||
if (![MPiOSConfig get].developmentFuelChecked || currentFuel < DBL_EPSILON)
|
if (![MPiOSConfig get].developmentFuelChecked || currentFuel < DBL_EPSILON)
|
||||||
[MPiOSConfig get].developmentFuelChecked = [NSDate date];
|
[MPiOSConfig get].developmentFuelChecked = [NSDate date];
|
||||||
|
[attributes addEntriesFromDictionary:@{
|
||||||
|
@"currentFuel" : @(currentFuel),
|
||||||
|
@"purchasedFuel": @(purchasedFuel),
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
||||||
forKey:transaction.payment.productIdentifier];
|
forKey:transaction.payment.productIdentifier];
|
||||||
[queue finishTransaction:transaction];
|
[queue finishTransaction:transaction];
|
||||||
|
|
||||||
|
if ([[MPConfig get].sendInfo boolValue]) {
|
||||||
|
#ifdef CRASHLYTICS
|
||||||
|
SKProduct *product = self.products[transaction.payment.productIdentifier];
|
||||||
|
for (int q = 0; q < transaction.payment.quantity; ++q)
|
||||||
|
[Answers logPurchaseWithPrice:product.price currency:[product.priceLocale objectForKey:NSLocaleCurrencyCode]
|
||||||
|
success:@YES itemName:product.localizedTitle itemType:@"InApp"
|
||||||
|
itemId:product.productIdentifier customAttributes:attributes];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SKPaymentTransactionStateRestored: {
|
case SKPaymentTransactionStateRestored: {
|
||||||
inf( @"restored: %@", transaction.payment.productIdentifier );
|
inf( @"Restored: %@", transaction.payment.productIdentifier );
|
||||||
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
||||||
forKey:transaction.payment.productIdentifier];
|
forKey:transaction.payment.productIdentifier];
|
||||||
[queue finishTransaction:transaction];
|
[queue finishTransaction:transaction];
|
||||||
@@ -171,21 +227,38 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
|||||||
case SKPaymentTransactionStateDeferred:
|
case SKPaymentTransactionStateDeferred:
|
||||||
break;
|
break;
|
||||||
case SKPaymentTransactionStateFailed:
|
case SKPaymentTransactionStateFailed:
|
||||||
err( @"Transaction failed: %@, reason: %@", transaction.payment.productIdentifier, [transaction.error fullDescription] );
|
MPError( transaction.error, @"Transaction failed: %@.", transaction.payment.productIdentifier );
|
||||||
[queue finishTransaction:transaction];
|
[queue finishTransaction:transaction];
|
||||||
|
|
||||||
|
if ([[MPConfig get].sendInfo boolValue]) {
|
||||||
|
#ifdef CRASHLYTICS
|
||||||
|
SKProduct *product = self.products[transaction.payment.productIdentifier];
|
||||||
|
[Answers logPurchaseWithPrice:product.price currency:[product.priceLocale objectForKey:NSLocaleCurrencyCode]
|
||||||
|
success:@NO itemName:product.localizedTitle itemType:@"InApp" itemId:product.productIdentifier
|
||||||
|
customAttributes:@{
|
||||||
|
@"state" : @"Failed",
|
||||||
|
@"quantity": @(transaction.payment.quantity),
|
||||||
|
@"reason" : [transaction.error localizedFailureReason]?: [transaction.error localizedDescription],
|
||||||
|
}];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id<MPInAppDelegate> productObserver in self.productObservers)
|
|
||||||
[productObserver updateWithTransaction:transaction];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![[NSUserDefaults standardUserDefaults] synchronize])
|
if (![[NSUserDefaults standardUserDefaults] synchronize])
|
||||||
wrn( @"Couldn't synchronize after transaction updates." );
|
wrn( @"Couldn't synchronize after transaction updates." );
|
||||||
|
|
||||||
|
NSMutableDictionary<NSString *, SKPaymentTransaction *> *allTransactions = [[self transactions] mutableCopy];
|
||||||
|
for (SKPaymentTransaction *transaction in transactions)
|
||||||
|
allTransactions[transaction.payment.productIdentifier] = transaction;
|
||||||
|
for (id<MPInAppDelegate> productObserver in self.productObservers)
|
||||||
|
[productObserver updateWithProducts:self.products transactions:allTransactions];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
|
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
|
||||||
|
|
||||||
err( @"StoreKit restore failed: %@", [error fullDescription] );
|
MPError( error, @"StoreKit restore failed." );
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -16,18 +16,19 @@
|
|||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
|
#import <Crashlytics/Answers.h>
|
||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
|
|
||||||
@interface MPAppDelegate_Shared()
|
@interface MPAppDelegate_Shared()
|
||||||
|
|
||||||
@property(strong, nonatomic) MPKey *key;
|
@property(strong, atomic) MPKey *key;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPAppDelegate_Shared(Key)
|
@implementation MPAppDelegate_Shared(Key)
|
||||||
|
|
||||||
static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigin *keyOrigin) {
|
- (NSDictionary *)createKeyQueryforUser:(MPUserEntity *)user origin:(out MPKeyOrigin *)keyOrigin {
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
if (user.touchID && kSecUseAuthenticationUI) {
|
if (user.touchID && kSecUseAuthenticationUI) {
|
||||||
@@ -37,17 +38,17 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
|||||||
CFErrorRef acError = NULL;
|
CFErrorRef acError = NULL;
|
||||||
id accessControl = (__bridge_transfer id)SecAccessControlCreateWithFlags( kCFAllocatorDefault,
|
id accessControl = (__bridge_transfer id)SecAccessControlCreateWithFlags( kCFAllocatorDefault,
|
||||||
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlTouchIDCurrentSet, &acError );
|
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlTouchIDCurrentSet, &acError );
|
||||||
if (!accessControl || acError)
|
if (!accessControl)
|
||||||
err( @"Could not use TouchID on this device: %@", acError );
|
MPError( (__bridge_transfer NSError *)acError, @"Could not use TouchID on this device." );
|
||||||
|
|
||||||
else
|
else
|
||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:@{
|
attributes:@{
|
||||||
(__bridge id)kSecAttrService : @"Saved Master Password",
|
(__bridge id)kSecAttrService : @"Saved Master Password",
|
||||||
(__bridge id)kSecAttrAccount : user.name?: @"",
|
(__bridge id)kSecAttrAccount : user.name?: @"",
|
||||||
(__bridge id)kSecAttrAccessControl : accessControl,
|
(__bridge id)kSecAttrAccessControl : accessControl,
|
||||||
(__bridge id)kSecUseAuthenticationUI : (__bridge id)kSecUseAuthenticationUIAllow,
|
(__bridge id)kSecUseAuthenticationUI: (__bridge id)kSecUseAuthenticationUIAllow,
|
||||||
(__bridge id)kSecUseOperationPrompt :
|
(__bridge id)kSecUseOperationPrompt :
|
||||||
strf( @"Access %@'s master password.", user.name ),
|
strf( @"Access %@'s master password.", user.name ),
|
||||||
}
|
}
|
||||||
matches:nil];
|
matches:nil];
|
||||||
@@ -59,10 +60,10 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
|||||||
|
|
||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:@{
|
attributes:@{
|
||||||
(__bridge id)kSecAttrService: @"Saved Master Password",
|
(__bridge id)kSecAttrService : @"Saved Master Password",
|
||||||
(__bridge id)kSecAttrAccount: user.name?: @"",
|
(__bridge id)kSecAttrAccount : user.name?: @"",
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
(__bridge id)kSecAttrAccessible : (__bridge id)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly?: kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
|
(__bridge id)kSecAttrAccessible: (__bridge id)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly?: kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
matches:nil];
|
matches:nil];
|
||||||
@@ -71,34 +72,44 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
|||||||
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
|
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||||
|
|
||||||
MPKeyOrigin keyOrigin;
|
MPKeyOrigin keyOrigin;
|
||||||
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
|
NSDictionary *keyQuery = [self createKeyQueryforUser:user origin:&keyOrigin];
|
||||||
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery];
|
id<MPAlgorithm> keyAlgorithm = user.algorithm;
|
||||||
if (!keyData) {
|
MPKey *key = [[MPKey alloc] initForFullName:user.name withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
|
||||||
|
return ![algorithm isEqual:keyAlgorithm]? nil:
|
||||||
|
PearlMainQueueAwait( (id)^{
|
||||||
|
return [PearlKeyChain dataOfItemForQuery:keyQuery];
|
||||||
|
} );
|
||||||
|
} keyOrigin:keyOrigin];
|
||||||
|
|
||||||
|
if ([key keyIDForAlgorithm:user.algorithm])
|
||||||
|
inf( @"Found key in keychain for user: %@", user.userID );
|
||||||
|
|
||||||
|
else {
|
||||||
inf( @"No key found in keychain for user: %@", user.userID );
|
inf( @"No key found in keychain for user: %@", user.userID );
|
||||||
return nil;
|
key = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
inf( @"Found key in keychain for user: %@", user.userID );
|
return key;
|
||||||
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm keyOrigin:keyOrigin];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||||
|
|
||||||
if (user.saveKey) {
|
if (user.saveKey) {
|
||||||
NSData *keyData = [self.key keyDataForAlgorithm:user.algorithm];
|
MPMasterKey masterKey = [self.key keyForAlgorithm:user.algorithm];
|
||||||
if (keyData) {
|
if (masterKey) {
|
||||||
[self forgetSavedKeyFor:user];
|
[self forgetSavedKeyFor:user];
|
||||||
|
|
||||||
inf( @"Saving key in keychain for user: %@", user.userID );
|
inf( @"Saving key in keychain for user: %@", user.userID );
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:createKeyQuery( user, YES, nil )
|
[PearlKeyChain addOrUpdateItemForQuery:[self createKeyQueryforUser:user origin:nil] withAttributes:@{
|
||||||
withAttributes:@{ (__bridge id)kSecValueData: keyData }];
|
(__bridge id)kSecValueData: [NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize]
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
|
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
|
||||||
|
|
||||||
OSStatus result = [PearlKeyChain deleteItemForQuery:createKeyQuery( user, NO, nil )];
|
OSStatus result = [PearlKeyChain deleteItemForQuery:[self createKeyQueryforUser:user origin:nil]];
|
||||||
if (result == noErr) {
|
if (result == noErr) {
|
||||||
inf( @"Removed key from keychain for user: %@", user.userID );
|
inf( @"Removed key from keychain for user: %@", user.userID );
|
||||||
|
|
||||||
@@ -168,6 +179,14 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
|||||||
else
|
else
|
||||||
dbg( @"Automatic login failed for user: %@", user.userID );
|
dbg( @"Automatic login failed for user: %@", user.userID );
|
||||||
|
|
||||||
|
if ([[MPConfig get].sendInfo boolValue]) {
|
||||||
|
#ifdef CRASHLYTICS
|
||||||
|
[Answers logLoginWithMethod:password? @"Password": @"Automatic" success:@NO customAttributes:@{
|
||||||
|
@"algorithm": @(user.algorithm.version),
|
||||||
|
}];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
inf( @"Logged in user: %@", user.userID );
|
inf( @"Logged in user: %@", user.userID );
|
||||||
@@ -189,8 +208,11 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
|||||||
@try {
|
@try {
|
||||||
if ([[MPConfig get].sendInfo boolValue]) {
|
if ([[MPConfig get].sendInfo boolValue]) {
|
||||||
#ifdef CRASHLYTICS
|
#ifdef CRASHLYTICS
|
||||||
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
|
|
||||||
[[Crashlytics sharedInstance] setUserName:user.userID];
|
[[Crashlytics sharedInstance] setUserName:user.userID];
|
||||||
|
|
||||||
|
[Answers logLoginWithMethod:password? @"Password": @"Automatic" success:@YES customAttributes:@{
|
||||||
|
@"algorithm": @(user.algorithm.version),
|
||||||
|
}];
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,28 +252,22 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
|||||||
NSString *content;
|
NSString *content;
|
||||||
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
|
while (!(content = [site.algorithm storedPasswordForSite:(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.
|
||||||
__block NSString *masterPassword = nil;
|
NSString *masterPassword = nil;
|
||||||
|
|
||||||
#ifdef PEARL_UIKIT
|
#ifdef PEARL_UIKIT
|
||||||
dispatch_group_t recoverPasswordGroup = dispatch_group_create();
|
masterPassword = PearlAwait( ^(void (^setResult)(id)) {
|
||||||
dispatch_group_enter( recoverPasswordGroup );
|
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
||||||
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
message:PearlString(
|
||||||
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
|
@"Your old master password is required to migrate the stored password for %@",
|
||||||
site.name )
|
site.name )
|
||||||
viewStyle:UIAlertViewStyleSecureTextInput
|
viewStyle:UIAlertViewStyleSecureTextInput
|
||||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||||
@try {
|
|
||||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||||
// Don't Migrate
|
setResult( nil );
|
||||||
return;
|
else
|
||||||
|
setResult( [alert_ textFieldAtIndex:0].text );
|
||||||
masterPassword = [alert_ textFieldAtIndex:0].text;
|
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
|
||||||
}
|
} );
|
||||||
@finally {
|
|
||||||
dispatch_group_leave( recoverPasswordGroup );
|
|
||||||
}
|
|
||||||
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
|
|
||||||
dispatch_group_wait( recoverPasswordGroup, DISPATCH_TIME_FOREVER );
|
|
||||||
#endif
|
#endif
|
||||||
if (!masterPassword)
|
if (!masterPassword)
|
||||||
// Don't Migrate
|
// Don't Migrate
|
||||||
|
|||||||
@@ -19,16 +19,18 @@
|
|||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
|
|
||||||
@interface MPAppDelegate_Shared : PearlAppDelegate
|
@interface MPAppDelegate_Shared : PearlAppDelegate
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
@interface MPAppDelegate_Shared : NSObject<PearlConfigDelegate>
|
@interface MPAppDelegate_Shared : NSObject<PearlConfigDelegate>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@property(strong, nonatomic, readonly) MPKey *key;
|
@property(strong, atomic, readonly) MPKey *key;
|
||||||
@property(strong, nonatomic, readonly) NSManagedObjectID *activeUserOID;
|
@property(strong, atomic, readonly) NSManagedObjectID *activeUserOID;
|
||||||
@property(strong, nonatomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
|
@property(strong, atomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
|
||||||
|
|
||||||
+ (instancetype)get;
|
+ (instancetype)get;
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
|
|
||||||
@interface MPAppDelegate_Shared()
|
@interface MPAppDelegate_Shared()
|
||||||
|
|
||||||
@property(strong, nonatomic) MPKey *key;
|
@property(strong, atomic) MPKey *key;
|
||||||
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
|
@property(strong, atomic) NSManagedObjectID *activeUserOID;
|
||||||
@property(strong, nonatomic) NSPersistentStoreCoordinator *storeCoordinator;
|
@property(strong, atomic) NSPersistentStoreCoordinator *storeCoordinator;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -74,11 +74,7 @@
|
|||||||
|
|
||||||
- (void)setActiveUser:(MPUserEntity *)activeUser {
|
- (void)setActiveUser:(MPUserEntity *)activeUser {
|
||||||
|
|
||||||
NSError *error;
|
self.activeUserOID = activeUser.permanentObjectID;
|
||||||
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
|
|
||||||
err( @"Failed to obtain a permanent object ID after setting active user: %@", [error fullDescription] );
|
|
||||||
|
|
||||||
self.activeUserOID = activeUser.objectID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)handleCoordinatorError:(NSError *)error {
|
- (void)handleCoordinatorError:(NSError *)error {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ 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;
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
||||||
- (void)deleteAndResetStore;
|
- (void)deleteAndResetStore;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
|
#import "mpw-marshall.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
||||||
@@ -131,6 +132,25 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock {
|
||||||
|
|
||||||
|
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
|
||||||
|
if (!privateManagedObjectContextIfReady)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
return PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, privateManagedObjectContextIfReady, nil,
|
||||||
|
^(id host, NSNotification *note) {
|
||||||
|
NSMutableDictionary *affectedObjects = [NSMutableDictionary new];
|
||||||
|
for (NSManagedObject *object in note.userInfo[NSInsertedObjectsKey])
|
||||||
|
affectedObjects[object.objectID] = NSInsertedObjectsKey;
|
||||||
|
for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey])
|
||||||
|
affectedObjects[object.objectID] = NSUpdatedObjectsKey;
|
||||||
|
for (NSManagedObject *object in note.userInfo[NSDeletedObjectsKey])
|
||||||
|
affectedObjects[object.objectID] = NSDeletedObjectsKey;
|
||||||
|
changedBlock( affectedObjects );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
|
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
|
||||||
|
|
||||||
[self loadStore];
|
[self loadStore];
|
||||||
@@ -196,19 +216,21 @@ 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+
|
||||||
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
|
||||||
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
|
else
|
||||||
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
|
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
||||||
[mainManagedObjectContext performBlock:^{
|
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
|
||||||
@try {
|
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainContext, NSNotification *note) {
|
||||||
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
|
[mainContext performBlock:^{
|
||||||
}
|
@try {
|
||||||
@catch (NSException *exception) {
|
[mainContext mergeChangesFromContextDidSaveNotification:note];
|
||||||
err( @"While merging changes:\n%@", [exception fullDescription] );
|
}
|
||||||
}
|
@catch (NSException *exception) {
|
||||||
}];
|
err( @"While merging changes:\n%@", [exception fullDescription] );
|
||||||
} );
|
}
|
||||||
|
}];
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
// Create a new store coordinator.
|
// Create a new store coordinator.
|
||||||
@@ -216,7 +238,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
NSURL *localStoreURL = [self localStoreURL];
|
NSURL *localStoreURL = [self localStoreURL];
|
||||||
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
|
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
|
||||||
withIntermediateDirectories:YES attributes:nil error:&error]) {
|
withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||||
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
|
MPError( error, @"Couldn't create our application support directory." );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
|
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
|
||||||
@@ -225,7 +247,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
NSInferMappingModelAutomaticallyOption : @YES,
|
NSInferMappingModelAutomaticallyOption : @YES,
|
||||||
STORE_OPTIONS
|
STORE_OPTIONS
|
||||||
} error:&error]) {
|
} error:&error]) {
|
||||||
err( @"Failed to open store: %@", [error fullDescription] );
|
MPError( error, @"Failed to open store." );
|
||||||
self.storeCorrupted = @YES;
|
self.storeCorrupted = @YES;
|
||||||
[self handleCoordinatorError:error];
|
[self handleCoordinatorError:error];
|
||||||
return;
|
return;
|
||||||
@@ -234,12 +256,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
|
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
|
||||||
|
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
||||||
|
[self.mainManagedObjectContext saveToStore];
|
||||||
|
} );
|
||||||
#else
|
#else
|
||||||
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
|
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
|
||||||
#endif
|
|
||||||
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
||||||
[self.mainManagedObjectContext saveToStore];
|
[self.mainManagedObjectContext saveToStore];
|
||||||
} );
|
} );
|
||||||
|
#endif
|
||||||
|
|
||||||
// Perform a data sanity check on the newly loaded store to find and fix any issues.
|
// Perform a data sanity check on the newly loaded store to find and fix any issues.
|
||||||
if ([[MPConfig get].checkInconsistency boolValue])
|
if ([[MPConfig get].checkInconsistency boolValue])
|
||||||
@@ -265,10 +290,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
for (NSPersistentStore *store in self.storeCoordinator.persistentStores) {
|
for (NSPersistentStore *store in self.storeCoordinator.persistentStores) {
|
||||||
if (![self.storeCoordinator removePersistentStore:store error:&error])
|
if (![self.storeCoordinator removePersistentStore:store error:&error])
|
||||||
err( @"Couldn't remove persistence store from coordinator: %@", [error fullDescription] );
|
MPError( error, @"Couldn't remove persistence store from coordinator." );
|
||||||
}
|
}
|
||||||
if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error])
|
if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error])
|
||||||
err( @"Couldn't remove persistence store at URL %@: %@", self.localStoreURL, [error fullDescription] );
|
MPError( error, @"Couldn't remove persistence store at URL %@.", self.localStoreURL );
|
||||||
|
|
||||||
[self loadStore];
|
[self loadStore];
|
||||||
}
|
}
|
||||||
@@ -286,7 +311,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
fetchRequest.entity = entity;
|
fetchRequest.entity = entity;
|
||||||
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
|
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
|
||||||
if (!objects) {
|
if (!objects) {
|
||||||
err( @"Failed to fetch %@ objects: %@", entity, [error fullDescription] );
|
MPError( error, @"Failed to fetch %@ objects.", entity );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +336,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
|
|
||||||
- (void)migrateStore {
|
- (void)migrateStore {
|
||||||
|
|
||||||
MPStoreMigrationLevel migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
|
MPStoreMigrationLevel migrationLevel = (MPStoreMigrationLevel)
|
||||||
|
[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
|
||||||
if (migrationLevel >= MPStoreMigrationLevelCurrent)
|
if (migrationLevel >= MPStoreMigrationLevelCurrent)
|
||||||
// Local store up-to-date.
|
// Local store up-to-date.
|
||||||
return;
|
return;
|
||||||
@@ -353,8 +379,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{ STORE_OPTIONS }
|
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{ STORE_OPTIONS }
|
||||||
toStore:newLocalStoreURL withOptions:@{ STORE_OPTIONS }
|
toStore:newLocalStoreURL withOptions:@{ STORE_OPTIONS }
|
||||||
model:nil error:&error]) {
|
error:&error]) {
|
||||||
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
|
MPError( error, @"Couldn't migrate the old store to the new location." );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,14 +426,52 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
NSMigratePersistentStoresAutomaticallyOption: @YES,
|
NSMigratePersistentStoresAutomaticallyOption: @YES,
|
||||||
NSInferMappingModelAutomaticallyOption : @YES,
|
NSInferMappingModelAutomaticallyOption : @YES,
|
||||||
STORE_OPTIONS
|
STORE_OPTIONS
|
||||||
} model:nil error:&error]) {
|
} error:&error]) {
|
||||||
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
|
MPError( error, @"Couldn't migrate the old store to the new location." );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//- (BOOL)migrateV3LocalStore {
|
||||||
|
//
|
||||||
|
// inf( @"Migrating V3 local store" );
|
||||||
|
// NSURL *localStoreURL = [self localStoreURL];
|
||||||
|
// if (![[NSFileManager defaultManager] fileExistsAtPath:localStoreURL.path isDirectory:NULL]) {
|
||||||
|
// inf( @"No V3 local store to migrate." );
|
||||||
|
// return YES;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// NSError *error = nil;
|
||||||
|
// NSDictionary<NSString *, id> *metadata = [NSPersistentStore metadataForPersistentStoreWithURL:localStoreURL error:&error];
|
||||||
|
// if (!metadata) {
|
||||||
|
// MPError( error, @"Couldn't inspect metadata for store: %@", localStoreURL );
|
||||||
|
// return NO;
|
||||||
|
// }
|
||||||
|
// NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:
|
||||||
|
// [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:metadata]];
|
||||||
|
// if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil
|
||||||
|
// URL:localStoreURL options:@{ STORE_OPTIONS }
|
||||||
|
// error:&error]) {
|
||||||
|
// MPError( error, @"Couldn't open V3 local store to migrate." );
|
||||||
|
// return NO;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||||
|
// [context performBlockAndWait:^{
|
||||||
|
// context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
||||||
|
// context.persistentStoreCoordinator = coordinator;
|
||||||
|
// for (MPStoredSiteEntity *storedSite in [[MPStoredSiteEntity fetchRequest] execute:&error]) {
|
||||||
|
// id contentObject = [storedSite valueForKey:@"contentObject"];
|
||||||
|
// if ([contentObject isKindOfClass:[NSData class]])
|
||||||
|
// storedSite.contentObject = contentObject;
|
||||||
|
// }
|
||||||
|
// }];
|
||||||
|
//
|
||||||
|
// return YES;
|
||||||
|
//}
|
||||||
|
|
||||||
#pragma mark - Utilities
|
#pragma mark - Utilities
|
||||||
|
|
||||||
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion {
|
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion {
|
||||||
@@ -436,10 +500,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
site.lastUsed = [NSDate date];
|
site.lastUsed = [NSDate date];
|
||||||
site.algorithm = algorithm;
|
site.algorithm = algorithm;
|
||||||
|
|
||||||
NSError *error = nil;
|
|
||||||
if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] error:&error])
|
|
||||||
err( @"Failed to obtain a permanent object ID after creating new site: %@", [error fullDescription] );
|
|
||||||
|
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
completion( site, context );
|
completion( site, context );
|
||||||
@@ -468,18 +528,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
newSite.algorithm = site.algorithm;
|
newSite.algorithm = site.algorithm;
|
||||||
newSite.loginName = site.loginName;
|
newSite.loginName = site.loginName;
|
||||||
|
|
||||||
NSError *error = nil;
|
|
||||||
if (![context obtainPermanentIDsForObjects:@[ newSite ] error:&error])
|
|
||||||
err( @"Failed to obtain a permanent object ID after changing object type: %@", [error fullDescription] );
|
|
||||||
|
|
||||||
[context deleteObject:site];
|
[context deleteObject:site];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.permanentObjectID];
|
||||||
site = newSite;
|
site = newSite;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.permanentObjectID];
|
||||||
return site;
|
return site;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,7 +572,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||||
options:(NSRegularExpressionOptions)0 error:&error];
|
options:(NSRegularExpressionOptions)0 error:&error];
|
||||||
if (error) {
|
if (error) {
|
||||||
err( @"Error loading the header pattern: %@", [error fullDescription] );
|
MPError( error, @"Error loading the header pattern." );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,19 +586,20 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
options:(NSRegularExpressionOptions)0 error:&error]
|
options:(NSRegularExpressionOptions)0 error:&error]
|
||||||
];
|
];
|
||||||
if (error) {
|
if (error) {
|
||||||
err( @"Error loading the site patterns: %@", [error fullDescription] );
|
MPError( error, @"Error loading the site patterns." );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse import data.
|
// Parse import data.
|
||||||
inf( @"Importing sites." );
|
inf( @"Importing sites." );
|
||||||
__block MPUserEntity *user = nil;
|
|
||||||
id<MPAlgorithm> importAlgorithm = nil;
|
|
||||||
NSUInteger importFormat = 0;
|
NSUInteger importFormat = 0;
|
||||||
|
__block MPUserEntity *user = nil;
|
||||||
NSUInteger importAvatar = NSNotFound;
|
NSUInteger importAvatar = NSNotFound;
|
||||||
NSString *importBundleVersion = nil, *importUserName = nil;
|
|
||||||
NSData *importKeyID = nil;
|
NSData *importKeyID = nil;
|
||||||
|
NSString *importBundleVersion = nil, *importUserName = nil;
|
||||||
|
id<MPAlgorithm> importAlgorithm = nil;
|
||||||
|
MPSiteType importDefaultType = (MPSiteType)0;
|
||||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||||
NSMutableSet *sitesToDelete = [NSMutableSet set];
|
NSMutableSet *sitesToDelete = [NSMutableSet set];
|
||||||
@@ -573,14 +630,22 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
||||||
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
|
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
|
||||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
|
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
|
||||||
if ([headerName isEqualToString:@"User Name"]) {
|
|
||||||
|
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;
|
importUserName = headerValue;
|
||||||
|
|
||||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
||||||
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
||||||
if (!users) {
|
if (!users) {
|
||||||
err( @"While looking for user: %@, error: %@", importUserName, [error fullDescription] );
|
MPError( error, @"While looking for user: %@.", importUserName );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
if ([users count] > 1) {
|
if ([users count] > 1) {
|
||||||
@@ -591,21 +656,18 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
user = [users lastObject];
|
user = [users lastObject];
|
||||||
dbg( @"Existing user? %@", [user debugDescription] );
|
dbg( @"Existing user? %@", [user debugDescription] );
|
||||||
}
|
}
|
||||||
|
if ([headerName isEqualToString:@"Avatar"])
|
||||||
|
importAvatar = (NSUInteger)[headerValue integerValue];
|
||||||
if ([headerName isEqualToString:@"Key ID"])
|
if ([headerName isEqualToString:@"Key ID"])
|
||||||
importKeyID = [headerValue decodeHex];
|
importKeyID = [headerValue decodeHex];
|
||||||
if ([headerName isEqualToString:@"Version"]) {
|
if ([headerName isEqualToString:@"Version"]) {
|
||||||
importBundleVersion = headerValue;
|
importBundleVersion = headerValue;
|
||||||
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
|
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
|
||||||
}
|
}
|
||||||
if ([headerName isEqualToString:@"Format"]) {
|
if ([headerName isEqualToString:@"Algorithm"])
|
||||||
importFormat = (NSUInteger)[headerValue integerValue];
|
importAlgorithm = MPAlgorithmForVersion( (MPAlgorithmVersion)[headerValue integerValue] );
|
||||||
if (importFormat >= [sitePatterns count]) {
|
if ([headerName isEqualToString:@"Default Type"])
|
||||||
err( @"Unsupported import format: %lu", (unsigned long)importFormat );
|
importDefaultType = (MPSiteType)[headerValue integerValue];
|
||||||
return MPImportResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ([headerName isEqualToString:@"Avatar"])
|
|
||||||
importAvatar = (NSUInteger)[headerValue integerValue];
|
|
||||||
if ([headerName isEqualToString:@"Passwords"]) {
|
if ([headerName isEqualToString:@"Passwords"]) {
|
||||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
if ([headerValue isEqualToString:@"VISIBLE"])
|
||||||
clearText = YES;
|
clearText = YES;
|
||||||
@@ -667,7 +729,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
|
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
|
||||||
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
|
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
|
||||||
if (!existingSites) {
|
if (!existingSites) {
|
||||||
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, [error fullDescription] );
|
MPError( error, @"Lookup of existing sites failed for site: %@, user: %@.", siteName, user.userID );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
if ([existingSites count]) {
|
if ([existingSites count]) {
|
||||||
@@ -709,6 +771,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
if (user) {
|
if (user) {
|
||||||
if (importAvatar != NSNotFound)
|
if (importAvatar != NSNotFound)
|
||||||
user.avatar = importAvatar;
|
user.avatar = importAvatar;
|
||||||
|
if (importDefaultType)
|
||||||
|
user.defaultType = importDefaultType;
|
||||||
dbg( @"Updating User: %@", [user debugDescription] );
|
dbg( @"Updating User: %@", [user debugDescription] );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -716,6 +780,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
user.name = importUserName;
|
user.name = importUserName;
|
||||||
user.algorithm = MPAlgorithmDefault;
|
user.algorithm = MPAlgorithmDefault;
|
||||||
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
|
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
|
||||||
|
user.defaultType = importDefaultType?: user.algorithm.defaultType;
|
||||||
if (importAvatar != NSNotFound)
|
if (importAvatar != NSNotFound)
|
||||||
user.avatar = importAvatar;
|
user.avatar = importAvatar;
|
||||||
dbg( @"Created User: %@", [user debugDescription] );
|
dbg( @"Created User: %@", [user debugDescription] );
|
||||||
@@ -776,68 +841,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
|||||||
MPUserEntity *activeUser = [self activeUserForMainThread];
|
MPUserEntity *activeUser = [self activeUserForMainThread];
|
||||||
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
|
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
|
||||||
|
|
||||||
// Header.
|
MPMarshalledUser exportUser = mpw_marshall_user( activeUser.name.UTF8String,
|
||||||
NSMutableString *export = [NSMutableString new];
|
[self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version );
|
||||||
[export appendFormat:@"# Master Password site export\n"];
|
exportUser.avatar = activeUser.avatar;
|
||||||
if (revealPasswords)
|
exportUser.defaultType = activeUser.defaultType;
|
||||||
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
|
exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970;
|
||||||
else
|
|
||||||
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
|
|
||||||
[export appendFormat:@"# \n"];
|
|
||||||
[export appendFormat:@"##\n"];
|
|
||||||
[export appendFormat:@"# User Name: %@\n", activeUser.name];
|
|
||||||
[export appendFormat:@"# Avatar: %lu\n", (unsigned long)activeUser.avatar];
|
|
||||||
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
|
|
||||||
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
|
|
||||||
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
|
|
||||||
[export appendFormat:@"# Format: 1\n"];
|
|
||||||
if (revealPasswords)
|
|
||||||
[export appendFormat:@"# Passwords: VISIBLE\n"];
|
|
||||||
else
|
|
||||||
[export appendFormat:@"# Passwords: PROTECTED\n"];
|
|
||||||
[export appendFormat:@"##\n"];
|
|
||||||
[export appendFormat:@"#\n"];
|
|
||||||
[export appendFormat:@"# Last Times Password Login\t Site\tSite\n"];
|
|
||||||
[export appendFormat:@"# used used type name\t name\tpassword\n"];
|
|
||||||
|
|
||||||
// Sites.
|
|
||||||
for (MPSiteEntity *site in activeUser.sites) {
|
for (MPSiteEntity *site in activeUser.sites) {
|
||||||
NSDate *lastUsed = site.lastUsed;
|
MPMarshalledSite exportSite = mpw_marshall_site( &exportUser,
|
||||||
NSUInteger uses = site.uses;
|
site.name.UTF8String, site.type, site.counter, site.algorithm.version );
|
||||||
MPSiteType type = site.type;
|
exportSite.loginName = site.loginName.UTF8String;
|
||||||
id<MPAlgorithm> algorithm = site.algorithm;
|
exportSite.url = site.url.UTF8String;
|
||||||
NSUInteger counter = 0;
|
exportSite.uses = site.uses;
|
||||||
NSString *loginName = site.loginName;
|
exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
|
||||||
NSString *siteName = site.name;
|
|
||||||
NSString *content = nil;
|
|
||||||
|
|
||||||
// Generated-specific
|
for (MPSiteQuestionEntity *siteQuestion in site.questions)
|
||||||
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String );
|
||||||
counter = ((MPGeneratedSiteEntity *)site).counter;
|
|
||||||
|
|
||||||
|
|
||||||
// Determine the content to export.
|
|
||||||
if (!(type & MPSiteFeatureDevicePrivate)) {
|
|
||||||
if (revealPasswords)
|
|
||||||
content = [site.algorithm resolvePasswordForSite:site usingKey:self.key];
|
|
||||||
else if (type & MPSiteFeatureExportContent)
|
|
||||||
content = [site.algorithm exportPasswordForSite:site usingKey:self.key];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *lastUsedExport = [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed];
|
|
||||||
long usesExport = (long)uses;
|
|
||||||
NSString *typeExport = strf( @"%lu:%lu:%lu", (long)type, (long)[algorithm version], (long)counter );
|
|
||||||
NSString *loginNameExport = loginName?: @"";
|
|
||||||
NSString *contentExport = content?: @"";
|
|
||||||
[export appendFormat:@"%@ %8ld %8S %25S\t%25S\t%@\n",
|
|
||||||
lastUsedExport, usesExport,
|
|
||||||
(const unsigned short *)[typeExport cStringUsingEncoding:NSUTF16StringEncoding],
|
|
||||||
(const unsigned short *)[loginNameExport cStringUsingEncoding:NSUTF16StringEncoding],
|
|
||||||
(const unsigned short *)[siteName cStringUsingEncoding:NSUTF16StringEncoding],
|
|
||||||
contentExport];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return export;
|
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser );
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPConfig.h
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 02/01/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "Pearl.h"
|
#import "Pearl.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPConfig.m
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 02/01/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
|
|
||||||
@@ -20,7 +30,7 @@
|
|||||||
[self.defaults registerDefaults:@{
|
[self.defaults registerDefaults:@{
|
||||||
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
|
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
|
||||||
|
|
||||||
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
|
NSStringFromSelector( @selector( sendInfo ) ) : @YES,
|
||||||
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
|
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
|
||||||
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
|
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
|
||||||
NSStringFromSelector( @selector( checkInconsistency ) ): @NO,
|
NSStringFromSelector( @selector( checkInconsistency ) ): @NO,
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPEntities.h
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword-iOS
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 31/05/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "MPSiteEntity.h"
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
#import "MPStoredSiteEntity.h"
|
#import "MPStoredSiteEntity+CoreDataClass.h"
|
||||||
#import "MPGeneratedSiteEntity.h"
|
#import "MPGeneratedSiteEntity+CoreDataClass.h"
|
||||||
#import "MPUserEntity.h"
|
#import "MPUserEntity+CoreDataClass.h"
|
||||||
#import "MPAlgorithm.h"
|
#import "MPAlgorithm.h"
|
||||||
#import "MPFixable.h"
|
#import "MPFixable.h"
|
||||||
|
|
||||||
@@ -22,6 +32,12 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface NSManagedObject(MP)
|
||||||
|
|
||||||
|
- (NSManagedObjectID *)permanentObjectID;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@interface MPSiteQuestionEntity(MP)
|
@interface MPSiteQuestionEntity(MP)
|
||||||
|
|
||||||
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key;
|
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key;
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPEntities.m
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword-iOS
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 31/05/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
@@ -16,25 +26,41 @@
|
|||||||
- (BOOL)saveToStore {
|
- (BOOL)saveToStore {
|
||||||
|
|
||||||
__block BOOL success = YES;
|
__block BOOL success = YES;
|
||||||
if ([self hasChanges]) {
|
if ([self hasChanges])
|
||||||
[self performBlockAndWait:^{
|
[self performBlockAndWait:^{
|
||||||
@try {
|
@try {
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
if (!(success = [self save:&error]))
|
if (!(success = [self save:&error]))
|
||||||
err( @"While saving: %@", [error fullDescription] );
|
MPError( error, @"While saving." );
|
||||||
}
|
}
|
||||||
@catch (NSException *exception) {
|
@catch (NSException *exception) {
|
||||||
success = NO;
|
success = NO;
|
||||||
err( @"While saving: %@", [exception fullDescription] );
|
err( @"While saving.\n%@", [exception fullDescription] );
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
|
||||||
|
|
||||||
return success && (!self.parentContext || [self.parentContext saveToStore]);
|
return success && (!self.parentContext || [self.parentContext saveToStore]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@implementation NSManagedObject(MP)
|
||||||
|
|
||||||
|
- (NSManagedObjectID *)permanentObjectID {
|
||||||
|
|
||||||
|
NSManagedObjectID *objectID = self.objectID;
|
||||||
|
if ([objectID isTemporaryID]) {
|
||||||
|
NSError *error = nil;
|
||||||
|
if (![self.managedObjectContext obtainPermanentIDsForObjects:@[ self ] error:&error])
|
||||||
|
MPError( error, @"Failed to obtain permanent object ID." );
|
||||||
|
objectID = self.objectID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectID.isTemporaryID? nil: objectID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation MPSiteQuestionEntity(MP)
|
@implementation MPSiteQuestionEntity(MP)
|
||||||
|
|
||||||
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key {
|
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key {
|
||||||
@@ -237,8 +263,8 @@
|
|||||||
// 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.",
|
||||||
self.name, self.user.name, (long)self.type, (long)MPSiteTypeGeneratedLong );
|
self.name, self.user.name, (long)self.type, (long)[self.algorithm defaultType] );
|
||||||
self.type = MPSiteTypeGeneratedLong;
|
self.type = [self.algorithm defaultType];
|
||||||
return MPFixableResultProblemsFixed;
|
return MPFixableResultProblemsFixed;
|
||||||
} );
|
} );
|
||||||
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
||||||
@@ -330,7 +356,7 @@
|
|||||||
|
|
||||||
- (MPSiteType)defaultType {
|
- (MPSiteType)defaultType {
|
||||||
|
|
||||||
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
|
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDefaultType:(MPSiteType)aDefaultType {
|
- (void)setDefaultType:(MPSiteType)aDefaultType {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/**
|
//==============================================================================
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
// This file is part of Master Password.
|
||||||
*
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPFixable.h
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MPFixable
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by lhunath on 2014-04-26.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/**
|
//==============================================================================
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
// This file is part of Master Password.
|
||||||
*
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPFixable.m
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MPFixable
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by lhunath on 2014-04-26.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPFixable.h"
|
#import "MPFixable.h"
|
||||||
|
|
||||||
|
|||||||
20
platform-darwin/Source/MPGeneratedSiteEntity+CoreDataClass.h
Normal file
20
platform-darwin/Source/MPGeneratedSiteEntity+CoreDataClass.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// MPGeneratedSiteEntity+CoreDataClass.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPGeneratedSiteEntity : MPSiteEntity
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#import "MPGeneratedSiteEntity+CoreDataProperties.h"
|
||||||
13
platform-darwin/Source/MPGeneratedSiteEntity+CoreDataClass.m
Normal file
13
platform-darwin/Source/MPGeneratedSiteEntity+CoreDataClass.m
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// MPGeneratedSiteEntity+CoreDataClass.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPGeneratedSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
@implementation MPGeneratedSiteEntity
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MPGeneratedSiteEntity+CoreDataProperties.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPGeneratedSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPGeneratedSiteEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPGeneratedSiteEntity *> *)fetchRequest;
|
||||||
|
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *counter_;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// MPGeneratedSiteEntity+CoreDataProperties.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPGeneratedSiteEntity+CoreDataProperties.h"
|
||||||
|
|
||||||
|
@implementation MPGeneratedSiteEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPGeneratedSiteEntity *> *)fetchRequest {
|
||||||
|
return [[NSFetchRequest alloc] initWithEntityName:@"MPGeneratedSiteEntity"];
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamic counter_;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// MPGeneratedSiteEntity.h
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
#import "MPSiteEntity.h"
|
|
||||||
|
|
||||||
@interface MPGeneratedSiteEntity : MPSiteEntity
|
|
||||||
|
|
||||||
@property(nonatomic, retain) NSNumber *counter_;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//
|
|
||||||
// MPGeneratedSiteEntity.m
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPGeneratedSiteEntity.h"
|
|
||||||
|
|
||||||
@implementation MPGeneratedSiteEntity
|
|
||||||
|
|
||||||
@dynamic counter_;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
/**
|
//==============================================================================
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
// This file is part of Master Password.
|
||||||
*
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPKey
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 16/07/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "MPAlgorithm.h"
|
#import "MPAlgorithm.h"
|
||||||
|
#import "mpw-types.h"
|
||||||
|
|
||||||
@protocol MPAlgorithm;
|
@protocol MPAlgorithm;
|
||||||
|
|
||||||
@@ -28,16 +30,15 @@ typedef NS_ENUM( NSUInteger, MPKeyOrigin ) {
|
|||||||
|
|
||||||
@interface MPKey : NSObject
|
@interface MPKey : NSObject
|
||||||
|
|
||||||
@property(nonatomic, readonly) NSString *fullName;
|
|
||||||
@property(nonatomic, readonly) MPKeyOrigin origin;
|
@property(nonatomic, readonly) MPKeyOrigin origin;
|
||||||
|
@property(nonatomic, readonly, copy) NSString *fullName;
|
||||||
|
|
||||||
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||||
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
|
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
|
||||||
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin;
|
keyOrigin:(MPKeyOrigin)origin;
|
||||||
|
|
||||||
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
|
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;
|
- (MPMasterKey)keyForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength;
|
|
||||||
|
|
||||||
- (BOOL)isEqualToKey:(MPKey *)key;
|
- (BOOL)isEqualToKey:(MPKey *)key;
|
||||||
|
|
||||||
|
|||||||
@@ -1,94 +1,83 @@
|
|||||||
/**
|
//==============================================================================
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
// This file is part of Master Password.
|
||||||
*
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPKey
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 16/07/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPAlgorithm.h"
|
#import "MPAlgorithm.h"
|
||||||
|
|
||||||
@interface MPKey()
|
@interface MPKey()
|
||||||
|
|
||||||
@property(nonatomic) NSString *fullName;
|
|
||||||
@property(nonatomic) MPKeyOrigin origin;
|
@property(nonatomic) MPKeyOrigin origin;
|
||||||
@property(nonatomic) NSString *masterPassword;
|
@property(nonatomic, copy) NSString *fullName;
|
||||||
|
@property(nonatomic, copy) NSData *( ^keyResolver )(id<MPAlgorithm>);
|
||||||
|
@property(nonatomic, strong) NSCache *keyCache;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPKey {
|
@implementation MPKey;
|
||||||
NSCache *_keyCache;
|
|
||||||
};
|
|
||||||
|
|
||||||
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
|
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
|
||||||
|
|
||||||
|
return [self initForFullName:fullName withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
|
||||||
|
return [algorithm keyDataForFullName:self.fullName withMasterPassword:masterPassword];
|
||||||
|
} keyOrigin:MPKeyOriginMasterPassword];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
|
||||||
|
keyOrigin:(MPKeyOrigin)origin {
|
||||||
|
|
||||||
if (!(self = [super init]))
|
if (!(self = [super init]))
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
_keyCache = [NSCache new];
|
self.keyCache = [NSCache new];
|
||||||
self.fullName = fullName;
|
|
||||||
self.origin = MPKeyOriginMasterPassword;
|
|
||||||
self.masterPassword = masterPassword;
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
|
|
||||||
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin {
|
|
||||||
|
|
||||||
if (!(self = [self initForFullName:fullName withMasterPassword:nil]))
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
self.origin = origin;
|
self.origin = origin;
|
||||||
[_keyCache setObject:keyData forKey:algorithm];
|
self.fullName = fullName;
|
||||||
|
self.keyResolver = keyResolver;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm {
|
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm {
|
||||||
|
|
||||||
return [algorithm keyIDForKeyData:[self keyDataForAlgorithm:algorithm]];
|
return [algorithm keyIDForKey:[self keyForAlgorithm:algorithm]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm {
|
- (MPMasterKey)keyForAlgorithm:(id<MPAlgorithm>)algorithm {
|
||||||
|
|
||||||
NSData *keyData = [_keyCache objectForKey:algorithm];
|
@synchronized (self) {
|
||||||
if (keyData)
|
NSData *keyData = [self.keyCache objectForKey:algorithm];
|
||||||
return keyData;
|
if (!keyData) {
|
||||||
|
keyData = self.keyResolver( algorithm );
|
||||||
|
if (keyData)
|
||||||
|
[self.keyCache setObject:keyData forKey:algorithm];
|
||||||
|
}
|
||||||
|
|
||||||
keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword];
|
return keyData.length == MPMasterKeySize? keyData.bytes: NULL;
|
||||||
if (keyData)
|
}
|
||||||
[_keyCache setObject:keyData forKey:algorithm];
|
|
||||||
|
|
||||||
return keyData;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
|
|
||||||
|
|
||||||
NSData *keyData = [self keyDataForAlgorithm:algorithm];
|
|
||||||
return [keyData subdataWithRange:NSMakeRange( 0, MIN( subKeyLength, keyData.length ) )];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqualToKey:(MPKey *)key {
|
- (BOOL)isEqualToKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword];
|
return [[self keyIDForAlgorithm:MPAlgorithmDefault] isEqualToData:[key keyIDForAlgorithm:MPAlgorithmDefault]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)object {
|
- (BOOL)isEqual:(id)object {
|
||||||
|
|
||||||
if (![object isKindOfClass:[MPKey class]])
|
return [object isKindOfClass:[MPKey class]] && [self isEqualToKey:object];
|
||||||
return NO;
|
|
||||||
|
|
||||||
return [self isEqualToKey:object];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
22
platform-darwin/Source/MPSiteEntity+CoreDataClass.h
Normal file
22
platform-darwin/Source/MPSiteEntity+CoreDataClass.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MPSiteEntity+CoreDataClass.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreData/CoreData.h>
|
||||||
|
|
||||||
|
@class MPSiteQuestionEntity, MPUserEntity, NSObject;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPSiteEntity : NSManagedObject
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#import "MPSiteEntity+CoreDataProperties.h"
|
||||||
16
platform-darwin/Source/MPSiteEntity+CoreDataClass.m
Normal file
16
platform-darwin/Source/MPSiteEntity+CoreDataClass.m
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// MPSiteEntity+CoreDataClass.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
|
#import "MPSiteQuestionEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
#import "MPUserEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
@implementation MPSiteEntity
|
||||||
|
|
||||||
|
@end
|
||||||
48
platform-darwin/Source/MPSiteEntity+CoreDataProperties.h
Normal file
48
platform-darwin/Source/MPSiteEntity+CoreDataProperties.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// MPSiteEntity+CoreDataProperties.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPSiteEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPSiteEntity *> *)fetchRequest;
|
||||||
|
|
||||||
|
@property (nullable, nonatomic, retain) NSObject *content;
|
||||||
|
@property (nullable, nonatomic, copy) NSDate *lastUsed;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *loginGenerated_;
|
||||||
|
@property (nullable, nonatomic, copy) NSString *loginName;
|
||||||
|
@property (nullable, nonatomic, copy) NSString *name;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *requiresExplicitMigration_;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *type_;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *uses_;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *version_;
|
||||||
|
@property (nullable, nonatomic, copy) NSString *url;
|
||||||
|
@property (nullable, nonatomic, retain) NSOrderedSet<MPSiteQuestionEntity *> *questions;
|
||||||
|
@property (nullable, nonatomic, retain) MPUserEntity *user;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPSiteEntity (CoreDataGeneratedAccessors)
|
||||||
|
|
||||||
|
- (void)insertObject:(MPSiteQuestionEntity *)value inQuestionsAtIndex:(NSUInteger)idx;
|
||||||
|
- (void)removeObjectFromQuestionsAtIndex:(NSUInteger)idx;
|
||||||
|
- (void)insertQuestions:(NSArray<MPSiteQuestionEntity *> *)value atIndexes:(NSIndexSet *)indexes;
|
||||||
|
- (void)removeQuestionsAtIndexes:(NSIndexSet *)indexes;
|
||||||
|
- (void)replaceObjectInQuestionsAtIndex:(NSUInteger)idx withObject:(MPSiteQuestionEntity *)value;
|
||||||
|
- (void)replaceQuestionsAtIndexes:(NSIndexSet *)indexes withQuestions:(NSArray<MPSiteQuestionEntity *> *)values;
|
||||||
|
- (void)addQuestionsObject:(MPSiteQuestionEntity *)value;
|
||||||
|
- (void)removeQuestionsObject:(MPSiteQuestionEntity *)value;
|
||||||
|
- (void)addQuestions:(NSOrderedSet<MPSiteQuestionEntity *> *)values;
|
||||||
|
- (void)removeQuestions:(NSOrderedSet<MPSiteQuestionEntity *> *)values;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
30
platform-darwin/Source/MPSiteEntity+CoreDataProperties.m
Normal file
30
platform-darwin/Source/MPSiteEntity+CoreDataProperties.m
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// MPSiteEntity+CoreDataProperties.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSiteEntity+CoreDataProperties.h"
|
||||||
|
|
||||||
|
@implementation MPSiteEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPSiteEntity *> *)fetchRequest {
|
||||||
|
return [[NSFetchRequest alloc] initWithEntityName:@"MPSiteEntity"];
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamic content;
|
||||||
|
@dynamic lastUsed;
|
||||||
|
@dynamic loginGenerated_;
|
||||||
|
@dynamic loginName;
|
||||||
|
@dynamic name;
|
||||||
|
@dynamic requiresExplicitMigration_;
|
||||||
|
@dynamic type_;
|
||||||
|
@dynamic uses_;
|
||||||
|
@dynamic version_;
|
||||||
|
@dynamic url;
|
||||||
|
@dynamic questions;
|
||||||
|
@dynamic user;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
//
|
|
||||||
// MPSiteEntity.h
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
|
|
||||||
@class MPSiteQuestionEntity, MPUserEntity;
|
|
||||||
|
|
||||||
@interface MPSiteEntity : NSManagedObject
|
|
||||||
|
|
||||||
//@property (nonatomic, retain) id content; // Hide here, reveal in MPStoredSiteEntity
|
|
||||||
@property(nonatomic, retain) NSDate *lastUsed;
|
|
||||||
@property(nonatomic, retain) NSNumber *loginGenerated_;
|
|
||||||
@property(nonatomic, retain) NSString *loginName;
|
|
||||||
@property(nonatomic, retain) NSString *name;
|
|
||||||
@property(nonatomic, retain) NSNumber *requiresExplicitMigration_;
|
|
||||||
@property(nonatomic, retain) NSNumber *type_;
|
|
||||||
@property(nonatomic, retain) NSNumber *uses_;
|
|
||||||
@property(nonatomic, retain) NSNumber *version_;
|
|
||||||
@property(nonatomic, retain) NSOrderedSet *questions;
|
|
||||||
@property(nonatomic, retain) MPUserEntity *user;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface MPSiteEntity(CoreDataGeneratedAccessors)
|
|
||||||
|
|
||||||
- (void)insertObject:(MPSiteQuestionEntity *)value inQuestionsAtIndex:(NSUInteger)idx;
|
|
||||||
- (void)removeObjectFromQuestionsAtIndex:(NSUInteger)idx;
|
|
||||||
- (void)insertQuestions:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
|
|
||||||
- (void)removeQuestionsAtIndexes:(NSIndexSet *)indexes;
|
|
||||||
- (void)replaceObjectInQuestionsAtIndex:(NSUInteger)idx withObject:(MPSiteQuestionEntity *)value;
|
|
||||||
- (void)replaceQuestionsAtIndexes:(NSIndexSet *)indexes withQuestions:(NSArray *)values;
|
|
||||||
- (void)addQuestionsObject:(MPSiteQuestionEntity *)value;
|
|
||||||
- (void)removeQuestionsObject:(MPSiteQuestionEntity *)value;
|
|
||||||
- (void)addQuestions:(NSOrderedSet *)values;
|
|
||||||
- (void)removeQuestions:(NSOrderedSet *)values;
|
|
||||||
@end
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// MPSiteEntity.m
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPSiteEntity.h"
|
|
||||||
#import "MPSiteQuestionEntity.h"
|
|
||||||
#import "MPUserEntity.h"
|
|
||||||
|
|
||||||
@implementation MPSiteEntity
|
|
||||||
|
|
||||||
//@dynamic content;
|
|
||||||
@dynamic lastUsed;
|
|
||||||
@dynamic loginGenerated_;
|
|
||||||
@dynamic loginName;
|
|
||||||
@dynamic name;
|
|
||||||
@dynamic requiresExplicitMigration_;
|
|
||||||
@dynamic type_;
|
|
||||||
@dynamic uses_;
|
|
||||||
@dynamic version_;
|
|
||||||
@dynamic questions;
|
|
||||||
@dynamic user;
|
|
||||||
|
|
||||||
@end
|
|
||||||
22
platform-darwin/Source/MPSiteQuestionEntity+CoreDataClass.h
Normal file
22
platform-darwin/Source/MPSiteQuestionEntity+CoreDataClass.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MPSiteQuestionEntity+CoreDataClass.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreData/CoreData.h>
|
||||||
|
|
||||||
|
@class MPSiteEntity;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPSiteQuestionEntity : NSManagedObject
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#import "MPSiteQuestionEntity+CoreDataProperties.h"
|
||||||
14
platform-darwin/Source/MPSiteQuestionEntity+CoreDataClass.m
Normal file
14
platform-darwin/Source/MPSiteQuestionEntity+CoreDataClass.m
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// MPSiteQuestionEntity+CoreDataClass.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSiteQuestionEntity+CoreDataClass.h"
|
||||||
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
@implementation MPSiteQuestionEntity
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// MPSiteQuestionEntity+CoreDataProperties.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSiteQuestionEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPSiteQuestionEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPSiteQuestionEntity *> *)fetchRequest;
|
||||||
|
|
||||||
|
@property (nullable, nonatomic, copy) NSString *keyword;
|
||||||
|
@property (nullable, nonatomic, retain) MPSiteEntity *site;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// MPSiteQuestionEntity+CoreDataProperties.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSiteQuestionEntity+CoreDataProperties.h"
|
||||||
|
|
||||||
|
@implementation MPSiteQuestionEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPSiteQuestionEntity *> *)fetchRequest {
|
||||||
|
return [[NSFetchRequest alloc] initWithEntityName:@"MPSiteQuestionEntity"];
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamic keyword;
|
||||||
|
@dynamic site;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
//
|
|
||||||
// MPSiteQuestionEntity.h
|
|
||||||
// MasterPassword-iOS
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-27.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
|
|
||||||
@class MPSiteEntity;
|
|
||||||
|
|
||||||
@interface MPSiteQuestionEntity : NSManagedObject
|
|
||||||
|
|
||||||
@property(nonatomic, retain) NSString *keyword;
|
|
||||||
@property(nonatomic, retain) MPSiteEntity *site;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// MPSiteQuestionEntity.m
|
|
||||||
// MasterPassword-iOS
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-27.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPSiteQuestionEntity.h"
|
|
||||||
#import "MPSiteEntity.h"
|
|
||||||
|
|
||||||
@implementation MPSiteQuestionEntity
|
|
||||||
|
|
||||||
@dynamic keyword;
|
|
||||||
@dynamic site;
|
|
||||||
|
|
||||||
@end
|
|
||||||
22
platform-darwin/Source/MPStoredSiteEntity+CoreDataClass.h
Normal file
22
platform-darwin/Source/MPStoredSiteEntity+CoreDataClass.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MPStoredSiteEntity+CoreDataClass.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
@class NSObject;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPStoredSiteEntity : MPSiteEntity
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#import "MPStoredSiteEntity+CoreDataProperties.h"
|
||||||
13
platform-darwin/Source/MPStoredSiteEntity+CoreDataClass.m
Normal file
13
platform-darwin/Source/MPStoredSiteEntity+CoreDataClass.m
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// MPStoredSiteEntity+CoreDataClass.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPStoredSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
@implementation MPStoredSiteEntity
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MPStoredSiteEntity+CoreDataProperties.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-05-01.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPStoredSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPStoredSiteEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPStoredSiteEntity *> *)fetchRequest;
|
||||||
|
|
||||||
|
@property (nullable, nonatomic, retain) NSData *contentObject;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// MPStoredSiteEntity+CoreDataProperties.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-05-01.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPStoredSiteEntity+CoreDataProperties.h"
|
||||||
|
|
||||||
|
@implementation MPStoredSiteEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPStoredSiteEntity *> *)fetchRequest {
|
||||||
|
return [[NSFetchRequest alloc] initWithEntityName:@"MPStoredSiteEntity"];
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamic contentObject;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// MPStoredSiteEntity.h
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
#import "MPSiteEntity.h"
|
|
||||||
|
|
||||||
@interface MPStoredSiteEntity : MPSiteEntity
|
|
||||||
|
|
||||||
@property(nonatomic, retain) NSData *contentObject;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//
|
|
||||||
// MPStoredSiteEntity.m
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPStoredSiteEntity.h"
|
|
||||||
|
|
||||||
@implementation MPStoredSiteEntity
|
|
||||||
|
|
||||||
@dynamic contentObject;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,13 +1,27 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPTypes.h
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 02/01/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#import <Crashlytics/Crashlytics.h>
|
||||||
|
#import <Crashlytics/Answers.h>
|
||||||
|
|
||||||
__BEGIN_DECLS
|
__BEGIN_DECLS
|
||||||
extern NSString *const MPErrorDomain;
|
extern NSString *const MPErrorDomain;
|
||||||
|
extern NSInteger const MPErrorHangCode;
|
||||||
|
|
||||||
extern NSString *const MPSignedInNotification;
|
extern NSString *const MPSignedInNotification;
|
||||||
extern NSString *const MPSignedOutNotification;
|
extern NSString *const MPSignedOutNotification;
|
||||||
@@ -19,4 +33,21 @@ extern NSString *const MPFoundInconsistenciesNotification;
|
|||||||
|
|
||||||
extern NSString *const MPSitesImportedNotificationUserKey;
|
extern NSString *const MPSitesImportedNotificationUserKey;
|
||||||
extern NSString *const MPInconsistenciesFixResultUserKey;
|
extern NSString *const MPInconsistenciesFixResultUserKey;
|
||||||
|
|
||||||
__END_DECLS
|
__END_DECLS
|
||||||
|
|
||||||
|
#ifdef CRASHLYTICS
|
||||||
|
#define MPError(error_, message, ...) ({ \
|
||||||
|
err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
|
||||||
|
\
|
||||||
|
if ([[MPConfig get].sendInfo boolValue]) { \
|
||||||
|
[[Crashlytics sharedInstance] recordError:error_ withAdditionalUserInfo:@{ \
|
||||||
|
@"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \
|
||||||
|
}]; \
|
||||||
|
} \
|
||||||
|
})
|
||||||
|
#else
|
||||||
|
#define MPError(error_, message, ...) ({ \
|
||||||
|
err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
|
||||||
|
})
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPTypes.c
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 02/01/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPTypes.h"
|
#import "MPTypes.h"
|
||||||
|
|
||||||
NSString *const MPErrorDomain = @"MPErrorDomain";
|
NSString *const MPErrorDomain = @"MPErrorDomain";
|
||||||
|
NSInteger const MPErrorHangCode = 1;
|
||||||
|
|
||||||
NSString *const MPSignedInNotification = @"MPSignedInNotification";
|
NSString *const MPSignedInNotification = @"MPSignedInNotification";
|
||||||
NSString *const MPSignedOutNotification = @"MPSignedOutNotification";
|
NSString *const MPSignedOutNotification = @"MPSignedOutNotification";
|
||||||
|
|||||||
22
platform-darwin/Source/MPUserEntity+CoreDataClass.h
Normal file
22
platform-darwin/Source/MPUserEntity+CoreDataClass.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MPUserEntity+CoreDataClass.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreData/CoreData.h>
|
||||||
|
|
||||||
|
@class MPSiteEntity;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPUserEntity : NSManagedObject
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#import "MPUserEntity+CoreDataProperties.h"
|
||||||
14
platform-darwin/Source/MPUserEntity+CoreDataClass.m
Normal file
14
platform-darwin/Source/MPUserEntity+CoreDataClass.m
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// MPUserEntity+CoreDataClass.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPUserEntity+CoreDataClass.h"
|
||||||
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
@implementation MPUserEntity
|
||||||
|
|
||||||
|
@end
|
||||||
39
platform-darwin/Source/MPUserEntity+CoreDataProperties.h
Normal file
39
platform-darwin/Source/MPUserEntity+CoreDataProperties.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// MPUserEntity+CoreDataProperties.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPUserEntity+CoreDataClass.h"
|
||||||
|
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MPUserEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPUserEntity *> *)fetchRequest;
|
||||||
|
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *avatar_;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *defaultType_;
|
||||||
|
@property (nullable, nonatomic, retain) NSData *keyID;
|
||||||
|
@property (nullable, nonatomic, copy) NSDate *lastUsed;
|
||||||
|
@property (nullable, nonatomic, copy) NSString *name;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *saveKey_;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *touchID_;
|
||||||
|
@property (nullable, nonatomic, copy) NSNumber *version_;
|
||||||
|
@property (nullable, nonatomic, retain) NSSet<MPSiteEntity *> *sites;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPUserEntity (CoreDataGeneratedAccessors)
|
||||||
|
|
||||||
|
- (void)addSitesObject:(MPSiteEntity *)value;
|
||||||
|
- (void)removeSitesObject:(MPSiteEntity *)value;
|
||||||
|
- (void)addSites:(NSSet<MPSiteEntity *> *)values;
|
||||||
|
- (void)removeSites:(NSSet<MPSiteEntity *> *)values;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
27
platform-darwin/Source/MPUserEntity+CoreDataProperties.m
Normal file
27
platform-darwin/Source/MPUserEntity+CoreDataProperties.m
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// MPUserEntity+CoreDataProperties.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2017-04-30.
|
||||||
|
// Copyright © 2017 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPUserEntity+CoreDataProperties.h"
|
||||||
|
|
||||||
|
@implementation MPUserEntity (CoreDataProperties)
|
||||||
|
|
||||||
|
+ (NSFetchRequest<MPUserEntity *> *)fetchRequest {
|
||||||
|
return [[NSFetchRequest alloc] initWithEntityName:@"MPUserEntity"];
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamic avatar_;
|
||||||
|
@dynamic defaultType_;
|
||||||
|
@dynamic keyID;
|
||||||
|
@dynamic lastUsed;
|
||||||
|
@dynamic name;
|
||||||
|
@dynamic saveKey_;
|
||||||
|
@dynamic touchID_;
|
||||||
|
@dynamic version_;
|
||||||
|
@dynamic sites;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
//
|
|
||||||
// MPUserEntity.h
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
|
|
||||||
@class MPSiteEntity;
|
|
||||||
|
|
||||||
@interface MPUserEntity : NSManagedObject
|
|
||||||
|
|
||||||
@property(nonatomic, retain) NSNumber *avatar_;
|
|
||||||
@property(nonatomic, retain) NSNumber *defaultType_;
|
|
||||||
@property(nonatomic, retain) NSData *keyID;
|
|
||||||
@property(nonatomic, retain) NSDate *lastUsed;
|
|
||||||
@property(nonatomic, retain) NSString *name;
|
|
||||||
@property(nonatomic, retain) NSNumber *saveKey_;
|
|
||||||
@property(nonatomic, retain) NSNumber *touchID_;
|
|
||||||
@property(nonatomic, retain) NSNumber *version_;
|
|
||||||
@property(nonatomic, retain) NSSet *sites;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface MPUserEntity(CoreDataGeneratedAccessors)
|
|
||||||
|
|
||||||
- (void)addSitesObject:(MPSiteEntity *)value;
|
|
||||||
- (void)removeSitesObject:(MPSiteEntity *)value;
|
|
||||||
- (void)addSites:(NSSet *)values;
|
|
||||||
- (void)removeSites:(NSSet *)values;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// MPUserEntity.m
|
|
||||||
// MasterPassword-Mac
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-21.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPUserEntity.h"
|
|
||||||
#import "MPSiteEntity.h"
|
|
||||||
|
|
||||||
@implementation MPUserEntity
|
|
||||||
|
|
||||||
@dynamic avatar_;
|
|
||||||
@dynamic defaultType_;
|
|
||||||
@dynamic keyID;
|
|
||||||
@dynamic lastUsed;
|
|
||||||
@dynamic name;
|
|
||||||
@dynamic saveKey_;
|
|
||||||
@dynamic touchID_;
|
|
||||||
@dynamic version_;
|
|
||||||
@dynamic sites;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -18,9 +18,13 @@
|
|||||||
|
|
||||||
#import "MPGradientView.h"
|
#import "MPGradientView.h"
|
||||||
|
|
||||||
@implementation MPGradientView {
|
@interface MPGradientView()
|
||||||
NSGradient *gradient;
|
|
||||||
}
|
@property(nonatomic, strong) NSGradient *gradient;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPGradientView
|
||||||
|
|
||||||
- (id)initWithFrame:(NSRect)frame {
|
- (id)initWithFrame:(NSRect)frame {
|
||||||
|
|
||||||
@@ -51,28 +55,28 @@
|
|||||||
- (void)setStartingColor:(NSColor *)startingColor {
|
- (void)setStartingColor:(NSColor *)startingColor {
|
||||||
|
|
||||||
_startingColor = startingColor;
|
_startingColor = startingColor;
|
||||||
gradient = nil;
|
self.gradient = nil;
|
||||||
[self setNeedsDisplay:YES];
|
[self setNeedsDisplay:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setEndingColor:(NSColor *)endingColor {
|
- (void)setEndingColor:(NSColor *)endingColor {
|
||||||
|
|
||||||
_endingColor = endingColor;
|
_endingColor = endingColor;
|
||||||
gradient = nil;
|
self.gradient = nil;
|
||||||
[self setNeedsDisplay:YES];
|
[self setNeedsDisplay:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setAngle:(NSInteger)angle {
|
- (void)setAngle:(NSInteger)angle {
|
||||||
|
|
||||||
_angle = angle;
|
_angle = angle;
|
||||||
gradient = nil;
|
self.gradient = nil;
|
||||||
[self setNeedsDisplay:YES];
|
[self setNeedsDisplay:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setRatio:(CGFloat)ratio {
|
- (void)setRatio:(CGFloat)ratio {
|
||||||
|
|
||||||
_ratio = ratio;
|
_ratio = ratio;
|
||||||
gradient = nil;
|
self.gradient = nil;
|
||||||
[self setNeedsDisplay:YES];
|
[self setNeedsDisplay:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +88,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[(gradient?: (gradient = [[NSGradient alloc] initWithColorsAndLocations:
|
[(self.gradient?: (self.gradient = [[NSGradient alloc] initWithColorsAndLocations:
|
||||||
self.startingColor, (CGFloat)0.f,
|
self.startingColor, (CGFloat)0.f,
|
||||||
[self.startingColor blendedColorWithFraction:0.5f ofColor:self.endingColor], self.ratio,
|
[self.startingColor blendedColorWithFraction:0.5f ofColor:self.endingColor], self.ratio,
|
||||||
self.endingColor, (CGFloat)1.f, nil]))
|
self.endingColor, (CGFloat)1.f, nil]))
|
||||||
|
|||||||
@@ -28,10 +28,10 @@
|
|||||||
|
|
||||||
[super windowDidLoad];
|
[super windowDidLoad];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
|
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, nil, ^(id host, NSNotification *note) {
|
||||||
queue:nil usingBlock:^(NSNotification *note) {
|
PearlRemoveNotificationObserversFrom( host );
|
||||||
[MPMacAppDelegate get].initialWindowController = nil;
|
[MPMacAppDelegate get].initialWindowController = nil;
|
||||||
}];
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Actions
|
#pragma mark - Actions
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
#import "MPPasswordWindowController.h"
|
#import "MPSitesWindowController.h"
|
||||||
#import "MPInitialWindowController.h"
|
#import "MPInitialWindowController.h"
|
||||||
|
|
||||||
@interface MPMacAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
|
@interface MPMacAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
|
||||||
|
|
||||||
@property(nonatomic, strong) NSStatusItem *statusView;
|
@property(nonatomic, strong) NSStatusItem *statusView;
|
||||||
@property(nonatomic, strong) MPPasswordWindowController *passwordWindowController;
|
@property(nonatomic, strong) MPSitesWindowController *sitesWindowController;
|
||||||
@property(nonatomic, strong) MPInitialWindowController *initialWindowController;
|
@property(nonatomic, strong) MPInitialWindowController *initialWindowController;
|
||||||
@property(nonatomic, weak) IBOutlet NSMenuItem *lockItem;
|
@property(nonatomic, weak) IBOutlet NSMenuItem *lockItem;
|
||||||
@property(nonatomic, weak) IBOutlet NSMenuItem *showItem;
|
@property(nonatomic, weak) IBOutlet NSMenuItem *showItem;
|
||||||
|
|||||||
@@ -72,13 +72,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||||
if ([crashlyticsAPIKey length]) {
|
if ([crashlyticsAPIKey length]) {
|
||||||
inf(@"Initializing Crashlytics");
|
inf(@"Initializing Crashlytics");
|
||||||
#if defined (DEBUG) || defined (ADHOC)
|
#if DEBUG
|
||||||
[Crashlytics sharedInstance].debugMode = YES;
|
[Crashlytics sharedInstance].debugMode = YES;
|
||||||
#endif
|
#endif
|
||||||
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];
|
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||||
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
|
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
|
||||||
[[Crashlytics sharedInstance] setUserName:@"Anonymous"];
|
[[Crashlytics sharedInstance] setUserName:@"Anonymous"];
|
||||||
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
|
|
||||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
|
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
|
||||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||||
PearlLogLevel level = PearlLogLevelInfo;
|
PearlLogLevel level = PearlLogLevelInfo;
|
||||||
@@ -172,17 +171,17 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||||
// Save changes in the application's managed object context before the application terminates.
|
// Save changes in the application's managed object context before the application terminates.
|
||||||
|
|
||||||
NSManagedObjectContext *context = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
|
NSManagedObjectContext *mainContext = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
|
||||||
if (!context)
|
if (!mainContext)
|
||||||
return NSTerminateNow;
|
return NSTerminateNow;
|
||||||
|
|
||||||
if (![context commitEditing])
|
if (![mainContext commitEditing])
|
||||||
return NSTerminateCancel;
|
return NSTerminateCancel;
|
||||||
|
|
||||||
if (![context hasChanges])
|
if (![mainContext hasChanges])
|
||||||
return NSTerminateNow;
|
return NSTerminateNow;
|
||||||
|
|
||||||
[context saveToStore];
|
[mainContext saveToStore];
|
||||||
return NSTerminateNow;
|
return NSTerminateNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,80 +268,80 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
NSURL *url = openPanel.URL;
|
NSURL *url = openPanel.URL;
|
||||||
[openPanel close];
|
[openPanel close];
|
||||||
|
|
||||||
[[NSURLSession sharedSession]
|
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
|
||||||
dataTaskWithURL:url completionHandler:^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
||||||
if (error)
|
if (error)
|
||||||
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
|
MPError( error, @"While reading imported sites from %@.", url );
|
||||||
if (!importedSitesData)
|
if (!importedSitesData)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
||||||
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
|
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
|
||||||
__block NSString *masterPassword = nil;
|
__block NSString *masterPassword = nil;
|
||||||
|
|
||||||
PearlMainQueueWait( ^{
|
PearlMainQueueWait( ^{
|
||||||
NSAlert *alert = [NSAlert new];
|
NSAlert *alert = [NSAlert new];
|
||||||
[alert addButtonWithTitle:@"Unlock"];
|
[alert addButtonWithTitle:@"Unlock"];
|
||||||
[alert addButtonWithTitle:@"Cancel"];
|
[alert addButtonWithTitle:@"Cancel"];
|
||||||
alert.messageText = @"Import File's Master Password";
|
alert.messageText = @"Import File's Master Password";
|
||||||
alert.informativeText = strf( @"%@'s export was done using a different master password.\n"
|
alert.informativeText = strf( @"%@'s export was done using a different master password.\n"
|
||||||
@"Enter that master password to unlock the exported data.", userName );
|
@"Enter that master password to unlock the exported data.", userName );
|
||||||
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||||
[alert layout];
|
[alert layout];
|
||||||
if ([alert runModal] == NSAlertFirstButtonReturn)
|
if ([alert runModal] == NSAlertFirstButtonReturn)
|
||||||
masterPassword = ((NSTextField *)alert.accessoryView).stringValue;
|
masterPassword = ((NSTextField *)alert.accessoryView).stringValue;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return masterPassword;
|
return masterPassword;
|
||||||
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
|
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
|
||||||
__block NSString *masterPassword = nil;
|
__block NSString *masterPassword = nil;
|
||||||
|
|
||||||
PearlMainQueueWait( ^{
|
PearlMainQueueWait( ^{
|
||||||
NSAlert *alert = [NSAlert new];
|
NSAlert *alert = [NSAlert new];
|
||||||
[alert addButtonWithTitle:@"Import"];
|
[alert addButtonWithTitle:@"Import"];
|
||||||
[alert addButtonWithTitle:@"Cancel"];
|
[alert addButtonWithTitle:@"Cancel"];
|
||||||
alert.messageText = strf( @"Master Password for\n%@", userName );
|
alert.messageText = strf( @"Master Password for\n%@", userName );
|
||||||
alert.informativeText = strf( @"Imports %lu sites, overwriting %lu.",
|
alert.informativeText = strf( @"Imports %lu sites, overwriting %lu.",
|
||||||
(unsigned long)importCount, (unsigned long)deleteCount );
|
(unsigned long)importCount, (unsigned long)deleteCount );
|
||||||
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||||
[alert layout];
|
[alert layout];
|
||||||
if ([alert runModal] == NSAlertFirstButtonReturn)
|
if ([alert runModal] == NSAlertFirstButtonReturn)
|
||||||
masterPassword = ((NSTextField *)alert.accessoryView).stringValue;
|
masterPassword = ((NSTextField *)alert.accessoryView).stringValue;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return masterPassword;
|
return masterPassword;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case MPImportResultSuccess: {
|
case MPImportResultSuccess: {
|
||||||
[self updateUsers];
|
[self updateUsers];
|
||||||
|
|
||||||
NSAlert *alert = [NSAlert new];
|
NSAlert *alert = [NSAlert new];
|
||||||
alert.messageText = @"Successfully imported sites.";
|
alert.messageText = @"Successfully imported sites.";
|
||||||
[alert runModal];
|
[alert runModal];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPImportResultCancelled:
|
case MPImportResultCancelled:
|
||||||
break;
|
break;
|
||||||
case MPImportResultInternalError:
|
case MPImportResultInternalError:
|
||||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||||
NSLocalizedDescriptionKey: @"Import failed because of an internal error."
|
NSLocalizedDescriptionKey: @"Import failed because of an internal error."
|
||||||
}]] runModal];
|
}]] runModal];
|
||||||
break;
|
break;
|
||||||
case MPImportResultMalformedInput:
|
case MPImportResultMalformedInput:
|
||||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||||
NSLocalizedDescriptionKey: @"The import doesn't look like a Master Password export."
|
NSLocalizedDescriptionKey: @"The import doesn't look like a Master Password export."
|
||||||
}]] runModal];
|
}]] runModal];
|
||||||
break;
|
break;
|
||||||
case MPImportResultInvalidPassword:
|
case MPImportResultInvalidPassword:
|
||||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||||
NSLocalizedDescriptionKey: @"Incorrect master password for the import sites."
|
NSLocalizedDescriptionKey: @"Incorrect master password for the import sites."
|
||||||
}]] runModal];
|
}]] runModal];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}];
|
}] resume];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)togglePreference:(id)sender {
|
- (IBAction)togglePreference:(id)sender {
|
||||||
@@ -389,20 +388,16 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
NSString *name = [(NSSecureTextField *)alert.accessoryView stringValue];
|
NSString *name = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
||||||
inManagedObjectContext:moc];
|
inManagedObjectContext:context];
|
||||||
newUser.name = name;
|
newUser.name = name;
|
||||||
[moc saveToStore];
|
[context saveToStore];
|
||||||
NSError *error = nil;
|
[self setActiveUser:newUser];
|
||||||
if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error])
|
|
||||||
err( @"Failed to obtain permanent object ID for new user: %@", [error fullDescription] );
|
|
||||||
|
|
||||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
PearlMainQueue( ^{
|
||||||
[self updateUsers];
|
|
||||||
[self setActiveUser:newUser];
|
|
||||||
[self showPasswordWindow:nil];
|
[self showPasswordWindow:nil];
|
||||||
}];
|
} );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,10 +411,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
if ([alert runModal] != NSAlertFirstButtonReturn)
|
if ([alert runModal] != NSAlertFirstButtonReturn)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[moc deleteObject:[self activeUserInContext:moc]];
|
[context deleteObject:[self activeUserInContext:context]];
|
||||||
[self setActiveUser:nil];
|
[self setActiveUser:nil];
|
||||||
[moc saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||||
[self updateUsers];
|
[self updateUsers];
|
||||||
@@ -435,8 +430,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
|
|
||||||
- (IBAction)terminate:(id)sender {
|
- (IBAction)terminate:(id)sender {
|
||||||
|
|
||||||
[self.passwordWindowController close];
|
[self.sitesWindowController close];
|
||||||
self.passwordWindowController = nil;
|
self.sitesWindowController = nil;
|
||||||
|
|
||||||
[NSApp terminate:nil];
|
[NSApp terminate:nil];
|
||||||
}
|
}
|
||||||
@@ -465,11 +460,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
prof_rewind( @"activeUserForMainThread" );
|
prof_rewind( @"activeUserForMainThread" );
|
||||||
|
|
||||||
// Don't show window if we weren't already running (ie. if we haven't been activated before).
|
// Don't show window if we weren't already running (ie. if we haven't been activated before).
|
||||||
if (!self.passwordWindowController)
|
if (!self.sitesWindowController)
|
||||||
self.passwordWindowController = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
self.sitesWindowController = [[MPSitesWindowController alloc] initWithWindowNibName:@"MPSitesWindowController"];
|
||||||
prof_rewind( @"initWithWindow" );
|
prof_rewind( @"initWithWindow" );
|
||||||
|
|
||||||
[self.passwordWindowController showWindow:self];
|
[self.sitesWindowController showWindow:self];
|
||||||
prof_finish( @"showWindow" );
|
prof_finish( @"showWindow" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,20 +511,23 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
|
|
||||||
NSError *coordinateError = nil;
|
NSError *coordinateError = nil;
|
||||||
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
|
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
|
||||||
[[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError
|
[[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0
|
||||||
byAccessor:^(NSURL *newURL) {
|
error:&coordinateError byAccessor:
|
||||||
NSError *writeError = nil;
|
^(NSURL *newURL) {
|
||||||
if (![exportedSites writeToURL:newURL atomically:NO
|
NSError *writeError = nil;
|
||||||
encoding:NSUTF8StringEncoding
|
if (![exportedSites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
|
||||||
error:&writeError])
|
MPError( writeError, @"Could not write to the export file." );
|
||||||
PearlMainQueue( ^{
|
|
||||||
[[NSAlert alertWithError:writeError] runModal];
|
PearlMainQueue( ^{
|
||||||
} );
|
[[NSAlert alertWithError:writeError] runModal];
|
||||||
}];
|
} );
|
||||||
if (coordinateError)
|
}];
|
||||||
|
if (coordinateError) {
|
||||||
|
MPError( coordinateError, @"Write access to the export file could not be obtained." );
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
[[NSAlert alertWithError:coordinateError] runModal];
|
[[NSAlert alertWithError:coordinateError] runModal];
|
||||||
} );
|
} );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateUsers {
|
- (void)updateUsers {
|
||||||
@@ -567,7 +565,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
||||||
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
|
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
|
||||||
if (!users)
|
if (!users)
|
||||||
err( @"Failed to load users: %@", [error fullDescription] );
|
MPError( error, @"Failed to load users." );
|
||||||
|
|
||||||
if (![users count]) {
|
if (![users count]) {
|
||||||
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];
|
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];
|
||||||
@@ -579,7 +577,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
for (MPUserEntity *user in users) {
|
for (MPUserEntity *user in users) {
|
||||||
NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector( selectUser: ) keyEquivalent:@""];
|
NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector( selectUser: ) keyEquivalent:@""];
|
||||||
[userItem setTarget:self];
|
[userItem setTarget:self];
|
||||||
[userItem setRepresentedObject:[user objectID]];
|
[userItem setRepresentedObject:user.permanentObjectID];
|
||||||
[[self.usersItem submenu] addItem:userItem];
|
[[self.usersItem submenu] addItem:userItem];
|
||||||
|
|
||||||
if (!mainActiveUser && [user.name isEqualToString:[MPMacConfig get].usedUserName])
|
if (!mainActiveUser && [user.name isEqualToString:[MPMacConfig get].usedUserName])
|
||||||
@@ -606,7 +604,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
- (void)updateMenuItems {
|
- (void)updateMenuItems {
|
||||||
|
|
||||||
MPUserEntity *activeUser = [self activeUserForMainThread];
|
MPUserEntity *activeUser = [self activeUserForMainThread];
|
||||||
// if (!(self.showItem.enabled = ![self.passwordWindowController.window isVisible])) {
|
// if (!(self.showItem.enabled = ![self.sitesWindowController.window isVisible])) {
|
||||||
// self.showItem.title = @"Show (Showing)";
|
// self.showItem.title = @"Show (Showing)";
|
||||||
// self.showItem.toolTip = @"Master Password is already showing.";
|
// self.showItem.toolTip = @"Master Password is already showing.";
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -25,8 +25,7 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPMacApplication {
|
@implementation MPMacApplication
|
||||||
}
|
|
||||||
|
|
||||||
- (void)sendEvent:(NSEvent *)event {
|
- (void)sendEvent:(NSEvent *)event {
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPMacConfig.h
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 02/01/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPConfig.h"
|
#import "MPConfig.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// This file is part of Master Password.
|
||||||
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
//
|
//
|
||||||
// MPMacConfig.m
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MasterPassword
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 02/01/12.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
@implementation MPMacConfig
|
@implementation MPMacConfig
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/**
|
//==============================================================================
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
// This file is part of Master Password.
|
||||||
*
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPNoStateButton.h
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MPNoStateButton
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by lhunath on 14-10-27.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
/**
|
//==============================================================================
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
// This file is part of Master Password.
|
||||||
*
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPNoStateButton.h
|
// Master Password is free software: you can redistribute it and/or modify
|
||||||
// MPNoStateButton
|
// 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.
|
||||||
//
|
//
|
||||||
// Created by lhunath on 14-10-27.
|
// Master Password is distributed in the hope that it will be useful,
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPNoStateButton.h"
|
#import "MPNoStateButton.h"
|
||||||
|
|
||||||
@implementation MPNoStateButtonCell {
|
@implementation MPNoStateButtonCell
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setState:(NSInteger)state {
|
- (void)setState:(NSInteger)state {
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
|
||||||
*
|
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// MPPasswordWindow.h
|
|
||||||
// MPPasswordWindow
|
|
||||||
//
|
|
||||||
// Created by lhunath on 2014-06-19.
|
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
@interface MPPasswordWindow : NSWindow
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
|
||||||
*
|
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
|
||||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// MPPasswordWindow.h
|
|
||||||
// MPPasswordWindow
|
|
||||||
//
|
|
||||||
// Created by lhunath on 2014-06-19.
|
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPPasswordWindow.h"
|
|
||||||
|
|
||||||
@implementation MPPasswordWindow
|
|
||||||
|
|
||||||
#pragma mark - Life
|
|
||||||
|
|
||||||
- (BOOL)canBecomeKeyWindow {
|
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - State
|
|
||||||
|
|
||||||
- (void)update {
|
|
||||||
|
|
||||||
if ([[MPMacConfig get].fullScreen boolValue]) {
|
|
||||||
[self setLevel:NSScreenSaverWindowLevel];
|
|
||||||
[self setFrame:self.screen.frame display:YES];
|
|
||||||
}
|
|
||||||
else if (self.level != NSNormalWindowLevel) {
|
|
||||||
[self setLevel:NSNormalWindowLevel];
|
|
||||||
[self setFrame:NSMakeRect( 0, 0, 640, 600 ) display:NO];
|
|
||||||
[self center];
|
|
||||||
}
|
|
||||||
|
|
||||||
[super update];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<capability name="stacking Non-gravity area distributions on NSStackView" minToolsVersion="7.0" minSystemVersion="10.11"/>
|
<capability name="stacking Non-gravity area distributions on NSStackView" minToolsVersion="7.0" minSystemVersion="10.11"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="MPPasswordWindowController">
|
<customObject id="-2" userLabel="File's Owner" customClass="MPSitesWindowController">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="inputLabel" destination="OnR-s6-d4P" id="p6G-Ut-cdu"/>
|
<outlet property="inputLabel" destination="OnR-s6-d4P" id="p6G-Ut-cdu"/>
|
||||||
<outlet property="passwordTypesBox" destination="bZe-7q-i6q" id="Ai3-pt-i6K"/>
|
<outlet property="passwordTypesBox" destination="bZe-7q-i6q" id="Ai3-pt-i6K"/>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</customObject>
|
</customObject>
|
||||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
<window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MPPasswordWindow">
|
<window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MPSitesWindow">
|
||||||
<windowStyleMask key="styleMask" texturedBackground="YES" unifiedTitleAndToolbar="YES" fullSizeContentView="YES"/>
|
<windowStyleMask key="styleMask" texturedBackground="YES" unifiedTitleAndToolbar="YES" fullSizeContentView="YES"/>
|
||||||
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/>
|
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/>
|
||||||
<rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/>
|
<rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/>
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "MPSiteEntity.h"
|
#import "MPSiteEntity+CoreDataClass.h"
|
||||||
#import "MPAlgorithm.h"
|
#import "MPAlgorithm.h"
|
||||||
#import "MPUserEntity.h"
|
#import "MPUserEntity+CoreDataClass.h"
|
||||||
|
|
||||||
@class MPSiteEntity;
|
@class MPSiteEntity;
|
||||||
|
|
||||||
|
|||||||
@@ -17,16 +17,19 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPSiteModel.h"
|
#import "MPSiteModel.h"
|
||||||
#import "MPSiteEntity.h"
|
|
||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "MPMacAppDelegate.h"
|
#import "MPMacAppDelegate.h"
|
||||||
|
|
||||||
@implementation MPSiteModel {
|
@interface MPSiteModel()
|
||||||
NSManagedObjectID *_entityOID;
|
|
||||||
BOOL _initialized;
|
@property(nonatomic, strong) NSManagedObjectID *entityOID;
|
||||||
}
|
@property(nonatomic) BOOL initialized;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPSiteModel
|
||||||
|
|
||||||
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||||
|
|
||||||
@@ -34,7 +37,7 @@
|
|||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
[self setEntity:entity fuzzyGroups:fuzzyGroups];
|
[self setEntity:entity fuzzyGroups:fuzzyGroups];
|
||||||
_initialized = YES;
|
self.initialized = YES;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -45,16 +48,16 @@
|
|||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
[self setTransientSiteName:siteName forUser:user];
|
[self setTransientSiteName:siteName forUser:user];
|
||||||
_initialized = YES;
|
self.initialized = YES;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||||
|
|
||||||
if ([_entityOID isEqual:entity.objectID])
|
if ([self.entityOID isEqual:entity.permanentObjectID])
|
||||||
return;
|
return;
|
||||||
_entityOID = entity.objectID;
|
self.entityOID = entity.permanentObjectID;
|
||||||
|
|
||||||
NSString *siteName = entity.name;
|
NSString *siteName = entity.name;
|
||||||
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName];
|
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName];
|
||||||
@@ -80,7 +83,6 @@
|
|||||||
self.uses = entity.uses_;
|
self.uses = entity.uses_;
|
||||||
self.counter = [entity isKindOfClass:[MPGeneratedSiteEntity class]]? [(MPGeneratedSiteEntity *)entity counter]: 0;
|
self.counter = [entity isKindOfClass:[MPGeneratedSiteEntity class]]? [(MPGeneratedSiteEntity *)entity counter]: 0;
|
||||||
self.loginGenerated = entity.loginGenerated;
|
self.loginGenerated = entity.loginGenerated;
|
||||||
NSLog( @"%@: loginGenerated: %d", self.name, self.loginGenerated );
|
|
||||||
|
|
||||||
// Find all password types and the index of the current type amongst them.
|
// Find all password types and the index of the current type amongst them.
|
||||||
[self updateContent:entity];
|
[self updateContent:entity];
|
||||||
@@ -88,7 +90,7 @@
|
|||||||
|
|
||||||
- (void)setTransientSiteName:(NSString *)siteName forUser:(MPUserEntity *)user {
|
- (void)setTransientSiteName:(NSString *)siteName forUser:(MPUserEntity *)user {
|
||||||
|
|
||||||
_entityOID = nil;
|
self.entityOID = nil;
|
||||||
|
|
||||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||||
paragraphStyle.alignment = NSCenterTextAlignment;
|
paragraphStyle.alignment = NSCenterTextAlignment;
|
||||||
@@ -110,28 +112,28 @@
|
|||||||
|
|
||||||
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
||||||
|
|
||||||
if (!_entityOID)
|
if (!self.entityOID)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
NSError *error;
|
NSError *error;
|
||||||
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:_entityOID error:&error];
|
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:self.entityOID error:&error];
|
||||||
if (!entity)
|
if (!entity)
|
||||||
err( @"Couldn't retrieve active site: %@", [error fullDescription] );
|
MPError( error, @"Couldn't retrieve active site." );
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setCounter:(NSUInteger)counter {
|
- (void)setCounter:(NSUInteger)counter {
|
||||||
|
|
||||||
if (counter == _counter)
|
if (self.counter == counter)
|
||||||
return;
|
return;
|
||||||
_counter = counter;
|
_counter = counter;
|
||||||
|
|
||||||
if (!_initialized)
|
if (!self.initialized)
|
||||||
// This wasn't a change to the entity.
|
// This wasn't a change to the entity.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_entityOID)
|
if (self.entityOID)
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPSiteEntity *entity = [self entityInContext:context];
|
MPSiteEntity *entity = [self entityInContext:context];
|
||||||
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||||
@@ -147,15 +149,15 @@
|
|||||||
|
|
||||||
- (void)setLoginGenerated:(BOOL)loginGenerated {
|
- (void)setLoginGenerated:(BOOL)loginGenerated {
|
||||||
|
|
||||||
if (loginGenerated == _loginGenerated)
|
if (self.loginGenerated == loginGenerated)
|
||||||
return;
|
return;
|
||||||
_loginGenerated = loginGenerated;
|
_loginGenerated = loginGenerated;
|
||||||
|
|
||||||
if (!_initialized)
|
if (!self.initialized)
|
||||||
// This wasn't a change to the entity.
|
// This wasn't a change to the entity.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_entityOID)
|
if (self.entityOID)
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPSiteEntity *entity = [self entityInContext:context];
|
MPSiteEntity *entity = [self entityInContext:context];
|
||||||
entity.loginGenerated = loginGenerated;
|
entity.loginGenerated = loginGenerated;
|
||||||
@@ -180,7 +182,7 @@
|
|||||||
self.algorithm = MPAlgorithmForVersion( algorithmVersion )?: self.algorithm;
|
self.algorithm = MPAlgorithmForVersion( algorithmVersion )?: self.algorithm;
|
||||||
[self didChangeValueForKey:@"outdated"];
|
[self didChangeValueForKey:@"outdated"];
|
||||||
|
|
||||||
if (_entityOID)
|
if (self.entityOID)
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPSiteEntity *entity = [self entityInContext:context];
|
MPSiteEntity *entity = [self entityInContext:context];
|
||||||
entity.algorithm = self.algorithm;
|
entity.algorithm = self.algorithm;
|
||||||
@@ -194,7 +196,7 @@
|
|||||||
|
|
||||||
- (void)setQuestion:(NSString *)question {
|
- (void)setQuestion:(NSString *)question {
|
||||||
|
|
||||||
if ([question isEqualToString:_question])
|
if ([self.question isEqualToString:question])
|
||||||
return;
|
return;
|
||||||
_question = question;
|
_question = question;
|
||||||
|
|
||||||
@@ -218,14 +220,14 @@
|
|||||||
|
|
||||||
- (BOOL)transient {
|
- (BOOL)transient {
|
||||||
|
|
||||||
return _entityOID == nil;
|
return self.entityOID == nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateContent {
|
- (void)updateContent {
|
||||||
|
|
||||||
if (_entityOID)
|
if (self.entityOID)
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
|
[self updateContent:[MPSiteEntity existingObjectWithID:self.entityOID inContext:context]];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
else
|
else
|
||||||
@@ -276,7 +278,6 @@
|
|||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
self.loginName = loginName;
|
self.loginName = loginName;
|
||||||
NSLog( @"%@: loginGenerated: %d, loginName: %@", self.name, self.loginGenerated, loginName );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,10 @@
|
|||||||
|
|
||||||
#import <AppKit/AppKit.h>
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
@class MPPasswordWindowController;
|
@class MPSitesWindowController;
|
||||||
|
|
||||||
@interface MPSitesTableView : NSTableView
|
@interface MPSitesTableView : NSTableView
|
||||||
|
|
||||||
@property(nonatomic, weak) MPPasswordWindowController *controller;
|
@property(nonatomic, weak) MPSitesWindowController *controller;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#import "MPSitesTableView.h"
|
#import "MPSitesTableView.h"
|
||||||
#import "MPPasswordWindowController.h"
|
#import "MPSitesWindowController.h"
|
||||||
|
|
||||||
@implementation MPSitesTableView
|
@implementation MPSitesTableView
|
||||||
|
|
||||||
|
|||||||
23
platform-darwin/Source/Mac/MPSitesWindow.h
Normal file
23
platform-darwin/Source/Mac/MPSitesWindow.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@interface MPSitesWindow : NSWindow
|
||||||
|
|
||||||
|
@end
|
||||||
49
platform-darwin/Source/Mac/MPSitesWindow.m
Normal file
49
platform-darwin/Source/Mac/MPSitesWindow.m
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//==============================================================================
|
||||||
|
// 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/>.
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#import "MPSitesWindow.h"
|
||||||
|
|
||||||
|
@implementation MPSitesWindow
|
||||||
|
|
||||||
|
#pragma mark - Life
|
||||||
|
|
||||||
|
- (BOOL)canBecomeKeyWindow {
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - State
|
||||||
|
|
||||||
|
- (void)update {
|
||||||
|
|
||||||
|
if ([[MPMacConfig get].fullScreen boolValue]) {
|
||||||
|
[self setLevel:NSScreenSaverWindowLevel];
|
||||||
|
[self setFrame:self.screen.frame display:YES];
|
||||||
|
}
|
||||||
|
else if (self.level != NSNormalWindowLevel) {
|
||||||
|
[self setLevel:NSNormalWindowLevel];
|
||||||
|
[self setFrame:NSMakeRect( 0, 0, 640, 600 ) display:NO];
|
||||||
|
[self center];
|
||||||
|
}
|
||||||
|
|
||||||
|
[super update];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Private
|
||||||
|
|
||||||
|
@end
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user