2
0

Compare commits

...

142 Commits

Author SHA1 Message Date
Maarten Billemont
30fdb54e94 Fix support for building without MPW_JSON enabled. 2017-09-06 00:32:57 -04:00
Maarten Billemont
4f552be5a9 Update cmake for source and improve feature checking in ./build 2017-09-06 00:31:49 -04:00
Maarten Billemont
1439df9f9a Add license headers to cli source files. 2017-09-04 19:37:36 -04:00
Maarten Billemont
e676a0e258 Release 2.6-cli-1. 2017-09-04 14:50:57 -04:00
Maarten Billemont
895df6377d Only distribute release tags, include TAG and VERSION. 2017-09-04 14:48:40 -04:00
Maarten Billemont
3d46f60ff4 Add VERSION to distribution archive. 2017-09-04 14:38:08 -04:00
Maarten Billemont
44d8ab6e53 Remove some pointless local's. 2017-09-04 14:29:25 -04:00
Maarten Billemont
cd70009c2c Move instructions into cli-c for distribution. 2017-09-04 14:17:20 -04:00
Maarten Billemont
4261160902 Some more refactoring. 2017-09-03 17:00:35 -04:00
Maarten Billemont
ced7aef5d7 Fix target assignment of mpw-cli-util.c 2017-09-03 16:53:14 -04:00
Maarten Billemont
63100913c5 libjson-c's upstream still has build issues, switch back to our fork. 2017-09-03 15:48:43 -04:00
Maarten Billemont
6904d4c427 purposeResult is not an owned reference. 2017-09-03 15:46:38 -04:00
Maarten Billemont
4271d77225 Remove debugging code. 2017-09-03 15:43:22 -04:00
Maarten Billemont
6811773e54 Refactor CLI code to make the flow more clear and the free'ing more reliable. 2017-09-03 15:41:12 -04:00
Maarten Billemont
060ce61030 Fix ./build's targets interpretation & other improvements. 2017-09-01 11:16:09 -04:00
Maarten Billemont
9a5e9ced31 More consistent spacing in usage output. 2017-08-31 15:32:58 -04:00
Maarten Billemont
568401a612 Extract cli utilities into mpw-cli-util to keep things cleaner. 2017-08-31 15:30:42 -04:00
Maarten Billemont
92a3a0ccbd Switch libjson-c back to upstream and fix some build issues with it. 2017-08-31 15:14:08 -04:00
Maarten Billemont
ba24c2be34 Rename env vars to make it clear they are for the mpw binary, add PATH support for askpass. 2017-08-31 13:42:03 -04:00
Maarten Billemont
019cefd3fb Add support for askpass program for reading answers from the user. 2017-08-31 13:37:28 -04:00
Maarten Billemont
eef82f7ed4 Don't infinite loop when prompting if no terminal is available. 2017-08-31 11:49:36 -04:00
Maarten Billemont
2dfe0f78b0 Build & link all sources at once to allow link-time optimization. 2017-08-31 11:48:34 -04:00
Maarten Billemont
627144b583 Make MPW_JSON optional as documented. 2017-08-31 11:48:10 -04:00
Maarten Billemont
fad0f5e5dd Add PearlCryptUtils back since PearlKeyChain uses it. 2017-08-30 19:05:42 -04:00
Maarten Billemont
8562338b62 Support for reading master password from an FD. 2017-08-30 19:05:21 -04:00
Maarten Billemont
17de69834e Declare __unused if not declared by compiler. 2017-08-30 13:27:59 -04:00
Maarten Billemont
aeeab7dbf6 Improve build script documentation and targets variable. 2017-08-30 10:18:23 -04:00
Maarten Billemont
ce60ba6c9f External libs configuration no longer used. 2017-08-30 09:58:01 -04:00
Maarten Billemont
d22f93e564 Format code. 2017-08-30 09:57:15 -04:00
Maarten Billemont
6f4f6b8d1e Copy TOTP counter support to v2 override. 2017-08-30 09:54:16 -04:00
Maarten Billemont
6fa8ee53cd Currently unused implementation of HOTP. 2017-08-30 09:40:51 -04:00
Maarten Billemont
23af56c150 Slight clean-up of types, includes and warnings. 2017-08-30 09:39:35 -04:00
Maarten Billemont
91828cbad7 Test script for CLI. 2017-08-30 09:38:23 -04:00
Maarten Billemont
40d2788ae0 Implement OTP counter feature for counter values of 0. 2017-08-30 09:35:55 -04:00
Maarten Billemont
21a3a28980 Copy args so we can re-use it. 2017-08-29 12:06:40 -04:00
Maarten Billemont
f5c7bee58f Remove unused Pearl-Crypto. 2017-08-29 01:01:38 -04:00
Maarten Billemont
e364f5159b Fix build warnings. 2017-08-28 23:48:24 -04:00
Maarten Billemont
74f9f1ca00 Fix up objc code to match new C API. 2017-08-28 19:37:51 -04:00
Maarten Billemont
328d38ac19 Remove PearlLayout. 2017-08-28 19:34:22 -04:00
Maarten Billemont
7735d82c7b Silence unused variable warnings for compiler check variables. 2017-08-28 19:25:51 -04:00
Maarten Billemont
1e7c200865 Remove non-standard asprintf from mpw-cli. 2017-08-28 18:25:58 -04:00
Maarten Billemont
724b357dd8 Create path for mpw.d sites files if it doesn't exist yet. 2017-08-28 17:58:57 -04:00
Maarten Billemont
a85efc5736 Remove all build-time external dependency fetching/building logic. 2017-08-27 11:17:45 -04:00
Maarten Billemont
9eb58119ea Remove dependency on external bcrypt. 2017-08-27 10:47:39 -04:00
Maarten Billemont
77b4ed2cfd Remove dependency on asprintf. 2017-08-27 09:25:53 -04:00
Maarten Billemont
011416690a Some warning cleanup. 2017-08-27 09:04:18 -04:00
Maarten Billemont
53eb5c8a73 Refactoring and fix up mpw_color. 2017-08-27 08:53:58 -04:00
Maarten Billemont
2f99855cd4 Remove non-standard host-endian functions. 2017-08-27 07:46:34 -04:00
Maarten Billemont
18eaeec1de Fix some rewrite bugs. 2017-08-23 00:53:14 -04:00
Maarten Billemont
5ee700c9b9 Small fix. 2017-08-23 00:05:50 -04:00
Maarten Billemont
a8949ca07e NULL out free'ed references. 2017-08-23 00:01:23 -04:00
Maarten Billemont
0a42579d9e Improved free'ing on error conditions. 2017-08-22 18:38:36 -04:00
Maarten Billemont
f2f8747126 Support for persisting login/question type & stateful types, null checking, cleanup and rewrite of CLI state. 2017-08-22 18:18:24 -04:00
Maarten Billemont
f83cdacab8 Document -M, -P, allow saving login name. 2017-08-22 11:38:04 -04:00
Maarten Billemont
98aeb02d32 Forgot to merge in i386 2017-08-13 19:06:06 -04:00
Maarten Billemont
2bbaeccd05 Forgot to merge in i386 2017-08-13 18:32:03 -04:00
Maarten Billemont
91e0a04e66 Add support for i386 in libjson-c and libsodium builds. 2017-08-13 16:53:18 -04:00
Maarten Billemont
661fc523ad Don't pass nil error to crashlytics. 2017-08-13 16:49:56 -04:00
Maarten Billemont
b9cbaf7343 Cross-compile fixes for iOS. 2017-08-13 15:52:08 -04:00
Maarten Billemont
e451308fdc Refactoring regression, use right algorithm version for siteKey. 2017-08-13 11:35:15 -04:00
Maarten Billemont
1b51c5efa4 Build script update. 2017-08-13 11:02:05 -04:00
Maarten Billemont
a8776eec58 Fix C cli API. 2017-08-13 08:50:16 -04:00
Maarten Billemont
d9cdb7ef83 Fix error name collision. 2017-08-13 01:00:03 -04:00
Maarten Billemont
28c7a64bd2 Fork json-c temporarily to fix some code issues. 2017-08-13 00:30:25 -04:00
Maarten Billemont
d7193f7753 Adapt macOS for new APIs. 2017-08-12 22:26:48 -04:00
Maarten Billemont
f5c7d11f0e Add marshalling metadata lookup & adapt iOS for new APIs. 2017-08-12 21:57:47 -04:00
Maarten Billemont
c0ba96daa2 Update Darwin platform project with solid support for linking libsodium & libjson-c 2017-08-11 01:42:03 -04:00
Maarten Billemont
b374d9e04a Some type fixes. 2017-08-10 21:29:59 -04:00
Maarten Billemont
2033ebdc72 Documentation improvements. 2017-08-10 12:48:04 -04:00
Maarten Billemont
c3bb896f40 Limit subkeys to 128-512 bit. 2017-08-10 12:45:25 -04:00
Maarten Billemont
4f7c28563d passwordType -> resultType, add derived class and key type. 2017-08-10 12:30:42 -04:00
Maarten Billemont
b1985a2bf2 Added version into compiled binary. 2017-08-08 00:00:14 -04:00
Maarten Billemont
ee50a4d025 Define type for siteCounter, add support for saving hybrid passwords. 2017-08-07 20:35:31 -04:00
Maarten Billemont
b26f5a82d7 Allow migration of redacted state & show URL. 2017-08-07 18:57:10 -04:00
Maarten Billemont
c044ae79cd Support updating the mpw, showing loginName, adding new sites & questions, fix password memory leak. 2017-08-07 17:42:38 -04:00
Maarten Billemont
a261538602 Slight adjustment in printf declarations. 2017-08-06 19:09:13 -04:00
Maarten Billemont
18daef7808 Fix some build warnings. 2017-08-06 18:56:37 -04:00
Maarten Billemont
68d1ab58b7 De-duplicate algorithm implementations and improve trace output. 2017-08-06 11:40:10 -04:00
Maarten Billemont
2b660adf00 Fix a NUL termination issue with aes decryption & add standard trace debugging. 2017-08-05 23:42:47 -04:00
Maarten Billemont
e15d01882f Update the mpsites file after the operation and memory cleanup. 2017-08-05 23:19:24 -04:00
Maarten Billemont
23491faccc Fix up and clean up base64 & aes code. 2017-08-05 21:52:00 -04:00
Maarten Billemont
5f2e1611f1 Fix mpw_tests.xml after purpose rename. 2017-08-05 19:14:25 -04:00
Maarten Billemont
9abacaf905 mpw_realloc solves some issues with the realloc API that can lead to leaks. 2017-08-05 19:04:42 -04:00
Maarten Billemont
322e056661 Implement algorithm support for hybrid personal passwords. 2017-08-05 17:33:45 -04:00
Maarten Billemont
228f8e4ed1 C API for hybrid passwords. 2017-08-04 10:43:46 -04:00
Maarten Billemont
d6415277d0 Wrap up error handling overhaul. 2017-08-04 09:36:03 -04:00
Maarten Billemont
db41a6635f Standardize C CLI exit codes. 2017-08-03 11:05:37 -04:00
Maarten Billemont
096919637f Abort CLI if master password doesn't match. 2017-08-03 01:13:15 -04:00
Maarten Billemont
434d70ebff Improve error handling for marshalling. 2017-08-03 01:07:19 -04:00
Maarten Billemont
bb8829b66f Fix mpw_push_buf regression. 2017-08-02 23:13:51 -04:00
Maarten Billemont
10f2c107c6 More improvements to error handling. 2017-08-02 14:26:41 -04:00
Maarten Billemont
03080b9ccd Render error messages as strings instead of internal codes. 2017-08-01 17:35:13 -04:00
Maarten Billemont
b00ad53e42 Some more error handling. 2017-08-01 17:13:30 -04:00
Maarten Billemont
99e286456e Better ftl failure handling. 2017-08-01 16:50:50 -04:00
Maarten Billemont
46cdf56944 Expose standard names for password type and purpose, and log them in the cli. 2017-08-01 14:34:15 -04:00
Maarten Billemont
9d5105a9e5 Rename identifiers to align better with their meaning (siteVariant -> keyPurpose, siteContext -> keyContext, siteType -> passwordType). 2017-08-01 13:45:54 -04:00
Maarten Billemont
3c5cb1673a Split the API into the three distinct phases of the mpw algorithm. 2017-08-01 08:31:39 -04:00
Maarten Billemont
13107063df Refactor marshall utilities out, some clean-up, and prepare iOS/macOS project for libjson-c 2017-07-28 09:50:26 -04:00
Maarten Billemont
8a73baa6bc Improved error detection and handling. 2017-07-23 17:05:49 -04:00
Maarten Billemont
b65fedf40d WIP - parsing JSON format. 2017-07-23 09:57:48 -04:00
Maarten Billemont
04ab276d93 Memory fixes to JSON export. 2017-07-23 00:48:38 -04:00
Maarten Billemont
6d88d6bde0 Refactor a bit. 2017-07-22 23:58:22 -04:00
Maarten Billemont
4103c6e659 Replace ftl by err. 2017-07-22 23:46:53 -04:00
Maarten Billemont
16004f2ffe Memory fixes & handle masterPassword verification, masterKey site algorithm scoping, etc. 2017-07-22 23:45:54 -04:00
Maarten Billemont
37c0d323d9 Complete parsing of mpsites for CLI defaults. 2017-07-22 21:38:53 -04:00
Maarten Billemont
560cb1a266 WIP performing marshalling of sites in C. 2017-07-15 21:13:49 -04:00
Maarten Billemont
738ad197b2 Fix the main thread lock-up detector across background events. 2017-06-06 22:10:02 -04:00
Maarten Billemont
cfcc5287db Fix bad assumption that there is always a zero'th group. 2017-06-04 09:46:41 -04:00
Maarten Billemont
0b5502b673 WIP site. 2017-06-04 09:35:27 -04:00
Maarten Billemont
d3e3c9d720 Fixes #174 - Login name copy button was made inoperative when hint was hidden. 2017-06-03 11:57:41 -04:00
Maarten Billemont
3c3f88d820 Initial work on a new web design. 2017-05-28 15:34:07 -04:00
Maarten Billemont
2e2c654ec9 Fix for fetchedResultsController getter, haslib() and ability to turn off libsodium. 2017-05-28 15:32:50 -04:00
Maarten Billemont
d361ae2381 Assist the user with store setup and allow continuing anyway. 2017-05-08 14:58:46 -04:00
Maarten Billemont
fcbb93762a Cleanup, renaming restructuring, etc. 2017-05-07 18:44:09 -04:00
Maarten Billemont
f86210f5da Ability to handle versions without suffix after platform. 2017-05-04 14:30:50 -04:00
Maarten Billemont
e96f678236 Introduce a main thread lockup test feature. 2017-05-04 13:57:12 -04:00
Maarten Billemont
8b9067ab4b Merge tag '2.5-ios-4'
2.5-ios-4
2017-05-01 18:44:08 -04:00
Maarten Billemont
5af383235a Fix issue causing site list to appear empty on login. 2017-05-01 18:43:16 -04:00
Maarten Billemont
25b13dfb22 Rollback temporary storyboard hack. 2017-05-01 18:41:55 -04:00
Maarten Billemont
635692ef09 Fix issue causing site list to appear empty on login. 2017-05-01 18:40:51 -04:00
Maarten Billemont
e6bab4e504 Support for associating a URL to sites. 2017-05-01 18:32:52 -04:00
Maarten Billemont
cd6b7e6051 Settle on a method of making the password cells visible in storyboard. 2017-04-30 19:08:34 -04:00
Maarten Billemont
b180202e07 Dismiss keyboard when dropping down preferences or app deactivates. 2017-04-30 18:54:07 -04:00
Maarten Billemont
f83f2af529 Fix store product images and http URL links. 2017-04-30 18:45:08 -04:00
Maarten Billemont
cf2c30cfe6 Convert store into template cells for products. 2017-04-30 17:48:03 -04:00
Maarten Billemont
834e94ebd5 Fix usage of dubious objectID in global context. 2017-04-29 23:52:57 -04:00
Maarten Billemont
6d9be3fdfe Add support for Answers and improved Fabric integration. 2017-04-29 23:03:50 -04:00
Maarten Billemont
07e55140ac Bump Pearl. 2017-04-29 18:07:06 -04:00
Maarten Billemont
fbbd08790d Pasteboard improvements, UI fixes and site name from pasteboard URL.
[UPDATED]   Timeout after 3 min for other pasteboard copies too.
[FIXED]     Sometimes cell content loading can fail, schedule a retry.
[UPDATED]   Dismiss keyboard when copying content.
[IMPROVED]  Handling of deactivation and reactivation observation.
[ADDED]     When a URL is in the pasteboard, search for the hostname.
2017-04-29 17:50:48 -04:00
Maarten Billemont
fcaa5d1d8c Some improvement to observing user changes. 2017-04-29 15:01:24 -04:00
Maarten Billemont
ea5be8efcb Rewrite handling of collection view and table view reloading for reliability. 2017-04-27 02:22:01 -04:00
Maarten Billemont
c8b4933c3d Expire the password from the clipboard after 3 minutes on iOS 10+. 2017-04-26 22:01:27 -04:00
Maarten Billemont
981ee171ae Update site for 2.5-cli-2 2017-04-22 12:35:47 -04:00
Maarten Billemont
3ed6b93736 Keep tarballs in site directory. 2017-04-22 12:33:46 -04:00
Maarten Billemont
56a515c5ea Improve clean methods. 2017-04-22 12:20:52 -04:00
Maarten Billemont
15ac7a2dbf Improve font size and font scaling fixes. 2017-04-22 12:00:47 -04:00
Maarten Billemont
c5c7999753 Ensure the tree is clean before building the distribution archive. 2017-04-22 11:15:07 -04:00
Maarten Billemont
bb58ed0169 Update In-App Settings Kit. 2017-04-22 10:03:31 -04:00
Maarten Billemont
4545a5c745 Improve readability of some of the smaller and thinner fonts. 2017-04-22 09:52:28 -04:00
Maarten Billemont
da8c7064fe Support for reduced transparency. 2017-04-20 22:29:10 -04:00
Maarten Billemont
d9bd604436 Improve support for import/export headers on iOS/Mac.
[ADDED]     iOS/macOS support for Full Name, Algorithm and Default Type mpsites headers.
2017-04-19 21:58:10 -04:00
Maarten Billemont
c99252809d Disable ADHOC and tester functionality / bypasses. 2017-04-18 20:31:38 -04:00
218 changed files with 12414 additions and 5819 deletions

