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
|
||||
platform-independent/cli-c/cli/*.o
|
||||
platform-independent/cli-c/mpw-*.tar.gz
|
||||
platform-independent/cli-c/mpw-*.tar.gz.sig
|
||||
platform-independent/cli-c/mpw
|
||||
platform-independent/cli-c/mpw-bench
|
||||
platform-independent/cli-c/mpw-tests
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -19,3 +19,9 @@
|
||||
[submodule "MasterPassword/Web/js/mpw-js"]
|
||||
path = platform-independent/web-js/js/mpw-js
|
||||
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:
|
||||
submodules: true
|
||||
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 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 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?
|
||||
|
||||
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.
|
||||
|
||||
@@ -69,12 +79,12 @@ Master Password is *not* a password manager. It does not store your website pas
|
||||
|
||||
## 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 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 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 try to keep password lists in sync or stored somewhere easily accessible - 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 - Master Password is always available.
|
||||
- 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 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 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 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 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 - 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 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
|
||||
|
||||
@@ -146,6 +185,10 @@ Go into the `gradle` directory and run `./gradlew build`. All Java components w
|
||||
- `platform-android/build/outputs/apk`:
|
||||
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
|
||||
|
||||
@@ -158,34 +201,42 @@ For example:
|
||||
./build && sudo ./install
|
||||
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-bench`: `openssl-dev`
|
||||
- `mpw-tests`: `openssl-dev`, `libxml2`
|
||||
- `mpw`
|
||||
|
||||
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:
|
||||
- `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
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Change an optional feature:
|
||||
There are a few toggleable features, to change them, pass them as environment variables:
|
||||
|
||||
mpw_color=0 ./build
|
||||
|
||||
Currently, there is only one toggleable feature:
|
||||
|
||||
- `mpw_color`: [default: 1] Colorized Identicon, depends on `ncurses-dev`.
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
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
|
||||
git=https://github.com/Tarsnap/scrypt.git
|
||||
pkg=http://www.tarsnap.com/scrypt/scrypt-1.2.0.tgz
|
||||
pkg_sha256=1754bc89405277c8ac14220377a4c240ddc34b1ce70882aa92cd01bfdc8569d4
|
||||
pkg=https://www.tarsnap.com/scrypt/scrypt-1.2.1.tgz
|
||||
pkg_sha256=4621f5e7da2f802e20850436219370092e9fcda93bd598f6d4236cce33f4c577
|
||||
|
||||
@@ -22,48 +22,150 @@
|
||||
#include "mpw-algorithm_v2.c"
|
||||
#include "mpw-algorithm_v3.c"
|
||||
|
||||
#define MP_N 32768
|
||||
#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) {
|
||||
MPMasterKey mpw_masterKey(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)
|
||||
return NULL;
|
||||
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_masterKeyForUser_v0( fullName, masterPassword );
|
||||
return mpw_masterKey_v0( fullName, masterPassword );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_masterKeyForUser_v1( fullName, masterPassword );
|
||||
return mpw_masterKey_v1( fullName, masterPassword );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_masterKeyForUser_v2( fullName, masterPassword );
|
||||
return mpw_masterKey_v2( fullName, masterPassword );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_masterKeyForUser_v3( fullName, masterPassword );
|
||||
return mpw_masterKey_v3( fullName, masterPassword );
|
||||
default:
|
||||
ftl( "Unsupported version: %d", algorithmVersion );
|
||||
err( "Unsupported version: %d\n", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_passwordForSite(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
||||
const MPSiteVariant siteVariant, const char *siteContext, const MPAlgorithmVersion algorithmVersion) {
|
||||
MPSiteKey mpw_siteKey(
|
||||
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)
|
||||
return NULL;
|
||||
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_passwordForSite_v0( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_passwordForSite_v1( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
return mpw_siteKey_v1( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_passwordForSite_v2( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_passwordForSite_v3( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
return mpw_siteKey_v3( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
// NOTE: mpw is currently NOT thread-safe.
|
||||
#include "mpw-types.h"
|
||||
|
||||
#ifndef _MPW_ALGORITHM_H
|
||||
#define _MPW_ALGORITHM_H
|
||||
|
||||
typedef enum( unsigned int, MPAlgorithmVersion ) {
|
||||
/** V0 did math with chars whose signedness was platform-dependent. */
|
||||
MPAlgorithmVersion0,
|
||||
@@ -28,16 +31,37 @@ typedef enum(unsigned int, MPAlgorithmVersion) {
|
||||
MPAlgorithmVersion2,
|
||||
/** V3 is the current version. */
|
||||
MPAlgorithmVersion3,
|
||||
|
||||
MPAlgorithmVersionCurrent = MPAlgorithmVersion3,
|
||||
MPAlgorithmVersionFirst = MPAlgorithmVersion0,
|
||||
MPAlgorithmVersionLast = MPAlgorithmVersion3,
|
||||
};
|
||||
#define MPAlgorithmVersionCurrent MPAlgorithmVersion3
|
||||
|
||||
/** 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. */
|
||||
const uint8_t *mpw_masterKeyForUser(
|
||||
* @return A new MPMasterKeySize-byte allocated buffer or NULL if an error occurred. */
|
||||
MPMasterKey mpw_masterKey(
|
||||
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
|
||||
|
||||
/** Encode a password for the site from the given master key and site parameters.
|
||||
* @return A newly allocated string or NULL if an allocation error occurred. */
|
||||
const char *mpw_passwordForSite(
|
||||
const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
||||
const MPSiteVariant siteVariant, const char *siteContext, const MPAlgorithmVersion algorithmVersion);
|
||||
/** Derive the site key for a user's site from the given master key and site parameters.
|
||||
* @return A new MPSiteKeySize-byte allocated buffer or NULL if an error occurred. */
|
||||
MPSiteKey mpw_siteKey(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
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-util.h"
|
||||
#include "base64.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
|
||||
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;
|
||||
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 );
|
||||
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) {
|
||||
|
||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||
if (!classCharacters)
|
||||
return '\0';
|
||||
|
||||
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 );
|
||||
trc( "algorithm: v%d\n", 0 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
|
||||
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.
|
||||
// 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;
|
||||
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_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
||||
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;
|
||||
}
|
||||
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
||||
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 );
|
||||
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )\n", 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 );
|
||||
if (!masterKey) {
|
||||
ftl( "Could not allocate master key: %d\n", errno );
|
||||
err( "Could not allocate master key: %s\n", strerror( errno ) );
|
||||
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;
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v0(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
static MPSiteKey mpw_siteKey_v0(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
trc( "algorithm: v%d\n", 0 );
|
||||
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 );
|
||||
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
|
||||
trc( "keyScope: %s\n", keyScope );
|
||||
|
||||
// TODO: Implement MPCounterValueTOTP
|
||||
|
||||
// Calculate the site seed.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
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 );
|
||||
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s\n",
|
||||
keyScope, mpw_hex_l( htonl( mpw_utf8_strlen( siteName ) ) ), siteName, mpw_hex_l( htonl( siteCounter ) ),
|
||||
keyContext? mpw_hex_l( htonl( mpw_utf8_strlen( keyContext ) ) ): NULL, keyContext );
|
||||
size_t siteSaltSize = 0;
|
||||
uint8_t *siteSalt = NULL;
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, htonl( mpw_utf8_strlen( siteName ) ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, htonl( siteCounter ) );
|
||||
if (keyContext) {
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, htonl( mpw_utf8_strlen( keyContext ) ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
|
||||
}
|
||||
if (!sitePasswordInfo) {
|
||||
ftl( "Could not allocate site seed info: %d\n", errno );
|
||||
if (!siteSalt || !siteSaltSize) {
|
||||
err( "Could not allocate site salt: %s\n", strerror( errno ) );
|
||||
mpw_free( siteSalt, siteSaltSize );
|
||||
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 );
|
||||
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
|
||||
if (!sitePasswordSeed) {
|
||||
ftl( "Could not allocate site seed: %d\n", errno );
|
||||
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )\n",
|
||||
mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
|
||||
mpw_free( siteSalt, siteSaltSize );
|
||||
if (!siteKey) {
|
||||
err( "Could not derive site key: %s\n", strerror( errno ) );
|
||||
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.
|
||||
const char *template = mpw_templateForType_v0( siteType, htons( 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 ) );
|
||||
const char *_siteKey = (const char *)siteKey;
|
||||
const char *template = mpw_templateForType_v0( resultType, htons( _siteKey[0] ) );
|
||||
trc( "template: %u => %s\n", htons( _siteKey[0] ), template );
|
||||
if (!template)
|
||||
return NULL;
|
||||
if (strlen( template ) > MPSiteKeySize) {
|
||||
err( "Template too long for password seed: %zu\n", strlen( template ) );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
sitePassword[c] = mpw_characterFromClass_v0( template[c], htons( sitePasswordSeed[c + 1] ) );
|
||||
trc( "class %c, index %u (0x%02X) -> character: %c\n",
|
||||
template[c], htons( sitePasswordSeed[c + 1] ), htons( sitePasswordSeed[c + 1] ), sitePassword[c] );
|
||||
sitePassword[c] = mpw_characterFromClass_v0( template[c], htons( _siteKey[c + 1] ) );
|
||||
trc( " - class: %c, index: %5u (0x%02hX) => character: %c\n",
|
||||
template[c], htons( _siteKey[c + 1] ), htons( _siteKey[c + 1] ), sitePassword[c] );
|
||||
}
|
||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
||||
trc( " => password: %s\n", 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-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
|
||||
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 );
|
||||
trc( "algorithm: v%d\n", 1 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
|
||||
trc( "masterPassword: %s\n", masterPassword );
|
||||
trc( "key scope: %s\n", mpKeyScope );
|
||||
// Algorithm version overrides.
|
||||
static MPMasterKey mpw_masterKey_v1(
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
// Calculate the master key salt.
|
||||
// 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;
|
||||
return mpw_masterKey_v0( fullName, masterPassword );
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v1(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
static MPSiteKey mpw_siteKey_v1(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
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 );
|
||||
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
}
|
||||
|
||||
// Calculate the site seed.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
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 ) );
|
||||
static const char *mpw_sitePasswordFromTemplate_v1(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||
|
||||
// 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 ) );
|
||||
const char *template = mpw_templateForType( resultType, siteKey[0] );
|
||||
trc( "template: %u => %s\n", siteKey[0], template );
|
||||
if (!template)
|
||||
return NULL;
|
||||
if (strlen( template ) > MPSiteKeySize) {
|
||||
err( "Template too long for password seed: %zu\n", strlen( template ) );
|
||||
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] );
|
||||
sitePassword[c] = mpw_characterFromClass( template[c], siteKey[c + 1] );
|
||||
trc( " - class: %c, index: %3u (0x%02hhX) => character: %c\n",
|
||||
template[c], siteKey[c + 1], siteKey[c + 1], sitePassword[c] );
|
||||
}
|
||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
||||
trc( " => password: %s\n", 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-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
|
||||
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 );
|
||||
trc( "algorithm: v%d\n", 2 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
|
||||
trc( "masterPassword: %s\n", masterPassword );
|
||||
trc( "key scope: %s\n", mpKeyScope );
|
||||
// Algorithm version overrides.
|
||||
static MPMasterKey mpw_masterKey_v2(
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
// Calculate the master key salt.
|
||||
// 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;
|
||||
return mpw_masterKey_v1( fullName, masterPassword );
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v2(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
static MPSiteKey mpw_siteKey_v2(
|
||||
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
trc( "algorithm: v%d\n", 2 );
|
||||
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 );
|
||||
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
|
||||
trc( "keyScope: %s\n", keyScope );
|
||||
|
||||
// TODO: Implement MPCounterValueTOTP
|
||||
|
||||
// Calculate the site seed.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
size_t sitePasswordInfoSize = 0;
|
||||
uint8_t *sitePasswordInfo = NULL;
|
||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
|
||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteName ) ) );
|
||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
||||
if (siteContext) {
|
||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteContext ) ) );
|
||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
|
||||
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s\n",
|
||||
keyScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName, mpw_hex_l( htonl( siteCounter ) ),
|
||||
keyContext? mpw_hex_l( htonl( strlen( keyContext ) ) ): NULL, keyContext );
|
||||
size_t siteSaltSize = 0;
|
||||
uint8_t *siteSalt = NULL;
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, htonl( strlen( siteName ) ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, htonl( siteCounter ) );
|
||||
if (keyContext) {
|
||||
mpw_push_int( &siteSalt, &siteSaltSize, htonl( strlen( keyContext ) ) );
|
||||
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
|
||||
}
|
||||
if (!sitePasswordInfo) {
|
||||
ftl( "Could not allocate site seed info: %d\n", errno );
|
||||
if (!siteSalt || !siteSaltSize) {
|
||||
err( "Could not allocate site salt: %s\n", strerror( errno ) );
|
||||
mpw_free( siteSalt, siteSaltSize );
|
||||
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 );
|
||||
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
|
||||
if (!sitePasswordSeed) {
|
||||
ftl( "Could not allocate site seed: %d\n", errno );
|
||||
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )\n",
|
||||
mpw_id_buf( masterKey, MPMasterKeySize ) );
|
||||
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
|
||||
mpw_free( siteSalt, siteSaltSize );
|
||||
if (!siteKey) {
|
||||
err( "Could not allocate site key: %s\n", strerror( errno ) );
|
||||
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.
|
||||
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;
|
||||
return siteKey;
|
||||
}
|
||||
|
||||
// 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 ) );
|
||||
static const char *mpw_sitePasswordFromTemplate_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return sitePassword;
|
||||
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromCrypt_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
|
||||
|
||||
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, cipherText );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromDerive_v2(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||
|
||||
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-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
#define MP_N 32768LU
|
||||
#define MP_r 8U
|
||||
#define MP_p 2U
|
||||
|
||||
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 );
|
||||
trc( "algorithm: v%d\n", 3 );
|
||||
trc( "fullName: %s (%zu)\n", fullName, strlen( fullName ) );
|
||||
trc( "masterPassword: %s\n", masterPassword );
|
||||
trc( "key scope: %s\n", mpKeyScope );
|
||||
// Algorithm version overrides.
|
||||
static MPMasterKey mpw_masterKey_v3(
|
||||
const char *fullName, const char *masterPassword) {
|
||||
|
||||
const char *keyScope = mpw_scopeForPurpose( MPKeyPurposeAuthentication );
|
||||
trc( "keyScope: %s\n", keyScope );
|
||||
|
||||
// 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;
|
||||
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_string( &masterKeySalt, &masterKeySaltSize, fullName );
|
||||
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;
|
||||
}
|
||||
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
|
||||
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 );
|
||||
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )\n", 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 );
|
||||
if (!masterKey) {
|
||||
ftl( "Could not allocate master key: %d\n", errno );
|
||||
err( "Could not allocate master key: %s\n", strerror( errno ) );
|
||||
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;
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v3(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
|
||||
const MPSiteVariant siteVariant, const char *siteContext) {
|
||||
static MPSiteKey mpw_siteKey_v3(
|
||||
const MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
|
||||
const MPKeyPurpose keyPurpose, const char *keyContext) {
|
||||
|
||||
const char *siteScope = mpw_scopeForVariant( siteVariant );
|
||||
trc( "algorithm: v%d\n", 3 );
|
||||
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.
|
||||
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
|
||||
size_t sitePasswordInfoSize = 0;
|
||||
uint8_t *sitePasswordInfo = NULL;
|
||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
|
||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteName ) ) );
|
||||
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
||||
if (siteContext) {
|
||||
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( 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.
|
||||
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;
|
||||
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
}
|
||||
|
||||
// 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 ) );
|
||||
static const char *mpw_sitePasswordFromTemplate_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return sitePassword;
|
||||
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromCrypt_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
|
||||
|
||||
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, cipherText );
|
||||
}
|
||||
|
||||
static const char *mpw_sitePasswordFromDerive_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
|
||||
|
||||
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
static const char *mpw_siteState_v3(
|
||||
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state) {
|
||||
|
||||
return mpw_siteState_v2( masterKey, siteKey, resultType, state );
|
||||
}
|
||||
|
||||
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,7 +29,31 @@
|
||||
#include "mpw-types.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.
|
||||
size_t stdTypeNameOffset = 0;
|
||||
@@ -41,43 +65,79 @@ const MPSiteType mpw_typeWithName(const char *typeName) {
|
||||
stdTypeName[c] = (char)tolower( typeName[c + stdTypeNameOffset] );
|
||||
stdTypeName[stdTypeNameSize] = '\0';
|
||||
|
||||
// Find what site type is represented by the type name.
|
||||
if (0 == strcmp( stdTypeName, "x" ) || 0 == strcmp( stdTypeName, "max" ) || 0 == strcmp( stdTypeName, "maximum" ))
|
||||
return MPSiteTypeGeneratedMaximum;
|
||||
if (0 == strcmp( stdTypeName, "l" ) || 0 == strcmp( stdTypeName, "long" ))
|
||||
return MPSiteTypeGeneratedLong;
|
||||
if (0 == strcmp( stdTypeName, "m" ) || 0 == strcmp( stdTypeName, "med" ) || 0 == strcmp( stdTypeName, "medium" ))
|
||||
return MPSiteTypeGeneratedMedium;
|
||||
if (0 == strcmp( stdTypeName, "b" ) || 0 == strcmp( stdTypeName, "basic" ))
|
||||
return MPSiteTypeGeneratedBasic;
|
||||
if (0 == strcmp( stdTypeName, "s" ) || 0 == strcmp( stdTypeName, "short" ))
|
||||
return MPSiteTypeGeneratedShort;
|
||||
if (0 == strcmp( stdTypeName, "i" ) || 0 == strcmp( stdTypeName, "pin" ))
|
||||
return MPSiteTypeGeneratedPIN;
|
||||
if (0 == strcmp( stdTypeName, "n" ) || 0 == strcmp( stdTypeName, "name" ))
|
||||
return MPSiteTypeGeneratedName;
|
||||
if (0 == strcmp( stdTypeName, "p" ) || 0 == strcmp( stdTypeName, "phrase" ))
|
||||
return MPSiteTypeGeneratedPhrase;
|
||||
// Find what password type is represented by the type name.
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateMaximum ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateMaximum;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateLong ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateLong;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateMedium ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateMedium;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateBasic ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateBasic;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateShort ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateShort;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplatePIN ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplatePIN;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplateName ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
return MPResultTypeTemplateName;
|
||||
if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0)
|
||||
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 );
|
||||
return MPSiteTypeGeneratedLong;
|
||||
dbg( "Not a generated type name: %s\n", stdTypeName );
|
||||
return (MPResultType)ERR;
|
||||
}
|
||||
|
||||
const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
||||
const char *mpw_nameForType(MPResultType resultType) {
|
||||
|
||||
if (!(type & MPSiteTypeClassGenerated)) {
|
||||
ftl( "Not a generated type: %d", type );
|
||||
*count = 0;
|
||||
switch (resultType) {
|
||||
case MPResultTypeTemplateMaximum:
|
||||
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;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MPSiteTypeGeneratedMaximum: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplateMaximum:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
|
||||
}
|
||||
case MPSiteTypeGeneratedLong: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplateLong:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
|
||||
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
|
||||
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
|
||||
@@ -85,83 +145,89 @@ const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
||||
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
|
||||
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
|
||||
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
|
||||
}
|
||||
case MPSiteTypeGeneratedMedium: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplateMedium:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"CvcnoCvc", "CvcCvcno" );
|
||||
}
|
||||
case MPSiteTypeGeneratedBasic: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplateBasic:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"aaanaaan", "aannaaan", "aaannaaa" );
|
||||
}
|
||||
case MPSiteTypeGeneratedShort: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplateShort:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"Cvcn" );
|
||||
}
|
||||
case MPSiteTypeGeneratedPIN: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplatePIN:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"nnnn" );
|
||||
}
|
||||
case MPSiteTypeGeneratedName: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplateName:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"cvccvcvcv" );
|
||||
}
|
||||
case MPSiteTypeGeneratedPhrase: {
|
||||
return mpw_alloc_array( *count, const char *,
|
||||
case MPResultTypeTemplatePhrase:
|
||||
return mpw_alloc_array( count, const char *,
|
||||
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
|
||||
}
|
||||
default: {
|
||||
ftl( "Unknown generated type: %d", type );
|
||||
*count = 0;
|
||||
dbg( "Unknown generated type: %d\n", type );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
|
||||
const char *mpw_templateForType(MPResultType type, uint8_t seedByte) {
|
||||
|
||||
size_t count = 0;
|
||||
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 );
|
||||
|
||||
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.
|
||||
size_t stdVariantNameSize = strlen( variantName );
|
||||
char stdVariantName[stdVariantNameSize + 1];
|
||||
for (size_t c = 0; c < stdVariantNameSize; ++c)
|
||||
stdVariantName[c] = (char)tolower( variantName[c] );
|
||||
stdVariantName[stdVariantNameSize] = '\0';
|
||||
size_t stdPurposeNameSize = strlen( purposeName );
|
||||
char stdPurposeName[stdPurposeNameSize + 1];
|
||||
for (size_t c = 0; c < stdPurposeNameSize; ++c)
|
||||
stdPurposeName[c] = (char)tolower( purposeName[c] );
|
||||
stdPurposeName[stdPurposeNameSize] = '\0';
|
||||
|
||||
if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" ))
|
||||
return MPSiteVariantPassword;
|
||||
if (0 == strcmp( stdVariantName, "l" ) || 0 == strcmp( stdVariantName, "login" ))
|
||||
return MPSiteVariantLogin;
|
||||
if (0 == strcmp( stdVariantName, "a" ) || 0 == strcmp( stdVariantName, "answer" ))
|
||||
return MPSiteVariantAnswer;
|
||||
if (strncmp( mpw_nameForPurpose( MPKeyPurposeAuthentication ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||
return MPKeyPurposeAuthentication;
|
||||
if (strncmp( mpw_nameForPurpose( MPKeyPurposeIdentification ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||
return MPKeyPurposeIdentification;
|
||||
if (strncmp( mpw_nameForPurpose( MPKeyPurposeRecovery ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
|
||||
return MPKeyPurposeRecovery;
|
||||
|
||||
fprintf( stderr, "Not a variant name: %s", stdVariantName );
|
||||
abort();
|
||||
dbg( "Not a purpose name: %s\n", stdPurposeName );
|
||||
return (MPKeyPurpose)ERR;
|
||||
}
|
||||
|
||||
const char *mpw_scopeForVariant(MPSiteVariant variant) {
|
||||
const char *mpw_nameForPurpose(MPKeyPurpose purpose) {
|
||||
|
||||
switch (variant) {
|
||||
case MPSiteVariantPassword: {
|
||||
return "com.lyndir.masterpassword";
|
||||
}
|
||||
case MPSiteVariantLogin: {
|
||||
return "com.lyndir.masterpassword.login";
|
||||
}
|
||||
case MPSiteVariantAnswer: {
|
||||
return "com.lyndir.masterpassword.answer";
|
||||
}
|
||||
switch (purpose) {
|
||||
case MPKeyPurposeAuthentication:
|
||||
return "authentication";
|
||||
case MPKeyPurposeIdentification:
|
||||
return "identification";
|
||||
case MPKeyPurposeRecovery:
|
||||
return "recovery";
|
||||
default: {
|
||||
fprintf( stderr, "Unknown variant: %d", variant );
|
||||
abort();
|
||||
dbg( "Unknown purpose: %d\n", purpose );
|
||||
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 ' ':
|
||||
return " ";
|
||||
default: {
|
||||
fprintf( stderr, "Unknown character class: %c", characterClass );
|
||||
abort();
|
||||
dbg( "Unknown character class: %c\n", characterClass );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,5 +265,8 @@ const char *mpw_charactersInClass(char characterClass) {
|
||||
const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
|
||||
|
||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||
if (!classCharacters)
|
||||
return '\0';
|
||||
|
||||
return classCharacters[seedByte % strlen( classCharacters )];
|
||||
}
|
||||
|
||||
@@ -18,71 +18,118 @@
|
||||
|
||||
#ifndef _MPW_TYPES_H
|
||||
#define _MPW_TYPES_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef NS_ENUM
|
||||
#define enum(_type, _name) NS_ENUM(_type, _name)
|
||||
#else
|
||||
//#ifdef NS_ENUM
|
||||
//#define enum(_type, _name) NS_ENUM(_type, _name)
|
||||
//#else
|
||||
#define enum(_type, _name) _type _name; enum
|
||||
#endif
|
||||
|
||||
#define MP_dkLen 64
|
||||
//#endif
|
||||
|
||||
//// 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. */
|
||||
MPSiteVariantPassword,
|
||||
MPKeyPurposeAuthentication,
|
||||
/** Generate a name for identification. */
|
||||
MPSiteVariantLogin,
|
||||
/** Generate an answer to a security question. */
|
||||
MPSiteVariantAnswer,
|
||||
MPKeyPurposeIdentification,
|
||||
/** Generate a recovery token. */
|
||||
MPKeyPurposeRecovery,
|
||||
};
|
||||
|
||||
typedef enum( unsigned int, MPSiteTypeClass ) {
|
||||
/** Generate the password. */
|
||||
MPSiteTypeClassGenerated = 1 << 4,
|
||||
/** Store the password. */
|
||||
MPSiteTypeClassStored = 1 << 5,
|
||||
// bit 4 - 9
|
||||
typedef enum( uint16_t, MPResultTypeClass ) {
|
||||
/** Use the site key to generate a password from a template. */
|
||||
MPResultTypeClassTemplate = 1 << 4,
|
||||
/** 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. */
|
||||
MPSiteFeatureExportContent = 1 << 10,
|
||||
/** Never export content. */
|
||||
MPSiteFeatureDevicePrivate = 1 << 11,
|
||||
/** Don't use this as the primary authentication result type. */
|
||||
MPSiteFeatureAlternative = 1 << 12,
|
||||
};
|
||||
|
||||
typedef enum( unsigned int, MPSiteType) {
|
||||
MPSiteTypeGeneratedMaximum = 0x0 | MPSiteTypeClassGenerated | 0x0,
|
||||
MPSiteTypeGeneratedLong = 0x1 | MPSiteTypeClassGenerated | 0x0,
|
||||
MPSiteTypeGeneratedMedium = 0x2 | MPSiteTypeClassGenerated | 0x0,
|
||||
MPSiteTypeGeneratedBasic = 0x4 | MPSiteTypeClassGenerated | 0x0,
|
||||
MPSiteTypeGeneratedShort = 0x3 | MPSiteTypeClassGenerated | 0x0,
|
||||
MPSiteTypeGeneratedPIN = 0x5 | MPSiteTypeClassGenerated | 0x0,
|
||||
MPSiteTypeGeneratedName = 0xE | MPSiteTypeClassGenerated | 0x0,
|
||||
MPSiteTypeGeneratedPhrase = 0xF | MPSiteTypeClassGenerated | 0x0,
|
||||
// bit 0-3 | MPResultTypeClass | MPSiteFeature
|
||||
typedef enum( uint32_t, MPResultType ) {
|
||||
/** pg^VMAUBk5x3p%HP%i4= */
|
||||
MPResultTypeTemplateMaximum = 0x0 | MPResultTypeClassTemplate | 0x0,
|
||||
/** BiroYena8:Kixa */
|
||||
MPResultTypeTemplateLong = 0x1 | MPResultTypeClassTemplate | 0x0,
|
||||
/** BirSuj0- */
|
||||
MPResultTypeTemplateMedium = 0x2 | MPResultTypeClassTemplate | 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,
|
||||
MPSiteTypeStoredDevicePrivate = 0x1 | MPSiteTypeClassStored | MPSiteFeatureDevicePrivate,
|
||||
/** Custom saved password. */
|
||||
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.
|
||||
|
||||
/**
|
||||
* @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.
|
||||
@@ -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.
|
||||
* 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
|
||||
* 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.
|
||||
|
||||
@@ -16,66 +16,115 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef COLOR
|
||||
#if MPW_COLOR
|
||||
#include <unistd.h>
|
||||
#include <curses.h>
|
||||
#include <term.h>
|
||||
#endif
|
||||
|
||||
#include <scrypt/sha256.h>
|
||||
#if HAS_CPERCIVA
|
||||
#include <scrypt/crypto_scrypt.h>
|
||||
#include <scrypt/sha256.h>
|
||||
#elif HAS_SODIUM
|
||||
#include "sodium.h"
|
||||
#ifdef SODIUM_LIBRARY_MINIMAL
|
||||
#include "crypto_stream_aes128ctr.h"
|
||||
#include "crypto_kdf_blake2b.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
||||
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.
|
||||
return;
|
||||
return false;
|
||||
|
||||
*bufferSize += pushSize;
|
||||
uint8_t *resizedBuffer = realloc( *buffer, *bufferSize );
|
||||
if (!resizedBuffer) {
|
||||
if (!mpw_realloc( buffer, bufferSize, pushSize )) {
|
||||
// realloc failed, we can't push. Mark the buffer as broken.
|
||||
mpw_free( *buffer, *bufferSize - pushSize );
|
||||
*bufferSize = (size_t)-1;
|
||||
mpw_free( *buffer, *bufferSize );
|
||||
*bufferSize = (size_t)ERR;
|
||||
*buffer = NULL;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
*buffer = resizedBuffer;
|
||||
uint8_t *pushDst = *buffer + *bufferSize - pushSize;
|
||||
memcpy( pushDst, pushBuffer, pushSize );
|
||||
uint8_t *bufferOffset = *buffer + *bufferSize - pushSize;
|
||||
memcpy( bufferOffset, 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, ...) {
|
||||
|
||||
va_list args;
|
||||
va_start( args, pushFormat );
|
||||
char *pushString = NULL;
|
||||
bool success = vasprintf( &pushString, pushFormat, args ) >= 0 && mpw_string_push( string, pushString );
|
||||
va_end( args );
|
||||
mpw_free_string( pushString );
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mpw_push_int(uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt) {
|
||||
|
||||
return mpw_push_buf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
|
||||
}
|
||||
|
||||
bool __mpw_realloc(void **buffer, size_t *bufferSize, const size_t deltaSize) {
|
||||
|
||||
if (!buffer)
|
||||
return false;
|
||||
|
||||
void *newBuffer = realloc( *buffer, (bufferSize? *bufferSize: 0) + deltaSize );
|
||||
if (!newBuffer)
|
||||
return false;
|
||||
|
||||
*buffer = newBuffer;
|
||||
if (bufferSize)
|
||||
*bufferSize += deltaSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mpw_free(const void *buffer, const size_t bufferSize) {
|
||||
|
||||
if (!buffer)
|
||||
return false;
|
||||
|
||||
if (buffer) {
|
||||
memset( (void *)buffer, 0, bufferSize );
|
||||
free( (void *)buffer );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void mpw_free_string(const char *string) {
|
||||
bool mpw_free_string(const char *string) {
|
||||
|
||||
mpw_free( string, strlen( string ) );
|
||||
return string && mpw_free( string, strlen( string ) );
|
||||
}
|
||||
|
||||
uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
||||
uint8_t const *mpw_kdf_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
|
||||
uint64_t N, uint32_t r, uint32_t p) {
|
||||
|
||||
if (!secret || !salt)
|
||||
@@ -85,46 +134,200 @@ uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_
|
||||
if (!key)
|
||||
return NULL;
|
||||
|
||||
#if HAS_CPERCIVA
|
||||
if (crypto_scrypt( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) < 0) {
|
||||
mpw_free( key, keySize );
|
||||
return NULL;
|
||||
}
|
||||
#elif HAS_SODIUM
|
||||
if (crypto_pwhash_scryptsalsa208sha256_ll( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) != 0) {
|
||||
mpw_free( key, keySize );
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
#error No crypto support for mpw_scrypt.
|
||||
#endif
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
uint8_t const *mpw_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 (!buffer)
|
||||
if (!key || !keySize || !subkeySize) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *subkey = malloc( subkeySize );
|
||||
if (!subkey)
|
||||
return NULL;
|
||||
|
||||
HMAC_SHA256_Buf( key, keySize, salt, saltSize, buffer );
|
||||
return buffer;
|
||||
#if HAS_SODIUM
|
||||
if (keySize < crypto_generichash_blake2b_KEYBYTES_MIN || keySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
|
||||
subkeySize < crypto_generichash_blake2b_KEYBYTES_MIN || subkeySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
|
||||
contextSize < crypto_generichash_blake2b_BYTES_MIN || contextSize > crypto_generichash_blake2b_BYTES_MAX ||
|
||||
(personal && strlen( personal ) > crypto_generichash_blake2b_PERSONALBYTES)) {
|
||||
errno = EINVAL;
|
||||
free( subkey );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *mpw_id_buf(const void *buf, size_t length) {
|
||||
uint8_t saltBuf[crypto_generichash_blake2b_SALTBYTES];
|
||||
bzero( saltBuf, sizeof saltBuf );
|
||||
if (id) {
|
||||
uint64_t id_n = htonll( id );
|
||||
memcpy( saltBuf, &id_n, sizeof id_n );
|
||||
}
|
||||
|
||||
uint8_t personalBuf[crypto_generichash_blake2b_PERSONALBYTES];
|
||||
bzero( personalBuf, sizeof saltBuf );
|
||||
if (personal && strlen( personal ))
|
||||
memcpy( personalBuf, personal, strlen( personal ) );
|
||||
|
||||
if (crypto_generichash_blake2b_salt_personal( subkey, subkeySize, context, contextSize, key, keySize, saltBuf, personalBuf ) != 0) {
|
||||
mpw_free( subkey, subkeySize );
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
#error No crypto support for mpw_kdf_blake2b.
|
||||
#endif
|
||||
|
||||
return subkey;
|
||||
}
|
||||
|
||||
uint8_t const *mpw_hash_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *message, const size_t messageSize) {
|
||||
|
||||
if (!key || !keySize || !message || !messageSize)
|
||||
return NULL;
|
||||
|
||||
#if HAS_CPERCIVA
|
||||
uint8_t *const mac = malloc( 32 );
|
||||
if (!mac)
|
||||
return NULL;
|
||||
|
||||
HMAC_SHA256_Buf( key, keySize, message, messageSize, mac );
|
||||
#elif HAS_SODIUM
|
||||
uint8_t *const mac = malloc( crypto_auth_hmacsha256_BYTES );
|
||||
if (!mac)
|
||||
return NULL;
|
||||
|
||||
crypto_auth_hmacsha256_state state;
|
||||
if (crypto_auth_hmacsha256_init( &state, key, keySize ) != 0 ||
|
||||
crypto_auth_hmacsha256_update( &state, message, messageSize ) != 0 ||
|
||||
crypto_auth_hmacsha256_final( &state, mac ) != 0) {
|
||||
mpw_free( mac, crypto_auth_hmacsha256_BYTES );
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
#error No crypto support for mpw_hmac_sha256.
|
||||
#endif
|
||||
|
||||
return mac;
|
||||
}
|
||||
|
||||
static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t keySize, const uint8_t *buf, const size_t bufSize) {
|
||||
|
||||
#if HAS_SODIUM
|
||||
if (!key || keySize < crypto_stream_KEYBYTES)
|
||||
return NULL;
|
||||
|
||||
uint8_t nonce[crypto_stream_NONCEBYTES];
|
||||
bzero( (void *)nonce, sizeof( nonce ) );
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
if (encrypt) {
|
||||
uint8_t *const cipherBuf = malloc( bufSize );
|
||||
if (crypto_stream_aes128ctr_xor( cipherBuf, buf, bufSize, nonce, key ) != 0) {
|
||||
mpw_free( cipherBuf, bufSize );
|
||||
return NULL;
|
||||
}
|
||||
return cipherBuf;
|
||||
}
|
||||
else {
|
||||
uint8_t *const plainBuf = malloc( bufSize );
|
||||
if (crypto_stream_aes128ctr( plainBuf, bufSize, nonce, key ) != 0) {
|
||||
mpw_free( plainBuf, bufSize );
|
||||
return NULL;
|
||||
}
|
||||
for (size_t c = 0; c < bufSize; ++c)
|
||||
plainBuf[c] = buf[c] ^ plainBuf[c];
|
||||
return plainBuf;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
#else
|
||||
#error No crypto support for mpw_aes.
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t const *mpw_aes_encrypt(const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, const size_t bufSize) {
|
||||
|
||||
return mpw_aes( true, key, keySize, plainBuf, bufSize );
|
||||
}
|
||||
|
||||
uint8_t const *mpw_aes_decrypt(const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, const size_t bufSize) {
|
||||
|
||||
return mpw_aes( false, key, keySize, cipherBuf, bufSize );
|
||||
}
|
||||
|
||||
MPKeyID mpw_id_buf(const void *buf, size_t length) {
|
||||
|
||||
if (!buf)
|
||||
return "<unset>";
|
||||
|
||||
#if HAS_CPERCIVA
|
||||
uint8_t hash[32];
|
||||
SHA256_Buf( buf, length, hash );
|
||||
#elif HAS_SODIUM
|
||||
uint8_t hash[crypto_hash_sha256_BYTES];
|
||||
crypto_hash_sha256( hash, buf, length );
|
||||
#else
|
||||
#error No crypto support for mpw_id_buf.
|
||||
#endif
|
||||
|
||||
return mpw_hex( hash, 32 );
|
||||
return mpw_hex( hash, sizeof( hash ) / sizeof( uint8_t ) );
|
||||
}
|
||||
|
||||
static char **mpw_hex_buf = NULL;
|
||||
static unsigned int mpw_hex_buf_i = 0;
|
||||
bool mpw_id_buf_equals(const char *id1, const char *id2) {
|
||||
|
||||
size_t size = strlen( id1 );
|
||||
if (size != strlen( id2 ))
|
||||
return false;
|
||||
|
||||
for (size_t c = 0; c < size; ++c)
|
||||
if (tolower( id1[c] ) != tolower( id2[c] ))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *str_str;
|
||||
|
||||
const char *mpw_str(const char *format, ...) {
|
||||
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vasprintf( &str_str, format, args );
|
||||
va_end( args );
|
||||
|
||||
return str_str;
|
||||
}
|
||||
|
||||
static char **mpw_hex_buf;
|
||||
static unsigned int mpw_hex_buf_i;
|
||||
|
||||
const char *mpw_hex(const void *buf, size_t length) {
|
||||
|
||||
// FIXME
|
||||
if (!mpw_hex_buf) {
|
||||
mpw_hex_buf = malloc( 10 * sizeof( char * ) );
|
||||
for (uint8_t i = 0; i < 10; ++i)
|
||||
mpw_hex_buf[i] = NULL;
|
||||
}
|
||||
// FIXME: Not thread-safe
|
||||
if (!mpw_hex_buf)
|
||||
mpw_hex_buf = calloc( 10, sizeof( char * ) );
|
||||
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
|
||||
|
||||
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++)
|
||||
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
|
||||
|
||||
@@ -147,7 +350,7 @@ static int initputvar() {
|
||||
free( putvarc );
|
||||
if (!termsetup) {
|
||||
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 );
|
||||
return 0;
|
||||
}
|
||||
@@ -174,8 +377,11 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
|
||||
"♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌"
|
||||
};
|
||||
|
||||
uint8_t identiconSeed[32];
|
||||
HMAC_SHA256_Buf( masterPassword, strlen( masterPassword ), fullName, strlen( fullName ), identiconSeed );
|
||||
const uint8_t *identiconSeed = mpw_hash_hmac_sha256( (const uint8_t *)masterPassword, strlen( masterPassword ),
|
||||
(const uint8_t *)fullName,
|
||||
strlen( fullName ) );
|
||||
if (!identiconSeed)
|
||||
return NULL;
|
||||
|
||||
char *colorString, *resetString;
|
||||
#ifdef COLOR
|
||||
@@ -203,6 +409,7 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
|
||||
accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
|
||||
resetString );
|
||||
|
||||
mpw_free( identiconSeed, 32 );
|
||||
free( colorString );
|
||||
free( resetString );
|
||||
return identicon;
|
||||
|
||||
@@ -16,99 +16,167 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#ifndef _MPW_UTIL_H
|
||||
#define _MPW_UTIL_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
|
||||
//// Logging.
|
||||
|
||||
#ifndef trc
|
||||
int mpw_verbosity;
|
||||
extern int mpw_verbosity;
|
||||
#define trc_level 3
|
||||
#define trc(...) \
|
||||
/** Logging internal state. */
|
||||
#define trc(...) ({ \
|
||||
if (mpw_verbosity >= 3) \
|
||||
fprintf( stderr, __VA_ARGS__ )
|
||||
fprintf( stderr, __VA_ARGS__ ); })
|
||||
#endif
|
||||
#ifndef dbg
|
||||
#define dbg_level 2
|
||||
#define dbg(...) \
|
||||
/** Logging state and events interesting when investigating issues. */
|
||||
#define dbg(...) ({ \
|
||||
if (mpw_verbosity >= 2) \
|
||||
fprintf( stderr, __VA_ARGS__ )
|
||||
fprintf( stderr, __VA_ARGS__ ); })
|
||||
#endif
|
||||
#ifndef inf
|
||||
#define inf_level 1
|
||||
#define inf(...) \
|
||||
/** User messages. */
|
||||
#define inf(...) ({ \
|
||||
if (mpw_verbosity >= 1) \
|
||||
fprintf( stderr, __VA_ARGS__ )
|
||||
fprintf( stderr, __VA_ARGS__ ); })
|
||||
#endif
|
||||
#ifndef wrn
|
||||
#define wrn_level 0
|
||||
#define wrn(...) \
|
||||
/** Recoverable issues and user suggestions. */
|
||||
#define wrn(...) ({ \
|
||||
if (mpw_verbosity >= 0) \
|
||||
fprintf( stderr, __VA_ARGS__ )
|
||||
fprintf( stderr, __VA_ARGS__ ); })
|
||||
#endif
|
||||
#ifndef err
|
||||
#define err_level -1
|
||||
#define err(...) \
|
||||
/** Unrecoverable issues. */
|
||||
#define err(...) ({ \
|
||||
if (mpw_verbosity >= -1) \
|
||||
fprintf( stderr, __VA_ARGS__ )
|
||||
fprintf( stderr, __VA_ARGS__ ); })
|
||||
#endif
|
||||
#ifndef ftl
|
||||
#define ftl_level -2
|
||||
#define ftl(...) \
|
||||
do { \
|
||||
/** Issues that lead to abortion. */
|
||||
#define ftl(...) ({ \
|
||||
if (mpw_verbosity >= -2) \
|
||||
fprintf( stderr, __VA_ARGS__ ); \
|
||||
exit( 2 ); \
|
||||
} while (0)
|
||||
fprintf( stderr, __VA_ARGS__ ); })
|
||||
#endif
|
||||
|
||||
#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
|
||||
|
||||
//// 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, ...) ({ \
|
||||
_type stackElements[] = { __VA_ARGS__ }; \
|
||||
_count = sizeof( stackElements ) / sizeof( _type ); \
|
||||
if (_count) \
|
||||
*_count = sizeof( stackElements ) / sizeof( _type ); \
|
||||
_type *allocElements = malloc( sizeof( stackElements ) ); \
|
||||
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
|
||||
allocElements; \
|
||||
})
|
||||
|
||||
/** 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);
|
||||
/** 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);
|
||||
/** 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. */
|
||||
void mpw_push_int(
|
||||
bool mpw_push_int(
|
||||
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. */
|
||||
void mpw_free(
|
||||
bool mpw_free(
|
||||
const void *buffer, const size_t bufferSize);
|
||||
/** Free a string after zero'ing its contents. */
|
||||
void mpw_free_string(
|
||||
bool mpw_free_string(
|
||||
const char *string);
|
||||
|
||||
//// Cryptographic functions.
|
||||
|
||||
/** Perform a scrypt-based key derivation on the given key using the given salt and scrypt parameters.
|
||||
* @return A new keySize-size allocated buffer. */
|
||||
uint8_t const *mpw_scrypt(
|
||||
/** Derive a key from the given secret and salt using the scrypt KDF.
|
||||
* @return A new keySize allocated buffer containing the key. */
|
||||
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);
|
||||
/** Calculate a SHA256-based HMAC by encrypting the given salt with the given key.
|
||||
* @return A new 32-byte allocated buffer. */
|
||||
uint8_t const *mpw_hmac_sha256(
|
||||
/** Derive a subkey from the given key using the blake2b KDF.
|
||||
* @return A new keySize allocated buffer containing the key. */
|
||||
uint8_t const *mpw_kdf_blake2b(
|
||||
const size_t subkeySize, const uint8_t *key, const size_t keySize,
|
||||
const uint8_t *context, const size_t contextSize, const uint64_t id, const char *personal);
|
||||
/** 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);
|
||||
/** 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.
|
||||
|
||||
/** 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.
|
||||
* @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_l(uint32_t number);
|
||||
/** Encode a fingerprint for a buffer.
|
||||
* @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.
|
||||
* @return A newly allocated string. */
|
||||
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. */
|
||||
const size_t mpw_utf8_strlen(const char *utf8String);
|
||||
|
||||
#endif // _MPW_UTIL_H
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
||||
<siteName>masterpasswordapp.com</siteName>
|
||||
<siteCounter>1</siteCounter>
|
||||
<siteType>GeneratedLong</siteType>
|
||||
<siteVariant>Password</siteVariant>
|
||||
<resultType>GeneratedLong</resultType>
|
||||
<keyPurpose>Authentication</keyPurpose>
|
||||
<result><!-- abstract --></result>
|
||||
</case>
|
||||
|
||||
@@ -32,45 +32,45 @@
|
||||
<result>LiheCuwhSerz6)</result>
|
||||
</case>
|
||||
<case id="v3_loginName" parent="v3">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v3_securityAnswer" parent="v3">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<keyContext>question</keyContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v3_type_maximum" parent="v3">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<resultType>GeneratedMaximum</resultType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v3_type_medium" parent="v3">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<resultType>GeneratedMedium</resultType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v3_type_basic" parent="v3">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<resultType>GeneratedBasic</resultType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v3_type_short" parent="v3">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<resultType>GeneratedShort</resultType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v3_type_pin" parent="v3">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<resultType>GeneratedPIN</resultType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v3_type_name" parent="v3">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v3_type_phrase" parent="v3">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v3_counter_ceiling" parent="v3">
|
||||
@@ -98,45 +98,45 @@
|
||||
<result>LiheCuwhSerz6)</result>
|
||||
</case>
|
||||
<case id="v2_loginName" parent="v2">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v2_securityAnswer" parent="v2">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<keyContext>question</keyContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v2_type_maximum" parent="v2">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<resultType>GeneratedMaximum</resultType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v2_type_medium" parent="v2">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<resultType>GeneratedMedium</resultType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v2_type_basic" parent="v2">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<resultType>GeneratedBasic</resultType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v2_type_short" parent="v2">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<resultType>GeneratedShort</resultType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v2_type_pin" parent="v2">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<resultType>GeneratedPIN</resultType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v2_type_name" parent="v2">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v2_type_phrase" parent="v2">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v2_counter_ceiling" parent="v2">
|
||||
@@ -164,45 +164,45 @@
|
||||
<result>WawiYarp2@Kodh</result>
|
||||
</case>
|
||||
<case id="v1_loginName" parent="v1">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>wohzaqage</result>
|
||||
</case>
|
||||
<case id="v1_securityAnswer" parent="v1">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>xin diyjiqoja hubu</result>
|
||||
</case>
|
||||
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<keyContext>question</keyContext>
|
||||
<result>xogx tem cegyiva jab</result>
|
||||
</case>
|
||||
<case id="v1_type_maximum" parent="v1">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<resultType>GeneratedMaximum</resultType>
|
||||
<result>W6@692^B1#&@gVdSdLZ@</result>
|
||||
</case>
|
||||
<case id="v1_type_medium" parent="v1">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<resultType>GeneratedMedium</resultType>
|
||||
<result>Jej2$Quv</result>
|
||||
</case>
|
||||
<case id="v1_type_basic" parent="v1">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<resultType>GeneratedBasic</resultType>
|
||||
<result>WAo2xIg6</result>
|
||||
</case>
|
||||
<case id="v1_type_short" parent="v1">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<resultType>GeneratedShort</resultType>
|
||||
<result>Jej2</result>
|
||||
</case>
|
||||
<case id="v1_type_pin" parent="v1">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<resultType>GeneratedPIN</resultType>
|
||||
<result>7662</result>
|
||||
</case>
|
||||
<case id="v1_type_name" parent="v1">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>jejraquvo</result>
|
||||
</case>
|
||||
<case id="v1_type_phrase" parent="v1">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>jejr quv cabsibu tam</result>
|
||||
</case>
|
||||
<case id="v1_counter_ceiling" parent="v1">
|
||||
@@ -230,45 +230,45 @@
|
||||
<result>HahiVana2@Nole</result>
|
||||
</case>
|
||||
<case id="v0_loginName" parent="v0">
|
||||
<siteVariant>Login</siteVariant>
|
||||
<siteType>GeneratedName</siteType>
|
||||
<keyPurpose>Identification</keyPurpose>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>lozwajave</result>
|
||||
</case>
|
||||
<case id="v0_securityAnswer" parent="v0">
|
||||
<siteVariant>Answer</siteVariant>
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<keyPurpose>Recovery</keyPurpose>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>miy lirfijoja dubu</result>
|
||||
</case>
|
||||
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
|
||||
<siteContext>question</siteContext>
|
||||
<keyContext>question</keyContext>
|
||||
<result>movm bex gevrica jaf</result>
|
||||
</case>
|
||||
<case id="v0_type_maximum" parent="v0">
|
||||
<siteType>GeneratedMaximum</siteType>
|
||||
<resultType>GeneratedMaximum</resultType>
|
||||
<result>w1!3bA3icmRAc)SS@lwl</result>
|
||||
</case>
|
||||
<case id="v0_type_medium" parent="v0">
|
||||
<siteType>GeneratedMedium</siteType>
|
||||
<resultType>GeneratedMedium</resultType>
|
||||
<result>Fej7]Jug</result>
|
||||
</case>
|
||||
<case id="v0_type_basic" parent="v0">
|
||||
<siteType>GeneratedBasic</siteType>
|
||||
<resultType>GeneratedBasic</resultType>
|
||||
<result>wvH7irC1</result>
|
||||
</case>
|
||||
<case id="v0_type_short" parent="v0">
|
||||
<siteType>GeneratedShort</siteType>
|
||||
<resultType>GeneratedShort</resultType>
|
||||
<result>Fej7</result>
|
||||
</case>
|
||||
<case id="v0_type_pin" parent="v0">
|
||||
<siteType>GeneratedPIN</siteType>
|
||||
<resultType>GeneratedPIN</resultType>
|
||||
<result>2117</result>
|
||||
</case>
|
||||
<case id="v0_type_name" parent="v0">
|
||||
<siteType>GeneratedName</siteType>
|
||||
<resultType>GeneratedName</resultType>
|
||||
<result>fejrajugo</result>
|
||||
</case>
|
||||
<case id="v0_type_phrase" parent="v0">
|
||||
<siteType>GeneratedPhrase</siteType>
|
||||
<resultType>GeneratedPhrase</resultType>
|
||||
<result>fejr jug gabsibu bax</result>
|
||||
</case>
|
||||
<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"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
useCustomWorkingDirectory = "YES"
|
||||
customWorkingDirectory = "/Users/lhunath/Documents/workspace/lyndir/MasterPassword/platform-independent/cli-c"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : 9223372036854775807,
|
||||
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : 9223372036854775807,
|
||||
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : 9223372036854775807,
|
||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 9223372036854775807,
|
||||
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : 9223372036854775807,
|
||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 9223372036854775807,
|
||||
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : 9223372036854775807,
|
||||
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : 0
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1DC75A27-0030-4493-ACE8-E1D49AB9A549",
|
||||
@@ -20,8 +21,9 @@
|
||||
"8A15A8EA0B3D0B497C4883425BC74DF995224BB3" : "MasterPassword\/platform-darwin\/External\/jrswizzle\/",
|
||||
"1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA" : "MasterPassword\/platform-darwin\/External\/InAppSettingsKit\/",
|
||||
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : "MasterPassword\/platform-darwin\/External\/uicolor-utilities\/",
|
||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/platform-darwin\/External\/AttributedMarkdown\/",
|
||||
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : "MasterPassword\/platform-darwin\/External\/Pearl\/",
|
||||
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/platform-darwin\/External\/AttributedMarkdown\/",
|
||||
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : "MasterPassword\/platform-darwin\/External\/libsodium\/",
|
||||
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : "MasterPassword\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "MasterPassword",
|
||||
@@ -58,6 +60,11 @@
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"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",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
|
||||
@@ -35,11 +35,15 @@ setSettingWithTitle() {
|
||||
done
|
||||
}
|
||||
|
||||
description=$(git describe --always --dirty --long --match '*-release')
|
||||
version=${description%-g*}
|
||||
release=${version%%-*} build=${version##*-}
|
||||
printf -v version '%s.%d' "$release" "$build"
|
||||
printf -v commit '%s' "${description##*-g}"
|
||||
case $PLATFORM_NAME in
|
||||
macosx) platform=mac ;;
|
||||
iphone*) platform=ios ;;
|
||||
*) ftl 'ERROR: Unknown platform: %s.' "$PLATFORM_NAME"; exit 1 ;;
|
||||
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"
|
||||
setPlistWithKey CFBundleVersion "$(hr "${version%%.*}" 14).${version#*.}"
|
||||
@@ -51,12 +55,14 @@ setSettingWithTitle "Copyright" "$(getPlistWithKey NSHumanReadableCopyright)"
|
||||
|
||||
if [[ $DEPLOYMENT_LOCATION = YES ]]; then
|
||||
# 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
|
||||
[[ $description != *-dirty ]] || \
|
||||
{ passed=0; err 'ERROR: Cannot release a dirty version, first commit any changes.'; }
|
||||
[[ -r "$crashlyticsPlist" && $(PlistBuddy -c "Print :'API Key'" "$crashlyticsPlist" 2>/dev/null) ]] || \
|
||||
{ passed=0; err 'ERROR: Cannot release: Crashlytics API key is missing.'; }
|
||||
[[ $build == 0 ]] || \
|
||||
{ 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 )) || \
|
||||
{ ftl "Failed to pass release checks. Fix the above errors and re-try. Aborting."; exit 1; }
|
||||
fi
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
//==============================================================================
|
||||
|
||||
#import "MPKey.h"
|
||||
#import "MPStoredSiteEntity.h"
|
||||
#import "MPGeneratedSiteEntity.h"
|
||||
#import "MPSiteQuestionEntity.h"
|
||||
#import "MPStoredSiteEntity+CoreDataClass.h"
|
||||
#import "MPGeneratedSiteEntity+CoreDataClass.h"
|
||||
#import "MPSiteQuestionEntity+CoreDataClass.h"
|
||||
#import "mpw-algorithm.h"
|
||||
|
||||
#define MPAlgorithmDefaultVersion MPAlgorithmVersionCurrent
|
||||
@@ -49,7 +49,7 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
||||
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
||||
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
||||
- (NSData *)keyIDForKey:(MPMasterKey)masterKey;
|
||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||
|
||||
- (NSString *)nameOfType:(MPSiteType)type;
|
||||
@@ -58,6 +58,7 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
||||
- (Class)classOfType:(MPSiteType)type;
|
||||
- (NSArray *)allTypes;
|
||||
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
|
||||
- (MPSiteType)defaultType;
|
||||
- (MPSiteType)nextType:(MPSiteType)type;
|
||||
- (MPSiteType)previousType:(MPSiteType)type;
|
||||
|
||||
|
||||
@@ -24,26 +24,20 @@
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPAppDelegate_InApp.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 */
|
||||
#define CRACKING_PER_SECOND 2495000000UL
|
||||
#define CRACKING_PRICE 350
|
||||
|
||||
NSOperationQueue *_mpwQueue = nil;
|
||||
static NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
@implementation MPAlgorithmV0 {
|
||||
BN_CTX *_ctx;
|
||||
}
|
||||
@implementation MPAlgorithmV0
|
||||
|
||||
- (id)init {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
_ctx = BN_CTX_new();
|
||||
|
||||
static dispatch_once_t once = 0;
|
||||
dispatch_once( &once, ^{
|
||||
_mpwQueue = [NSOperationQueue new];
|
||||
@@ -54,12 +48,6 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
|
||||
BN_CTX_free( _ctx );
|
||||
_ctx = NULL;
|
||||
}
|
||||
|
||||
- (MPAlgorithmVersion)version {
|
||||
|
||||
return MPAlgorithmVersion0;
|
||||
@@ -105,7 +93,7 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
||||
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
|
||||
if (!migrationSites) {
|
||||
err( @"While looking for sites to migrate: %@", [error fullDescription] );
|
||||
MPError( error, @"While looking for sites to migrate." );
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -140,21 +128,21 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
__block NSData *keyData;
|
||||
[self mpw_perform:^{
|
||||
NSDate *start = [NSDate date];
|
||||
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] );
|
||||
if (masterKeyBytes) {
|
||||
keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen];
|
||||
MPMasterKey masterKey = mpw_masterKey( fullName.UTF8String, masterPassword.UTF8String, [self version] );
|
||||
if (masterKey) {
|
||||
keyData = [NSData dataWithBytes:masterKey length:MPMasterKeySize];
|
||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
|
||||
fullName, masterPassword, [self keyIDForKeyData:keyData], -[start timeIntervalSinceNow] );
|
||||
mpw_free( masterKeyBytes, MP_dkLen );
|
||||
fullName, masterPassword, [self keyIDForKey:masterKey], -[start timeIntervalSinceNow] );
|
||||
mpw_free( masterKey, MPMasterKeySize );
|
||||
}
|
||||
}];
|
||||
|
||||
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 {
|
||||
@@ -298,6 +286,11 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
return allTypes;
|
||||
}
|
||||
|
||||
- (MPSiteType)defaultType {
|
||||
|
||||
return MPSiteTypeGeneratedLong;
|
||||
}
|
||||
|
||||
- (MPSiteType)nextType:(MPSiteType)type {
|
||||
|
||||
switch (type) {
|
||||
@@ -321,9 +314,9 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
return MPSiteTypeStoredDevicePrivate;
|
||||
case MPSiteTypeStoredDevicePrivate:
|
||||
return MPSiteTypeGeneratedPhrase;
|
||||
default:
|
||||
return MPSiteTypeGeneratedLong;
|
||||
}
|
||||
|
||||
return [self defaultType];
|
||||
}
|
||||
|
||||
- (MPSiteType)previousType:(MPSiteType)type {
|
||||
@@ -338,28 +331,28 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||
|
||||
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
|
||||
usingKey:(MPKey *)key {
|
||||
|
||||
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 {
|
||||
|
||||
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
|
||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||
|
||||
__block NSString *content;
|
||||
__block NSString *content = nil;
|
||||
[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] );
|
||||
if (contentBytes) {
|
||||
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
|
||||
@@ -403,7 +396,7 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
||||
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
|
||||
@@ -419,7 +412,7 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:encryptionKey padding:YES];
|
||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||
@@ -442,58 +435,38 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
setResult( result_ );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
} );
|
||||
}
|
||||
|
||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
setResult( result_ );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
} );
|
||||
}
|
||||
|
||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
setResult( result_ );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
} );
|
||||
}
|
||||
|
||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
setResult( result_ );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||
@@ -510,9 +483,11 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
resultBlock( loginName || !loginGenerated? loginName:
|
||||
[algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
||||
if (!loginGenerated || [loginName length])
|
||||
resultBlock( loginName );
|
||||
else
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -545,9 +520,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
@@ -561,9 +535,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
@@ -575,9 +548,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
@@ -596,9 +568,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -617,9 +588,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
|
||||
algorithm = question.site.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -743,7 +713,7 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
return nil;
|
||||
NSData *decryptedContent = nil;
|
||||
if ([encryptedContent length]) {
|
||||
NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
NSData *encryptionKey = [key keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
|
||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
|
||||
}
|
||||
if (!decryptedContent)
|
||||
@@ -761,18 +731,17 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
if (!templates)
|
||||
return NO;
|
||||
|
||||
BIGNUM *permutations = BN_new(), *templatePermutations = BN_new();
|
||||
NSDecimalNumber *permutations = [NSDecimalNumber zero], *templatePermutations;
|
||||
for (size_t t = 0; t < count; ++t) {
|
||||
const char *template = templates[t];
|
||||
BN_one( templatePermutations );
|
||||
templatePermutations = [NSDecimalNumber one];
|
||||
|
||||
for (NSUInteger c = 0; c < strlen( template ); ++c)
|
||||
BN_mul_word( templatePermutations,
|
||||
(BN_ULONG)strlen( mpw_charactersInClass( template[c] ) ) );
|
||||
templatePermutations = [templatePermutations decimalNumberByMultiplyingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:strlen( mpw_charactersInClass( template[c] ) )]];
|
||||
|
||||
BN_add( permutations, permutations, templatePermutations );
|
||||
permutations = [permutations decimalNumberByAdding:templatePermutations];
|
||||
}
|
||||
BN_free( templatePermutations );
|
||||
free( templates );
|
||||
|
||||
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 {
|
||||
|
||||
BIGNUM *permutations = BN_new();
|
||||
BN_one( permutations );
|
||||
NSDecimalNumber *permutations = [NSDecimalNumber one];
|
||||
|
||||
for (NSUInteger c = 0; c < [password length]; ++c) {
|
||||
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" ]) {
|
||||
char const *charactersForClass = mpw_charactersInClass( characterClass.UTF8String[0] );
|
||||
|
||||
if (strchr( charactersForClass, passwordCharacter )) {
|
||||
// Found class for password character.
|
||||
characterEntropy = (BN_ULONG)strlen( charactersForClass );
|
||||
characterEntropy = strlen( charactersForClass );
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!characterEntropy)
|
||||
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];
|
||||
}
|
||||
|
||||
- (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.
|
||||
BIGNUM *secondsToCrack = BN_dup( permutations );
|
||||
BN_div_word( secondsToCrack, CRACKING_PER_SECOND );
|
||||
NSDecimalNumber *secondsToCrack = [permutations decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PER_SECOND]];
|
||||
|
||||
// Modify seconds needed by applying our hardware budget.
|
||||
switch (attacker) {
|
||||
case MPAttacker1:
|
||||
break;
|
||||
case MPAttacker5K:
|
||||
BN_mul_word( secondsToCrack, CRACKING_PRICE );
|
||||
BN_div_word( secondsToCrack, 5000 );
|
||||
secondsToCrack = [secondsToCrack decimalNumberByMultiplyingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PRICE]];
|
||||
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:5000]];
|
||||
break;
|
||||
case MPAttacker20M:
|
||||
BN_mul_word( secondsToCrack, CRACKING_PRICE );
|
||||
BN_div_word( secondsToCrack, 20000000 );
|
||||
secondsToCrack = [secondsToCrack decimalNumberByMultiplyingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PRICE]];
|
||||
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:20000000]];
|
||||
break;
|
||||
case MPAttacker5B:
|
||||
BN_mul_word( secondsToCrack, CRACKING_PRICE );
|
||||
BN_div_word( secondsToCrack, 5000 );
|
||||
BN_div_word( secondsToCrack, 1000000 );
|
||||
secondsToCrack = [secondsToCrack decimalNumberByMultiplyingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:CRACKING_PRICE]];
|
||||
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:5000]];
|
||||
secondsToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:1000000]];
|
||||
break;
|
||||
}
|
||||
|
||||
BIGNUM *max = BN_new();
|
||||
BN_set_word( max, (BN_ULONG)-1 );
|
||||
NSDecimalNumber *ulong_max = (id)[[NSDecimalNumber alloc] initWithUnsignedLong:ULONG_MAX];
|
||||
|
||||
BIGNUM *hoursToCrack = BN_dup( secondsToCrack );
|
||||
BN_div_word( hoursToCrack, 3600 );
|
||||
if (BN_cmp( hoursToCrack, max ) < 0)
|
||||
timeToCrack->hours = BN_get_word( hoursToCrack );
|
||||
NSDecimalNumber *hoursToCrack = [secondsToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:3600L]];
|
||||
if ([hoursToCrack compare:ulong_max] == NSOrderedAscending)
|
||||
timeToCrack->hours = (unsigned long long)[hoursToCrack doubleValue];
|
||||
else
|
||||
timeToCrack->hours = (BN_ULONG)-1;
|
||||
timeToCrack->hours = ULONG_MAX;
|
||||
|
||||
BIGNUM *daysToCrack = BN_dup( hoursToCrack );
|
||||
BN_div_word( daysToCrack, 24 );
|
||||
if (BN_cmp( daysToCrack, max ) < 0)
|
||||
timeToCrack->days = BN_get_word( daysToCrack );
|
||||
NSDecimalNumber *daysToCrack = [hoursToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:24L]];
|
||||
if ([daysToCrack compare:ulong_max] == NSOrderedAscending)
|
||||
timeToCrack->days = (unsigned long long)[daysToCrack doubleValue];
|
||||
else
|
||||
timeToCrack->days = (BN_ULONG)-1;
|
||||
timeToCrack->days = ULONG_MAX;
|
||||
|
||||
BIGNUM *weeksToCrack = BN_dup( daysToCrack );
|
||||
BN_div_word( weeksToCrack, 7 );
|
||||
if (BN_cmp( weeksToCrack, max ) < 0)
|
||||
timeToCrack->weeks = BN_get_word( weeksToCrack );
|
||||
NSDecimalNumber *weeksToCrack = [daysToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:7L]];
|
||||
if ([weeksToCrack compare:ulong_max] == NSOrderedAscending)
|
||||
timeToCrack->weeks = (unsigned long long)[weeksToCrack doubleValue];
|
||||
else
|
||||
timeToCrack->weeks = (BN_ULONG)-1;
|
||||
timeToCrack->weeks = ULONG_MAX;
|
||||
|
||||
BIGNUM *monthsToCrack = BN_dup( daysToCrack );
|
||||
BN_div_word( monthsToCrack, 31 );
|
||||
if (BN_cmp( monthsToCrack, max ) < 0)
|
||||
timeToCrack->months = BN_get_word( monthsToCrack );
|
||||
NSDecimalNumber *monthsToCrack = [daysToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:31L]];
|
||||
if ([monthsToCrack compare:ulong_max] == NSOrderedAscending)
|
||||
timeToCrack->months = (unsigned long long)[monthsToCrack doubleValue];
|
||||
else
|
||||
timeToCrack->months = (BN_ULONG)-1;
|
||||
timeToCrack->months = ULONG_MAX;
|
||||
|
||||
BIGNUM *yearsToCrack = BN_dup( daysToCrack );
|
||||
BN_div_word( yearsToCrack, 356 );
|
||||
if (BN_cmp( yearsToCrack, max ) < 0)
|
||||
timeToCrack->years = BN_get_word( yearsToCrack );
|
||||
NSDecimalNumber *yearsToCrack = [daysToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:356L]];
|
||||
if ([yearsToCrack compare:ulong_max] == NSOrderedAscending)
|
||||
timeToCrack->years = (unsigned long long)[yearsToCrack doubleValue];
|
||||
else
|
||||
timeToCrack->years = (BN_ULONG)-1;
|
||||
timeToCrack->years = ULONG_MAX;
|
||||
|
||||
BIGNUM *universesToCrack = BN_dup( yearsToCrack );
|
||||
BN_div_word( universesToCrack, 14000 );
|
||||
BN_div_word( universesToCrack, 1000000 );
|
||||
if (BN_cmp( universesToCrack, max ) < 0)
|
||||
timeToCrack->universes = BN_get_word( universesToCrack );
|
||||
NSDecimalNumber *universesToCrack = [yearsToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:14000L]];
|
||||
universesToCrack = [universesToCrack decimalNumberByDividingBy:
|
||||
(id)[[NSDecimalNumber alloc] initWithUnsignedLong:1000000L]];
|
||||
if ([universesToCrack compare:ulong_max] == NSOrderedAscending)
|
||||
timeToCrack->universes = (unsigned long long)[universesToCrack doubleValue];
|
||||
else
|
||||
timeToCrack->universes = (BN_ULONG)-1;
|
||||
|
||||
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 );
|
||||
timeToCrack->universes = ULONG_MAX;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -25,17 +25,20 @@
|
||||
#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid"
|
||||
#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
|
||||
|
||||
- (void)updateWithProducts:(NSArray /* SKProduct */ *)products;
|
||||
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction;
|
||||
- (void)updateWithProducts:(NSDictionary<NSString *, SKProduct *> *)products
|
||||
transactions:(NSDictionary<NSString *, SKPaymentTransaction *> *)transactions;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPAppDelegate_Shared(InApp)
|
||||
|
||||
- (NSDictionary<NSString *, SKProduct *> *)products;
|
||||
- (NSDictionary<NSString *, SKPaymentTransaction *> *)transactions;
|
||||
|
||||
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||
|
||||
|
||||
@@ -23,9 +23,20 @@
|
||||
|
||||
@implementation MPAppDelegate_Shared(InApp)
|
||||
|
||||
PearlAssociatedObjectProperty( NSArray*, Products, products );
|
||||
PearlAssociatedObjectProperty( NSDictionary*, Products, products );
|
||||
|
||||
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 {
|
||||
|
||||
if (!self.productObservers)
|
||||
@@ -33,7 +44,7 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
[self.productObservers addObject:delegate];
|
||||
|
||||
if (self.products)
|
||||
[delegate updateWithProducts:self.products];
|
||||
[delegate updateWithProducts:self.products transactions:[self transactions]];
|
||||
else
|
||||
[self reloadProducts];
|
||||
}
|
||||
@@ -75,7 +86,7 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
// Consumable product.
|
||||
return NO;
|
||||
|
||||
#if ADHOC || DEBUG
|
||||
#if DEBUG
|
||||
// All features are unlocked for beta / debug / mac versions.
|
||||
return YES;
|
||||
#else
|
||||
@@ -93,19 +104,40 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
if (![[MPAppDelegate_Shared get] canMakePayments]) {
|
||||
[PearlAlert showAlertWithTitle:@"Store Not Set Up" message:
|
||||
@"Try logging using the App Store or from Settings."
|
||||
[PearlAlert showAlertWithTitle:@"App Store Not Set Up" message:
|
||||
@"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
|
||||
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;
|
||||
}
|
||||
#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]) {
|
||||
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
|
||||
if (payment) {
|
||||
payment.quantity = quantity;
|
||||
[[self paymentQueue] addPayment:payment];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -114,15 +146,22 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
|
||||
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
||||
|
||||
inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers );
|
||||
self.products = response.products;
|
||||
if ([response.invalidProductIdentifiers count])
|
||||
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)
|
||||
[productObserver updateWithProducts:self.products];
|
||||
[productObserver updateWithProducts:self.products transactions:[self transactions]];
|
||||
}
|
||||
|
||||
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
|
||||
|
||||
MPError( error, @"StoreKit request (%@) failed.", request );
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
[PearlAlert showAlertWithTitle:@"Purchase Failed" message:
|
||||
strf( @"%@\n\n%@", error.localizedDescription,
|
||||
@@ -131,7 +170,6 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
cancelTitle:@"OK" otherTitles:nil];
|
||||
#else
|
||||
#endif
|
||||
err( @"StoreKit request (%@) failed: %@", request, [error fullDescription] );
|
||||
}
|
||||
|
||||
- (void)requestDidFinish:(SKRequest *)request {
|
||||
@@ -145,23 +183,41 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
|
||||
for (SKPaymentTransaction *transaction in transactions) {
|
||||
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
|
||||
|
||||
switch (transaction.transactionState) {
|
||||
case SKPaymentTransactionStatePurchased: {
|
||||
inf( @"purchased: %@", transaction.payment.productIdentifier );
|
||||
inf( @"Purchased: %@", transaction.payment.productIdentifier );
|
||||
NSMutableDictionary *attributes = [NSMutableDictionary new];
|
||||
|
||||
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
|
||||
float currentFuel = [[MPiOSConfig get].developmentFuelRemaining floatValue];
|
||||
float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE;
|
||||
[MPiOSConfig get].developmentFuelRemaining = @(currentFuel + purchasedFuel);
|
||||
if (![MPiOSConfig get].developmentFuelChecked || currentFuel < DBL_EPSILON)
|
||||
[MPiOSConfig get].developmentFuelChecked = [NSDate date];
|
||||
[attributes addEntriesFromDictionary:@{
|
||||
@"currentFuel" : @(currentFuel),
|
||||
@"purchasedFuel": @(purchasedFuel),
|
||||
}];
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
||||
forKey:transaction.payment.productIdentifier];
|
||||
[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;
|
||||
}
|
||||
case SKPaymentTransactionStateRestored: {
|
||||
inf( @"restored: %@", transaction.payment.productIdentifier );
|
||||
inf( @"Restored: %@", transaction.payment.productIdentifier );
|
||||
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
||||
forKey:transaction.payment.productIdentifier];
|
||||
[queue finishTransaction:transaction];
|
||||
@@ -171,21 +227,38 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
case SKPaymentTransactionStateDeferred:
|
||||
break;
|
||||
case SKPaymentTransactionStateFailed:
|
||||
err( @"Transaction failed: %@, reason: %@", transaction.payment.productIdentifier, [transaction.error fullDescription] );
|
||||
MPError( transaction.error, @"Transaction failed: %@.", transaction.payment.productIdentifier );
|
||||
[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;
|
||||
}
|
||||
|
||||
for (id<MPInAppDelegate> productObserver in self.productObservers)
|
||||
[productObserver updateWithTransaction:transaction];
|
||||
}
|
||||
|
||||
if (![[NSUserDefaults standardUserDefaults] synchronize])
|
||||
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 {
|
||||
|
||||
err( @"StoreKit restore failed: %@", [error fullDescription] );
|
||||
MPError( error, @"StoreKit restore failed." );
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,18 +16,19 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import <Crashlytics/Answers.h>
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@interface MPAppDelegate_Shared()
|
||||
|
||||
@property(strong, nonatomic) MPKey *key;
|
||||
@property(strong, atomic) MPKey *key;
|
||||
|
||||
@end
|
||||
|
||||
@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 (user.touchID && kSecUseAuthenticationUI) {
|
||||
@@ -37,8 +38,8 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
||||
CFErrorRef acError = NULL;
|
||||
id accessControl = (__bridge_transfer id)SecAccessControlCreateWithFlags( kCFAllocatorDefault,
|
||||
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlTouchIDCurrentSet, &acError );
|
||||
if (!accessControl || acError)
|
||||
err( @"Could not use TouchID on this device: %@", acError );
|
||||
if (!accessControl)
|
||||
MPError( (__bridge_transfer NSError *)acError, @"Could not use TouchID on this device." );
|
||||
|
||||
else
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
@@ -71,34 +72,44 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
||||
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
MPKeyOrigin keyOrigin;
|
||||
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
|
||||
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery];
|
||||
if (!keyData) {
|
||||
NSDictionary *keyQuery = [self createKeyQueryforUser:user origin:&keyOrigin];
|
||||
id<MPAlgorithm> keyAlgorithm = user.algorithm;
|
||||
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 );
|
||||
return nil;
|
||||
key = nil;
|
||||
}
|
||||
|
||||
inf( @"Found key in keychain for user: %@", user.userID );
|
||||
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm keyOrigin:keyOrigin];
|
||||
return key;
|
||||
}
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
if (user.saveKey) {
|
||||
NSData *keyData = [self.key keyDataForAlgorithm:user.algorithm];
|
||||
if (keyData) {
|
||||
MPMasterKey masterKey = [self.key keyForAlgorithm:user.algorithm];
|
||||
if (masterKey) {
|
||||
[self forgetSavedKeyFor:user];
|
||||
|
||||
inf( @"Saving key in keychain for user: %@", user.userID );
|
||||
[PearlKeyChain addOrUpdateItemForQuery:createKeyQuery( user, YES, nil )
|
||||
withAttributes:@{ (__bridge id)kSecValueData: keyData }];
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[self createKeyQueryforUser:user origin:nil] withAttributes:@{
|
||||
(__bridge id)kSecValueData: [NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize]
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:createKeyQuery( user, NO, nil )];
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:[self createKeyQueryforUser:user origin:nil]];
|
||||
if (result == noErr) {
|
||||
inf( @"Removed key from keychain for user: %@", user.userID );
|
||||
|
||||
@@ -168,6 +179,14 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
||||
else
|
||||
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;
|
||||
}
|
||||
inf( @"Logged in user: %@", user.userID );
|
||||
@@ -189,8 +208,11 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
||||
@try {
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
#ifdef CRASHLYTICS
|
||||
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
|
||||
[[Crashlytics sharedInstance] setUserName:user.userID];
|
||||
|
||||
[Answers logLoginWithMethod:password? @"Password": @"Automatic" success:@YES customAttributes:@{
|
||||
@"algorithm": @(user.algorithm.version),
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -230,28 +252,22 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
||||
NSString *content;
|
||||
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.
|
||||
__block NSString *masterPassword = nil;
|
||||
NSString *masterPassword = nil;
|
||||
|
||||
#ifdef PEARL_UIKIT
|
||||
dispatch_group_t recoverPasswordGroup = dispatch_group_create();
|
||||
dispatch_group_enter( recoverPasswordGroup );
|
||||
masterPassword = PearlAwait( ^(void (^setResult)(id)) {
|
||||
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
||||
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
|
||||
message:PearlString(
|
||||
@"Your old master password is required to migrate the stored password for %@",
|
||||
site.name )
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
@try {
|
||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||
// Don't Migrate
|
||||
return;
|
||||
|
||||
masterPassword = [alert_ textFieldAtIndex:0].text;
|
||||
}
|
||||
@finally {
|
||||
dispatch_group_leave( recoverPasswordGroup );
|
||||
}
|
||||
setResult( nil );
|
||||
else
|
||||
setResult( [alert_ textFieldAtIndex:0].text );
|
||||
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
|
||||
dispatch_group_wait( recoverPasswordGroup, DISPATCH_TIME_FOREVER );
|
||||
} );
|
||||
#endif
|
||||
if (!masterPassword)
|
||||
// Don't Migrate
|
||||
|
||||
@@ -19,16 +19,18 @@
|
||||
#import "MPEntities.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
@interface MPAppDelegate_Shared : PearlAppDelegate
|
||||
|
||||
#else
|
||||
|
||||
@interface MPAppDelegate_Shared : NSObject<PearlConfigDelegate>
|
||||
|
||||
#endif
|
||||
|
||||
@property(strong, nonatomic, readonly) MPKey *key;
|
||||
@property(strong, nonatomic, readonly) NSManagedObjectID *activeUserOID;
|
||||
@property(strong, nonatomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
|
||||
@property(strong, atomic, readonly) MPKey *key;
|
||||
@property(strong, atomic, readonly) NSManagedObjectID *activeUserOID;
|
||||
@property(strong, atomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
|
||||
|
||||
+ (instancetype)get;
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
@interface MPAppDelegate_Shared()
|
||||
|
||||
@property(strong, nonatomic) MPKey *key;
|
||||
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
|
||||
@property(strong, nonatomic) NSPersistentStoreCoordinator *storeCoordinator;
|
||||
@property(strong, atomic) MPKey *key;
|
||||
@property(strong, atomic) NSManagedObjectID *activeUserOID;
|
||||
@property(strong, atomic) NSPersistentStoreCoordinator *storeCoordinator;
|
||||
|
||||
@end
|
||||
|
||||
@@ -74,11 +74,7 @@
|
||||
|
||||
- (void)setActiveUser:(MPUserEntity *)activeUser {
|
||||
|
||||
NSError *error;
|
||||
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;
|
||||
self.activeUserOID = activeUser.permanentObjectID;
|
||||
}
|
||||
|
||||
- (void)handleCoordinatorError:(NSError *)error {
|
||||
|
||||
@@ -35,6 +35,7 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
|
||||
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock;
|
||||
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
||||
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock;
|
||||
+ (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock;
|
||||
|
||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
||||
- (void)deleteAndResetStore;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "mpw-marshall.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
||||
@@ -131,6 +132,25 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
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 {
|
||||
|
||||
[self loadStore];
|
||||
@@ -196,13 +216,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
|
||||
|
||||
if ([self.mainManagedObjectContext respondsToSelector:@selector( automaticallyMergesChangesFromParent )]) // iOS 10+
|
||||
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
|
||||
else
|
||||
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
||||
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
|
||||
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
|
||||
[mainManagedObjectContext performBlock:^{
|
||||
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainContext, NSNotification *note) {
|
||||
[mainContext performBlock:^{
|
||||
@try {
|
||||
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
|
||||
[mainContext mergeChangesFromContextDidSaveNotification:note];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err( @"While merging changes:\n%@", [exception fullDescription] );
|
||||
@@ -216,7 +238,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
NSURL *localStoreURL = [self localStoreURL];
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
|
||||
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;
|
||||
}
|
||||
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
|
||||
@@ -225,7 +247,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
NSInferMappingModelAutomaticallyOption : @YES,
|
||||
STORE_OPTIONS
|
||||
} error:&error]) {
|
||||
err( @"Failed to open store: %@", [error fullDescription] );
|
||||
MPError( error, @"Failed to open store." );
|
||||
self.storeCorrupted = @YES;
|
||||
[self handleCoordinatorError:error];
|
||||
return;
|
||||
@@ -234,12 +256,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
|
||||
#else
|
||||
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
|
||||
#endif
|
||||
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
||||
[self.mainManagedObjectContext saveToStore];
|
||||
} );
|
||||
#else
|
||||
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
|
||||
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
||||
[self.mainManagedObjectContext saveToStore];
|
||||
} );
|
||||
#endif
|
||||
|
||||
// Perform a data sanity check on the newly loaded store to find and fix any issues.
|
||||
if ([[MPConfig get].checkInconsistency boolValue])
|
||||
@@ -265,10 +290,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
NSError *error = nil;
|
||||
for (NSPersistentStore *store in self.storeCoordinator.persistentStores) {
|
||||
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])
|
||||
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];
|
||||
}
|
||||
@@ -286,7 +311,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
fetchRequest.entity = entity;
|
||||
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
|
||||
if (!objects) {
|
||||
err( @"Failed to fetch %@ objects: %@", entity, [error fullDescription] );
|
||||
MPError( error, @"Failed to fetch %@ objects.", entity );
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -311,7 +336,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
- (void)migrateStore {
|
||||
|
||||
MPStoreMigrationLevel migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
|
||||
MPStoreMigrationLevel migrationLevel = (MPStoreMigrationLevel)
|
||||
[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
|
||||
if (migrationLevel >= MPStoreMigrationLevelCurrent)
|
||||
// Local store up-to-date.
|
||||
return;
|
||||
@@ -353,8 +379,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
NSError *error = nil;
|
||||
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{ STORE_OPTIONS }
|
||||
toStore:newLocalStoreURL withOptions:@{ STORE_OPTIONS }
|
||||
model:nil error:&error]) {
|
||||
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
|
||||
error:&error]) {
|
||||
MPError( error, @"Couldn't migrate the old store to the new location." );
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -400,14 +426,52 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
NSMigratePersistentStoresAutomaticallyOption: @YES,
|
||||
NSInferMappingModelAutomaticallyOption : @YES,
|
||||
STORE_OPTIONS
|
||||
} model:nil error:&error]) {
|
||||
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
|
||||
} error:&error]) {
|
||||
MPError( error, @"Couldn't migrate the old store to the new location." );
|
||||
return NO;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion {
|
||||
@@ -436,10 +500,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
site.lastUsed = [NSDate date];
|
||||
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];
|
||||
|
||||
completion( site, context );
|
||||
@@ -468,18 +528,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
newSite.algorithm = site.algorithm;
|
||||
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 saveToStore];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.permanentObjectID];
|
||||
site = newSite;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.permanentObjectID];
|
||||
return site;
|
||||
}
|
||||
|
||||
@@ -516,7 +572,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:(NSRegularExpressionOptions)0 error:&error];
|
||||
if (error) {
|
||||
err( @"Error loading the header pattern: %@", [error fullDescription] );
|
||||
MPError( error, @"Error loading the header pattern." );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
}
|
||||
@@ -530,19 +586,20 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
options:(NSRegularExpressionOptions)0 error:&error]
|
||||
];
|
||||
if (error) {
|
||||
err( @"Error loading the site patterns: %@", [error fullDescription] );
|
||||
MPError( error, @"Error loading the site patterns." );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse import data.
|
||||
inf( @"Importing sites." );
|
||||
__block MPUserEntity *user = nil;
|
||||
id<MPAlgorithm> importAlgorithm = nil;
|
||||
NSUInteger importFormat = 0;
|
||||
__block MPUserEntity *user = nil;
|
||||
NSUInteger importAvatar = NSNotFound;
|
||||
NSString *importBundleVersion = nil, *importUserName = nil;
|
||||
NSData *importKeyID = nil;
|
||||
NSString *importBundleVersion = nil, *importUserName = nil;
|
||||
id<MPAlgorithm> importAlgorithm = nil;
|
||||
MPSiteType importDefaultType = (MPSiteType)0;
|
||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSMutableSet *sitesToDelete = [NSMutableSet set];
|
||||
@@ -573,14 +630,22 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
||||
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
|
||||
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;
|
||||
|
||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
||||
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
||||
if (!users) {
|
||||
err( @"While looking for user: %@, error: %@", importUserName, [error fullDescription] );
|
||||
MPError( error, @"While looking for user: %@.", importUserName );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
if ([users count] > 1) {
|
||||
@@ -591,21 +656,18 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
user = [users lastObject];
|
||||
dbg( @"Existing user? %@", [user debugDescription] );
|
||||
}
|
||||
if ([headerName isEqualToString:@"Avatar"])
|
||||
importAvatar = (NSUInteger)[headerValue integerValue];
|
||||
if ([headerName isEqualToString:@"Key ID"])
|
||||
importKeyID = [headerValue decodeHex];
|
||||
if ([headerName isEqualToString:@"Version"]) {
|
||||
importBundleVersion = headerValue;
|
||||
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
|
||||
}
|
||||
if ([headerName isEqualToString:@"Format"]) {
|
||||
importFormat = (NSUInteger)[headerValue integerValue];
|
||||
if (importFormat >= [sitePatterns count]) {
|
||||
err( @"Unsupported import format: %lu", (unsigned long)importFormat );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
}
|
||||
if ([headerName isEqualToString:@"Avatar"])
|
||||
importAvatar = (NSUInteger)[headerValue integerValue];
|
||||
if ([headerName isEqualToString:@"Algorithm"])
|
||||
importAlgorithm = MPAlgorithmForVersion( (MPAlgorithmVersion)[headerValue integerValue] );
|
||||
if ([headerName isEqualToString:@"Default Type"])
|
||||
importDefaultType = (MPSiteType)[headerValue integerValue];
|
||||
if ([headerName isEqualToString:@"Passwords"]) {
|
||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
||||
clearText = YES;
|
||||
@@ -667,7 +729,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
|
||||
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
|
||||
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;
|
||||
}
|
||||
if ([existingSites count]) {
|
||||
@@ -709,6 +771,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
if (user) {
|
||||
if (importAvatar != NSNotFound)
|
||||
user.avatar = importAvatar;
|
||||
if (importDefaultType)
|
||||
user.defaultType = importDefaultType;
|
||||
dbg( @"Updating User: %@", [user debugDescription] );
|
||||
}
|
||||
else {
|
||||
@@ -716,6 +780,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
user.name = importUserName;
|
||||
user.algorithm = MPAlgorithmDefault;
|
||||
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
|
||||
user.defaultType = importDefaultType?: user.algorithm.defaultType;
|
||||
if (importAvatar != NSNotFound)
|
||||
user.avatar = importAvatar;
|
||||
dbg( @"Created User: %@", [user debugDescription] );
|
||||
@@ -776,68 +841,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
MPUserEntity *activeUser = [self activeUserForMainThread];
|
||||
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
|
||||
|
||||
// Header.
|
||||
NSMutableString *export = [NSMutableString new];
|
||||
[export appendFormat:@"# Master Password site export\n"];
|
||||
if (revealPasswords)
|
||||
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
|
||||
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"];
|
||||
MPMarshalledUser exportUser = mpw_marshall_user( activeUser.name.UTF8String,
|
||||
[self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version );
|
||||
exportUser.avatar = activeUser.avatar;
|
||||
exportUser.defaultType = activeUser.defaultType;
|
||||
exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970;
|
||||
|
||||
|
||||
// Sites.
|
||||
for (MPSiteEntity *site in activeUser.sites) {
|
||||
NSDate *lastUsed = site.lastUsed;
|
||||
NSUInteger uses = site.uses;
|
||||
MPSiteType type = site.type;
|
||||
id<MPAlgorithm> algorithm = site.algorithm;
|
||||
NSUInteger counter = 0;
|
||||
NSString *loginName = site.loginName;
|
||||
NSString *siteName = site.name;
|
||||
NSString *content = nil;
|
||||
MPMarshalledSite exportSite = mpw_marshall_site( &exportUser,
|
||||
site.name.UTF8String, site.type, site.counter, site.algorithm.version );
|
||||
exportSite.loginName = site.loginName.UTF8String;
|
||||
exportSite.url = site.url.UTF8String;
|
||||
exportSite.uses = site.uses;
|
||||
exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
|
||||
|
||||
// Generated-specific
|
||||
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||
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];
|
||||
for (MPSiteQuestionEntity *siteQuestion in site.questions)
|
||||
mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String );
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPConfig.h
|
||||
// MasterPassword
|
||||
// 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 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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 "Pearl.h"
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPConfig.m
|
||||
// MasterPassword
|
||||
// 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 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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 "MPAppDelegate_Shared.h"
|
||||
|
||||
@@ -20,7 +30,7 @@
|
||||
[self.defaults registerDefaults:@{
|
||||
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
|
||||
|
||||
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
|
||||
NSStringFromSelector( @selector( sendInfo ) ) : @YES,
|
||||
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
|
||||
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
|
||||
NSStringFromSelector( @selector( checkInconsistency ) ): @NO,
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPEntities.h
|
||||
// MasterPassword-iOS
|
||||
// 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 31/05/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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 <Foundation/Foundation.h>
|
||||
#import "MPSiteEntity.h"
|
||||
#import "MPStoredSiteEntity.h"
|
||||
#import "MPGeneratedSiteEntity.h"
|
||||
#import "MPUserEntity.h"
|
||||
#import "MPSiteEntity+CoreDataClass.h"
|
||||
#import "MPStoredSiteEntity+CoreDataClass.h"
|
||||
#import "MPGeneratedSiteEntity+CoreDataClass.h"
|
||||
#import "MPUserEntity+CoreDataClass.h"
|
||||
#import "MPAlgorithm.h"
|
||||
#import "MPFixable.h"
|
||||
|
||||
@@ -22,6 +32,12 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface NSManagedObject(MP)
|
||||
|
||||
- (NSManagedObjectID *)permanentObjectID;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPSiteQuestionEntity(MP)
|
||||
|
||||
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key;
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPEntities.m
|
||||
// MasterPassword-iOS
|
||||
// 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 31/05/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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 "MPEntities.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
@@ -16,25 +26,41 @@
|
||||
- (BOOL)saveToStore {
|
||||
|
||||
__block BOOL success = YES;
|
||||
if ([self hasChanges]) {
|
||||
if ([self hasChanges])
|
||||
[self performBlockAndWait:^{
|
||||
@try {
|
||||
NSError *error = nil;
|
||||
if (!(success = [self save:&error]))
|
||||
err( @"While saving: %@", [error fullDescription] );
|
||||
MPError( error, @"While saving." );
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
success = NO;
|
||||
err( @"While saving: %@", [exception fullDescription] );
|
||||
err( @"While saving.\n%@", [exception fullDescription] );
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
return success && (!self.parentContext || [self.parentContext saveToStore]);
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key {
|
||||
@@ -237,8 +263,8 @@
|
||||
// Invalid self.user.defaultType
|
||||
result = MPApplyFix( result, ^MPFixableResult {
|
||||
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
||||
self.name, self.user.name, (long)self.type, (long)MPSiteTypeGeneratedLong );
|
||||
self.type = MPSiteTypeGeneratedLong;
|
||||
self.name, self.user.name, (long)self.type, (long)[self.algorithm defaultType] );
|
||||
self.type = [self.algorithm defaultType];
|
||||
return MPFixableResultProblemsFixed;
|
||||
} );
|
||||
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
||||
@@ -330,7 +356,7 @@
|
||||
|
||||
- (MPSiteType)defaultType {
|
||||
|
||||
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
|
||||
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
|
||||
}
|
||||
|
||||
- (void)setDefaultType:(MPSiteType)aDefaultType {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPFixable.h
|
||||
// MPFixable
|
||||
// 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 lhunath on 2014-04-26.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
// 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 <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPFixable.m
|
||||
// MPFixable
|
||||
// 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 lhunath on 2014-04-26.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
// 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 "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)
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// 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.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
// 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 <Foundation/Foundation.h>
|
||||
#import "MPAlgorithm.h"
|
||||
#import "mpw-types.h"
|
||||
|
||||
@protocol MPAlgorithm;
|
||||
|
||||
@@ -28,16 +30,15 @@ typedef NS_ENUM( NSUInteger, MPKeyOrigin ) {
|
||||
|
||||
@interface MPKey : NSObject
|
||||
|
||||
@property(nonatomic, readonly) NSString *fullName;
|
||||
@property(nonatomic, readonly) MPKeyOrigin origin;
|
||||
@property(nonatomic, readonly, copy) NSString *fullName;
|
||||
|
||||
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
|
||||
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin;
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
|
||||
keyOrigin:(MPKeyOrigin)origin;
|
||||
|
||||
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength;
|
||||
- (MPMasterKey)keyForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
|
||||
- (BOOL)isEqualToKey:(MPKey *)key;
|
||||
|
||||
|
||||
@@ -1,94 +1,83 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// 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.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
// 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 "MPAlgorithm.h"
|
||||
|
||||
@interface MPKey()
|
||||
|
||||
@property(nonatomic) NSString *fullName;
|
||||
@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
|
||||
|
||||
@implementation MPKey {
|
||||
NSCache *_keyCache;
|
||||
};
|
||||
@implementation MPKey;
|
||||
|
||||
- (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]))
|
||||
return nil;
|
||||
|
||||
_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.keyCache = [NSCache new];
|
||||
|
||||
self.origin = origin;
|
||||
[_keyCache setObject:keyData forKey:algorithm];
|
||||
self.fullName = fullName;
|
||||
self.keyResolver = keyResolver;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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) {
|
||||
NSData *keyData = [self.keyCache objectForKey:algorithm];
|
||||
if (!keyData) {
|
||||
keyData = self.keyResolver( algorithm );
|
||||
if (keyData)
|
||||
return keyData;
|
||||
|
||||
keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword];
|
||||
if (keyData)
|
||||
[_keyCache setObject:keyData forKey:algorithm];
|
||||
|
||||
return keyData;
|
||||
[self.keyCache setObject:keyData forKey:algorithm];
|
||||
}
|
||||
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
|
||||
|
||||
NSData *keyData = [self keyDataForAlgorithm:algorithm];
|
||||
return [keyData subdataWithRange:NSMakeRange( 0, MIN( subKeyLength, keyData.length ) )];
|
||||
return keyData.length == MPMasterKeySize? keyData.bytes: NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (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 {
|
||||
|
||||
if (![object isKindOfClass:[MPKey class]])
|
||||
return NO;
|
||||
|
||||
return [self isEqualToKey:object];
|
||||
return [object isKindOfClass:[MPKey class]] && [self isEqualToKey:object];
|
||||
}
|
||||
|
||||
@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
|
||||
// MasterPassword
|
||||
// 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 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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 <Crashlytics/Crashlytics.h>
|
||||
#import <Crashlytics/Answers.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
extern NSString *const MPErrorDomain;
|
||||
extern NSInteger const MPErrorHangCode;
|
||||
|
||||
extern NSString *const MPSignedInNotification;
|
||||
extern NSString *const MPSignedOutNotification;
|
||||
@@ -19,4 +33,21 @@ extern NSString *const MPFoundInconsistenciesNotification;
|
||||
|
||||
extern NSString *const MPSitesImportedNotificationUserKey;
|
||||
extern NSString *const MPInconsistenciesFixResultUserKey;
|
||||
|
||||
__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
|
||||
// MasterPassword
|
||||
// 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 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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 "MPTypes.h"
|
||||
|
||||
NSString *const MPErrorDomain = @"MPErrorDomain";
|
||||
NSInteger const MPErrorHangCode = 1;
|
||||
|
||||
NSString *const MPSignedInNotification = @"MPSignedInNotification";
|
||||
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"
|
||||
|
||||
@implementation MPGradientView {
|
||||
NSGradient *gradient;
|
||||
}
|
||||
@interface MPGradientView()
|
||||
|
||||
@property(nonatomic, strong) NSGradient *gradient;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPGradientView
|
||||
|
||||
- (id)initWithFrame:(NSRect)frame {
|
||||
|
||||
@@ -51,28 +55,28 @@
|
||||
- (void)setStartingColor:(NSColor *)startingColor {
|
||||
|
||||
_startingColor = startingColor;
|
||||
gradient = nil;
|
||||
self.gradient = nil;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setEndingColor:(NSColor *)endingColor {
|
||||
|
||||
_endingColor = endingColor;
|
||||
gradient = nil;
|
||||
self.gradient = nil;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setAngle:(NSInteger)angle {
|
||||
|
||||
_angle = angle;
|
||||
gradient = nil;
|
||||
self.gradient = nil;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setRatio:(CGFloat)ratio {
|
||||
|
||||
_ratio = ratio;
|
||||
gradient = nil;
|
||||
self.gradient = nil;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
@@ -84,7 +88,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
[(gradient?: (gradient = [[NSGradient alloc] initWithColorsAndLocations:
|
||||
[(self.gradient?: (self.gradient = [[NSGradient alloc] initWithColorsAndLocations:
|
||||
self.startingColor, (CGFloat)0.f,
|
||||
[self.startingColor blendedColorWithFraction:0.5f ofColor:self.endingColor], self.ratio,
|
||||
self.endingColor, (CGFloat)1.f, nil]))
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
|
||||
[super windowDidLoad];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
|
||||
queue:nil usingBlock:^(NSNotification *note) {
|
||||
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, nil, ^(id host, NSNotification *note) {
|
||||
PearlRemoveNotificationObserversFrom( host );
|
||||
[MPMacAppDelegate get].initialWindowController = nil;
|
||||
}];
|
||||
} );
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPPasswordWindowController.h"
|
||||
#import "MPSitesWindowController.h"
|
||||
#import "MPInitialWindowController.h"
|
||||
|
||||
@interface MPMacAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSStatusItem *statusView;
|
||||
@property(nonatomic, strong) MPPasswordWindowController *passwordWindowController;
|
||||
@property(nonatomic, strong) MPSitesWindowController *sitesWindowController;
|
||||
@property(nonatomic, strong) MPInitialWindowController *initialWindowController;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *lockItem;
|
||||
@property(nonatomic, weak) IBOutlet NSMenuItem *showItem;
|
||||
|
||||
@@ -72,13 +72,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
inf(@"Initializing Crashlytics");
|
||||
#if defined (DEBUG) || defined (ADHOC)
|
||||
#if DEBUG
|
||||
[Crashlytics sharedInstance].debugMode = YES;
|
||||
#endif
|
||||
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
|
||||
[[Crashlytics sharedInstance] setUserName:@"Anonymous"];
|
||||
[[Crashlytics sharedInstance] setObjectValue:@"Anonymous" forKey:@"username"];
|
||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelInfo;
|
||||
@@ -172,17 +171,17 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
// Save changes in the application's managed object context before the application terminates.
|
||||
|
||||
NSManagedObjectContext *context = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!context)
|
||||
NSManagedObjectContext *mainContext = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!mainContext)
|
||||
return NSTerminateNow;
|
||||
|
||||
if (![context commitEditing])
|
||||
if (![mainContext commitEditing])
|
||||
return NSTerminateCancel;
|
||||
|
||||
if (![context hasChanges])
|
||||
if (![mainContext hasChanges])
|
||||
return NSTerminateNow;
|
||||
|
||||
[context saveToStore];
|
||||
[mainContext saveToStore];
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
@@ -269,10 +268,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
NSURL *url = openPanel.URL;
|
||||
[openPanel close];
|
||||
|
||||
[[NSURLSession sharedSession]
|
||||
dataTaskWithURL:url completionHandler:^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
||||
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
|
||||
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
||||
if (error)
|
||||
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
|
||||
MPError( error, @"While reading imported sites from %@.", url );
|
||||
if (!importedSitesData)
|
||||
return;
|
||||
|
||||
@@ -342,7 +341,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
break;
|
||||
}
|
||||
} );
|
||||
}];
|
||||
}] resume];
|
||||
}
|
||||
|
||||
- (IBAction)togglePreference:(id)sender {
|
||||
@@ -389,20 +388,16 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
return;
|
||||
|
||||
NSString *name = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
||||
inManagedObjectContext:moc];
|
||||
inManagedObjectContext:context];
|
||||
newUser.name = name;
|
||||
[moc saveToStore];
|
||||
NSError *error = nil;
|
||||
if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error])
|
||||
err( @"Failed to obtain permanent object ID for new user: %@", [error fullDescription] );
|
||||
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self updateUsers];
|
||||
[context saveToStore];
|
||||
[self setActiveUser:newUser];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self showPasswordWindow:nil];
|
||||
}];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -416,10 +411,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
if ([alert runModal] != NSAlertFirstButtonReturn)
|
||||
return;
|
||||
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
[moc deleteObject:[self activeUserInContext:moc]];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[context deleteObject:[self activeUserInContext:context]];
|
||||
[self setActiveUser:nil];
|
||||
[moc saveToStore];
|
||||
[context saveToStore];
|
||||
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self updateUsers];
|
||||
@@ -435,8 +430,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
|
||||
- (IBAction)terminate:(id)sender {
|
||||
|
||||
[self.passwordWindowController close];
|
||||
self.passwordWindowController = nil;
|
||||
[self.sitesWindowController close];
|
||||
self.sitesWindowController = nil;
|
||||
|
||||
[NSApp terminate:nil];
|
||||
}
|
||||
@@ -465,11 +460,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
prof_rewind( @"activeUserForMainThread" );
|
||||
|
||||
// Don't show window if we weren't already running (ie. if we haven't been activated before).
|
||||
if (!self.passwordWindowController)
|
||||
self.passwordWindowController = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
||||
if (!self.sitesWindowController)
|
||||
self.sitesWindowController = [[MPSitesWindowController alloc] initWithWindowNibName:@"MPSitesWindowController"];
|
||||
prof_rewind( @"initWithWindow" );
|
||||
|
||||
[self.passwordWindowController showWindow:self];
|
||||
[self.sitesWindowController showWindow:self];
|
||||
prof_finish( @"showWindow" );
|
||||
}
|
||||
|
||||
@@ -516,21 +511,24 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
|
||||
NSError *coordinateError = nil;
|
||||
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
|
||||
[[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError
|
||||
byAccessor:^(NSURL *newURL) {
|
||||
[[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0
|
||||
error:&coordinateError byAccessor:
|
||||
^(NSURL *newURL) {
|
||||
NSError *writeError = nil;
|
||||
if (![exportedSites writeToURL:newURL atomically:NO
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&writeError])
|
||||
if (![exportedSites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
|
||||
MPError( writeError, @"Could not write to the export file." );
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[[NSAlert alertWithError:writeError] runModal];
|
||||
} );
|
||||
}];
|
||||
if (coordinateError)
|
||||
if (coordinateError) {
|
||||
MPError( coordinateError, @"Write access to the export file could not be obtained." );
|
||||
PearlMainQueue( ^{
|
||||
[[NSAlert alertWithError:coordinateError] runModal];
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateUsers {
|
||||
|
||||
@@ -567,7 +565,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
||||
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (!users)
|
||||
err( @"Failed to load users: %@", [error fullDescription] );
|
||||
MPError( error, @"Failed to load users." );
|
||||
|
||||
if (![users count]) {
|
||||
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) {
|
||||
NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector( selectUser: ) keyEquivalent:@""];
|
||||
[userItem setTarget:self];
|
||||
[userItem setRepresentedObject:[user objectID]];
|
||||
[userItem setRepresentedObject:user.permanentObjectID];
|
||||
[[self.usersItem submenu] addItem:userItem];
|
||||
|
||||
if (!mainActiveUser && [user.name isEqualToString:[MPMacConfig get].usedUserName])
|
||||
@@ -606,7 +604,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
- (void)updateMenuItems {
|
||||
|
||||
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.toolTip = @"Master Password is already showing.";
|
||||
// }
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMacApplication {
|
||||
}
|
||||
@implementation MPMacApplication
|
||||
|
||||
- (void)sendEvent:(NSEvent *)event {
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPMacConfig.h
|
||||
// MasterPassword
|
||||
// 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 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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 "MPConfig.h"
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPMacConfig.m
|
||||
// MasterPassword
|
||||
// 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 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// 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/>.
|
||||
//==============================================================================
|
||||
|
||||
@implementation MPMacConfig
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPNoStateButton.h
|
||||
// MPNoStateButton
|
||||
// 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 lhunath on 14-10-27.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
// 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 <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// MPNoStateButton.h
|
||||
// MPNoStateButton
|
||||
// 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 lhunath on 14-10-27.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
// 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 "MPNoStateButton.h"
|
||||
|
||||
@implementation MPNoStateButtonCell {
|
||||
}
|
||||
@implementation MPNoStateButtonCell
|
||||
|
||||
- (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"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MPPasswordWindowController">
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MPSitesWindowController">
|
||||
<connections>
|
||||
<outlet property="inputLabel" destination="OnR-s6-d4P" id="p6G-Ut-cdu"/>
|
||||
<outlet property="passwordTypesBox" destination="bZe-7q-i6q" id="Ai3-pt-i6K"/>
|
||||
@@ -26,7 +26,7 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<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"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/>
|
||||
<rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
//==============================================================================
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPSiteEntity.h"
|
||||
#import "MPSiteEntity+CoreDataClass.h"
|
||||
#import "MPAlgorithm.h"
|
||||
#import "MPUserEntity.h"
|
||||
#import "MPUserEntity+CoreDataClass.h"
|
||||
|
||||
@class MPSiteEntity;
|
||||
|
||||
|
||||
@@ -17,16 +17,19 @@
|
||||
//==============================================================================
|
||||
|
||||
#import "MPSiteModel.h"
|
||||
#import "MPSiteEntity.h"
|
||||
#import "MPEntities.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPMacAppDelegate.h"
|
||||
|
||||
@implementation MPSiteModel {
|
||||
NSManagedObjectID *_entityOID;
|
||||
BOOL _initialized;
|
||||
}
|
||||
@interface MPSiteModel()
|
||||
|
||||
@property(nonatomic, strong) NSManagedObjectID *entityOID;
|
||||
@property(nonatomic) BOOL initialized;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPSiteModel
|
||||
|
||||
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||
|
||||
@@ -34,7 +37,7 @@
|
||||
return nil;
|
||||
|
||||
[self setEntity:entity fuzzyGroups:fuzzyGroups];
|
||||
_initialized = YES;
|
||||
self.initialized = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -45,16 +48,16 @@
|
||||
return nil;
|
||||
|
||||
[self setTransientSiteName:siteName forUser:user];
|
||||
_initialized = YES;
|
||||
self.initialized = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||
|
||||
if ([_entityOID isEqual:entity.objectID])
|
||||
if ([self.entityOID isEqual:entity.permanentObjectID])
|
||||
return;
|
||||
_entityOID = entity.objectID;
|
||||
self.entityOID = entity.permanentObjectID;
|
||||
|
||||
NSString *siteName = entity.name;
|
||||
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName];
|
||||
@@ -80,7 +83,6 @@
|
||||
self.uses = entity.uses_;
|
||||
self.counter = [entity isKindOfClass:[MPGeneratedSiteEntity class]]? [(MPGeneratedSiteEntity *)entity counter]: 0;
|
||||
self.loginGenerated = entity.loginGenerated;
|
||||
NSLog( @"%@: loginGenerated: %d", self.name, self.loginGenerated );
|
||||
|
||||
// Find all password types and the index of the current type amongst them.
|
||||
[self updateContent:entity];
|
||||
@@ -88,7 +90,7 @@
|
||||
|
||||
- (void)setTransientSiteName:(NSString *)siteName forUser:(MPUserEntity *)user {
|
||||
|
||||
_entityOID = nil;
|
||||
self.entityOID = nil;
|
||||
|
||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||
paragraphStyle.alignment = NSCenterTextAlignment;
|
||||
@@ -110,28 +112,28 @@
|
||||
|
||||
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
||||
|
||||
if (!_entityOID)
|
||||
if (!self.entityOID)
|
||||
return nil;
|
||||
|
||||
NSError *error;
|
||||
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:_entityOID error:&error];
|
||||
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:self.entityOID error:&error];
|
||||
if (!entity)
|
||||
err( @"Couldn't retrieve active site: %@", [error fullDescription] );
|
||||
MPError( error, @"Couldn't retrieve active site." );
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
- (void)setCounter:(NSUInteger)counter {
|
||||
|
||||
if (counter == _counter)
|
||||
if (self.counter == counter)
|
||||
return;
|
||||
_counter = counter;
|
||||
|
||||
if (!_initialized)
|
||||
if (!self.initialized)
|
||||
// This wasn't a change to the entity.
|
||||
return;
|
||||
|
||||
if (_entityOID)
|
||||
if (self.entityOID)
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self entityInContext:context];
|
||||
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||
@@ -147,15 +149,15 @@
|
||||
|
||||
- (void)setLoginGenerated:(BOOL)loginGenerated {
|
||||
|
||||
if (loginGenerated == _loginGenerated)
|
||||
if (self.loginGenerated == loginGenerated)
|
||||
return;
|
||||
_loginGenerated = loginGenerated;
|
||||
|
||||
if (!_initialized)
|
||||
if (!self.initialized)
|
||||
// This wasn't a change to the entity.
|
||||
return;
|
||||
|
||||
if (_entityOID)
|
||||
if (self.entityOID)
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self entityInContext:context];
|
||||
entity.loginGenerated = loginGenerated;
|
||||
@@ -180,7 +182,7 @@
|
||||
self.algorithm = MPAlgorithmForVersion( algorithmVersion )?: self.algorithm;
|
||||
[self didChangeValueForKey:@"outdated"];
|
||||
|
||||
if (_entityOID)
|
||||
if (self.entityOID)
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [self entityInContext:context];
|
||||
entity.algorithm = self.algorithm;
|
||||
@@ -194,7 +196,7 @@
|
||||
|
||||
- (void)setQuestion:(NSString *)question {
|
||||
|
||||
if ([question isEqualToString:_question])
|
||||
if ([self.question isEqualToString:question])
|
||||
return;
|
||||
_question = question;
|
||||
|
||||
@@ -218,14 +220,14 @@
|
||||
|
||||
- (BOOL)transient {
|
||||
|
||||
return _entityOID == nil;
|
||||
return self.entityOID == nil;
|
||||
}
|
||||
|
||||
- (void)updateContent {
|
||||
|
||||
if (_entityOID)
|
||||
if (self.entityOID)
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
|
||||
[self updateContent:[MPSiteEntity existingObjectWithID:self.entityOID inContext:context]];
|
||||
}];
|
||||
|
||||
else
|
||||
@@ -276,7 +278,6 @@
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.loginName = loginName;
|
||||
NSLog( @"%@: loginGenerated: %d, loginName: %@", self.name, self.loginGenerated, loginName );
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@class MPPasswordWindowController;
|
||||
@class MPSitesWindowController;
|
||||
|
||||
@interface MPSitesTableView : NSTableView
|
||||
|
||||
@property(nonatomic, weak) MPPasswordWindowController *controller;
|
||||
@property(nonatomic, weak) MPSitesWindowController *controller;
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#import "MPSitesTableView.h"
|
||||
#import "MPPasswordWindowController.h"
|
||||
#import "MPSitesWindowController.h"
|
||||
|
||||
@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