16
.gitignore vendored
View File

@@ -13,8 +13,6 @@ xcuserdata/
DerivedData/
# Generated
/platform-independent/cli-c/VERSION
/platform-independent/cli-c/mpw-*.tar.gz
/platform-darwin/Resources/Media/Images.xcassets/
# Media
@@ -31,17 +29,3 @@ local.properties
# Maven
target
dependency-reduced-pom.xml
# C
core/c/*.o
core/c/lib/*/.unpacked
core/c/lib/*/.patched
core/c/lib/*/src
core/c/lib/include
platform-independent/cli-c/cli/*.o
platform-independent/cli-c/mpw-*.tar.gz
platform-independent/cli-c/mpw-*.tar.gz.sig
platform-independent/cli-c/mpw
platform-independent/cli-c/mpw-bench
platform-independent/cli-c/mpw-tests
platform-independent/cli-c/VERSION

3
.gitmodules vendored
View File

@@ -22,3 +22,6 @@
[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/lhunath/json-c.git

View File

@@ -4,7 +4,7 @@ env: TERM=dumb SHLVL=0
git:
submodules: true
script:
- "( brew install libsodium )"
- "( cd ./platform-independent/cli-c && ./clean && targets='mpw mpw-bench mpw-tests' ./build && ./mpw-tests )"
- "( brew install libsodium json-c )"
- "( cd ./platform-independent/cli-c && ./clean && targets='mpw mpw-bench mpw-tests' ./build && ./mpw-tests && ./mpw-cli-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' )"

View File

@@ -194,49 +194,7 @@ Note that in order to build the Android application, you will need to have the A
Go into the `platform-independent/cli-c` directory and run `./build`. The native command-line client will then be built.
When the build completes, you will have an `mpw` binary you can use. You can copy it into your `PATH` or use the `./install` script to help you do so.
For example:
./build && sudo ./install
mpw -h
Normally, this is all you need to do, however note that there are a few dependencies that need to be met, depending on which targets you are building (by default, only the `mpw` target is built):
- `mpw`
The C implementation depends either on `libsodium` or Tarsnap's `scrypt` and `openssl-dev`.
We recommend you install `libsodium`. If `libsodium` is not installed when `./build` is executed, the script will try to download and statically link Tarsnap's `scrypt` instead. Tarsnap's `scrypt` depends on you having `openssl-dev` installed.
If you have `mpw_color` enabled (it is enabled by default), the build also depends on `ncurses-dev` to communicate with the terminal.
- `mpw-bench`
This tool compares the performance of a few cryptographic algorithms, including bcrypt. The `./build` script will try to automatically download and statically link `bcrypt`.
- `mpw-tests`
This tool runs a suite of tests to ensure the correct passwords are being generated by the algorithm under various circumstances. The test suite is declared in `mpw-tests.xml` which needs to exist in the current working directory when running the tool. In addition, `libxml2` is used to parse the file, so this target depends on you having it installed when running `./build`.
Finally, there are a few different ways you can modify the build process.
To build additional targets, set the `targets` environment variable:
targets='mpw mpw-tests' ./build
To pass additional compiler arguments, eg. add a library search path, pass them as arguments to the script:
./build -L/usr/local/lib
There are a few toggleable features, to change them, pass them as environment variables:
mpw_color=0 ./build
Currently, there is only one toggleable feature:
- `mpw_color`: [default: 1] Colorized Identicon, depends on `ncurses-dev`.
For detailed instructions, see [the native CLI instructions](platform-independent/cli-c/README.md).
## Support

155
core/c/base64.c Normal file
View 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 uint8_t 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++) = (uint8_t)(b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
*(plainCursor++) = (uint8_t)(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++) = (uint8_t)(b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
if (b64Remaining > 2)
*(plainCursor++) = (uint8_t)(b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
if (b64Remaining > 3)
*(plainCursor++) = (uint8_t)(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) {
size_t 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
View 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);

View File

@@ -1,4 +0,0 @@
home=http://www.openwall.com/crypt/
pkg=http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz
pkg_sha256=83fa01fca6996fe8d882b7f8e9ba0305a5664936100b01481ea3c6a8ce8d72fd
patches=(arm)

View File

@@ -1,12 +0,0 @@
--- x86.S 2014-11-21 09:09:58.000000000 -0500
+++ x86.S 2014-11-21 09:11:01.000000000 -0500
@@ -199,5 +199,9 @@
#endif
#if defined(__ELF__) && defined(__linux__)
+#if defined(__arm__)
+.section .note.GNU-stack,"",%progbits
+#else
.section .note.GNU-stack,"",@progbits
#endif
+#endif

View File

@@ -1,4 +0,0 @@
home=http://www.tarsnap.com/scrypt.html
git=https://github.com/Tarsnap/scrypt.git
pkg=https://www.tarsnap.com/scrypt/scrypt-1.2.1.tgz
pkg_sha256=4621f5e7da2f802e20850436219370092e9fcda93bd598f6d4236cce33f4c577

View File

@@ -1,38 +0,0 @@
diff -ruN /Users/lhunath/.src/scrypt/Makefile ./Makefile
--- /Users/lhunath/.src/scrypt/Makefile 2014-05-02 11:28:58.000000000 -0400
+++ ./Makefile 2014-05-02 12:07:27.000000000 -0400
@@ -2,11 +2,11 @@
VER?= nosse
SRCS= main.c
LDADD+= -lcrypto
-WARNS?= 6
+WARNS?= 0
# We have a config file for FreeBSD
CFLAGS += -I .
-CFLAGS += -DCONFIG_H_FILE=\"config_freebsd.h\"
+CFLAGS += -DCONFIG_H_FILE=\"config_osx.h\"
# Include all possible object files containing built scrypt code.
CLEANFILES += crypto_scrypt-ref.o
diff -ruN /Users/lhunath/.src/scrypt/lib/util/memlimit.c ./lib/util/memlimit.c
--- /Users/lhunath/.src/scrypt/lib/util/memlimit.c 2014-05-02 11:28:58.000000000 -0400
+++ ./lib/util/memlimit.c 2014-05-02 11:52:42.000000000 -0400
@@ -75,7 +75,7 @@
* have returned to us.
*/
if (usermemlen == sizeof(uint64_t))
- usermem = *(uint64_t *)usermembuf;
+ usermem = *(uint64_t *)(void *)usermembuf;
else if (usermemlen == sizeof(uint32_t))
usermem = SIZE_MAX;
else
diff -ruN /Users/lhunath/.src/scrypt/lib/util/memlimit.c ./lib/util/memlimit.c
--- /Users/lhunath/.src/scrypt/config_osx.h 1969-12-31 19:00:00.000000000 -0500
+++ config_osx.h 2014-05-02 12:06:55.000000000 -0400
@@ -0,0 +1,5 @@
+/* A default configuration for FreeBSD, used if there is no config.h. */
+
+#define HAVE_POSIX_MEMALIGN 1
+#define HAVE_SYSCTL_HW_USERMEM 1
+#define HAVE_SYS_PARAM_H 1

View File

@@ -22,48 +22,174 @@
#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
MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
const uint8_t *mpw_masterKeyForUser(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
if (fullName && !strlen( fullName ))
fullName = NULL;
if (masterPassword && !strlen( masterPassword ))
masterPassword = NULL;
trc( "-- mpw_masterKey (algorithm: %u)\n", algorithmVersion );
trc( "fullName: %s\n", fullName );
trc( "masterPassword.id: %s\n", masterPassword? mpw_id_buf( masterPassword, strlen( masterPassword ) ): NULL );
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) {
if (siteName && !strlen( siteName ))
siteName = NULL;
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
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) {
if (siteName && !strlen( siteName ))
siteName = NULL;
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
if (resultParam && !strlen( resultParam ))
resultParam = NULL;
MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
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 & MPResultTypeClassStateful) {
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 *resultParam,
const MPAlgorithmVersion algorithmVersion) {
if (siteName && !strlen( siteName ))
siteName = NULL;
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
if (resultParam && !strlen( resultParam ))
resultParam = NULL;
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( "resultParam: %s\n", resultParam );
if (!masterKey || !resultParam)
return NULL;
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_siteState_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_siteState_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_siteState_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_siteState_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d\n", algorithmVersion );
return NULL;
}
}

View File

@@ -19,7 +19,10 @@
// NOTE: mpw is currently NOT thread-safe.
#include "mpw-types.h"
typedef enum(unsigned int, MPAlgorithmVersion) {
#ifndef _MPW_ALGORITHM_H
#define _MPW_ALGORITHM_H
typedef mpw_enum( unsigned int, MPAlgorithmVersion ) {
/** V0 did math with chars whose signedness was platform-dependent. */
MPAlgorithmVersion0,
/** V1 miscounted the byte-length of multi-byte site names. */
@@ -28,16 +31,39 @@ 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);
/** Generate a site result token from the given parameters.
* @param resultParam A parameter for the resultType. For stateful result types, the output of mpw_siteState.
* @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);
/** Encrypt a stateful site token for persistence.
* @param resultParam A parameter for the resultType. For stateful result types, the desired mpw_siteResult.
* @return A newly allocated string or NULL if an error occurred. */
const char *mpw_siteState(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion);
#endif // _MPW_ALGORITHM_H

View File

@@ -18,123 +18,238 @@
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <time.h>
#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
#define MP_otp_window 5 * 60 /* s */
static const char *mpw_templateForType_v0(MPSiteType type, uint16_t seedByte) {
// Algorithm version helpers.
static const char *mpw_templateForType_v0(MPResultType type, uint16_t templateIndex) {
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[templateIndex % count]: NULL;
free( templates );
return template;
}
static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedByte) {
static const char mpw_characterFromClass_v0(char characterClass, uint16_t classIndex) {
const char *classCharacters = mpw_charactersInClass( characterClass );
return classCharacters[seedByte % strlen( classCharacters )];
if (!classCharacters)
return '\0';
return classCharacters[classIndex % 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( (uint32_t)mpw_utf8_strlen( 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, keyScope );
mpw_push_int( &masterKeySalt, &masterKeySaltSize, (uint32_t)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 );
mpw_free( masterKeySalt, masterKeySaltSize );
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 derive 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, MPCounterValue siteCounter,
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 );
// OTP counter value.
if (siteCounter == MPCounterValueTOTP)
siteCounter = ((uint32_t)time( NULL ) / MP_otp_window) * MP_otp_window;
// 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( (uint32_t)mpw_utf8_strlen( siteName ) ), siteName, mpw_hex_l( siteCounter ),
keyContext? mpw_hex_l( (uint32_t)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, (uint32_t)mpw_utf8_strlen( siteName ) );
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
mpw_push_int( &siteSalt, &siteSaltSize, siteCounter );
if (keyContext) {
mpw_push_int( &siteSalt, &siteSaltSize, (uint32_t)mpw_utf8_strlen( keyContext ) );
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
}
if (!sitePasswordInfo) {
ftl( "Could not allocate site seed info: %d\n", errno );
if (!siteSalt) {
err( "Could not allocate site salt: %s\n", strerror( errno ) );
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 __unused masterKey, MPSiteKey siteKey, MPResultType resultType, const char __unused *resultParam) {
const char *_siteKey = (const char *)siteKey;
// 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 ) );
uint16_t seedByte;
mpw_uint16( (uint16_t)_siteKey[0], (uint8_t *)&seedByte );
const char *template = mpw_templateForType_v0( resultType, seedByte );
trc( "template: %u => %s\n", seedByte, 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_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] );
mpw_uint16( (uint16_t)_siteKey[c + 1], (uint8_t *)&seedByte );
sitePassword[c] = mpw_characterFromClass_v0( template[c], seedByte );
trc( " - class: %c, index: %5u (0x%02hX) => character: %c\n",
template[c], seedByte, seedByte, sitePassword[c] );
}
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
trc( " => password: %s\n", sitePassword );
return sitePassword;
}
static const char *mpw_sitePasswordFromCrypt_v0(
MPMasterKey masterKey, MPSiteKey __unused siteKey, MPResultType __unused 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 );
mpw_free( &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 ) ) );
return plainText;
}
static const char *mpw_sitePasswordFromDerive_v0(
MPMasterKey __unused masterKey, MPSiteKey siteKey, 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 *b64Key = calloc( 1, b64Max + 1 );
if (mpw_base64_encode( b64Key, resultKey, keySize ) < 0) {
err( "Base64 encoding error." );
mpw_free_string( &b64Key );
}
else
trc( "b64 encoded -> key.id: %s\n", mpw_id_buf( b64Key, strlen( b64Key ) ) );
mpw_free( &resultKey, keySize );
return b64Key;
}
default:
err( "Unsupported derived password type: %d\n", resultType );
return NULL;
}
}
static const char *mpw_siteState_v0(
MPMasterKey masterKey, MPSiteKey __unused siteKey, MPResultType __unused 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 );
}
else
trc( "b64 encoded -> cipherText: %s = %s\n", cipherText, mpw_hex( cipherText, sizeof( cipherText ) ) );
mpw_free( &cipherBuf, bufSize );
return cipherText;
}

View File

@@ -17,109 +17,82 @@
//==============================================================================
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#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
#define MP_otp_window 5 * 60 /* s */
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, MPCounterValue siteCounter,
MPKeyPurpose keyPurpose, const char *keyContext);
const char *mpw_sitePasswordFromCrypt_v0(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText);
const char *mpw_sitePasswordFromDerive_v0(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
const char *mpw_siteState_v0(
MPMasterKey masterKey, MPSiteKey siteKey, 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, MPCounterValue siteCounter,
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 __unused masterKey, MPSiteKey siteKey, MPResultType resultType, const char __unused *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 ) );
uint8_t seedByte = siteKey[0];
const char *template = mpw_templateForType( resultType, seedByte );
trc( "template: %u => %s\n", seedByte, 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] );
seedByte = siteKey[c + 1];
sitePassword[c] = mpw_characterFromClass( template[c], seedByte );
trc( " - class: %c, index: %3u (0x%02hhX) => character: %c\n",
template[c], seedByte, seedByte, sitePassword[c] );
}
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
trc( " => password: %s\n", sitePassword );
return sitePassword;
}
static const char *mpw_sitePasswordFromCrypt_v1(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText) {
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, cipherText );
}
static const char *mpw_sitePasswordFromDerive_v1(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_siteState_v1(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state) {
return mpw_siteState_v0( masterKey, siteKey, resultType, state );
}

View File

@@ -18,108 +18,98 @@
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <time.h>
#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
#define MP_otp_window 5 * 60 /* s */
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, MPResultType resultType, const char *resultParam);
const char *mpw_sitePasswordFromCrypt_v1(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText);
const char *mpw_sitePasswordFromDerive_v1(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
const char *mpw_siteState_v1(
MPMasterKey masterKey, MPSiteKey siteKey, 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, MPCounterValue siteCounter,
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 );
// OTP counter value.
if (siteCounter == MPCounterValueTOTP)
siteCounter = ((uint32_t)time( NULL ) / MP_otp_window) * MP_otp_window;
// 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( (uint32_t)strlen( siteName ) ), siteName, mpw_hex_l( siteCounter ),
keyContext? mpw_hex_l( (uint32_t)strlen( keyContext ) ): NULL, keyContext );
size_t siteSaltSize = 0;
uint8_t *siteSalt = NULL;
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
mpw_push_int( &siteSalt, &siteSaltSize, (uint32_t)strlen( siteName ) );
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
mpw_push_int( &siteSalt, &siteSaltSize, siteCounter );
if (keyContext) {
mpw_push_int( &siteSalt, &siteSaltSize, (uint32_t)strlen( keyContext ) );
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
}
if (!sitePasswordInfo) {
ftl( "Could not allocate site seed info: %d\n", errno );
if (!siteSalt) {
err( "Could not allocate site salt: %s\n", strerror( errno ) );
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 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 ) );
// Determine the template.
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
trc( "type %d, template: %s\n", siteType, template );
if (strlen( template ) > 32) {
ftl( "Template too long for password seed: %lu", strlen( template ) );
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
return NULL;
}
// Encode the password from the seed using the template.
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
for (size_t c = 0; c < strlen( template ); ++c) {
sitePassword[c] = mpw_characterFromClass( template[c], sitePasswordSeed[c + 1] );
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
sitePassword[c] );
}
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
return sitePassword;
return siteKey;
}
static const char *mpw_sitePasswordFromTemplate_v2(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_sitePasswordFromCrypt_v2(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText) {
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, cipherText );
}
static const char *mpw_sitePasswordFromDerive_v2(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_siteState_v2(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state) {
return mpw_siteState_v1( masterKey, siteKey, resultType, state );
}

View File

@@ -18,108 +18,88 @@
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#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
#define MP_otp_window 5 * 60 /* s */
static const uint8_t *mpw_masterKeyForUser_v3(const char *fullName, const char *masterPassword) {
// Inherited functions.
MPSiteKey mpw_siteKey_v2(
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
MPKeyPurpose keyPurpose, const char *keyContext);
const char *mpw_sitePasswordFromTemplate_v2(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
const char *mpw_sitePasswordFromCrypt_v2(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText);
const char *mpw_sitePasswordFromDerive_v2(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam);
const char *mpw_siteState_v2(
MPMasterKey masterKey, MPSiteKey siteKey, 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( (uint32_t)strlen( fullName ) ), fullName );
size_t masterKeySaltSize = 0;
uint8_t *masterKeySalt = NULL;
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( strlen( fullName ) ) );
mpw_push_string( &masterKeySalt, &masterKeySaltSize, keyScope );
mpw_push_int( &masterKeySalt, &masterKeySaltSize, (uint32_t)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 );
mpw_free( masterKeySalt, masterKeySaltSize );
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 derive 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(
MPMasterKey masterKey, const char *siteName, MPCounterValue siteCounter,
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;
}
// Encode the password from the seed using the template.
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
for (size_t c = 0; c < strlen( template ); ++c) {
sitePassword[c] = mpw_characterFromClass( template[c], sitePasswordSeed[c + 1] );
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
sitePassword[c] );
}
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
return sitePassword;
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
}
static const char *mpw_sitePasswordFromTemplate_v3(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_sitePasswordFromCrypt_v3(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *cipherText) {
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, cipherText );
}
static const char *mpw_sitePasswordFromDerive_v3(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_siteState_v3(
MPMasterKey masterKey, MPSiteKey siteKey, MPResultType resultType, const char *state) {
return mpw_siteState_v2( masterKey, siteKey, resultType, state );
}

115
core/c/mpw-marshall-util.c Normal file
View File

@@ -0,0 +1,115 @@
//==============================================================================
// 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(const char **in, const 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;
}
#if MPW_JSON
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 );
}
int64_t mpw_get_json_int(
json_object *obj, const char *section, int64_t defaultValue) {
json_object *json_value = mpw_get_json_section( obj, section );
if (!json_value)
return defaultValue;
return json_object_get_int64( 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;
}
#endif
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;
}

View File

@@ -0,0 +1,73 @@
//==============================================================================
// 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>
#if MPW_JSON
#include "json-c/json.h"
#endif
#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(
const char **in, const char *eol, char *delim);
/** Convert an RFC 3339 time string into epoch time. */
time_t mpw_mktime(
const char *time);
/// JSON parsing.
#if MPW_JSON
/** 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. */
int64_t mpw_get_json_int(
json_object *obj, const char *section, int64_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);
#endif
/// 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

924
core/c/mpw-marshall.c Normal file
View File

@@ -0,0 +1,924 @@
//==============================================================================
// 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,
.loginContent = NULL,
.loginType = MPResultTypeTemplateName,
.url = NULL,
.uses = 0,
.lastUsed = 0,
.questions_count = 0,
.questions = NULL,
};
return site;
};
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword) {
if (!mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count ))
return NULL;
if (!keyword)
keyword = "";
MPMarshalledQuestion *question = &site->questions[site->questions_count - 1];
*question = (MPMarshalledQuestion){
.keyword = strdup( keyword ),
.content = NULL,
.type = MPResultTypeTemplatePhrase,
};
return question;
}
bool mpw_marshal_info_free(
MPMarshallInfo **info) {
if (!info || !*info)
return true;
bool success = true;
success &= mpw_free_strings( &(*info)->fullName, &(*info)->keyID, NULL );
success &= mpw_free( info, sizeof( MPMarshallInfo ) );
return success;
}
bool mpw_marshal_free(
MPMarshalledUser **user) {
if (!user || !*user)
return true;
bool success = true;
success &= mpw_free_strings( &(*user)->fullName, &(*user)->masterPassword, NULL );
for (size_t s = 0; s < (*user)->sites_count; ++s) {
MPMarshalledSite *site = &(*user)->sites[s];
success &= mpw_free_strings( &site->name, &site->content, &site->loginContent, &site->url, NULL );
for (size_t q = 0; q < site->questions_count; ++q) {
MPMarshalledQuestion *question = &site->questions[q];
success &= mpw_free_strings( &question->keyword, &question->content, NULL );
}
success &= mpw_free( &site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count );
}
success &= mpw_free( &(*user)->sites, sizeof( MPMarshalledSite ) * (*user)->sites_count );
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, *loginContent = 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;
}
content = mpw_siteResult( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
}
else {
// Redacted
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = strdup( site->loginContent );
}
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,
loginContent?: "", site->name, content?: "" );
mpw_free_strings( &content, &loginContent, NULL );
}
mpw_free( &masterKey, MPMasterKeySize );
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return true;
}
#if MPW_JSON
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( (int32_t)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( (int32_t)user->algorithm ) );
json_object_object_add( json_user, "default_type", json_object_new_int( (int32_t)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, *loginContent = 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;
}
content = mpw_siteResult( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
}
else {
// Redacted
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = strdup( site->loginContent );
}
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( (int32_t)site->type ) );
json_object_object_add( json_site, "counter", json_object_new_int( (int32_t)site->counter ) );
json_object_object_add( json_site, "algorithm", json_object_new_int( (int32_t)site->algorithm ) );
if (content)
json_object_object_add( json_site, "password", json_object_new_string( content ) );
if (loginContent)
json_object_object_add( json_site, "login_name", json_object_new_string( loginContent ) );
json_object_object_add( json_site, "login_type", json_object_new_int( (int32_t)site->loginType ) );
json_object_object_add( json_site, "uses", json_object_new_int( (int32_t)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 );
json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) );
if (!user->redacted) {
// Clear Text
const char *answerContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, question->type, question->content, site->algorithm );
json_object_object_add( json_site_question, "answer", json_object_new_string( answerContent ) );
}
else {
// Redacted
if (site->type & MPSiteFeatureExportContent && question->content && strlen( question->content ))
json_object_object_add( json_site_question, "answer", json_object_new_string( question->content ) );
}
}
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_strings( &content, &loginContent, NULL );
}
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){ .type = MPMarshallSuccess };
return true;
}
#endif
bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) {
switch (outFormat) {
case MPMarshallFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return false;
case MPMarshallFormatFlat:
return mpw_marshall_write_flat( out, user, error );
#if MPW_JSON
case MPMarshallFormatJSON:
return mpw_marshall_write_json( out, user, error );
#endif
default:
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
return false;
}
}
static void mpw_marshall_read_flat_info(
const char *in, MPMarshallInfo *info) {
info->algorithm = MPAlgorithmVersionCurrent;
// Parse import data.
bool headerStarted = false;
for (const 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 (*positionInLine == '#')
// ## ends header
break;
// Header
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue)
continue;
if (strcmp( headerName, "Algorithm" ) == 0)
info->algorithm = (MPAlgorithmVersion)atoi( headerValue );
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
info->fullName = strdup( headerValue );
if (strcmp( headerName, "Key ID" ) == 0)
info->keyID = strdup( headerValue );
if (strcmp( headerName, "Passwords" ) == 0)
info->redacted = strcmp( headerValue, "VISIBLE" ) != 0;
if (strcmp( headerName, "Date" ) == 0)
info->date = mpw_mktime( headerValue );
mpw_free_strings( &headerName, &headerValue, NULL );
continue;
}
}
}
static MPMarshalledUser *mpw_marshall_read_flat(
const char *in, const char *masterPassword, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) {
error->type = MPMarshallErrorStructure;
error->description = mpw_str( "No input data." );
return NULL;
}
// 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 (const 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_strings( &headerName, &headerValue, NULL );
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->uses = (unsigned int)atoi( str_uses );
site->lastUsed = siteLastUsed;
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;
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else {
// Redacted
if (siteContent && strlen( siteContent ))
site->content = strdup( siteContent );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = strdup( siteLoginName );
}
}
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_strings( &str_lastUsed, &str_uses, &str_type, &str_algorithm, &str_counter, NULL );
mpw_free_strings( &siteLoginName, &siteName, &siteContent, NULL );
}
mpw_free_strings( &fullName, &keyID, NULL );
mpw_free( &masterKey, MPMasterKeySize );
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return user;
}
#if MPW_JSON
static void mpw_marshall_read_json_info(
const char *in, MPMarshallInfo *info) {
// 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)
return;
// Section: "export"
int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 );
if (fileFormat < 1)
return;
info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true );
info->date = mpw_mktime( mpw_get_json_string( json_file, "export.date", NULL ) );
// Section: "user"
info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
info->fullName = strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) );
info->keyID = strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) );
json_object_put( json_file );
}
static MPMarshalledUser *mpw_marshall_read_json(
const char *in, const char *masterPassword, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) {
error->type = MPMarshallErrorStructure;
error->description = mpw_str( "No input data." );
return NULL;
}
// 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"
int64_t fileFormat = 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 );
int64_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", (int32_t)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", (int32_t)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 );
MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName );
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->loginType = siteLoginType;
site->url = siteURL? strdup( siteURL ): NULL;
site->uses = siteUses;
site->lastUsed = siteLastUsed;
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;
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else {
// Redacted
if (siteContent && strlen( siteContent ))
site->content = strdup( siteContent );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = strdup( siteLoginName );
}
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 ) {
MPMarshalledQuestion *question = mpw_marshal_question( site, json_site_question.key );
const char *answerContent = mpw_get_json_string( json_site_question.val, "answer", NULL );
question->type = (MPResultType)mpw_get_json_int( json_site_question.val, "type", MPResultTypeTemplatePhrase );
if (!user->redacted) {
// Clear Text
if (answerContent && strlen( answerContent ))
question->content = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, question->type, answerContent, site->algorithm );
}
else {
// Redacted
if (answerContent && strlen( answerContent ))
question->content = strdup( answerContent );
}
}
}
json_object_put( json_file );
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return user;
}
#endif
MPMarshallInfo *mpw_marshall_read_info(
const char *in) {
MPMarshallInfo *info = malloc( sizeof( MPMarshallInfo ) );
*info = (MPMarshallInfo){ .format = MPMarshallFormatNone };
if (in && strlen( in )) {
if (in[0] == '#') {
*info = (MPMarshallInfo){ .format = MPMarshallFormatFlat };
mpw_marshall_read_flat_info( in, info );
}
else if (in[0] == '{') {
*info = (MPMarshallInfo){ .format = MPMarshallFormatJSON };
#if MPW_JSON
mpw_marshall_read_json_info( in, info );
#endif
}
}
return info;
}
MPMarshalledUser *mpw_marshall_read(
const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) {
switch (inFormat) {
case MPMarshallFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess };
return false;
case MPMarshallFormatFlat:
return mpw_marshall_read_flat( in, masterPassword, error );
#if MPW_JSON
case MPMarshallFormatJSON:
return mpw_marshall_read_json( in, masterPassword, error );
#endif
default:
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) };
return NULL;
}
}
const MPMarshallFormat mpw_formatWithName(
const char *formatName) {
if (!formatName || !strlen( formatName ))
return MPMarshallFormatNone;
// 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( MPMarshallFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatNone;
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 MPMarshallFormatNone:
return "none";
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 MPMarshallFormatNone:
return NULL;
case MPMarshallFormatFlat:
return "mpsites";
case MPMarshallFormatJSON:
return "mpsites.json";
default: {
dbg( "Unknown format: %d\n", format );
return NULL;
}
}
}

159
core/c/mpw-marshall.h Normal file
View File

@@ -0,0 +1,159 @@
//==============================================================================
// 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 mpw_enum( unsigned int, MPMarshallFormat ) {
/** Generate a key for authentication. */
MPMarshallFormatNone,
/** Generate a key for authentication. */
MPMarshallFormatFlat,
/** Generate a name for identification. */
MPMarshallFormatJSON,
#if MPW_JSON
MPMarshallFormatDefault = MPMarshallFormatJSON,
#else
MPMarshallFormatDefault = MPMarshallFormatFlat,
#endif
};
typedef mpw_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;
const char *content;
MPResultType type;
} MPMarshalledQuestion;
typedef struct MPMarshalledSite {
const char *name;
const char *content;
MPResultType type;
MPCounterValue counter;
MPAlgorithmVersion algorithm;
const char *loginContent;
MPResultType loginType;
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;
typedef struct MPMarshallInfo {
MPMarshallFormat format;
MPAlgorithmVersion algorithm;
const char *fullName;
const char *keyID;
bool redacted;
time_t date;
} MPMarshallInfo;
//// Marshalling.
/** Write the user and all associated data out to the given output buffer using the given marshalling format. */
bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error);
/** Try to read metadata on the sites in the input buffer. */
MPMarshallInfo *mpw_marshall_read_info(
const char *in);
/** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */
MPMarshalledUser *mpw_marshall_read(
const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error);
//// Utilities.
/** Create a new user object ready for marshalling. */
MPMarshalledUser *mpw_marshall_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
/** Create a new site attached to the given user object, ready for marshalling. */
MPMarshalledSite *mpw_marshall_site(
MPMarshalledUser *user,
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
/** Create a new question attached to the given site object, ready for marshalling. */
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword);
/** Free the given user object and all associated data. */
bool mpw_marshal_info_free(
MPMarshallInfo **info);
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);
/**
* @return The file extension that's recommended for files that use the given marshalling format.
*/
const char *mpw_marshall_format_extension(
const MPMarshallFormat format);
#endif // _MPW_MARSHALL_H

View File

@@ -16,68 +16,121 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef COLOR
#include <curses.h>
#include <term.h>
#endif
#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 MPResultTypeStatefulPersonal;
if ('D' == typeName[0])
return MPResultTypeStatefulDevice;
if ('k' == typeName[0])
return MPResultTypeDeriveKey;
}
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it.
size_t stdTypeNameOffset = 0;
size_t stdTypeNameSize = strlen( typeName );
if (strstr(typeName, "Generated" ) == typeName)
if (strstr( typeName, "Generated" ) == typeName)
stdTypeNameSize -= (stdTypeNameOffset = strlen( "Generated" ));
char stdTypeName[stdTypeNameSize + 1];
for (size_t c = 0; c < stdTypeNameSize; ++c)
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( MPResultTypeStatefulPersonal ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeStatefulPersonal;
if (strncmp( mpw_nameForType( MPResultTypeStatefulDevice ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeStatefulDevice;
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 MPResultTypeStatefulPersonal:
return "personal";
case MPResultTypeStatefulDevice:
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 +138,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 templateIndex) {
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[templateIndex % 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 +249,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 +258,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 )];
}

View File

@@ -18,71 +18,122 @@
#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)
#define mpw_enum(_type, _name) NS_ENUM(_type, _name)
#else
#define enum(_type, _name) _type _name; enum
#define mpw_enum(_type, _name) _type _name; enum
#endif
#define MP_dkLen 64
#ifndef __unused
#define __unused __attribute__((unused))
#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 mpw_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 mpw_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. */
MPResultTypeClassStateful = 1 << 5,
/** Use the site key to derive a site-specific object. */
MPResultTypeClassDerive = 1 << 6,
};
typedef enum( unsigned int, MPSiteFeature ) {
// bit 10 - 15
typedef mpw_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 mpw_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. */
MPResultTypeStatefulPersonal = 0x0 | MPResultTypeClassStateful | MPSiteFeatureExportContent,
/** Custom saved password that should not be exported from the device. */
MPResultTypeStatefulDevice = 0x1 | MPResultTypeClassStateful | MPSiteFeatureDevicePrivate,
/** Derive a unique binary key. */
MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative,
MPResultTypeDefault = MPResultTypeTemplateLong,
};
typedef mpw_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 +141,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 templateIndex);
/**
* @return An internal string that contains all the characters that occur in the given character class.

View File

@@ -16,74 +16,156 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#if COLOR
#if MPW_COLOR
#include <unistd.h>
#include <curses.h>
#include <term.h>
#endif
#if HAS_CPERCIVA
#if MPW_CPERCIVA
#include <scrypt/crypto_scrypt.h>
#include <scrypt/sha256.h>
#elif HAS_SODIUM
#elif MPW_SODIUM
#include "sodium.h"
#endif
#ifndef trc
int mpw_verbosity;
#endif
#include "mpw-util.h"
void mpw_push_buf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) {
#ifdef inf_level
int mpw_verbosity = inf_level;
#endif
if (*bufferSize == (size_t)-1)
void mpw_uint16(const uint16_t number, uint8_t buf[2]) {
buf[0] = (uint8_t)((number >> 8L) & UINT8_MAX);
buf[1] = (uint8_t)((number >> 0L) & UINT8_MAX);
}
void mpw_uint32(const uint32_t number, uint8_t buf[4]) {
buf[0] = (uint8_t)((number >> 24) & UINT8_MAX);
buf[1] = (uint8_t)((number >> 16) & UINT8_MAX);
buf[2] = (uint8_t)((number >> 8L) & UINT8_MAX);
buf[3] = (uint8_t)((number >> 0L) & UINT8_MAX);
}
void mpw_uint64(const uint64_t number, uint8_t buf[8]) {
buf[0] = (uint8_t)((number >> 56) & UINT8_MAX);
buf[1] = (uint8_t)((number >> 48) & UINT8_MAX);
buf[2] = (uint8_t)((number >> 40) & UINT8_MAX);
buf[3] = (uint8_t)((number >> 32) & UINT8_MAX);
buf[4] = (uint8_t)((number >> 24) & UINT8_MAX);
buf[5] = (uint8_t)((number >> 16) & UINT8_MAX);
buf[6] = (uint8_t)((number >> 8L) & UINT8_MAX);
buf[7] = (uint8_t)((number >> 0L) & UINT8_MAX);
}
bool mpw_push_buf(uint8_t **buffer, size_t *bufferSize, const void *pushBuffer, const size_t pushSize) {
if (!buffer || !bufferSize || !pushBuffer || !pushSize)
return false;
if (*bufferSize == (size_t)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;
*buffer = NULL;
return;
mpw_free( buffer, *bufferSize );
*bufferSize = (size_t)ERR;
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 **buffer, size_t *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 **string, const char *pushString) {
mpw_push_buf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
if (!string || !pushString)
return false;
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 **string, const char *pushFormat, ...) {
if (buffer) {
memset( (void *)buffer, 0, bufferSize );
free( (void *)buffer );
}
va_list args;
va_start( args, pushFormat );
bool success = mpw_string_push( string, mpw_vstr( pushFormat, args ) );
va_end( args );
return success;
}
void mpw_free_string(const char *string) {
bool mpw_push_int(uint8_t **buffer, size_t *bufferSize, const uint32_t pushInt) {
mpw_free( string, strlen( string ) );
uint8_t pushBuf[4 /* 32 / 8 */];
mpw_uint32( pushInt, pushBuf );
return mpw_push_buf( buffer, bufferSize, &pushBuf, sizeof( pushBuf ) );
}
uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize) {
if (!buffer)
return false;
void *newBuffer = realloc( (void *)*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 || !*buffer)
return false;
memset( (void *)*buffer, 0, bufferSize );
free( (void *)*buffer );
*buffer = NULL;
return true;
}
bool __mpw_free_string(const char **string) {
return *string && __mpw_free( (const void **)string, strlen( *string ) );
}
bool __mpw_free_strings(const char **strings, ...) {
bool success = true;
va_list args;
va_start( args, strings );
success &= mpw_free_string( strings );
for (const char **string; (string = va_arg( args, const char ** ));)
success &= mpw_free_string( string );
va_end( args );
return success;
}
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)
@@ -93,114 +175,315 @@ uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_
if (!key)
return NULL;
#if HAS_CPERCIVA
#if MPW_CPERCIVA
if (crypto_scrypt( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) < 0) {
mpw_free( key, keySize );
mpw_free( &key, keySize );
return NULL;
}
#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 );
#elif MPW_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) {
#if HAS_CPERCIVA
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;
#elif HAS_SODIUM
uint8_t *const buffer = malloc( crypto_auth_hmacsha256_BYTES );
if (!buffer)
#if MPW_SODIUM
if (keySize < crypto_generichash_blake2b_KEYBYTES_MIN || keySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
subkeySize < crypto_generichash_blake2b_KEYBYTES_MIN || subkeySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
contextSize < crypto_generichash_blake2b_BYTES_MIN || contextSize > crypto_generichash_blake2b_BYTES_MAX ||
(personal && strlen( personal ) > crypto_generichash_blake2b_PERSONALBYTES)) {
errno = EINVAL;
free( subkey );
return NULL;
}
uint8_t saltBuf[crypto_generichash_blake2b_SALTBYTES];
bzero( saltBuf, sizeof saltBuf );
if (id)
mpw_uint64( id, saltBuf );
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 MPW_CPERCIVA
uint8_t *const mac = malloc( 32 );
if (!mac)
return NULL;
HMAC_SHA256_Buf( key, keySize, message, messageSize, mac );
#elif MPW_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, salt, saltSize ) != 0 ||
crypto_auth_hmacsha256_final( &state, buffer ) != 0) {
mpw_free( buffer, crypto_auth_hmacsha256_BYTES );
crypto_auth_hmacsha256_update( &state, message, messageSize ) != 0 ||
crypto_auth_hmacsha256_final( &state, mac ) != 0) {
mpw_free( &mac, crypto_auth_hmacsha256_BYTES );
return NULL;
}
return buffer;
#else
#error No crypto support for mpw_hmac_sha256.
#endif
return NULL;
return mac;
}
const char *mpw_id_buf(const void *buf, size_t length) {
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_CPERCIVA
#if MPW_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 );
}
#if UNUSED
const char *mpw_hotp(const uint8_t *key, size_t keySize, uint64_t movingFactor, uint8_t digits, uint8_t truncationOffset) {
// Hash the moving factor with the key.
uint8_t counter[8];
mpw_uint64( movingFactor, counter );
uint8_t hash[20];
hmac_sha1( key, keySize, counter, sizeof( counter ), hash );
// Determine the offset to select OTP bytes from.
int offset;
if ((truncationOffset >= 0) && (truncationOffset < (sizeof( hash ) - 4)))
offset = truncationOffset;
else
offset = hash[sizeof( hash ) - 1] & 0xf;
// Select four bytes from the truncation offset.
uint32_t otp = 0U
| ((hash[offset + 0] & 0x7f) << 24)
| ((hash[offset + 1] & 0xff) << 16)
| ((hash[offset + 2] & 0xff) << 8)
| ((hash[offset + 3] & 0xff) << 0);
// Render the OTP as `digits` decimal digits.
otp %= (int)pow(10, digits);
return strdup( mpw_str( "%0*d", digits, otp ) );
}
#endif
MPKeyID mpw_id_buf(const void *buf, size_t length) {
if (!buf)
return "<unset>";
#if MPW_CPERCIVA
uint8_t hash[32];
SHA256_Buf( buf, length, hash );
return mpw_hex( hash, 32 );
#elif HAS_SODIUM
#elif MPW_SODIUM
uint8_t hash[crypto_hash_sha256_BYTES];
crypto_hash_sha256( hash, buf, length );
return mpw_hex( hash, crypto_hash_sha256_BYTES );
#else
#error No crypto support for mpw_id_buf.
#endif
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;
}
const char *mpw_str(const char *format, ...) {
va_list args;
va_start( args, format );
const char *str_str = mpw_vstr( format, args );
va_end( args );
return str_str;
}
const char *mpw_vstr(const char *format, va_list args) {
// TODO: We should find a way to get rid of this shared storage medium.
// TODO: Not thread-safe
static char *str_str;
static size_t str_str_max;
if (!str_str && !(str_str = calloc( str_str_max = 1, sizeof( char ) )))
return NULL;
do {
va_list args_attempt;
va_copy( args_attempt, args );
size_t len = (size_t)vsnprintf( str_str, str_str_max, format, args_attempt );
va_end( args_attempt );
if ((int)len < 0)
return NULL;
if (len < str_str_max)
break;
if (!mpw_realloc( &str_str, &str_str_max, len - str_str_max + 1 ))
return NULL;
} while (true);
return str_str;
}
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;
}
// TODO: We should find a way to get rid of this shared storage medium.
// TODO: Not thread-safe
static char **mpw_hex_buf;
static unsigned int mpw_hex_buf_i;
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 );
for (size_t kH = 0; kH < length; kH++)
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
if (mpw_realloc( &mpw_hex_buf[mpw_hex_buf_i], NULL, length * 2 + 1 ))
for (size_t kH = 0; kH < length; kH++)
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
return mpw_hex_buf[mpw_hex_buf_i];
}
const char *mpw_hex_l(uint32_t number) {
return mpw_hex( &number, sizeof( number ) );
uint8_t buf[4 /* 32 / 8 */];
buf[0] = (uint8_t)((number >> 24) & UINT8_MAX);
buf[1] = (uint8_t)((number >> 16) & UINT8_MAX);
buf[2] = (uint8_t)((number >> 8L) & UINT8_MAX);
buf[3] = (uint8_t)((number >> 0L) & UINT8_MAX);
return mpw_hex( &buf, sizeof( buf ) );
}
#ifdef COLOR
static int putvari;
static char *putvarc = NULL;
static int termsetup;
static int initputvar() {
if (!isatty(STDERR_FILENO))
return 0;
if (putvarc)
free( putvarc );
#if MPW_COLOR
static char *str_tputs;
static int str_tputs_cursor;
static const int str_tputs_max = 256;
static bool mpw_setupterm() {
if (!isatty( STDERR_FILENO ))
return false;
static bool termsetup;
if (!termsetup) {
int status;
if (! (termsetup = (setupterm( NULL, STDERR_FILENO, &status ) == 0 && status == 1))) {
wrn( "Terminal doesn't support color (setupterm errno %d).\n", status );
return 0;
int errret;
if (!(termsetup = (setupterm( NULL, STDERR_FILENO, &errret ) == OK))) {
wrn( "Terminal doesn't support color (setupterm errret %d).\n", errret );
return false;
}
}
putvarc=(char *)calloc(256, sizeof(char));
putvari=0;
return 1;
return true;
}
static int putvar(int c) {
putvarc[putvari++]=c;
return 0;
static int mpw_tputc(int c) {
if (++str_tputs_cursor < str_tputs_max) {
str_tputs[str_tputs_cursor] = (char)c;
return OK;
}
return ERR;
}
static char *mpw_tputs(const char *str, int affcnt) {
if (str_tputs)
mpw_free( &str_tputs, str_tputs_max );
str_tputs = calloc( str_tputs_max, sizeof( char ) );
str_tputs_cursor = -1;
char *result = tputs( str, affcnt, mpw_tputc ) == ERR? NULL: strndup( str_tputs, str_tputs_max );
if (str_tputs)
mpw_free( &str_tputs, str_tputs_max );
return result;
}
#endif
const char *mpw_identicon(const char *fullName, const char *masterPassword) {
@@ -214,21 +497,20 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
};
const uint8_t *identiconSeed = mpw_hmac_sha256( (const uint8_t *)masterPassword, strlen( masterPassword ), (const uint8_t *)fullName, strlen( fullName ) );
const uint8_t *identiconSeed = mpw_hash_hmac_sha256(
(const uint8_t *)masterPassword, strlen( masterPassword ),
(const uint8_t *)fullName, strlen( fullName ) );
if (!identiconSeed)
return NULL;
char *colorString, *resetString;
#ifdef COLOR
if (initputvar()) {
#ifdef MPW_COLOR
if (mpw_setupterm()) {
uint8_t colorIdentifier = (uint8_t)(identiconSeed[4] % 7 + 1);
tputs(tparm(tgetstr("AF", NULL), colorIdentifier), 1, putvar);
colorString = calloc(strlen(putvarc) + 1, sizeof(char));
strcpy(colorString, putvarc);
tputs(tgetstr("me", NULL), 1, putvar);
resetString = calloc(strlen(putvarc) + 1, sizeof(char));
strcpy(resetString, putvarc);
} else
colorString = mpw_tputs( tparm( tgetstr( "AF", NULL ), colorIdentifier ), 1 );
resetString = mpw_tputs( tgetstr( "me", NULL ), 1 );
}
else
#endif
{
colorString = calloc( 1, sizeof( char ) );
@@ -244,9 +526,8 @@ 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 );
mpw_free( &identiconSeed, 32 );
mpw_free_strings( &colorString, &resetString, NULL );
return identicon;
}

View File

@@ -16,99 +16,191 @@
// 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
extern int mpw_verbosity;
#define trc_level 3
#define trc(...) \
if (mpw_verbosity >= 3) \
fprintf( stderr, __VA_ARGS__ )
#define trc_level 3
/** Logging internal state. */
#define trc(...) ({ \
if (mpw_verbosity >= trc_level) \
fprintf( stderr, __VA_ARGS__ ); })
#endif
#ifndef dbg
#define dbg_level 2
#define dbg(...) \
if (mpw_verbosity >= 2) \
fprintf( stderr, __VA_ARGS__ )
#define dbg_level 2
/** Logging state and events interesting when investigating issues. */
#define dbg(...) ({ \
if (mpw_verbosity >= dbg_level) \
fprintf( stderr, __VA_ARGS__ ); })
#endif
#ifndef inf
#define inf_level 1
#define inf(...) \
if (mpw_verbosity >= 1) \
fprintf( stderr, __VA_ARGS__ )
#define inf_level 1
/** User messages. */
#define inf(...) ({ \
if (mpw_verbosity >= inf_level) \
fprintf( stderr, __VA_ARGS__ ); })
#endif
#ifndef wrn
#define wrn_level 0
#define wrn(...) \
if (mpw_verbosity >= 0) \
fprintf( stderr, __VA_ARGS__ )
#define wrn_level 0
/** Recoverable issues and user suggestions. */
#define wrn(...) ({ \
if (mpw_verbosity >= wrn_level) \
fprintf( stderr, __VA_ARGS__ ); })
#endif
#ifndef err
#define err_level -1
#define err(...) \
if (mpw_verbosity >= -1) \
fprintf( stderr, __VA_ARGS__ )
#define err_level -1
/** Unrecoverable issues. */
#define err(...) ({ \
if (mpw_verbosity >= err_level) \
fprintf( stderr, __VA_ARGS__ ); })
#endif
#ifndef ftl
#define ftl_level -2
#define ftl(...) \
do { \
if (mpw_verbosity >= -2) \
fprintf( stderr, __VA_ARGS__ ); \
exit( 2 ); \
} while (0)
#define ftl_level -2
/** Issues that lead to abortion. */
#define ftl(...) ({ \
if (mpw_verbosity >= ftl_level) \
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 OK
#define OK 0
#endif
#ifndef stringify
#define stringify(s) #s
#endif
#ifndef stringify_def
#define stringify_def(s) stringify(s)
#endif
//// Buffers and memory.
/** Write a number to a byte buffer using mpw's endianness (big/network endian). */
void mpw_uint16(const uint16_t number, uint8_t buf[2]);
void mpw_uint32(const uint32_t number, uint8_t buf[4]);
void mpw_uint64(const uint64_t number, uint8_t buf[8]);
/** 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(
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
bool mpw_push_buf(
uint8_t **buffer, size_t *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(
uint8_t **buffer, size_t *const bufferSize, const char *pushString);
bool mpw_push_string(
uint8_t **buffer, size_t *bufferSize, const char *pushString);
/** Push a string onto another string. reallocs the target string and appends the source string. */
bool mpw_string_push(
char **string, const char *pushString);
bool mpw_string_pushf(
char **string, const char *pushFormat, ...);
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
void mpw_push_int(
uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt);
/** Free a buffer after zero'ing its contents. */
void mpw_free(
const void *buffer, const size_t bufferSize);
/** Free a string after zero'ing its contents. */
void mpw_free_string(
const char *string);
bool mpw_push_int(
uint8_t **buffer, size_t *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) \
({ typeof(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_realloc( (const void **)_b, bufferSize, deltaSize ); })
bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize);
/** Free a buffer after zero'ing its contents, then set the reference to NULL. */
#define mpw_free(buffer, bufferSize) \
({ typeof(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_free( (const void **)_b, bufferSize ); })
bool __mpw_free(
const void **buffer, const size_t bufferSize);
/** Free a string after zero'ing its contents, then set the reference to NULL. */
#define mpw_free_string(string) \
({ typeof(string) _s = string; const char *__s = *_s; (void)__s; __mpw_free_string( (const char **)_s ); })
bool __mpw_free_string(
const char **string);
/** Free strings after zero'ing their contents, then set the references to NULL. Terminate the va_list with NULL. */
#define mpw_free_strings(strings, ...) \
({ typeof(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (const char **)_s, __VA_ARGS__ ); })
bool __mpw_free_strings(
const char **strings, ...);
//// 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);
/** Calculate an OTP using RFC-4226.
* @return A newly allocated string containing exactly `digits` decimal OTP digits. */
#if UNUSED
const char *mpw_hotp(
const uint8_t *key, size_t keySize, uint64_t movingFactor, uint8_t digits, uint8_t truncationOffset);
#endif
//// 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, ...);
const char *mpw_vstr(const char *format, va_list args);
/** 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 +209,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

View File

@@ -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#&amp;@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#&amp;@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#&amp;@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">

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
BuildableName = "mpw-bench"
BlueprintName = "mpw-bench"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
BuildableName = "mpw-bench"
BlueprintName = "mpw-bench"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
BuildableName = "mpw-bench"
BlueprintName = "mpw-bench"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
BuildableName = "mpw-bench"
BlueprintName = "mpw-bench"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
BuildableName = "mpw-cli"
BlueprintName = "mpw-cli"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
BuildableName = "mpw-cli"
BlueprintName = "mpw-cli"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
BuildableName = "mpw-cli"
BlueprintName = "mpw-cli"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
BuildableName = "mpw-cli"
BlueprintName = "mpw-cli"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -11,6 +11,7 @@
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : 9223372036854775807,
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : 9223372036854775807,
"3ED8592497DB6A564366943C9AAD5A46341B5076" : 9223372036854775807,
"B38C14663FCFBB7024902D2DB1D013964189DC3B" : 9223372036854775807,
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : 9223372036854775807,
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : 0
},
@@ -23,6 +24,7 @@
"2FE140B36B7D26140DC8D5E5C639DC5900EFCF35" : "MasterPassword\/platform-darwin\/External\/uicolor-utilities\/",
"4DDCFFD91B41F00326AD14553BD66CFD366ABD91" : "MasterPassword\/platform-darwin\/External\/Pearl\/",
"3ED8592497DB6A564366943C9AAD5A46341B5076" : "MasterPassword\/platform-darwin\/External\/AttributedMarkdown\/",
"B38C14663FCFBB7024902D2DB1D013964189DC3B" : "MasterPassword\/platform-darwin\/External\/libjson-c\/",
"81A28796384A028E6C2D47C039DB8B3E5DD6D0FC" : "MasterPassword\/platform-darwin\/External\/libsodium\/",
"F788B28042EDBEF29EFE34687DA79A778C2CC260" : "MasterPassword\/"
},
@@ -70,6 +72,11 @@
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8A15A8EA0B3D0B497C4883425BC74DF995224BB3"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/json-c\/json-c.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B38C14663FCFBB7024902D2DB1D013964189DC3B"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Lyndir\/MasterPassword.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -e
cd "${BASH_SOURCE%/*}/../External/libjson-c"
[[ -e "${prefix=$PWD/libjson-c-ios}/lib/libjson-c.a" ]] && exit
# Prepare
autoreconf -Iautoconf-archive/m4 --verbose --install --symlink 2>&1 | sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /'
rm -rf "${prefix=$PWD/libjson-c-ios}"
mkdir -p "$prefix/lib" \
"${prefix_i386=$prefix/tmp/i386}" \
"${prefix_x86_64=$prefix/tmp/x86_64}" \
"${prefix_armv7=$prefix/tmp/armv7}" \
"${prefix_armv7s=$prefix/tmp/armv7s}" \
"${prefix_arm64=$prefix/tmp/arm64}"
# Targets
(
## ARCH: i386
export SDKROOT="$(xcrun --show-sdk-path --sdk iphonesimulator)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphonesimulator)/usr/bin:$PATH"
export CFLAGS="-arch i386 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g $CFLAGS"
export LDFLAGS="-arch i386 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s clean
./configure --host=i686-apple --disable-shared --prefix="$prefix_i386"
make -j3 install
)
(
## ARCH: x86_64
export SDKROOT="$(xcrun --show-sdk-path --sdk iphonesimulator)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphonesimulator)/usr/bin:$PATH"
export CFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g $CFLAGS"
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s clean
./configure --host=x86_64-apple --disable-shared --prefix="$prefix_x86_64"
make -j3 install
)
(
## ARCH: armv7
export SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
export CFLAGS="-mthumb -arch armv7 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g $CFLAGS"
export LDFLAGS="-mthumb -arch armv7 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s clean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_armv7"
make -j3 install
)
(
## ARCH: armv7s
export SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
export CFLAGS="-mthumb -arch armv7s -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g $CFLAGS"
export LDFLAGS="-mthumb -arch armv7s -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s clean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_armv7s"
make -j3 install
)
(
## ARCH: arm64
export SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
export CFLAGS="-mthumb -arch arm64 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g $CFLAGS"
export LDFLAGS="-mthumb -arch arm64 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s clean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_arm64"
make -j3 install
)
# Merge Binaries
mv -f -- "$prefix_arm64/include" "$prefix/"
lipo -create \
"$prefix_i386/lib/libjson-c.a" \
"$prefix_x86_64/lib/libjson-c.a" \
"$prefix_armv7/lib/libjson-c.a" \
"$prefix_armv7s/lib/libjson-c.a" \
"$prefix_arm64/lib/libjson-c.a" \
-output "$prefix/lib/libjson-c.a"
# Cleanup
rm -rf -- "$prefix/tmp"
make -s really-clean

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e
cd "${BASH_SOURCE%/*}/../External/libjson-c"
[[ -e "${prefix=$PWD/libjson-c-osx}/lib/libjson-c.a" ]] && exit
# Prepare
autoreconf -Iautoconf-archive/m4 --verbose --install --symlink 2>&1 | sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /'
rm -rf "${prefix=$PWD/libjson-c-osx}"
mkdir -p "$prefix"
# Targets
(
## ARCH: x86_64
export SDKROOT="$(xcrun --show-sdk-path --sdk macosx)"
export PATH="$(xcrun --show-sdk-platform-path --sdk macosx)/usr/bin:$PATH"
export CFLAGS="-arch x86_64 -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} -O2 -g $CFLAGS" # -flto
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} $LDFLAGS" # -flto
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s clean
./configure --disable-shared --prefix="$prefix"
make -j3 check
make -j3 install
)
# Cleanup
make -s really-clean

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -e
cd "${BASH_SOURCE%/*}/../External/libsodium"
[[ -e "${prefix=$PWD/libsodium-ios}/lib/libsodium.a" ]] && exit
# Prepare
autoreconf --verbose --install --symlink 2>&1 | sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /'
rm -rf "${prefix=$PWD/libsodium-ios}"
mkdir -p "$prefix/lib" \
"${prefix_i386=$prefix/tmp/i386}" \
"${prefix_x86_64=$prefix/tmp/x86_64}" \
"${prefix_armv7=$prefix/tmp/armv7}" \
"${prefix_armv7s=$prefix/tmp/armv7s}" \
"${prefix_arm64=$prefix/tmp/arm64}"
# Targets
(
## ARCH: i386
export SDKROOT="$(xcrun --show-sdk-path --sdk iphonesimulator)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphonesimulator)/usr/bin:$PATH"
export CFLAGS="-arch i386 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g -flto $CFLAGS"
export LDFLAGS="-arch i386 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean
./configure --host=i686-apple --disable-shared --prefix="$prefix_i386"
make -j3 install
)
(
## ARCH: x86_64
export SDKROOT="$(xcrun --show-sdk-path --sdk iphonesimulator)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphonesimulator)/usr/bin:$PATH"
export CFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g -flto $CFLAGS"
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --disable-shared --prefix="$prefix_x86_64"
make -j3 install
)
(
## ARCH: armv7
export SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
export CFLAGS="-mthumb -arch armv7 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g -flto $CFLAGS"
export LDFLAGS="-mthumb -arch armv7 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_armv7"
make -j3 install
)
(
## ARCH: armv7s
export SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
export CFLAGS="-mthumb -arch armv7s -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g -flto $CFLAGS"
export LDFLAGS="-mthumb -arch armv7s -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_armv7s"
make -j3 install
)
(
## ARCH: arm64
export SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
export CFLAGS="-mthumb -arch arm64 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -O2 -g -flto $CFLAGS"
export LDFLAGS="-mthumb -arch arm64 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_arm64"
make -j3 install
)
# Merge Binaries
mv -f -- "$prefix_arm64/include" "$prefix/"
lipo -create \
"$prefix_i386/lib/libsodium.a" \
"$prefix_x86_64/lib/libsodium.a" \
"$prefix_armv7/lib/libsodium.a" \
"$prefix_armv7s/lib/libsodium.a" \
"$prefix_arm64/lib/libsodium.a" \
-output "$prefix/lib/libsodium.a"
# Cleanup
rm -rf -- "$prefix/tmp"
make -s distclean

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -e
cd "${BASH_SOURCE%/*}/../External/libsodium"
[[ -e "${prefix=$PWD/libsodium-osx}/lib/libsodium.a" ]] && exit
# Inspired by libsodium/dist-build/osx.sh
# Prepare
autoreconf --verbose --install --symlink 2>&1 | sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /'
rm -rf "${prefix=$PWD/libsodium-osx}"
mkdir -p "$prefix"
# Targets
(
## ARCH: x86_64
export SDKROOT="$(xcrun --show-sdk-path --sdk macosx)"
export PATH="$(xcrun --show-sdk-platform-path --sdk macosx)/usr/bin:$PATH"
export CFLAGS="-arch x86_64 -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} -O2 -g -flto $CFLAGS"
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean
./configure --disable-shared --prefix="$prefix"
make -j3 check
make -j3 install
)
# Cleanup
make -s distclean

View File

@@ -41,9 +41,9 @@ case $PLATFORM_NAME in
*) ftl 'ERROR: Unknown platform: %s.' "$PLATFORM_NAME"; exit 1 ;;
esac
description=$(git describe --always --dirty --long --match "*-$platform-*")
description=$(git describe --always --dirty --long --match "*-$platform*")
version=${description%-g*} build=${version##*-} version=${version%-$build}
version=${version//-*-/.} release=${version%%-*} commit=${description##*-g}
version=${version//-$platform/} version=${version//-/.} commit=${description##*-g}
addPlistWithKey GITDescription string "$description"
setPlistWithKey CFBundleVersion "$(hr "${version%%.*}" 14).${version#*.}"
@@ -55,14 +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.'; }
[[ $build == 0 ]] || \
{ passed=0; err 'ERROR: Commit is not tagged for release, first tag accordingly.'; }
[[ -r "$crashlyticsPlist" && $(PlistBuddy -c "Print :'API Key'" "$crashlyticsPlist" 2>/dev/null) ]] || \
{ passed=0; err 'ERROR: Cannot release: Crashlytics API key is missing.'; }
[[ -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

View File

@@ -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,51 +49,47 @@ 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;
- (NSString *)shortNameOfType:(MPSiteType)type;
- (NSString *)classNameOfType:(MPSiteType)type;
- (Class)classOfType:(MPSiteType)type;
- (NSString *)nameOfType:(MPResultType)type;
- (NSString *)shortNameOfType:(MPResultType)type;
- (NSString *)classNameOfType:(MPResultType)type;
- (Class)classOfType:(MPResultType)type;
- (NSArray *)allTypes;
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
- (MPSiteType)nextType:(MPSiteType)type;
- (MPSiteType)previousType:(MPSiteType)type;
- (NSArray *)allTypesStartingWith:(MPResultType)startingType;
- (MPResultType)defaultType;
- (MPResultType)nextType:(MPResultType)type;
- (MPResultType)previousType:(MPResultType)type;
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
usingKey:(MPKey *)key;
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key;
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
withCounter:(MPCounterValue)counter usingKey:(MPKey *)key;
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
withCounter:(MPCounterValue)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key;
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key;
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey;
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock;
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site
usingKey:(MPKey *)siteKey;
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (void)importPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
@end

View File

@@ -29,7 +29,7 @@
#define CRACKING_PER_SECOND 2495000000UL
#define CRACKING_PRICE 350
NSOperationQueue *_mpwQueue = nil;
static NSOperationQueue *_mpwQueue = nil;
@implementation MPAlgorithmV0
@@ -93,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;
}
@@ -128,143 +128,152 @@ 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 {
- (NSString *)nameOfType:(MPResultType)type {
if (!type)
return nil;
switch (type) {
case MPSiteTypeGeneratedMaximum:
case MPResultTypeTemplateMaximum:
return @"Maximum Security Password";
case MPSiteTypeGeneratedLong:
case MPResultTypeTemplateLong:
return @"Long Password";
case MPSiteTypeGeneratedMedium:
case MPResultTypeTemplateMedium:
return @"Medium Password";
case MPSiteTypeGeneratedBasic:
case MPResultTypeTemplateBasic:
return @"Basic Password";
case MPSiteTypeGeneratedShort:
case MPResultTypeTemplateShort:
return @"Short Password";
case MPSiteTypeGeneratedPIN:
case MPResultTypeTemplatePIN:
return @"PIN";
case MPSiteTypeGeneratedName:
case MPResultTypeTemplateName:
return @"Name";
case MPSiteTypeGeneratedPhrase:
case MPResultTypeTemplatePhrase:
return @"Phrase";
case MPSiteTypeStoredPersonal:
case MPResultTypeStatefulPersonal:
return @"Personal Password";
case MPSiteTypeStoredDevicePrivate:
case MPResultTypeStatefulDevice:
return @"Device Private Password";
case MPResultTypeDeriveKey:
return @"Crypto Key";
}
Throw( @"Type not supported: %lu", (long)type );
}
- (NSString *)shortNameOfType:(MPSiteType)type {
- (NSString *)shortNameOfType:(MPResultType)type {
if (!type)
return nil;
switch (type) {
case MPSiteTypeGeneratedMaximum:
case MPResultTypeTemplateMaximum:
return @"Maximum";
case MPSiteTypeGeneratedLong:
case MPResultTypeTemplateLong:
return @"Long";
case MPSiteTypeGeneratedMedium:
case MPResultTypeTemplateMedium:
return @"Medium";
case MPSiteTypeGeneratedBasic:
case MPResultTypeTemplateBasic:
return @"Basic";
case MPSiteTypeGeneratedShort:
case MPResultTypeTemplateShort:
return @"Short";
case MPSiteTypeGeneratedPIN:
case MPResultTypeTemplatePIN:
return @"PIN";
case MPSiteTypeGeneratedName:
case MPResultTypeTemplateName:
return @"Name";
case MPSiteTypeGeneratedPhrase:
case MPResultTypeTemplatePhrase:
return @"Phrase";
case MPSiteTypeStoredPersonal:
case MPResultTypeStatefulPersonal:
return @"Personal";
case MPSiteTypeStoredDevicePrivate:
case MPResultTypeStatefulDevice:
return @"Device";
case MPResultTypeDeriveKey:
return @"Key";
}
Throw( @"Type not supported: %lu", (long)type );
}
- (NSString *)classNameOfType:(MPSiteType)type {
- (NSString *)classNameOfType:(MPResultType)type {
return NSStringFromClass( [self classOfType:type] );
}
- (Class)classOfType:(MPSiteType)type {
- (Class)classOfType:(MPResultType)type {
if (!type)
Throw( @"No type given." );
switch (type) {
case MPSiteTypeGeneratedMaximum:
case MPResultTypeTemplateMaximum:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedLong:
case MPResultTypeTemplateLong:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedMedium:
case MPResultTypeTemplateMedium:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedBasic:
case MPResultTypeTemplateBasic:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedShort:
case MPResultTypeTemplateShort:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedPIN:
case MPResultTypeTemplatePIN:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedName:
case MPResultTypeTemplateName:
return [MPGeneratedSiteEntity class];
case MPSiteTypeGeneratedPhrase:
case MPResultTypeTemplatePhrase:
return [MPGeneratedSiteEntity class];
case MPSiteTypeStoredPersonal:
case MPResultTypeStatefulPersonal:
return [MPStoredSiteEntity class];
case MPSiteTypeStoredDevicePrivate:
case MPResultTypeStatefulDevice:
return [MPStoredSiteEntity class];
case MPResultTypeDeriveKey:
break;
}
Throw( @"Type not supported: %lu", (long)type );
@@ -272,13 +281,13 @@ NSOperationQueue *_mpwQueue = nil;
- (NSArray *)allTypes {
return [self allTypesStartingWith:MPSiteTypeGeneratedPhrase];
return [self allTypesStartingWith:MPResultTypeTemplatePhrase];
}
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
- (NSArray *)allTypesStartingWith:(MPResultType)startingType {
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
MPSiteType currentType = startingType;
MPResultType currentType = startingType;
do {
[allTypes addObject:@(currentType)];
} while ((currentType = [self nextType:currentType]) != startingType);
@@ -286,194 +295,173 @@ NSOperationQueue *_mpwQueue = nil;
return allTypes;
}
- (MPSiteType)nextType:(MPSiteType)type {
- (MPResultType)defaultType {
switch (type) {
case MPSiteTypeGeneratedPhrase:
return MPSiteTypeGeneratedName;
case MPSiteTypeGeneratedName:
return MPSiteTypeGeneratedMaximum;
case MPSiteTypeGeneratedMaximum:
return MPSiteTypeGeneratedLong;
case MPSiteTypeGeneratedLong:
return MPSiteTypeGeneratedMedium;
case MPSiteTypeGeneratedMedium:
return MPSiteTypeGeneratedBasic;
case MPSiteTypeGeneratedBasic:
return MPSiteTypeGeneratedShort;
case MPSiteTypeGeneratedShort:
return MPSiteTypeGeneratedPIN;
case MPSiteTypeGeneratedPIN:
return MPSiteTypeStoredPersonal;
case MPSiteTypeStoredPersonal:
return MPSiteTypeStoredDevicePrivate;
case MPSiteTypeStoredDevicePrivate:
return MPSiteTypeGeneratedPhrase;
default:
return MPSiteTypeGeneratedLong;
}
return MPResultTypeTemplateLong;
}
- (MPSiteType)previousType:(MPSiteType)type {
- (MPResultType)nextType:(MPResultType)type {
MPSiteType previousType = type, nextType = type;
switch (type) {
case MPResultTypeTemplatePhrase:
return MPResultTypeTemplateName;
case MPResultTypeTemplateName:
return MPResultTypeTemplateMaximum;
case MPResultTypeTemplateMaximum:
return MPResultTypeTemplateLong;
case MPResultTypeTemplateLong:
return MPResultTypeTemplateMedium;
case MPResultTypeTemplateMedium:
return MPResultTypeTemplateBasic;
case MPResultTypeTemplateBasic:
return MPResultTypeTemplateShort;
case MPResultTypeTemplateShort:
return MPResultTypeTemplatePIN;
case MPResultTypeTemplatePIN:
return MPResultTypeStatefulPersonal;
case MPResultTypeStatefulPersonal:
return MPResultTypeStatefulDevice;
case MPResultTypeStatefulDevice:
return MPResultTypeTemplatePhrase;
case MPResultTypeDeriveKey:
break;
}
return [self defaultType];
}
- (MPResultType)previousType:(MPResultType)type {
MPResultType previousType = type, nextType = type;
while ((nextType = [self nextType:nextType]) != type)
previousType = nextType;
return previousType;
}
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
variant:MPSiteVariantLogin context:nil usingKey:key];
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplateName parameter:nil withCounter:MPCounterValueInitial
variant:MPKeyPurposeIdentification context:nil usingKey:key];
}
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
usingKey:(MPKey *)key {
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
withCounter:(MPCounterValue)counter usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
variant:MPSiteVariantPassword context:nil usingKey:key];
return [self mpwResultForSiteNamed:name ofType:type parameter:nil withCounter:counter
variant:MPKeyPurposeAuthentication context:nil usingKey:key];
}
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
variant:MPSiteVariantAnswer context:question usingKey:key];
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplatePhrase parameter:nil withCounter:MPCounterValueInitial
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 {
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
withCounter:(MPCounterValue)counter variant:(MPKeyPurpose)purpose context:(NSString *)context
usingKey:(MPKey *)key {
__block NSString *content;
__block NSString *result = nil;
[self mpw_perform:^{
char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes,
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
if (contentBytes) {
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
mpw_free_string( contentBytes );
char const *resultBytes = mpw_siteResult( [key keyForAlgorithm:self],
name.UTF8String, counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] );
if (resultBytes) {
result = [NSString stringWithCString:resultBytes encoding:NSUTF8StringEncoding];
mpw_free_string( &resultBytes );
}
}];
return content;
return result;
}
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
- (BOOL)savePassword:(NSString *)plainText toSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
return nil;
}
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
return [self decryptContent:site.contentObject usingKey:key];
}
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
wrn( @"Cannot save content to site with generated type %lu.", (long)site.type );
return NO;
}
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:encryptionKey padding:YES];
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
return NO;
((MPStoredSiteEntity *)site).contentObject = encryptedContent;
return YES;
}
case MPSiteTypeStoredDevicePrivate: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:encryptionKey padding:YES];
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
if (!encryptedContent)
[PearlKeyChain deleteItemForQuery:siteQuery];
else
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
(__bridge id)kSecValueData : encryptedContent,
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
#endif
}];
((MPStoredSiteEntity *)site).contentObject = nil;
return YES;
}
if (!(site.type & MPResultTypeClassStateful)) {
wrn( @"Can only save content to site with a stateful type: %lu.", (long)site.type );
return NO;
}
Throw( @"Unsupported type: %ld", (long)site.type );
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
return NO;
}
__block NSData *state = nil;
if (plainText)
[self mpw_perform:^{
char const *stateBytes = mpw_siteState( [key keyForAlgorithm:self], site.name.UTF8String,
MPCounterValueInitial, MPKeyPurposeAuthentication, NULL, site.type, plainText.UTF8String, [self version] );
if (stateBytes) {
state = [[NSString stringWithCString:stateBytes encoding:NSUTF8StringEncoding] decodeBase64];
mpw_free_string( &stateBytes );
}
}];
NSDictionary *siteQuery = [self queryForSite:site];
if (!state)
[PearlKeyChain deleteItemForQuery:siteQuery];
else
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
(__bridge id)kSecValueData: state,
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible:
site.type & MPSiteFeatureDevicePrivate? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
: (__bridge id)kSecAttrAccessibleWhenUnlocked,
#endif
}];
((MPStoredSiteEntity *)site).contentObject = nil;
return YES;
}
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
return PearlAwait( ^(void (^setResult)(id)) {
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
[self resolveLoginForSite:site usingKey:key result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
return PearlAwait( ^(void (^setResult)(id)) {
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
[self resolvePasswordForSite:site usingKey:key result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
return PearlAwait( ^(void (^setResult)(id)) {
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
[self resolveAnswerForSite:site usingKey:key result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key {
return PearlAwait( ^(void (^setResult)(id)) {
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
[self resolveAnswerForQuestion:question usingKey:key result:^(NSString *result_) {
setResult( result_ );
}];
} );
}
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
NSString *loginName = site.loginName;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!siteKey)
else if (!key)
err( @"Missing key." );
else
algorithm = site.algorithm;
@@ -482,244 +470,144 @@ NSOperationQueue *_mpwQueue = nil;
resultBlock( loginName );
else
PearlNotMainQueue( ^{
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
resultBlock( [algorithm mpwLoginForSiteNamed:name usingKey:key] );
} );
}
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
MPResultType type = site.type;
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!key)
err( @"Missing key." );
else
algorithm = site.algorithm;
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
case MPResultTypeTemplateMaximum:
case MPResultTypeTemplateLong:
case MPResultTypeTemplateMedium:
case MPResultTypeTemplateBasic:
case MPResultTypeTemplateShort:
case MPResultTypeTemplatePIN:
case MPResultTypeTemplateName:
case MPResultTypeTemplatePhrase: {
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
NSString *name = site.name;
MPSiteType type = site.type;
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!siteKey)
err( @"Missing key." );
else
algorithm = site.algorithm;
MPCounterValue counter = ((MPGeneratedSiteEntity *)site).counter;
PearlNotMainQueue( ^{
resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
resultBlock( [algorithm mpwTemplateForSiteNamed:name ofType:type withCounter:counter usingKey:key] );
} );
break;
}
case MPSiteTypeStoredPersonal: {
case MPResultTypeStatefulPersonal:
case MPResultTypeStatefulDevice: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
NSDictionary *siteQuery = [self queryForSite:site];
NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
state = state?: ((MPStoredSiteEntity *)site).contentObject;
PearlNotMainQueue( ^{
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
resultBlock( [algorithm mpwResultForSiteNamed:name ofType:type parameter:[state encodeBase64]
withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
usingKey:key] );
} );
break;
}
case MPSiteTypeStoredDevicePrivate: {
NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]],
@"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type,
[site class] );
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
PearlNotMainQueue( ^{
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
} );
case MPResultTypeDeriveKey:
break;
}
}
Throw( @"Type not supported: %lu", (long)type );
}
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!siteKey)
else if (!key)
err( @"Missing key." );
else
algorithm = site.algorithm;
PearlNotMainQueue( ^{
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:nil usingKey:key] );
} );
}
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key
result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
NSAssert( [[key keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
@"Site does not belong to current user." );
NSString *name = question.site.name;
NSString *keyword = question.keyword;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!siteKey)
else if (!key)
err( @"Missing key." );
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
algorithm = question.site.algorithm;
PearlNotMainQueue( ^{
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:keyword usingKey:key] );
} );
}
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
- (void)importPassword:(NSString *)cipherText protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase:
break;
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]])
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
else {
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
[self importClearTextPassword:clearContent intoSite:site usingKey:siteKey];
}
break;
}
case MPSiteTypeStoredDevicePrivate:
break;
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (cipherText && cipherText.length && site.type & MPResultTypeClassStateful) {
NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:site.type parameter:cipherText
withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
usingKey:importKey];
if (plainText)
[self savePassword:plainText toSite:site usingKey:key];
}
}
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
- (NSDictionary *)queryForSite:(MPSiteEntity *)site {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase:
break;
case MPSiteTypeStoredPersonal: {
[self savePassword:clearContent toSite:site usingKey:siteKey];
break;
}
case MPSiteTypeStoredDevicePrivate:
break;
}
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:@{
(__bridge id)kSecAttrService: site.type & MPSiteFeatureDevicePrivate? @"DevicePrivate": @"Private",
(__bridge id)kSecAttrAccount: site.name
} matches:nil];
}
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (!(site.type & MPSiteFeatureExportContent))
return nil;
NSString *result = nil;
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
result = nil;
break;
}
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
result = [((MPStoredSiteEntity *)site).contentObject encodeBase64];
break;
}
case MPSiteTypeStoredDevicePrivate: {
result = nil;
break;
}
}
return result;
NSDictionary *siteQuery = [self queryForSite:site];
NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
return [state?: ((MPStoredSiteEntity *)site).contentObject encodeBase64];
}
- (BOOL)migrateExplicitly:(BOOL)explicit {
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker {
return NO;
}
- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:@{
(__bridge id)kSecAttrService: @"DevicePrivate",
(__bridge id)kSecAttrAccount: name
}
matches:nil];
}
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
if (!key)
return nil;
NSData *decryptedContent = nil;
if ([encryptedContent length]) {
NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
}
if (!decryptedContent)
return nil;
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
if (!(type & MPSiteTypeClassGenerated))
if (!(type & MPResultTypeClassTemplate))
return NO;
size_t count = 0;
const char **templates = mpw_templatesForType( type, &count );

View File

@@ -33,7 +33,7 @@
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated) {
if (site.type & MPResultTypeClassTemplate) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;
return NO;

View File

@@ -33,7 +33,7 @@
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
if (site.type & MPResultTypeClassTemplate && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;
return NO;

View File

@@ -33,7 +33,7 @@
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated &&
if (site.type & MPResultTypeClassTemplate &&
site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;

View File

@@ -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;

View File

@@ -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];
payment.quantity = quantity;
[[self paymentQueue] addPayment:payment];
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

View File

@@ -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,17 +38,17 @@ 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
attributes:@{
(__bridge id)kSecAttrService : @"Saved Master Password",
(__bridge id)kSecAttrAccount : user.name?: @"",
(__bridge id)kSecAttrAccessControl : accessControl,
(__bridge id)kSecUseAuthenticationUI : (__bridge id)kSecUseAuthenticationUIAllow,
(__bridge id)kSecUseOperationPrompt :
(__bridge id)kSecAttrService : @"Saved Master Password",
(__bridge id)kSecAttrAccount : user.name?: @"",
(__bridge id)kSecAttrAccessControl : accessControl,
(__bridge id)kSecUseAuthenticationUI: (__bridge id)kSecUseAuthenticationUIAllow,
(__bridge id)kSecUseOperationPrompt :
strf( @"Access %@'s master password.", user.name ),
}
matches:nil];
@@ -59,10 +60,10 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:@{
(__bridge id)kSecAttrService: @"Saved Master Password",
(__bridge id)kSecAttrAccount: user.name?: @"",
(__bridge id)kSecAttrService : @"Saved Master Password",
(__bridge id)kSecAttrAccount : user.name?: @"",
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible : (__bridge id)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly?: kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
(__bridge id)kSecAttrAccessible: (__bridge id)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly?: kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
#endif
}
matches:nil];
@@ -71,7 +72,7 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
MPKeyOrigin keyOrigin;
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
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:
@@ -94,20 +95,21 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
- (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 );
@@ -177,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 );
@@ -198,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
}
}
@@ -235,9 +248,9 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
#endif
for (MPSiteEntity *site in user.sites) {
if (site.type & MPSiteTypeClassStored) {
if (site.type & MPResultTypeClassStateful) {
NSString *content;
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
while (!(content = [site.algorithm resolvePasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
NSString *masterPassword = nil;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -20,14 +20,6 @@
#import "MPFixable.h"
typedef NS_ENUM( NSUInteger, MPImportResult ) {
MPImportResultSuccess,
MPImportResultCancelled,
MPImportResultInvalidPassword,
MPImportResultMalformedInput,
MPImportResultInternalError,
};
@interface MPAppDelegate_Shared(Store)
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
@@ -35,16 +27,20 @@ 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;
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type;
- (MPImportResult)importSites:(NSString *)importedSitesString
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type;
- (void)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
result:(void ( ^ )(NSError *error))resultBlock;
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock;
@end

View File

@@ -17,6 +17,8 @@
//==============================================================================
#import "MPAppDelegate_Store.h"
#import "mpw-marshall.h"
#import "mpw-util.h"
#if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
@@ -131,6 +133,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,19 +217,21 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
[mainManagedObjectContext performBlock:^{
@try {
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
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 *mainContext, NSNotification *note) {
[mainContext performBlock:^{
@try {
[mainContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
// Create a new store coordinator.
@@ -216,7 +239,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 +248,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 +257,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
#if TARGET_OS_IPHONE
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#else
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, NSApp, [NSOperationQueue mainQueue],
#endif
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
[self.mainManagedObjectContext saveToStore];
} );
#endif
// Perform a data sanity check on the newly loaded store to find and fix any issues.
if ([[MPConfig get].checkInconsistency boolValue])
@@ -265,10 +291,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 +312,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 +337,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 +380,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 +427,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 {
@@ -425,7 +490,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return;
}
MPSiteType type = activeUser.defaultType;
MPResultType type = activeUser.defaultType;
id<MPAlgorithm> algorithm = MPAlgorithmDefault;
Class entityType = [algorithm classOfType:type];
@@ -436,17 +501,13 @@ 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 );
}];
}
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type {
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type {
if (site.type == type)
return site;
@@ -468,376 +529,225 @@ 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;
}
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
- (void)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
result:(void ( ^ )(NSError *error))resultBlock {
NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
__block MPImportResult result = MPImportResultCancelled;
do {
if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword
saveInContext:context];
NSError *error = [self importSites:importData askImportPassword:importPassword askUserPassword:userPassword
saveInContext:context];
PearlMainQueue( ^{
resultBlock( error );
} );
}])
break;
usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
} while (YES);
return result;
}
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword
saveInContext:(NSManagedObjectContext *)context {
- (NSError *)importSites:(NSString *)importData
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
askUserPassword:(NSString *( ^ )(NSString *userName))askUserPassword
saveInContext:(NSManagedObjectContext *)context {
// Compile patterns.
static NSRegularExpression *headerPattern;
static NSArray *sitePatterns;
NSError *error = nil;
if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc]
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:(NSRegularExpressionOptions)0 error:&error];
if (error) {
err( @"Error loading the header pattern: %@", [error fullDescription] );
return MPImportResultInternalError;
// Read metadata for the import file.
MPMarshallInfo *info = mpw_marshall_read_info( importData.UTF8String );
if (info->format == MPMarshallFormatNone)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
@"type" : @(MPMarshallErrorFormat),
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
}]), @"While importing sites." );
// Get master password for import file.
MPKey *importKey;
NSString *importMasterPassword;
do {
importMasterPassword = askImportPassword( @(info->fullName) );
if (!importMasterPassword) {
inf( @"Import cancelled." );
mpw_marshal_info_free( &info );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
}
}
if (!sitePatterns) {
sitePatterns = @[
[[NSRegularExpression alloc] // Format 0
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error],
[[NSRegularExpression alloc] // Format 1
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error]
];
if (error) {
err( @"Error loading the site patterns: %@", [error fullDescription] );
return MPImportResultInternalError;
}
}
importKey = [[MPKey alloc] initForFullName:@(info->fullName) withMasterPassword:importMasterPassword];
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( info->algorithm )] encodeHex]
caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame);
// Parse import data.
inf( @"Importing sites." );
__block MPUserEntity *user = nil;
id<MPAlgorithm> importAlgorithm = nil;
NSUInteger importFormat = 0;
NSUInteger importAvatar = NSNotFound;
NSString *importBundleVersion = nil, *importUserName = nil;
NSData *importKeyID = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *sitesToDelete = [NSMutableSet set];
NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header
if (!headerStarted) {
if ([importedSiteLine isEqualToString:@"##"])
headerStarted = YES;
continue;
}
if (headerEnded)
continue;
if ([importedSiteLine isEqualToString:@"##"]) {
headerEnded = YES;
continue;
MPMarshallError importError = { .type = MPMarshallSuccess };
MPMarshalledUser *importUser = mpw_marshall_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError );
mpw_marshal_info_free( &info );
@try {
if (!importUser || importError.type != MPMarshallSuccess)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
@"type" : @(importError.type),
NSLocalizedDescriptionKey: @(importError.description),
}]), @"While importing sites." );
// Find an existing user to update.
NSError *error = nil;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", @(importUser->fullName)];
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users)
return MPError( error, @"While looking for user: %@.", @(importUser->fullName) );
if ([users count] > 1)
return MPMakeError( @"While looking for user: %@, found more than one: %zu",
@(importUser->fullName), (size_t)[users count] );
// Get master key for user.
MPUserEntity *user = [users lastObject];
MPKey *userKey = importKey;
while (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) {
NSString *userMasterPassword = askUserPassword( user.name );
if (!userMasterPassword) {
inf( @"Import cancelled." );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
}
// Header
if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid header format in line: %@", importedSiteLine );
return MPImportResultMalformedInput;
}
NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
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] );
return MPImportResultInternalError;
}
if ([users count] > 1) {
err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] );
return MPImportResultInternalError;
}
user = [users lastObject];
dbg( @"Existing user? %@", [user debugDescription] );
}
if ([headerName isEqualToString:@"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:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
}
continue;
}
if (!headerEnded)
continue;
if (![importUserName length])
return MPImportResultMalformedInput;
if (![importedSiteLine length])
continue;
// Site
NSRegularExpression *sitePattern = sitePatterns[importFormat];
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid site format in line: %@", importedSiteLine );
return MPImportResultMalformedInput;
}
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent;
switch (importFormat) {
case 0:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = @"";
loginName = @"";
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
break;
case 1:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
if ([counter length])
counter = [counter substringFromIndex:1]; // Strip the leading colon.
loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]];
break;
default:
err( @"Unexpected import format: %lu", (unsigned long)importFormat );
return MPImportResultInternalError;
userKey = [[MPKey alloc] initForFullName:@(importUser->fullName) withMasterPassword:userMasterPassword];
}
// Find existing site.
if (user) {
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] );
return MPImportResultInternalError;
}
if ([existingSites count]) {
dbg( @"Existing sites: %@", existingSites );
[sitesToDelete addObjectsFromArray:existingSites];
}
// Update or create user.
if (!user) {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = @(importUser->fullName);
}
[importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
}
// Ask for confirmation to import these sites and the master password of the user.
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count],
(unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] );
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count],
[sitesToDelete count] );
if (!userMasterPassword) {
inf( @"Import cancelled." );
return MPImportResultCancelled;
}
MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword];
if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID])
return MPImportResultInvalidPassword;
__block MPKey *importKey = userKey;
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )];
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
return MPImportResultInvalidPassword;
// Delete existing sites.
if (sitesToDelete.count)
[sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] );
[context deleteObject:obj];
}];
// Make sure there is a user.
if (user) {
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Updating User: %@", [user debugDescription] );
}
else {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = importUserName;
user.algorithm = MPAlgorithmDefault;
user.algorithm = MPAlgorithmForVersion( importUser->algorithm );
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created User: %@", [user debugDescription] );
}
user.avatar = importUser->avatar;
user.defaultType = importUser->defaultType;
user.lastUsed = [NSDate dateWithTimeIntervalSince1970:MAX( user.lastUsed.timeIntervalSince1970, importUser->lastUsed )];
dbg( @"Importing user: %@", [user debugDescription] );
// Import new sites.
for (NSArray *siteElements in importedSiteSites) {
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
MPSiteType type = (MPSiteType)[siteElements[2] integerValue];
MPAlgorithmVersion version = (MPAlgorithmVersion)[siteElements[3] integerValue];
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
NSString *siteName = siteElements[6];
NSString *exportContent = siteElements[7];
// Update or create sites.
for (size_t s = 0; s < importUser->sites_count; ++s) {
MPMarshalledSite *importSite = &importUser->sites[s];
// Create new site.
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version );
Class entityType = [algorithm classOfType:type];
if (!entityType) {
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
return MPImportResultInternalError;
// Find an existing site to update.
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", @(importSite->name), user];
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
if (!existingSites)
return MPError( error, @"Lookup of existing sites failed for site: %@, user: %@", @(importSite->name), user.userID );
if ([existingSites count])
// Update existing site.
for (MPSiteEntity *site in existingSites) {
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
dbg( @"Updated site: %@", [site debugDescription] );
}
else {
// Create new site.
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( importSite->algorithm );
Class entityType = [algorithm classOfType:importSite->type];
if (!entityType)
return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->name), (long)importSite->type );
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.user = user;
[self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
dbg( @"Created site: %@", [site debugDescription] );
}
}
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.name = siteName;
site.loginName = loginName;
site.user = user;
site.type = type;
site.uses = uses;
site.lastUsed = lastUsed;
site.algorithm = algorithm;
if ([exportContent length]) {
if (clearText)
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
else
[site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey];
}
if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound)
((MPGeneratedSiteEntity *)site).counter = counter;
dbg( @"Created Site: %@", [site debugDescription] );
if (![context saveToStore])
return MPMakeError( @"Failed saving imported changes." );
inf( @"Import completed successfully." );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey: user
}];
return nil;
}
@finally {
mpw_marshal_free( &importUser );
}
if (![context saveToStore])
return MPImportResultInternalError;
inf( @"Import completed successfully." );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey: user
}];
return MPImportResultSuccess;
}
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
- (void)importSite:(const MPMarshalledSite *)importSite protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site
usingKey:(MPKey *)userKey {
MPUserEntity *activeUser = [self activeUserForMainThread];
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
site.name = @(importSite->name);
if (importSite->content)
[site.algorithm importPassword:@(importSite->content) protectedByKey:importKey intoSite:site usingKey:userKey];
site.type = importSite->type;
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
((MPGeneratedSiteEntity *)site).counter = importSite->counter;
site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
site.loginName = importSite->loginContent? @(importSite->loginContent): nil;
site.loginGenerated = importSite->loginType & MPResultTypeClassTemplate;
site.url = importSite->url? @(importSite->url): nil;
site.uses = importSite->uses;
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
}
// 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"];
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock {
// 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;
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user = [self activeUserInContext:context];
NSString *masterPassword = askImportPassword( user.name );
// Generated-specific
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)site).counter;
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
MPMarshalledUser *exportUser = mpw_marshall_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version );
exportUser->redacted = !revealPasswords;
exportUser->avatar = (unsigned int)user.avatar;
exportUser->defaultType = user.defaultType;
exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970;
for (MPSiteEntity *site in user.sites) {
MPCounterValue counter = MPCounterValueInitial;
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)site).counter;
NSString *content = revealPasswords
? [site.algorithm exportPasswordForSite:site usingKey:self.key]
: [site.algorithm resolvePasswordForSite:site usingKey:self.key];
// 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];
MPMarshalledSite *exportSite = mpw_marshall_site( exportUser,
site.name.UTF8String, site.type, counter, site.algorithm.version );
exportSite->content = content.UTF8String;
exportSite->loginContent = site.loginName.UTF8String;
exportSite->loginType = site.loginGenerated? MPResultTypeTemplateName: MPResultTypeStatefulPersonal;
exportSite->url = site.url.UTF8String;
exportSite->uses = (unsigned int)site.uses;
exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
for (MPSiteQuestionEntity *siteQuestion in site.questions)
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
}
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];
}
char *export = NULL;
MPMarshallError exportError = (MPMarshallError){ .type= MPMarshallSuccess };
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser, &exportError );
NSString *mpsites = nil;
if (export && exportError.type == MPMarshallSuccess)
mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
mpw_free_string( &export );
return export;
resultBlock( mpsites, exportError.type == MPMarshallSuccess? nil:
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
@"type" : @(exportError.type),
NSLocalizedDescriptionKey: @(exportError.description),
}] );
}];
}
@end

View File

@@ -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"

View File

@@ -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,

View File

@@ -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;
@@ -32,7 +48,7 @@
@interface MPSiteEntity(MP)<MPFixable>
@property(assign) BOOL loginGenerated;
@property(assign) MPSiteType type;
@property(assign) MPResultType type;
@property(readonly) NSString *typeName;
@property(readonly) NSString *typeShortName;
@property(readonly) NSString *typeClassName;
@@ -55,7 +71,7 @@
@interface MPGeneratedSiteEntity(MP)
@property(assign) NSUInteger counter;
@property(assign) MPCounterValue counter;
@end
@@ -64,7 +80,7 @@
@property(assign) NSUInteger avatar;
@property(assign) BOOL saveKey;
@property(assign) BOOL touchID;
@property(assign) MPSiteType defaultType;
@property(assign) MPResultType defaultType;
@property(readonly) NSString *userID;
@property(strong) id<MPAlgorithm> algorithm;

View File

@@ -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 {
@@ -56,9 +82,9 @@
return MPFixableResultNoProblems;
}
- (MPSiteType)type {
- (MPResultType)type {
return (MPSiteType)[self.type_ unsignedIntegerValue];
return (MPResultType)[self.type_ unsignedIntegerValue];
}
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
@@ -71,7 +97,7 @@
return [self.loginGenerated_ boolValue];
}
- (void)setType:(MPSiteType)aType {
- (void)setType:(MPResultType)aType {
self.type_ = @(aType);
}
@@ -225,7 +251,7 @@
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.type
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
@@ -233,18 +259,18 @@
self.type = self.user.defaultType;
return MPFixableResultProblemsFixed;
} );
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.user.defaultType
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]])
// Mismatch between self.type and self.class
result = MPApplyFix( result, ^MPFixableResult {
for (MPSiteType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
for (MPResultType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
self.name, self.user.name, (long)self.type, self.class, (long)newType );
@@ -260,12 +286,12 @@
return result;
}
- (NSUInteger)counter {
- (MPCounterValue)counter {
return [self.counter_ unsignedIntegerValue];
return (MPCounterValue)[self.counter_ unsignedIntegerValue];
}
- (void)setCounter:(NSUInteger)aCounter {
- (void)setCounter:(MPCounterValue)aCounter {
self.counter_ = @(aCounter);
}
@@ -328,12 +354,12 @@
self.touchID_ = @(aTouchID);
}
- (MPSiteType)defaultType {
- (MPResultType)defaultType {
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
return (MPResultType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
}
- (void)setDefaultType:(MPSiteType)aDefaultType {
- (void)setDefaultType:(MPResultType)aDefaultType {
self.defaultType_ = @(aDefaultType);
}

View File

@@ -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>

View File

@@ -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"

View 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"

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
@@ -36,8 +38,7 @@ typedef NS_ENUM( NSUInteger, MPKeyOrigin ) {
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;

View File

@@ -1,19 +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.
//
// 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"
@@ -22,12 +23,11 @@
@property(nonatomic) MPKeyOrigin origin;
@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 {
@@ -42,7 +42,7 @@
if (!(self = [super init]))
return nil;
_keyCache = [NSCache new];
self.keyCache = [NSCache new];
self.origin = origin;
self.fullName = fullName;
@@ -53,30 +53,23 @@
- (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 {
@synchronized (self) {
NSData *keyData = [_keyCache objectForKey:algorithm];
if (keyData)
return keyData;
NSData *keyData = [self.keyCache objectForKey:algorithm];
if (!keyData) {
keyData = self.keyResolver( algorithm );
if (keyData)
[self.keyCache setObject:keyData forKey:algorithm];
}
keyData = self.keyResolver( algorithm );
if (keyData)
[_keyCache setObject:keyData forKey:algorithm];
return keyData;
return keyData.length == MPMasterKeySize? keyData.bytes: NULL;
}
}
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
NSData *keyData = [self keyDataForAlgorithm:algorithm];
return [keyData subdataWithRange:NSMakeRange( 0, MIN( subKeyLength, keyData.length ) )];
}
- (BOOL)isEqualToKey:(MPKey *)key {
return [[self keyIDForAlgorithm:MPAlgorithmDefault] isEqualToData:[key keyIDForAlgorithm:MPAlgorithmDefault]];
@@ -84,10 +77,7 @@
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[MPKey class]])
return NO;
return [self isEqualToKey:object];
return [object isKindOfClass:[MPKey class]] && [self isEqualToKey:object];
}
@end

View 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"

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View 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"

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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"

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,13 +1,28 @@
//==============================================================================
// 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 NSInteger const MPErrorMarshallCode;
extern NSString *const MPSignedInNotification;
extern NSString *const MPSignedOutNotification;
@@ -19,4 +34,31 @@ extern NSString *const MPFoundInconsistenciesNotification;
extern NSString *const MPSitesImportedNotificationUserKey;
extern NSString *const MPInconsistenciesFixResultUserKey;
__END_DECLS
#ifdef CRASHLYTICS
#define MPError(error_, message, ...) ({ \
NSError *__error = error_; \
err( message @"%@%@", ##__VA_ARGS__, __error && [message length]? @"\n": @"", [__error fullDescription]?: @"" ); \
\
if (__error && [[MPConfig get].sendInfo boolValue]) { \
[[Crashlytics sharedInstance] recordError:__error withAdditionalUserInfo:@{ \
@"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \
}]; \
} \
__error; \
})
#else
#define MPError(error_, message, ...) ({ \
NSError *__error = error_; \
err( message @"%@%@", ##__VA_ARGS__, __error? @"\n": @"", [__error fullDescription]?: @"" ); \
__error; \
})
#endif
#define MPMakeError(message, ...) ({ \
MPError( [NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ \
NSLocalizedDescriptionKey: strf( message, ##__VA_ARGS__ ) \
}], @"" ); \
})

View File

@@ -1,14 +1,26 @@
//==============================================================================
// 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;
NSInteger const MPErrorMarshallCode = 1;
NSString *const MPSignedInNotification = @"MPSignedInNotification";
NSString *const MPSignedOutNotification = @"MPSignedOutNotification";

View 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"

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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]))

View File

@@ -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;

View File

@@ -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;
}
@@ -270,23 +269,22 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[openPanel close];
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
if (error)
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
^(NSData *importedSitesData, NSURLResponse *response, NSError *urlError) {
if (urlError)
[[NSAlert alertWithError:MPError( urlError, @"While reading imported sites from %@.", url )] runModal];
if (!importedSitesData)
return;
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
[self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
PearlMainQueueWait( ^{
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Unlock"];
[alert addButtonWithTitle:@"Cancel"];
alert.messageText = @"Import File's Master Password";
alert.informativeText = strf( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName );
alert.messageText = strf( @"Importing Sites For\n%@", userName );
alert.informativeText = @"Enter the master password used to create this export file.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
@@ -294,16 +292,15 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
} );
return masterPassword;
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
} askUserPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
PearlMainQueueWait( ^{
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Import"];
[alert addButtonWithTitle:@"Cancel"];
alert.messageText = strf( @"Master Password for\n%@", userName );
alert.informativeText = strf( @"Imports %lu sites, overwriting %lu.",
(unsigned long)importCount, (unsigned long)deleteCount );
alert.messageText = strf( @"Master Password For\n%@", userName );
alert.informativeText = @"Enter the current master password for this user.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
@@ -311,37 +308,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
} );
return masterPassword;
} result:^(NSError *error) {
[self updateUsers];
if (error && !(error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError))
[[NSAlert alertWithError:error] runModal];
}];
PearlMainQueue( ^{
switch (result) {
case MPImportResultSuccess: {
[self updateUsers];
NSAlert *alert = [NSAlert new];
alert.messageText = @"Successfully imported sites.";
[alert runModal];
break;
}
case MPImportResultCancelled:
break;
case MPImportResultInternalError:
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
NSLocalizedDescriptionKey: @"Import failed because of an internal error."
}]] runModal];
break;
case MPImportResultMalformedInput:
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
NSLocalizedDescriptionKey: @"The import doesn't look like a Master Password export."
}]] runModal];
break;
case MPImportResultInvalidPassword:
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
NSLocalizedDescriptionKey: @"Incorrect master password for the import sites."
}]] runModal];
break;
}
} );
}] resume];
}
@@ -389,20 +361,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] );
[context saveToStore];
[self setActiveUser:newUser];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateUsers];
[self setActiveUser:newUser];
PearlMainQueue( ^{
[self showPasswordWindow:nil];
}];
} );
}];
}
@@ -416,10 +384,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 +403,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 +433,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" );
}
@@ -514,22 +482,43 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if ([savePanel runModal] == NSFileHandlingPanelCancelButton)
return;
NSError *coordinateError = nil;
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
[[[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])
PearlMainQueue( ^{
[[NSAlert alertWithError:writeError] runModal];
} );
}];
if (coordinateError)
PearlMainQueue( ^{
[[NSAlert alertWithError:coordinateError] runModal];
} );
[self exportSitesRevealPasswords:revealPasswords
askExportPassword:^NSString *(NSString *userName) {
return PearlMainQueueAwait( ^id {
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Import"];
[alert addButtonWithTitle:@"Cancel"];
alert.messageText = strf( @"Master Password For\n%@", userName );
alert.informativeText = @"Enter the current master password for this user.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
return ((NSTextField *)alert.accessoryView).stringValue;
else
return nil;
} );
} result:^(NSString *mpsites, NSError *error) {
if (!mpsites || error) {
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( error, @"Failed to export mpsites." )] runModal];
} );
return;
}
NSError *coordinateError = nil;
[[[NSFileCoordinator alloc] initWithFilePresenter:nil]
coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError byAccessor:^(NSURL *newURL) {
NSError *writeError = nil;
if (![mpsites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( writeError, @"Could not write to the export file." )] runModal];
} );
}];
if (coordinateError)
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( coordinateError, @"Could not gain access to the export file." )] runModal];
} );
}];
}
- (void)updateUsers {
@@ -567,7 +556,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 +568,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 +595,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.";
// }

View File

@@ -25,8 +25,7 @@
@end
@implementation MPMacApplication {
}
@implementation MPMacApplication
- (void)sendEvent:(NSEvent *)event {

View File

@@ -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"

View File

@@ -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

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More