2
0

Compare commits

...

46 Commits
2.5-ios ... 2.6

Author SHA1 Message Date
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
133 changed files with 5875 additions and 2481 deletions

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/json-c/json-c.git

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 unsigned char b64ToBits[256] =
{
/* ASCII table */
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
size_t mpw_base64_decode_max(const char *b64Text) {
register const uint8_t *b64Cursor = (uint8_t *)b64Text;
while (b64ToBits[*(b64Cursor++)] <= 63);
int b64Size = (int)(b64Cursor - (uint8_t *)b64Text) - 1;
// Every 4 b64 chars yield 3 plain bytes => len = 3 * ceil(b64Size / 4)
return (size_t)(3 /*bytes*/ * ((b64Size + 4 /*chars*/ - 1) / 4 /*chars*/));
}
int mpw_base64_decode(uint8_t *plainBuf, const char *b64Text) {
register const uint8_t *b64Cursor = (uint8_t *)b64Text;
while (b64ToBits[*(b64Cursor++)] <= 63);
int b64Remaining = (int)(b64Cursor - (uint8_t *)b64Text) - 1;
b64Cursor = (uint8_t *)b64Text;
register uint8_t *plainCursor = plainBuf;
while (b64Remaining > 4) {
*(plainCursor++) = (b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
*(plainCursor++) = (b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
*(plainCursor++) = (b64ToBits[b64Cursor[2]] << 6 | b64ToBits[b64Cursor[3]]);
b64Cursor += 4;
b64Remaining -= 4;
}
/* Note: (b64Size == 1) would be an error, so just ingore that case */
if (b64Remaining > 1)
*(plainCursor++) = (b64ToBits[b64Cursor[0]] << 2 | b64ToBits[b64Cursor[1]] >> 4);
if (b64Remaining > 2)
*(plainCursor++) = (b64ToBits[b64Cursor[1]] << 4 | b64ToBits[b64Cursor[2]] >> 2);
if (b64Remaining > 3)
*(plainCursor++) = (b64ToBits[b64Cursor[2]] << 6 | b64ToBits[b64Cursor[3]]);
return (int)(plainCursor - plainBuf);
}
static const char basis_64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t mpw_base64_encode_max(size_t plainSize) {
// Every 3 plain bytes yield 4 b64 chars => len = 4 * ceil(plainSize / 3)
return 4 /*chars*/ * (plainSize + 3 /*bytes*/ - 1) / 3 /*bytes*/;
}
int mpw_base64_encode(char *b64Text, const uint8_t *plainBuf, size_t plainSize) {
int plainCursor = 0;
char *b64Cursor = b64Text;
for (; plainCursor < plainSize - 2; plainCursor += 3) {
*b64Cursor++ = basis_64[((plainBuf[plainCursor] >> 2)) & 0x3F];
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4) |
((plainBuf[plainCursor + 1] & 0xF0) >> 4)];
*b64Cursor++ = basis_64[((plainBuf[plainCursor + 1] & 0xF) << 2) |
((plainBuf[plainCursor + 2] & 0xC0) >> 6)];
*b64Cursor++ = basis_64[plainBuf[plainCursor + 2] & 0x3F];
}
if (plainCursor < plainSize) {
*b64Cursor++ = basis_64[(plainBuf[plainCursor] >> 2) & 0x3F];
if (plainCursor == (plainSize - 1)) {
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4)];
*b64Cursor++ = '=';
}
else {
*b64Cursor++ = basis_64[((plainBuf[plainCursor] & 0x3) << 4) |
((plainBuf[plainCursor + 1] & 0xF0) >> 4)];
*b64Cursor++ = basis_64[((plainBuf[plainCursor + 1] & 0xF) << 2)];
}
*b64Cursor++ = '=';
}
*b64Cursor = '\0';
return (int)(b64Cursor - b64Text);
}

78
core/c/base64.h Normal file
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

@@ -22,48 +22,150 @@
#include "mpw-algorithm_v2.c"
#include "mpw-algorithm_v3.c"
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_hash PearlHashSHA256
const uint8_t *mpw_masterKeyForUser(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
trc( "-- mpw_masterKey (algorithm: %u)\n", algorithmVersion );
trc( "fullName: %s\n", fullName );
trc( "masterPassword.id: %s\n", mpw_id_buf( masterPassword, strlen( masterPassword ) ) );
if (!fullName || !masterPassword)
return NULL;
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_masterKeyForUser_v0( fullName, masterPassword );
return mpw_masterKey_v0( fullName, masterPassword );
case MPAlgorithmVersion1:
return mpw_masterKeyForUser_v1( fullName, masterPassword );
return mpw_masterKey_v1( fullName, masterPassword );
case MPAlgorithmVersion2:
return mpw_masterKeyForUser_v2( fullName, masterPassword );
return mpw_masterKey_v2( fullName, masterPassword );
case MPAlgorithmVersion3:
return mpw_masterKeyForUser_v3( fullName, masterPassword );
return mpw_masterKey_v3( fullName, masterPassword );
default:
ftl( "Unsupported version: %d", algorithmVersion );
err( "Unsupported version: %d\n", algorithmVersion );
return NULL;
}
}
const char *mpw_passwordForSite(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext, const MPAlgorithmVersion algorithmVersion) {
MPSiteKey mpw_siteKey(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion) {
trc( "-- mpw_siteKey (algorithm: %u)\n", algorithmVersion );
trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter );
trc( "keyPurpose: %d (%s)\n", keyPurpose, mpw_nameForPurpose( keyPurpose ) );
trc( "keyContext: %s\n", keyContext );
if (!masterKey || !siteName)
return NULL;
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_passwordForSite_v0( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
case MPAlgorithmVersion1:
return mpw_passwordForSite_v1( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
return mpw_siteKey_v1( masterKey, siteName, siteCounter, keyPurpose, keyContext );
case MPAlgorithmVersion2:
return mpw_passwordForSite_v2( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
case MPAlgorithmVersion3:
return mpw_passwordForSite_v3( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
return mpw_siteKey_v3( masterKey, siteName, siteCounter, keyPurpose, keyContext );
default:
ftl( "Unsupported version: %d", algorithmVersion );
err( "Unsupported version: %d\n", algorithmVersion );
return NULL;
}
}
const char *mpw_siteResult(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion) {
MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
if (!siteKey)
return NULL;
trc( "-- mpw_siteResult (algorithm: %u)\n", algorithmVersion );
trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) );
trc( "resultParam: %s\n", resultParam );
char *sitePassword = NULL;
if (resultType & MPResultTypeClassTemplate) {
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_sitePasswordFromTemplate_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_sitePasswordFromTemplate_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d\n", algorithmVersion );
return NULL;
}
}
else if (resultType & MPResultTypeClassState) {
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_sitePasswordFromCrypt_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d\n", algorithmVersion );
return NULL;
}
}
else if (resultType & MPResultTypeClassDerive) {
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_sitePasswordFromDerive_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d\n", algorithmVersion );
return NULL;
}
}
else {
err( "Unsupported password type: %d\n", resultType );
}
return sitePassword;
}
const char *mpw_siteState(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *state,
const MPAlgorithmVersion algorithmVersion) {
MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
if (!siteKey)
return NULL;
trc( "-- mpw_siteState (algorithm: %u)\n", algorithmVersion );
trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) );
trc( "state: %s\n", state );
if (!masterKey || !state)
return NULL;
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_siteState_v0( masterKey, siteKey, resultType, state );
case MPAlgorithmVersion1:
return mpw_siteState_v1( masterKey, siteKey, resultType, state );
case MPAlgorithmVersion2:
return mpw_siteState_v2( masterKey, siteKey, resultType, state );
case MPAlgorithmVersion3:
return mpw_siteState_v3( masterKey, siteKey, resultType, state );
default:
err( "Unsupported version: %d\n", algorithmVersion );
return NULL;
}
}

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 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,37 @@ typedef enum(unsigned int, MPAlgorithmVersion) {
MPAlgorithmVersion2,
/** V3 is the current version. */
MPAlgorithmVersion3,
MPAlgorithmVersionCurrent = MPAlgorithmVersion3,
MPAlgorithmVersionFirst = MPAlgorithmVersion0,
MPAlgorithmVersionLast = MPAlgorithmVersion3,
};
#define MPAlgorithmVersionCurrent MPAlgorithmVersion3
/** Derive the master key for a user based on their name and master password.
* @return A new MP_dkLen-byte allocated buffer or NULL if an allocation error occurred. */
const uint8_t *mpw_masterKeyForUser(
* @return A new MPMasterKeySize-byte allocated buffer or NULL if an error occurred. */
MPMasterKey mpw_masterKey(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
/** Encode a password for the site from the given master key and site parameters.
* @return A newly allocated string or NULL if an allocation error occurred. */
const char *mpw_passwordForSite(
const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext, const MPAlgorithmVersion algorithmVersion);
/** Derive the site key for a user's site from the given master key and site parameters.
* @return A new MPSiteKeySize-byte allocated buffer or NULL if an error occurred. */
MPSiteKey mpw_siteKey(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion);
/** Encode a password for the site from the given site key.
* @return A newly allocated string or NULL if an error occurred. */
const char *mpw_siteResult(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion);
/** Perform symmetric encryption on a secret token's plainText.
* @return The newly allocated cipherText of the secret token encrypted by the masterKey. */
const char *mpw_siteState(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *state,
const MPAlgorithmVersion algorithmVersion);
#endif // _MPW_ALGORITHM_H

View File

@@ -22,17 +22,18 @@
#include "mpw-types.h"
#include "mpw-util.h"
#include "base64.h"
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_hash PearlHashSHA256
#define MP_N 32768LU
#define MP_r 8U
#define MP_p 2U
static const char *mpw_templateForType_v0(MPSiteType type, uint16_t seedByte) {
// Algorithm version helpers.
static const char *mpw_templateForType_v0(MPResultType type, uint16_t seedByte) {
size_t count = 0;
const char **templates = mpw_templatesForType( type, &count );
char const *template = count? templates[seedByte % count]: NULL;
char const *template = templates && count? templates[seedByte % count]: NULL;
free( templates );
return template;
}
@@ -40,101 +41,209 @@ static const char *mpw_templateForType_v0(MPSiteType type, uint16_t seedByte) {
static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedByte) {
const char *classCharacters = mpw_charactersInClass( characterClass );
if (!classCharacters)
return '\0';
return classCharacters[seedByte % strlen( classCharacters )];
}
static const uint8_t *mpw_masterKeyForUser_v0(const char *fullName, const char *masterPassword) {
// Algorithm version overrides.
static MPMasterKey mpw_masterKey_v0(
const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "algorithm: v%d\n", 0 );
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
const char *keyScope = mpw_scopeForPurpose( MPKeyPurposeAuthentication );
trc( "keyScope: %s\n", keyScope );
// Calculate the master key salt.
// masterKeySalt = mpKeyScope . #fullName . fullName
trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s\n",
keyScope, mpw_hex_l( htonl( mpw_utf8_strlen( fullName ) ) ), fullName );
size_t masterKeySaltSize = 0;
uint8_t *masterKeySalt = NULL;
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
mpw_push_string( &masterKeySalt, &masterKeySaltSize, keyScope );
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( mpw_utf8_strlen( fullName ) ) );
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
if (!masterKeySalt) {
ftl( "Could not allocate master key salt: %d\n", errno );
err( "Could not allocate master key salt: %s\n", strerror( errno ) );
return NULL;
}
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
trc( " => masterKeySalt.id: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
// Calculate the master key.
// masterKey = scrypt( masterPassword, masterKeySalt )
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )\n", MP_N, MP_r, MP_p );
MPMasterKey masterKey = mpw_kdf_scrypt( MPMasterKeySize, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
mpw_free( masterKeySalt, masterKeySaltSize );
if (!masterKey) {
ftl( "Could not allocate master key: %d\n", errno );
err( "Could not allocate master key: %s\n", strerror( errno ) );
return NULL;
}
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
trc( " => masterKey.id: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
return masterKey;
}
static const char *mpw_passwordForSite_v0(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext) {
static MPSiteKey mpw_siteKey_v0(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 0 );
trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
trc( "keyScope: %s\n", keyScope );
// TODO: Implement MPCounterValueTOTP
// Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
size_t sitePasswordInfoSize = 0;
uint8_t *sitePasswordInfo = NULL;
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteName ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
if (siteContext) {
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteContext ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s\n",
keyScope, mpw_hex_l( htonl( mpw_utf8_strlen( siteName ) ) ), siteName, mpw_hex_l( htonl( siteCounter ) ),
keyContext? mpw_hex_l( htonl( mpw_utf8_strlen( keyContext ) ) ): NULL, keyContext );
size_t siteSaltSize = 0;
uint8_t *siteSalt = NULL;
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
mpw_push_int( &siteSalt, &siteSaltSize, htonl( mpw_utf8_strlen( siteName ) ) );
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
mpw_push_int( &siteSalt, &siteSaltSize, htonl( siteCounter ) );
if (keyContext) {
mpw_push_int( &siteSalt, &siteSaltSize, htonl( mpw_utf8_strlen( keyContext ) ) );
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
}
if (!sitePasswordInfo) {
ftl( "Could not allocate site seed info: %d\n", errno );
if (!siteSalt || !siteSaltSize) {
err( "Could not allocate site salt: %s\n", strerror( errno ) );
mpw_free( siteSalt, siteSaltSize );
return NULL;
}
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
trc( " => siteSalt.id: %s\n", mpw_id_buf( siteSalt, siteSaltSize ) );
const char *sitePasswordSeed = (const char *)mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
if (!sitePasswordSeed) {
ftl( "Could not allocate site seed: %d\n", errno );
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )\n",
mpw_id_buf( masterKey, MPMasterKeySize ) );
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
mpw_free( siteSalt, siteSaltSize );
if (!siteKey) {
err( "Could not derive site key: %s\n", strerror( errno ) );
return NULL;
}
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
trc( " => siteKey.id: %s\n", mpw_id_buf( siteKey, MPSiteKeySize ) );
return siteKey;
}
static const char *mpw_sitePasswordFromTemplate_v0(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
// Determine the template.
const char *template = mpw_templateForType_v0( siteType, htons( sitePasswordSeed[0] ) );
trc( "type %d, template: %s\n", siteType, template );
if (strlen( template ) > 32) {
ftl( "Template too long for password seed: %lu", strlen( template ) );
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
const char *_siteKey = (const char *)siteKey;
const char *template = mpw_templateForType_v0( resultType, htons( _siteKey[0] ) );
trc( "template: %u => %s\n", htons( _siteKey[0] ), template );
if (!template)
return NULL;
if (strlen( template ) > MPSiteKeySize) {
err( "Template too long for password seed: %zu\n", strlen( template ) );
return NULL;
}
// Encode the password from the seed using the template.
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
char *sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
for (size_t c = 0; c < strlen( template ); ++c) {
sitePassword[c] = mpw_characterFromClass_v0( template[c], htons( sitePasswordSeed[c + 1] ) );
trc( "class %c, index %u (0x%02X) -> character: %c\n",
template[c], htons( sitePasswordSeed[c + 1] ), htons( sitePasswordSeed[c + 1] ), sitePassword[c] );
sitePassword[c] = mpw_characterFromClass_v0( template[c], htons( _siteKey[c + 1] ) );
trc( " - class: %c, index: %5u (0x%02hX) => character: %c\n",
template[c], htons( _siteKey[c + 1] ), htons( _siteKey[c + 1] ), sitePassword[c] );
}
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
trc( " => password: %s\n", sitePassword );
return sitePassword;
}
static const char *mpw_sitePasswordFromCrypt_v0(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
if (!cipherText) {
err( "Missing encrypted state.\n" );
return NULL;
}
// Base64-decode
uint8_t *cipherBuf = calloc( 1, mpw_base64_decode_max( cipherText ) );
size_t bufSize = (size_t)mpw_base64_decode( cipherBuf, cipherText );
if ((int)bufSize < 0) {
err( "Base64 decoding error." );
mpw_free( cipherBuf, mpw_base64_decode_max( cipherText ) );
return NULL;
}
trc( "b64 decoded: %zu bytes = %s\n", bufSize, mpw_hex( cipherBuf, bufSize ) );
// Decrypt
const uint8_t *plainBytes = mpw_aes_decrypt( masterKey, MPMasterKeySize, cipherBuf, bufSize );
const char *plainText = strndup( (char *)plainBytes, bufSize );
mpw_free( plainBytes, bufSize );
if (!plainText)
err( "AES decryption error: %s\n", strerror( errno ) );
trc( "decrypted -> plainText: %s = %s\n", plainText, mpw_hex( plainText, sizeof( plainText ) ) );
mpw_free( cipherBuf, bufSize );
return plainText;
}
static const char *mpw_sitePasswordFromDerive_v0(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
switch (resultType) {
case MPResultTypeDeriveKey: {
if (!resultParam) {
err( "Missing key size parameter.\n" );
return NULL;
}
int resultParamInt = atoi( resultParam );
if (resultParamInt < 128 || resultParamInt > 512 || resultParamInt % 8 != 0) {
err( "Parameter is not a valid key size (should be 128 - 512): %s\n", resultParam );
return NULL;
}
uint16_t keySize = (uint16_t)(resultParamInt / 8);
trc( "keySize: %u\n", keySize );
// Derive key
const uint8_t *resultKey = mpw_kdf_blake2b( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL );
if (!resultKey) {
err( "Could not derive result key: %s\n", strerror( errno ) );
return NULL;
}
// Base64-encode
size_t b64Max = mpw_base64_encode_max( keySize );
char *sitePassword = calloc( 1, b64Max + 1 );
if (mpw_base64_encode( sitePassword, resultKey, keySize ) < 0) {
err( "Base64 encoding error." );
mpw_free_string( sitePassword );
sitePassword = NULL;
}
trc( "b64 encoded -> key.id: %s\n", mpw_id_buf( sitePassword, strlen( sitePassword ) ) );
return sitePassword;
}
default:
err( "Unsupported derived password type: %d\n", resultType );
return NULL;
}
}
static const char *mpw_siteState_v0(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *plainText) {
// Encrypt
size_t bufSize = strlen( plainText );
const uint8_t *cipherBuf = mpw_aes_encrypt( masterKey, MPMasterKeySize, (const uint8_t *)plainText, bufSize );
if (!cipherBuf) {
err( "AES encryption error: %s\n", strerror( errno ) );
return NULL;
}
trc( "cipherBuf: %zu bytes = %s\n", bufSize, mpw_hex( cipherBuf, bufSize ) );
// Base64-encode
size_t b64Max = mpw_base64_encode_max( bufSize );
char *cipherText = calloc( 1, b64Max + 1 );
if (mpw_base64_encode( cipherText, cipherBuf, bufSize ) < 0) {
err( "Base64 encoding error." );
mpw_free_string( cipherText );
cipherText = NULL;
}
trc( "b64 encoded -> cipherText: %s = %s\n", cipherText, mpw_hex( cipherText, sizeof( cipherText ) ) );
mpw_free( cipherBuf, bufSize );
return cipherText;
}

View File

@@ -23,103 +23,76 @@
#include "mpw-types.h"
#include "mpw-util.h"
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_hash PearlHashSHA256
#define MP_N 32768LU
#define MP_r 8U
#define MP_p 2U
static const uint8_t *mpw_masterKeyForUser_v1(const char *fullName, const char *masterPassword) {
// Inherited functions.
MPMasterKey mpw_masterKey_v0(
const char *fullName, const char *masterPassword);
MPSiteKey mpw_siteKey_v0(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext);
const char *mpw_sitePasswordFromCrypt_v0(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText);
const char *mpw_sitePasswordFromDerive_v0(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
const char *mpw_siteState_v0(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state);
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "algorithm: v%d\n", 1 );
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
// Algorithm version overrides.
static MPMasterKey mpw_masterKey_v1(
const char *fullName, const char *masterPassword) {
// Calculate the master key salt.
// masterKeySalt = mpKeyScope . #fullName . fullName
size_t masterKeySaltSize = 0;
uint8_t *masterKeySalt = NULL;
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( mpw_utf8_strlen( fullName ) ) );
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
if (!masterKeySalt) {
ftl( "Could not allocate master key salt: %d\n", errno );
return NULL;
}
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
// Calculate the master key.
// masterKey = scrypt( masterPassword, masterKeySalt )
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
mpw_free( masterKeySalt, masterKeySaltSize );
if (!masterKey) {
ftl( "Could not allocate master key: %d\n", errno );
return NULL;
}
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
return masterKey;
return mpw_masterKey_v0( fullName, masterPassword );
}
static const char *mpw_passwordForSite_v1(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext) {
static MPSiteKey mpw_siteKey_v1(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 1 );
trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
}
// Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
size_t sitePasswordInfoSize = 0;
uint8_t *sitePasswordInfo = NULL;
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteName ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
if (siteContext) {
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_utf8_strlen( siteContext ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
}
if (!sitePasswordInfo) {
ftl( "Could not allocate site seed info: %d\n", errno );
return NULL;
}
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
const uint8_t *sitePasswordSeed = mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
if (!sitePasswordSeed) {
ftl( "Could not allocate site seed: %d\n", errno );
return NULL;
}
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
static const char *mpw_sitePasswordFromTemplate_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
// Determine the template.
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
trc( "type %d, template: %s\n", siteType, template );
if (strlen( template ) > 32) {
ftl( "Template too long for password seed: %lu", strlen( template ) );
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
const char *template = mpw_templateForType( resultType, siteKey[0] );
trc( "template: %u => %s\n", siteKey[0], template );
if (!template)
return NULL;
if (strlen( template ) > MPSiteKeySize) {
err( "Template too long for password seed: %zu\n", strlen( template ) );
return NULL;
}
// Encode the password from the seed using the template.
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
for (size_t c = 0; c < strlen( template ); ++c) {
sitePassword[c] = mpw_characterFromClass( template[c], sitePasswordSeed[c + 1] );
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
sitePassword[c] );
sitePassword[c] = mpw_characterFromClass( template[c], siteKey[c + 1] );
trc( " - class: %c, index: %3u (0x%02hhX) => character: %c\n",
template[c], siteKey[c + 1], siteKey[c + 1], sitePassword[c] );
}
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
trc( " => password: %s\n", sitePassword );
return sitePassword;
}
static const char *mpw_sitePasswordFromCrypt_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, cipherText );
}
static const char *mpw_sitePasswordFromDerive_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_siteState_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state) {
return mpw_siteState_v0( masterKey, siteKey, resultType, state );
}

View File

@@ -23,103 +23,92 @@
#include "mpw-types.h"
#include "mpw-util.h"
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_hash PearlHashSHA256
#define MP_N 32768LU
#define MP_r 8U
#define MP_p 2U
static const uint8_t *mpw_masterKeyForUser_v2(const char *fullName, const char *masterPassword) {
// Inherited functions.
MPMasterKey mpw_masterKey_v1(
const char *fullName, const char *masterPassword);
const char *mpw_sitePasswordFromTemplate_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
const char *mpw_sitePasswordFromCrypt_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText);
const char *mpw_sitePasswordFromDerive_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
const char *mpw_siteState_v1(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state);
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "algorithm: v%d\n", 2 );
trc( "fullName: %s (%zu)\n", fullName, mpw_utf8_strlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
// Algorithm version overrides.
static MPMasterKey mpw_masterKey_v2(
const char *fullName, const char *masterPassword) {
// Calculate the master key salt.
// masterKeySalt = mpKeyScope . #fullName . fullName
size_t masterKeySaltSize = 0;
uint8_t *masterKeySalt = NULL;
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( mpw_utf8_strlen( fullName ) ) );
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
if (!masterKeySalt) {
ftl( "Could not allocate master key salt: %d\n", errno );
return NULL;
}
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
// Calculate the master key.
// masterKey = scrypt( masterPassword, masterKeySalt )
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
mpw_free( masterKeySalt, masterKeySaltSize );
if (!masterKey) {
ftl( "Could not allocate master key: %d\n", errno );
return NULL;
}
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
return masterKey;
return mpw_masterKey_v1( fullName, masterPassword );
}
static const char *mpw_passwordForSite_v2(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext) {
static MPSiteKey mpw_siteKey_v2(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 2 );
trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
const char *keyScope = mpw_scopeForPurpose( keyPurpose );
trc( "keyScope: %s\n", keyScope );
// TODO: Implement MPCounterValueTOTP
// Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
size_t sitePasswordInfoSize = 0;
uint8_t *sitePasswordInfo = NULL;
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteName ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
if (siteContext) {
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteContext ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s\n",
keyScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName, mpw_hex_l( htonl( siteCounter ) ),
keyContext? mpw_hex_l( htonl( strlen( keyContext ) ) ): NULL, keyContext );
size_t siteSaltSize = 0;
uint8_t *siteSalt = NULL;
mpw_push_string( &siteSalt, &siteSaltSize, keyScope );
mpw_push_int( &siteSalt, &siteSaltSize, htonl( strlen( siteName ) ) );
mpw_push_string( &siteSalt, &siteSaltSize, siteName );
mpw_push_int( &siteSalt, &siteSaltSize, htonl( siteCounter ) );
if (keyContext) {
mpw_push_int( &siteSalt, &siteSaltSize, htonl( strlen( keyContext ) ) );
mpw_push_string( &siteSalt, &siteSaltSize, keyContext );
}
if (!sitePasswordInfo) {
ftl( "Could not allocate site seed info: %d\n", errno );
if (!siteSalt || !siteSaltSize) {
err( "Could not allocate site salt: %s\n", strerror( errno ) );
mpw_free( siteSalt, siteSaltSize );
return NULL;
}
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
trc( " => siteSalt.id: %s\n", mpw_id_buf( siteSalt, siteSaltSize ) );
const uint8_t *sitePasswordSeed = mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
if (!sitePasswordSeed) {
ftl( "Could not allocate site seed: %d\n", errno );
trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )\n",
mpw_id_buf( masterKey, MPMasterKeySize ) );
MPSiteKey siteKey = mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, siteSalt, siteSaltSize );
mpw_free( siteSalt, siteSaltSize );
if (!siteKey) {
err( "Could not allocate site key: %s\n", strerror( errno ) );
return NULL;
}
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
trc( " => siteKey.id: %s\n", mpw_id_buf( siteKey, MPSiteKeySize ) );
// Determine the template.
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
trc( "type %d, template: %s\n", siteType, template );
if (strlen( template ) > 32) {
ftl( "Template too long for password seed: %lu", strlen( template ) );
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
return NULL;
}
// 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, const MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_sitePasswordFromCrypt_v2(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, cipherText );
}
static const char *mpw_sitePasswordFromDerive_v2(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_siteState_v2(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state) {
return mpw_siteState_v1( masterKey, siteKey, resultType, state );
}

View File

@@ -23,103 +23,84 @@
#include "mpw-types.h"
#include "mpw-util.h"
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_hash PearlHashSHA256
#define MP_N 32768LU
#define MP_r 8U
#define MP_p 2U
static const uint8_t *mpw_masterKeyForUser_v3(const char *fullName, const char *masterPassword) {
// Inherited functions.
MPSiteKey mpw_siteKey_v2(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext);
const char *mpw_sitePasswordFromTemplate_v2(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
const char *mpw_sitePasswordFromCrypt_v2(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText);
const char *mpw_sitePasswordFromDerive_v2(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam);
const char *mpw_siteState_v2(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state);
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "algorithm: v%d\n", 3 );
trc( "fullName: %s (%zu)\n", fullName, strlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
// Algorithm version overrides.
static MPMasterKey mpw_masterKey_v3(
const char *fullName, const char *masterPassword) {
const char *keyScope = mpw_scopeForPurpose( MPKeyPurposeAuthentication );
trc( "keyScope: %s\n", keyScope );
// Calculate the master key salt.
// masterKeySalt = mpKeyScope . #fullName . fullName
trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s\n",
keyScope, mpw_hex_l( htonl( strlen( fullName ) ) ), fullName );
size_t masterKeySaltSize = 0;
uint8_t *masterKeySalt = NULL;
mpw_push_string( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
mpw_push_string( &masterKeySalt, &masterKeySaltSize, keyScope );
mpw_push_int( &masterKeySalt, &masterKeySaltSize, htonl( strlen( fullName ) ) );
mpw_push_string( &masterKeySalt, &masterKeySaltSize, fullName );
if (!masterKeySalt) {
ftl( "Could not allocate master key salt: %d\n", errno );
err( "Could not allocate master key salt: %s\n", strerror( errno ) );
return NULL;
}
trc( "masterKeySalt ID: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
trc( " => masterKeySalt.id: %s\n", mpw_id_buf( masterKeySalt, masterKeySaltSize ) );
// Calculate the master key.
// masterKey = scrypt( masterPassword, masterKeySalt )
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )\n", MP_N, MP_r, MP_p );
MPMasterKey masterKey = mpw_kdf_scrypt( MPMasterKeySize, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
mpw_free( masterKeySalt, masterKeySaltSize );
if (!masterKey) {
ftl( "Could not allocate master key: %d\n", errno );
err( "Could not allocate master key: %s\n", strerror( errno ) );
return NULL;
}
trc( "masterKey ID: %s\n", mpw_id_buf( masterKey, MP_dkLen ) );
trc( " => masterKey.id: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
return masterKey;
}
static const char *mpw_passwordForSite_v3(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext) {
static MPSiteKey mpw_siteKey_v3(
const MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 3 );
trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
// Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
size_t sitePasswordInfoSize = 0;
uint8_t *sitePasswordInfo = NULL;
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteName ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
if (siteContext) {
mpw_push_int( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteContext ) ) );
mpw_push_string( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
}
if (!sitePasswordInfo) {
ftl( "Could not allocate site seed info: %d\n", errno );
return NULL;
}
trc( "sitePasswordInfo ID: %s\n", mpw_id_buf( sitePasswordInfo, sitePasswordInfoSize ) );
const uint8_t *sitePasswordSeed = mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
if (!sitePasswordSeed) {
ftl( "Could not allocate site seed: %d\n", errno );
return NULL;
}
trc( "sitePasswordSeed ID: %s\n", mpw_id_buf( sitePasswordSeed, 32 ) );
// Determine the template.
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
trc( "type %d, template: %s\n", siteType, template );
if (strlen( template ) > 32) {
ftl( "Template too long for password seed: %lu", strlen( template ) );
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
return NULL;
}
// 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, const MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_sitePasswordFromCrypt_v3(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *cipherText) {
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, cipherText );
}
static const char *mpw_sitePasswordFromDerive_v3(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *resultParam) {
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
}
static const char *mpw_siteState_v3(
MPMasterKey masterKey, MPSiteKey siteKey, const MPResultType resultType, const char *state) {
return mpw_siteState_v2( masterKey, siteKey, resultType, state );
}

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

@@ -0,0 +1,114 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#include <string.h>
#include "mpw-marshall-util.h"
#include "mpw-util.h"
char *mpw_get_token(char **in, char *eol, char *delim) {
// Skip leading spaces.
for (; **in == ' '; ++*in);
// Find characters up to the first delim.
size_t len = strcspn( *in, delim );
char *token = len && len <= (size_t)(eol - *in)? strndup( *in, len ): NULL;
// Advance past the delimitor.
*in = min( eol, *in + len + 1 );
return token;
}
time_t mpw_mktime(
const char *time) {
struct tm tm = { .tm_isdst = -1, .tm_gmtoff = 0 };
if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) {
tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900
tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1
return mktime( &tm );
}
return false;
}
json_object *mpw_get_json_section(
json_object *obj, const char *section) {
json_object *json_value = obj;
char *sectionTokenizer = strdup( section ), *sectionToken = sectionTokenizer;
for (sectionToken = strtok( sectionToken, "." ); sectionToken; sectionToken = strtok( NULL, "." ))
if (!json_object_object_get_ex( json_value, sectionToken, &json_value ) || !json_value) {
trc( "While resolving: %s: Missing value for: %s\n", section, sectionToken );
json_value = NULL;
break;
}
free( sectionTokenizer );
return json_value;
}
const char *mpw_get_json_string(
json_object *obj, const char *section, const char *defaultValue) {
json_object *json_value = mpw_get_json_section( obj, section );
if (!json_value)
return defaultValue;
return json_object_get_string( json_value );
}
int32_t mpw_get_json_int(
json_object *obj, const char *section, int32_t defaultValue) {
json_object *json_value = mpw_get_json_section( obj, section );
if (!json_value)
return defaultValue;
return json_object_get_int( json_value );
}
bool mpw_get_json_boolean(
json_object *obj, const char *section, bool defaultValue) {
json_object *json_value = mpw_get_json_section( obj, section );
if (!json_value)
return defaultValue;
return json_object_get_boolean( json_value ) == TRUE;
}
bool mpw_update_masterKey(MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
const char *fullName, const char *masterPassword) {
if (*masterKeyAlgorithm != targetKeyAlgorithm) {
mpw_free( *masterKey, MPMasterKeySize );
*masterKeyAlgorithm = targetKeyAlgorithm;
*masterKey = mpw_masterKey(
fullName, masterPassword, *masterKeyAlgorithm );
if (!*masterKey) {
err( "Couldn't derive master key for user %s, algorithm %d.\n", fullName, *masterKeyAlgorithm );
return false;
}
}
return true;
}

View File

@@ -0,0 +1,69 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#ifndef _MPW_MARSHALL_UTIL_H
#define _MPW_MARSHALL_UTIL_H
#include <time.h>
#include <json-c/json.h>
#include "mpw-algorithm.h"
/// Type parsing.
/** Get a token from a string by searching until the first character in delim, no farther than eol.
* The input string reference is advanced beyond the token delimitor if one is found.
* @return A new string containing the token or NULL if the delim wasn't found before eol. */
char *mpw_get_token(
char **in, char *eol, char *delim);
/** Convert an RFC 3339 time string into epoch time. */
time_t mpw_mktime(
const char *time);
/// JSON parsing.
/** Search for a JSON child object in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return A new JSON object or NULL if one of the section's object keys was not found in the source object's tree. */
json_object *mpw_get_json_section(
json_object *obj, const char *section);
/** Search for a string in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return A new string or defaultValue if one of the section's object keys was not found in the source object's tree. */
const char *mpw_get_json_string(
json_object *obj, const char *section, const char *defaultValue);
/** Search for an integer in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return The integer value or defaultValue if one of the section's object keys was not found in the source object's tree. */
int32_t mpw_get_json_int(
json_object *obj, const char *section, int32_t defaultValue);
/** Search for a boolean in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return The boolean value or defaultValue if one of the section's object keys was not found in the source object's tree. */
bool mpw_get_json_boolean(
json_object *obj, const char *section, bool defaultValue);
/// mpw.
/** Calculate a master key if the target master key algorithm is different from the given master key algorithm.
* @return false if an error occurred during the derivation of the master key. */
bool mpw_update_masterKey(
MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
const char *fullName, const char *masterPassword);
#endif // _MPW_MARSHALL_UTIL_H

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

@@ -0,0 +1,747 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "mpw-marshall.h"
#include "mpw-util.h"
#include "mpw-marshall-util.h"
MPMarshalledUser *mpw_marshall_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
MPMarshalledUser *user;
if (!fullName || !masterPassword || !(user = malloc( sizeof( MPMarshalledUser ) )))
return NULL;
*user = (MPMarshalledUser){
.fullName = strdup( fullName ),
.masterPassword = strdup( masterPassword ),
.algorithm = algorithmVersion,
.redacted = true,
.avatar = 0,
.defaultType = MPResultTypeDefault,
.lastUsed = 0,
.sites_count = 0,
.sites = NULL,
};
return user;
};
MPMarshalledSite *mpw_marshall_site(
MPMarshalledUser *user, const char *siteName, const MPResultType resultType,
const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion) {
if (!siteName || !mpw_realloc( &user->sites, NULL, sizeof( MPMarshalledSite ) * ++user->sites_count ))
return NULL;
MPMarshalledSite *site = &user->sites[user->sites_count - 1];
*site = (MPMarshalledSite){
.name = strdup( siteName ),
.content = NULL,
.type = resultType,
.counter = siteCounter,
.algorithm = algorithmVersion,
.loginName = NULL,
.loginGenerated = false,
.url = NULL,
.uses = 0,
.lastUsed = 0,
.questions_count = 0,
.questions = NULL,
};
return site;
};
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword) {
if (!keyword || !mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count ))
return NULL;
MPMarshalledQuestion *question = &site->questions[site->questions_count - 1];
*question = (MPMarshalledQuestion){
.keyword = strdup( keyword ),
};
return question;
}
bool mpw_marshal_free(
MPMarshalledUser *user) {
if (!user)
return true;
bool success = true;
for (size_t s = 0; s < user->sites_count; ++s) {
MPMarshalledSite *site = &user->sites[s];
success &= mpw_free_string( site->name );
for (size_t q = 0; q < site->questions_count; ++q) {
MPMarshalledQuestion *question = &site->questions[q];
success &= mpw_free_string( question->keyword );
}
success &= mpw_free( site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count );
}
success &= mpw_free( user->sites, sizeof( MPMarshalledSite ) * user->sites_count );
success &= mpw_free_string( user->fullName );
success &= mpw_free_string( user->masterPassword );
success &= mpw_free( user, sizeof( MPMarshalledUser ) );
return success;
}
static bool mpw_marshall_write_flat(
char **out, const MPMarshalledUser *user, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
if (!user->fullName || !strlen( user->fullName )) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." };
return false;
}
if (!user->masterPassword || !strlen( user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Missing master password." };
return false;
}
MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return false;
}
mpw_string_pushf( out, "# Master Password site export\n" );
if (user->redacted)
mpw_string_pushf( out, "# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n" );
else
mpw_string_pushf( out, "# Export of site names and passwords in clear-text.\n" );
mpw_string_pushf( out, "# \n" );
mpw_string_pushf( out, "##\n" );
mpw_string_pushf( out, "# Format: %d\n", 1 );
char dateString[21];
time_t now = time( NULL );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
mpw_string_pushf( out, "# Date: %s\n", dateString );
mpw_string_pushf( out, "# User Name: %s\n", user->fullName );
mpw_string_pushf( out, "# Full Name: %s\n", user->fullName );
mpw_string_pushf( out, "# Avatar: %u\n", user->avatar );
mpw_string_pushf( out, "# Key ID: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
mpw_string_pushf( out, "# Algorithm: %d\n", user->algorithm );
mpw_string_pushf( out, "# Default Type: %d\n", user->defaultType );
mpw_string_pushf( out, "# Passwords: %s\n", user->redacted? "PROTECTED": "VISIBLE" );
mpw_string_pushf( out, "##\n" );
mpw_string_pushf( out, "#\n" );
mpw_string_pushf( out, "# Last Times Password Login\t Site\tSite\n" );
mpw_string_pushf( out, "# used used type name\t name\tpassword\n" );
// Sites.
for (size_t s = 0; s < user->sites_count; ++s) {
MPMarshalledSite *site = &user->sites[s];
if (!site->name || !strlen( site->name ))
continue;
const char *content = NULL;
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return false;
}
if (site->type & MPResultTypeClassTemplate)
content = mpw_siteResult( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
}
else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
// Redacted
content = strdup( site->content );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n",
dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter,
site->loginName?: "", site->name, content?: "" );
mpw_free_string( content );
}
mpw_free( masterKey, MPMasterKeySize );
*error = (MPMarshallError){ MPMarshallSuccess };
return true;
}
static bool mpw_marshall_write_json(
char **out, const MPMarshalledUser *user, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
if (!user->fullName || !strlen( user->fullName )) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." };
return false;
}
if (!user->masterPassword || !strlen( user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Missing master password." };
return false;
}
MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return false;
}
// Section: "export"
json_object *json_file = json_object_new_object();
json_object *json_export = json_object_new_object();
json_object_object_add( json_file, "export", json_export );
json_object_object_add( json_export, "format", json_object_new_int( 1 ) );
json_object_object_add( json_export, "redacted", json_object_new_boolean( user->redacted ) );
char dateString[21];
time_t now = time( NULL );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
json_object_object_add( json_export, "date", json_object_new_string( dateString ) );
// Section: "user"
json_object *json_user = json_object_new_object();
json_object_object_add( json_file, "user", json_user );
json_object_object_add( json_user, "avatar", json_object_new_int( (int)user->avatar ) );
json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) ))
json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) );
json_object_object_add( json_user, "key_id", json_object_new_string( mpw_id_buf( masterKey, MPMasterKeySize ) ) );
json_object_object_add( json_user, "algorithm", json_object_new_int( (int)user->algorithm ) );
json_object_object_add( json_user, "default_type", json_object_new_int( (int)user->defaultType ) );
// Section "sites"
json_object *json_sites = json_object_new_object();
json_object_object_add( json_file, "sites", json_sites );
for (size_t s = 0; s < user->sites_count; ++s) {
MPMarshalledSite *site = &user->sites[s];
if (!site->name || !strlen( site->name ))
continue;
const char *content = NULL;
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return false;
}
if (site->type & MPResultTypeClassTemplate)
content = mpw_siteResult( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
}
else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
// Redacted
content = strdup( site->content );
json_object *json_site = json_object_new_object();
json_object_object_add( json_sites, site->name, json_site );
json_object_object_add( json_site, "type", json_object_new_int( (int)site->type ) );
json_object_object_add( json_site, "counter", json_object_new_int( (int)site->counter ) );
json_object_object_add( json_site, "algorithm", json_object_new_int( (int)site->algorithm ) );
if (content)
json_object_object_add( json_site, "password", json_object_new_string( content ) );
if (site->loginName)
json_object_object_add( json_site, "login_name", json_object_new_string( site->loginName ) );
json_object_object_add( json_site, "login_generated", json_object_new_boolean( site->loginGenerated ) );
json_object_object_add( json_site, "uses", json_object_new_int( (int)site->uses ) );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) );
json_object *json_site_questions = json_object_new_object();
json_object_object_add( json_site, "questions", json_site_questions );
for (size_t q = 0; q < site->questions_count; ++q) {
MPMarshalledQuestion *question = &site->questions[q];
if (!question->keyword)
continue;
json_object *json_site_question = json_object_new_object();
json_object_object_add( json_site_questions, question->keyword, json_site_question );
if (!user->redacted) {
// Clear Text
const char *answer = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, MPResultTypeTemplatePhrase, NULL, site->algorithm );
if (answer)
json_object_object_add( json_site_question, "answer", json_object_new_string( answer ) );
}
}
json_object *json_site_mpw = json_object_new_object();
json_object_object_add( json_site, "_ext_mpw", json_site_mpw );
if (site->url)
json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) );
mpw_free_string( content );
}
mpw_string_pushf( out, "%s\n", json_object_to_json_string_ext( json_file, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED ) );
mpw_free( masterKey, MPMasterKeySize );
json_object_put( json_file );
*error = (MPMarshallError){ MPMarshallSuccess };
return true;
}
bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) {
switch (outFormat) {
case MPMarshallFormatFlat:
return mpw_marshall_write_flat( out, user, error );
case MPMarshallFormatJSON:
return mpw_marshall_write_json( out, user, error );
default:
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
return false;
}
}
static MPMarshalledUser *mpw_marshall_read_flat(
char *in, const char *masterPassword, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
// Parse import data.
MPMasterKey masterKey = NULL;
MPMarshalledUser *user = NULL;
unsigned int format = 0, avatar = 0;
char *fullName = NULL, *keyID = NULL;
MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1;
MPResultType defaultType = MPResultTypeDefault;
bool headerStarted = false, headerEnded = false, importRedacted = false;
for (char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
// Comment or header
if (*positionInLine == '#') {
++positionInLine;
if (!headerStarted) {
if (*positionInLine == '#')
// ## starts header
headerStarted = true;
// Comment before header
continue;
}
if (headerEnded)
// Comment after header
continue;
if (*positionInLine == '#') {
// ## ends header
headerEnded = true;
continue;
}
// Header
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue) {
error->type = MPMarshallErrorStructure;
error->description = mpw_str( "Invalid header: %s", strndup( positionInLine, (size_t)(endOfLine - positionInLine) ) );
return NULL;
}
if (strcmp( headerName, "Format" ) == 0)
format = (unsigned int)atoi( headerValue );
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
fullName = strdup( headerValue );
if (strcmp( headerName, "Avatar" ) == 0)
avatar = (unsigned int)atoi( headerValue );
if (strcmp( headerName, "Key ID" ) == 0)
keyID = strdup( headerValue );
if (strcmp( headerName, "Algorithm" ) == 0) {
int value = atoi( headerValue );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) };
return NULL;
}
algorithm = (MPAlgorithmVersion)value;
}
if (strcmp( headerName, "Default Type" ) == 0) {
int value = atoi( headerValue );
if (!mpw_nameForType( (MPResultType)value )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) };
return NULL;
}
defaultType = (MPResultType)value;
}
if (strcmp( headerName, "Passwords" ) == 0)
importRedacted = strcmp( headerValue, "VISIBLE" ) != 0;
mpw_free_string( headerName );
mpw_free_string( headerValue );
continue;
}
if (!headerEnded)
continue;
if (!fullName) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing header: Full Name" };
return NULL;
}
if (positionInLine >= endOfLine)
continue;
if (!user) {
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return NULL;
}
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Master password doesn't match key ID." };
return NULL;
}
if (!(user = mpw_marshall_user( fullName, masterPassword, algorithm ))) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new user." };
return NULL;
}
user->redacted = importRedacted;
user->avatar = avatar;
user->defaultType = defaultType;
}
// Site
char *siteLoginName = NULL, *siteName = NULL, *siteContent = NULL;
char *str_lastUsed = NULL, *str_uses = NULL, *str_type = NULL, *str_algorithm = NULL, *str_counter = NULL;
switch (format) {
case 0: {
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
char *typeAndVersion = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
if (typeAndVersion) {
str_type = strdup( strtok( typeAndVersion, ":" ) );
str_algorithm = strdup( strtok( NULL, "" ) );
mpw_free_string( typeAndVersion );
}
str_counter = strdup( "1" );
siteLoginName = NULL;
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
break;
}
case 1: {
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
char *typeAndVersionAndCounter = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
if (typeAndVersionAndCounter) {
str_type = strdup( strtok( typeAndVersionAndCounter, ":" ) );
str_algorithm = strdup( strtok( NULL, ":" ) );
str_counter = strdup( strtok( NULL, "" ) );
mpw_free_string( typeAndVersionAndCounter );
}
siteLoginName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
break;
}
default: {
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unexpected import format: %u", format ) };
return NULL;
}
}
if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) {
MPResultType siteType = (MPResultType)atoi( str_type );
if (!mpw_nameForType( siteType )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) };
return NULL;
}
long long int value = atoll( str_counter );
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) };
return NULL;
}
MPCounterValue siteCounter = (MPCounterValue)value;
value = atoll( str_algorithm );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm ) };
return NULL;
}
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
time_t siteLastUsed = mpw_mktime( str_lastUsed );
if (!siteLastUsed) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL;
}
MPMarshalledSite *site = mpw_marshall_site(
user, siteName, siteType, siteCounter, siteAlgorithm );
if (!site) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new site." };
return NULL;
}
site->loginName = siteLoginName? strdup( siteLoginName ): NULL;
site->uses = (unsigned int)atoi( str_uses );
site->lastUsed = siteLastUsed;
if (siteContent && strlen( siteContent )) {
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return NULL;
}
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
}
else
// Redacted
site->content = strdup( siteContent );
}
}
else {
error->type = MPMarshallErrorMissing;
error->description = mpw_str(
"Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginName, siteName );
return NULL;
}
mpw_free_string( str_lastUsed );
mpw_free_string( str_uses );
mpw_free_string( str_type );
mpw_free_string( str_algorithm );
mpw_free_string( str_counter );
mpw_free_string( siteLoginName );
mpw_free_string( siteName );
mpw_free_string( siteContent );
}
mpw_free_string( fullName );
mpw_free_string( keyID );
mpw_free( masterKey, MPMasterKeySize );
*error = (MPMarshallError){ MPMarshallSuccess };
return user;
}
static MPMarshalledUser *mpw_marshall_read_json(
char *in, const char *masterPassword, MPMarshallError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal };
// Parse JSON.
enum json_tokener_error json_error = json_tokener_success;
json_object *json_file = json_tokener_parse_verbose( in, &json_error );
if (!json_file || json_error != json_tokener_success) {
*error = (MPMarshallError){ MPMarshallErrorStructure, mpw_str( "JSON error: %s", json_tokener_error_desc( json_error ) ) };
return NULL;
}
// Parse import data.
MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = (MPAlgorithmVersion)-1;
MPMarshalledUser *user = NULL;
// Section: "export"
unsigned int fileFormat = (unsigned int)mpw_get_json_int( json_file, "export.format", 0 );
if (fileFormat < 1) {
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported format: %u", fileFormat ) };
return NULL;
}
bool fileRedacted = mpw_get_json_boolean( json_file, "export.redacted", true );
// Section: "user"
unsigned int avatar = (unsigned int)mpw_get_json_int( json_file, "user.avatar", 0 );
const char *fullName = mpw_get_json_string( json_file, "user.full_name", NULL );
const char *str_lastUsed = mpw_get_json_string( json_file, "user.last_used", NULL );
const char *keyID = mpw_get_json_string( json_file, "user.key_id", NULL );
int32_t value = mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user algorithm version: %u", value ) };
return NULL;
}
MPAlgorithmVersion algorithm = (MPAlgorithmVersion)value;
MPResultType defaultType = (MPResultType)mpw_get_json_int( json_file, "user.default_type", MPResultTypeDefault );
if (!mpw_nameForType( defaultType )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
return NULL;
}
time_t lastUsed = mpw_mktime( str_lastUsed );
if (!lastUsed) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
return NULL;
}
if (!fullName || !strlen( fullName )) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing value for full name." };
return NULL;
}
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return NULL;
}
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Master password doesn't match key ID." };
return NULL;
}
if (!(user = mpw_marshall_user( fullName, masterPassword, algorithm ))) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new user." };
return NULL;
}
user->redacted = fileRedacted;
user->avatar = avatar;
user->defaultType = defaultType;
user->lastUsed = lastUsed;
// Section "sites"
json_object_iter json_site;
json_object *json_sites = mpw_get_json_section( json_file, "sites" );
json_object_object_foreachC( json_sites, json_site ) {
const char *siteName = json_site.key;
value = mpw_get_json_int( json_site.val, "algorithm", user->algorithm );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site algorithm version: %s: %d", siteName, value ) };
return NULL;
}
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
MPResultType siteType = (MPResultType)mpw_get_json_int( json_site.val, "type", user->defaultType );
if (!mpw_nameForType( siteType )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) };
return NULL;
}
value = mpw_get_json_int( json_site.val, "counter", 1 );
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, value ) };
return NULL;
}
MPCounterValue siteCounter = (MPCounterValue)value;
const char *siteContent = mpw_get_json_string( json_site.val, "password", NULL );
const char *siteLoginName = mpw_get_json_string( json_site.val, "login_name", NULL );
bool siteLoginGenerated = mpw_get_json_boolean( json_site.val, "login_generated", false );
unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 );
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
time_t siteLastUsed = mpw_mktime( str_lastUsed );
if (!siteLastUsed) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL;
}
json_object *json_site_mpw = mpw_get_json_section( json_site.val, "_ext_mpw" );
const char *siteURL = mpw_get_json_string( json_site_mpw, "url", NULL );
MPMarshalledSite *site = mpw_marshall_site( user, siteName, siteType, siteCounter, siteAlgorithm );
if (!site) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new site." };
return NULL;
}
site->loginName = siteLoginName? strdup( siteLoginName ): NULL;
site->loginGenerated = siteLoginGenerated;
site->url = siteURL? strdup( siteURL ): NULL;
site->uses = siteUses;
site->lastUsed = siteLastUsed;
if (siteContent && strlen( siteContent )) {
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
return NULL;
}
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
}
else
// Redacted
site->content = strdup( siteContent );
}
json_object_iter json_site_question;
json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" );
json_object_object_foreachC( json_site_questions, json_site_question )
mpw_marshal_question( site, json_site_question.key );
}
json_object_put( json_file );
*error = (MPMarshallError){ MPMarshallSuccess };
return user;
}
MPMarshalledUser *mpw_marshall_read(
char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) {
switch (inFormat) {
case MPMarshallFormatFlat:
return mpw_marshall_read_flat( in, masterPassword, error );
case MPMarshallFormatJSON:
return mpw_marshall_read_json( in, masterPassword, error );
default:
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) };
return NULL;
}
}
const MPMarshallFormat mpw_formatWithName(
const char *formatName) {
// Lower-case to standardize it.
size_t stdFormatNameSize = strlen( formatName );
char stdFormatName[stdFormatNameSize + 1];
for (size_t c = 0; c < stdFormatNameSize; ++c)
stdFormatName[c] = (char)tolower( formatName[c] );
stdFormatName[stdFormatNameSize] = '\0';
if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatFlat;
if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatJSON;
dbg( "Not a format name: %s\n", stdFormatName );
return (MPMarshallFormat)ERR;
}
const char *mpw_nameForFormat(
const MPMarshallFormat format) {
switch (format) {
case MPMarshallFormatFlat:
return "flat";
case MPMarshallFormatJSON:
return "json";
default: {
dbg( "Unknown format: %d\n", format );
return NULL;
}
}
}
const char *mpw_marshall_format_extension(
const MPMarshallFormat format) {
switch (format) {
case MPMarshallFormatFlat:
return "mpsites";
case MPMarshallFormatJSON:
return "mpsites.json";
default: {
dbg( "Unknown format: %d\n", format );
return NULL;
}
}
}

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

@@ -0,0 +1,128 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#ifndef _MPW_MARSHALL_H
#define _MPW_MARSHALL_H
#include <time.h>
#include "mpw-algorithm.h"
//// Types.
typedef enum( unsigned int, MPMarshallFormat ) {
/** Generate a key for authentication. */
MPMarshallFormatFlat,
/** Generate a name for identification. */
MPMarshallFormatJSON,
MPMarshallFormatDefault = MPMarshallFormatJSON,
};
typedef enum( unsigned int, MPMarshallErrorType ) {
/** The marshalling operation completed successfully. */
MPMarshallSuccess,
/** An error in the structure of the marshall file interrupted marshalling. */
MPMarshallErrorStructure,
/** The marshall file uses an unsupported format version. */
MPMarshallErrorFormat,
/** A required value is missing or not specified. */
MPMarshallErrorMissing,
/** The given master password is not valid. */
MPMarshallErrorMasterPassword,
/** An illegal value was specified. */
MPMarshallErrorIllegal,
/** An internal system error interrupted marshalling. */
MPMarshallErrorInternal,
};
typedef struct MPMarshallError {
MPMarshallErrorType type;
const char *description;
} MPMarshallError;
typedef struct MPMarshalledQuestion {
const char *keyword;
} MPMarshalledQuestion;
typedef struct MPMarshalledSite {
const char *name;
const char *content;
MPResultType type;
MPCounterValue counter;
MPAlgorithmVersion algorithm;
const char *loginName;
bool loginGenerated;
const char *url;
unsigned int uses;
time_t lastUsed;
size_t questions_count;
MPMarshalledQuestion *questions;
} MPMarshalledSite;
typedef struct MPMarshalledUser {
const char *fullName;
const char *masterPassword;
MPAlgorithmVersion algorithm;
bool redacted;
unsigned int avatar;
MPResultType defaultType;
time_t lastUsed;
size_t sites_count;
MPMarshalledSite *sites;
} MPMarshalledUser;
//// Marshalling.
bool mpw_marshall_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error);
MPMarshalledUser *mpw_marshall_read(
char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error);
//// Utilities.
MPMarshalledUser *mpw_marshall_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
MPMarshalledSite *mpw_marshall_site(
MPMarshalledUser *user,
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword);
bool mpw_marshal_free(
MPMarshalledUser *user);
//// Format.
/**
* @return The purpose represented by the given name.
*/
const MPMarshallFormat mpw_formatWithName(
const char *formatName);
/**
* @return The standard name for the given purpose.
*/
const char *mpw_nameForFormat(
const MPMarshallFormat format);
const char *mpw_marshall_format_extension(
const MPMarshallFormat format);
#endif // _MPW_MARSHALL_H

View File

@@ -29,55 +29,115 @@
#include "mpw-types.h"
#include "mpw-util.h"
const MPSiteType mpw_typeWithName(const char *typeName) {
const MPResultType mpw_typeWithName(const char *typeName) {
// Find what password type is represented by the type letter.
if (strlen( typeName ) == 1) {
if ('x' == typeName[0])
return MPResultTypeTemplateMaximum;
if ('l' == typeName[0])
return MPResultTypeTemplateLong;
if ('m' == typeName[0])
return MPResultTypeTemplateMedium;
if ('b' == typeName[0])
return MPResultTypeTemplateBasic;
if ('s' == typeName[0])
return MPResultTypeTemplateShort;
if ('i' == typeName[0])
return MPResultTypeTemplatePIN;
if ('n' == typeName[0])
return MPResultTypeTemplateName;
if ('P' == typeName[0])
return MPResultTypeStatePersonal;
if ('D' == typeName[0])
return MPResultTypeStateDevice;
if ('k' == typeName[0])
return MPResultTypeDeriveKey;
}
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it.
size_t stdTypeNameOffset = 0;
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( MPResultTypeStatePersonal ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeStatePersonal;
if (strncmp( mpw_nameForType( MPResultTypeStateDevice ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeStateDevice;
if (strncmp( mpw_nameForType( MPResultTypeDeriveKey ), stdTypeName, strlen( stdTypeName ) ) == 0)
return MPResultTypeDeriveKey;
ftl( "Not a generated type name: %s", stdTypeName );
return MPSiteTypeGeneratedLong;
dbg( "Not a generated type name: %s\n", stdTypeName );
return (MPResultType)ERR;
}
const char **mpw_templatesForType(MPSiteType type, size_t *count) {
const char *mpw_nameForType(MPResultType resultType) {
if (!(type & MPSiteTypeClassGenerated)) {
ftl( "Not a generated type: %d", type );
*count = 0;
switch (resultType) {
case MPResultTypeTemplateMaximum:
return "maximum";
case MPResultTypeTemplateLong:
return "long";
case MPResultTypeTemplateMedium:
return "medium";
case MPResultTypeTemplateBasic:
return "basic";
case MPResultTypeTemplateShort:
return "short";
case MPResultTypeTemplatePIN:
return "pin";
case MPResultTypeTemplateName:
return "name";
case MPResultTypeTemplatePhrase:
return "phrase";
case MPResultTypeStatePersonal:
return "personal";
case MPResultTypeStateDevice:
return "device";
case MPResultTypeDeriveKey:
return "key";
default: {
dbg( "Unknown password type: %d\n", resultType );
return NULL;
}
}
}
const char **mpw_templatesForType(MPResultType type, size_t *count) {
if (!(type & MPResultTypeClassTemplate)) {
dbg( "Not a generated type: %d\n", type );
return NULL;
}
switch (type) {
case MPSiteTypeGeneratedMaximum: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplateMaximum:
return mpw_alloc_array( count, const char *,
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
}
case MPSiteTypeGeneratedLong: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplateLong:
return mpw_alloc_array( count, const char *,
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
@@ -85,83 +145,89 @@ const char **mpw_templatesForType(MPSiteType type, size_t *count) {
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
}
case MPSiteTypeGeneratedMedium: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplateMedium:
return mpw_alloc_array( count, const char *,
"CvcnoCvc", "CvcCvcno" );
}
case MPSiteTypeGeneratedBasic: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplateBasic:
return mpw_alloc_array( count, const char *,
"aaanaaan", "aannaaan", "aaannaaa" );
}
case MPSiteTypeGeneratedShort: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplateShort:
return mpw_alloc_array( count, const char *,
"Cvcn" );
}
case MPSiteTypeGeneratedPIN: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplatePIN:
return mpw_alloc_array( count, const char *,
"nnnn" );
}
case MPSiteTypeGeneratedName: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplateName:
return mpw_alloc_array( count, const char *,
"cvccvcvcv" );
}
case MPSiteTypeGeneratedPhrase: {
return mpw_alloc_array( *count, const char *,
case MPResultTypeTemplatePhrase:
return mpw_alloc_array( count, const char *,
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
}
default: {
ftl( "Unknown generated type: %d", type );
*count = 0;
dbg( "Unknown generated type: %d\n", type );
return NULL;
}
}
}
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
const char *mpw_templateForType(MPResultType type, uint8_t seedByte) {
size_t count = 0;
const char **templates = mpw_templatesForType( type, &count );
char const *template = count? templates[seedByte % count]: NULL;
char const *template = templates && count? templates[seedByte % count]: NULL;
free( templates );
return template;
}
const MPSiteVariant mpw_variantWithName(const char *variantName) {
const MPKeyPurpose mpw_purposeWithName(const char *purposeName) {
// Lower-case and trim optionally leading "generated" string from typeName to standardize it.
size_t stdVariantNameSize = strlen( variantName );
char stdVariantName[stdVariantNameSize + 1];
for (size_t c = 0; c < stdVariantNameSize; ++c)
stdVariantName[c] = (char)tolower( variantName[c] );
stdVariantName[stdVariantNameSize] = '\0';
size_t stdPurposeNameSize = strlen( purposeName );
char stdPurposeName[stdPurposeNameSize + 1];
for (size_t c = 0; c < stdPurposeNameSize; ++c)
stdPurposeName[c] = (char)tolower( purposeName[c] );
stdPurposeName[stdPurposeNameSize] = '\0';
if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" ))
return MPSiteVariantPassword;
if (0 == strcmp( stdVariantName, "l" ) || 0 == strcmp( stdVariantName, "login" ))
return MPSiteVariantLogin;
if (0 == strcmp( stdVariantName, "a" ) || 0 == strcmp( stdVariantName, "answer" ))
return MPSiteVariantAnswer;
if (strncmp( mpw_nameForPurpose( MPKeyPurposeAuthentication ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
return MPKeyPurposeAuthentication;
if (strncmp( mpw_nameForPurpose( MPKeyPurposeIdentification ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
return MPKeyPurposeIdentification;
if (strncmp( mpw_nameForPurpose( MPKeyPurposeRecovery ), stdPurposeName, strlen( stdPurposeName ) ) == 0)
return MPKeyPurposeRecovery;
fprintf( stderr, "Not a variant name: %s", stdVariantName );
abort();
dbg( "Not a purpose name: %s\n", stdPurposeName );
return (MPKeyPurpose)ERR;
}
const char *mpw_scopeForVariant(MPSiteVariant variant) {
const char *mpw_nameForPurpose(MPKeyPurpose purpose) {
switch (variant) {
case MPSiteVariantPassword: {
return "com.lyndir.masterpassword";
}
case MPSiteVariantLogin: {
return "com.lyndir.masterpassword.login";
}
case MPSiteVariantAnswer: {
return "com.lyndir.masterpassword.answer";
}
switch (purpose) {
case MPKeyPurposeAuthentication:
return "authentication";
case MPKeyPurposeIdentification:
return "identification";
case MPKeyPurposeRecovery:
return "recovery";
default: {
fprintf( stderr, "Unknown variant: %d", variant );
abort();
dbg( "Unknown purpose: %d\n", purpose );
return NULL;
}
}
}
const char *mpw_scopeForPurpose(MPKeyPurpose purpose) {
switch (purpose) {
case MPKeyPurposeAuthentication:
return "com.lyndir.masterpassword";
case MPKeyPurposeIdentification:
return "com.lyndir.masterpassword.login";
case MPKeyPurposeRecovery:
return "com.lyndir.masterpassword.answer";
default: {
dbg( "Unknown purpose: %d\n", purpose );
return NULL;
}
}
}
@@ -190,8 +256,8 @@ const char *mpw_charactersInClass(char characterClass) {
case ' ':
return " ";
default: {
fprintf( stderr, "Unknown character class: %c", characterClass );
abort();
dbg( "Unknown character class: %c\n", characterClass );
return NULL;
}
}
}
@@ -199,5 +265,8 @@ const char *mpw_charactersInClass(char characterClass) {
const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
const char *classCharacters = mpw_charactersInClass( characterClass );
if (!classCharacters)
return '\0';
return classCharacters[seedByte % strlen( classCharacters )];
}

View File

@@ -18,71 +18,118 @@
#ifndef _MPW_TYPES_H
#define _MPW_TYPES_H
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef NS_ENUM
#define enum(_type, _name) NS_ENUM(_type, _name)
#else
//#ifdef NS_ENUM
//#define enum(_type, _name) NS_ENUM(_type, _name)
//#else
#define enum(_type, _name) _type _name; enum
#endif
#define MP_dkLen 64
//#endif
//// Types.
typedef enum( unsigned int, MPSiteVariant ) {
#define MPMasterKeySize 64 /* bytes */
typedef const uint8_t *MPMasterKey;
#define MPSiteKeySize (256 / 8) /* bytes */ // Size of HMAC-SHA-256
typedef const uint8_t *MPSiteKey;
typedef const char *MPKeyID;
typedef enum( uint8_t, MPKeyPurpose ) {
/** Generate a key for authentication. */
MPSiteVariantPassword,
MPKeyPurposeAuthentication,
/** Generate a name for identification. */
MPSiteVariantLogin,
/** Generate an answer to a security question. */
MPSiteVariantAnswer,
MPKeyPurposeIdentification,
/** Generate a recovery token. */
MPKeyPurposeRecovery,
};
typedef enum( unsigned int, MPSiteTypeClass ) {
/** Generate the password. */
MPSiteTypeClassGenerated = 1 << 4,
/** Store the password. */
MPSiteTypeClassStored = 1 << 5,
// bit 4 - 9
typedef enum( uint16_t, MPResultTypeClass ) {
/** Use the site key to generate a password from a template. */
MPResultTypeClassTemplate = 1 << 4,
/** Use the site key to encrypt and decrypt a stateful entity. */
MPResultTypeClassState = 1 << 5,
/** Use the site key to derive a site-specific object. */
MPResultTypeClassDerive = 1 << 6,
};
typedef enum( unsigned int, MPSiteFeature ) {
// bit 10 - 15
typedef enum( uint16_t, MPSiteFeature ) {
/** Export the key-protected content data. */
MPSiteFeatureExportContent = 1 << 10,
/** Never export content. */
MPSiteFeatureDevicePrivate = 1 << 11,
/** Don't use this as the primary authentication result type. */
MPSiteFeatureAlternative = 1 << 12,
};
typedef enum( unsigned int, MPSiteType) {
MPSiteTypeGeneratedMaximum = 0x0 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedLong = 0x1 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedMedium = 0x2 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedBasic = 0x4 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedShort = 0x3 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedPIN = 0x5 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedName = 0xE | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedPhrase = 0xF | MPSiteTypeClassGenerated | 0x0,
// bit 0-3 | MPResultTypeClass | MPSiteFeature
typedef enum( uint32_t, MPResultType ) {
/** pg^VMAUBk5x3p%HP%i4= */
MPResultTypeTemplateMaximum = 0x0 | MPResultTypeClassTemplate | 0x0,
/** BiroYena8:Kixa */
MPResultTypeTemplateLong = 0x1 | MPResultTypeClassTemplate | 0x0,
/** BirSuj0- */
MPResultTypeTemplateMedium = 0x2 | MPResultTypeClassTemplate | 0x0,
/** pO98MoD0 */
MPResultTypeTemplateBasic = 0x4 | MPResultTypeClassTemplate | 0x0,
/** Bir8 */
MPResultTypeTemplateShort = 0x3 | MPResultTypeClassTemplate | 0x0,
/** 2798 */
MPResultTypeTemplatePIN = 0x5 | MPResultTypeClassTemplate | 0x0,
/** birsujano */
MPResultTypeTemplateName = 0xE | MPResultTypeClassTemplate | 0x0,
/** bir yennoquce fefi */
MPResultTypeTemplatePhrase = 0xF | MPResultTypeClassTemplate | 0x0,
MPSiteTypeStoredPersonal = 0x0 | MPSiteTypeClassStored | MPSiteFeatureExportContent,
MPSiteTypeStoredDevicePrivate = 0x1 | MPSiteTypeClassStored | MPSiteFeatureDevicePrivate,
/** Custom saved password. */
MPResultTypeStatePersonal = 0x0 | MPResultTypeClassState | MPSiteFeatureExportContent,
/** Custom saved password that should not be exported from the device. */
MPResultTypeStateDevice = 0x1 | MPResultTypeClassState | MPSiteFeatureDevicePrivate,
/** Derive a unique binary key. */
MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative,
MPResultTypeDefault = MPResultTypeTemplateLong,
};
typedef enum ( uint32_t, MPCounterValue ) {
/** Use a time-based counter value, resulting in a TOTP generator. */
MPCounterValueTOTP = 0,
/** The initial value for a site's counter. */
MPCounterValueInitial = 1,
MPCounterValueDefault = MPCounterValueInitial,
MPCounterValueFirst = MPCounterValueTOTP,
MPCounterValueLast = UINT32_MAX,
};
//// Type utilities.
/**
* @return The variant represented by the given name.
* @return The purpose represented by the given name.
*/
const MPSiteVariant mpw_variantWithName(const char *variantName);
const MPKeyPurpose mpw_purposeWithName(const char *purposeName);
/**
* @return An internal string containing the scope identifier to apply when encoding for the given variant.
* @return The standard name for the given purpose.
*/
const char *mpw_scopeForVariant(MPSiteVariant variant);
const char *mpw_nameForPurpose(MPKeyPurpose purpose);
/**
* @return An internal string containing the scope identifier to apply when encoding for the given purpose.
*/
const char *mpw_scopeForPurpose(MPKeyPurpose purpose);
/**
* @return The type represented by the given name.
* @return The password type represented by the given name.
*/
const MPSiteType mpw_typeWithName(const char *typeName);
const MPResultType mpw_typeWithName(const char *typeName);
/**
* @return The standard name for the given password type.
*/
const char *mpw_nameForType(MPResultType resultType);
/**
* @return A newly allocated array of internal strings that express the templates to use for the given type.
@@ -90,12 +137,12 @@ const MPSiteType mpw_typeWithName(const char *typeName);
* If an unsupported type is given, count will be 0 and will return NULL.
* The array needs to be free'ed, the strings themselves must not be free'ed or modified.
*/
const char **mpw_templatesForType(MPSiteType type, size_t *count);
const char **mpw_templatesForType(MPResultType type, size_t *count);
/**
* @return An internal string that contains the password encoding template of the given type
* for a seed that starts with the given byte.
*/
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte);
const char *mpw_templateForType(MPResultType type, uint8_t seedByte);
/**
* @return An internal string that contains all the characters that occur in the given character class.

View File

@@ -16,11 +16,11 @@
// 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>
@@ -31,59 +31,100 @@
#include <scrypt/sha256.h>
#elif HAS_SODIUM
#include "sodium.h"
#ifdef SODIUM_LIBRARY_MINIMAL
#include "crypto_stream_aes128ctr.h"
#include "crypto_kdf_blake2b.h"
#endif
#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) {
int mpw_verbosity = inf_level;
if (*bufferSize == (size_t)-1)
bool mpw_push_buf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) {
if (!buffer || !bufferSize || !pushBuffer || !pushSize)
return false;
if (*bufferSize == ERR)
// The buffer was marked as broken, it is missing a previous push. Abort to avoid corrupt content.
return;
return false;
*bufferSize += pushSize;
uint8_t *resizedBuffer = realloc( *buffer, *bufferSize );
if (!resizedBuffer) {
if (!mpw_realloc( buffer, bufferSize, pushSize )) {
// realloc failed, we can't push. Mark the buffer as broken.
mpw_free( *buffer, *bufferSize - pushSize );
*bufferSize = (size_t)-1;
mpw_free( *buffer, *bufferSize );
*bufferSize = (size_t)ERR;
*buffer = NULL;
return;
return false;
}
*buffer = resizedBuffer;
uint8_t *pushDst = *buffer + *bufferSize - pushSize;
memcpy( pushDst, pushBuffer, pushSize );
uint8_t *bufferOffset = *buffer + *bufferSize - pushSize;
memcpy( bufferOffset, pushBuffer, pushSize );
return true;
}
void mpw_push_string(uint8_t **buffer, size_t *const bufferSize, const char *pushString) {
bool mpw_push_string(uint8_t **const buffer, size_t *const bufferSize, const char *pushString) {
mpw_push_buf( buffer, bufferSize, pushString, strlen( pushString ) );
return pushString && mpw_push_buf( buffer, bufferSize, pushString, strlen( pushString ) );
}
void mpw_push_int(uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt) {
bool mpw_string_push(char **const string, const char *pushString) {
mpw_push_buf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
if (!*string)
*string = calloc( 1, sizeof( char ) );
size_t stringLength = strlen( *string );
return pushString && mpw_push_buf( (uint8_t **const)string, &stringLength, pushString, strlen( pushString ) + 1 );
}
void mpw_free(const void *buffer, const size_t bufferSize) {
bool mpw_string_pushf(char **const string, const char *pushFormat, ...) {
if (buffer) {
memset( (void *)buffer, 0, bufferSize );
free( (void *)buffer );
}
va_list args;
va_start( args, pushFormat );
char *pushString = NULL;
bool success = vasprintf( &pushString, pushFormat, args ) >= 0 && mpw_string_push( string, pushString );
va_end( args );
mpw_free_string( pushString );
return success;
}
void mpw_free_string(const char *string) {
bool mpw_push_int(uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt) {
mpw_free( string, strlen( string ) );
return mpw_push_buf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
}
uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
bool __mpw_realloc(void **buffer, size_t *bufferSize, const size_t deltaSize) {
if (!buffer)
return false;
void *newBuffer = realloc( *buffer, (bufferSize? *bufferSize: 0) + deltaSize );
if (!newBuffer)
return false;
*buffer = newBuffer;
if (bufferSize)
*bufferSize += deltaSize;
return true;
}
bool mpw_free(const void *buffer, const size_t bufferSize) {
if (!buffer)
return false;
memset( (void *)buffer, 0, bufferSize );
free( (void *)buffer );
return true;
}
bool mpw_free_string(const char *string) {
return string && mpw_free( string, strlen( string ) );
}
uint8_t const *mpw_kdf_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
uint64_t N, uint32_t r, uint32_t p) {
if (!secret || !salt)
@@ -99,74 +140,196 @@ uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_
return NULL;
}
#elif HAS_SODIUM
if (crypto_pwhash_scryptsalsa208sha256_ll( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize) != 0 ) {
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;
#if HAS_SODIUM
if (keySize < crypto_generichash_blake2b_KEYBYTES_MIN || keySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
subkeySize < crypto_generichash_blake2b_KEYBYTES_MIN || subkeySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
contextSize < crypto_generichash_blake2b_BYTES_MIN || contextSize > crypto_generichash_blake2b_BYTES_MAX ||
(personal && strlen( personal ) > crypto_generichash_blake2b_PERSONALBYTES)) {
errno = EINVAL;
free( subkey );
return NULL;
}
uint8_t saltBuf[crypto_generichash_blake2b_SALTBYTES];
bzero( saltBuf, sizeof saltBuf );
if (id) {
uint64_t id_n = htonll( id );
memcpy( saltBuf, &id_n, sizeof id_n );
}
uint8_t personalBuf[crypto_generichash_blake2b_PERSONALBYTES];
bzero( personalBuf, sizeof saltBuf );
if (personal && strlen( personal ))
memcpy( personalBuf, personal, strlen( personal ) );
if (crypto_generichash_blake2b_salt_personal( subkey, subkeySize, context, contextSize, key, keySize, saltBuf, personalBuf ) != 0) {
mpw_free( subkey, subkeySize );
return NULL;
}
#else
#error No crypto support for mpw_kdf_blake2b.
#endif
return subkey;
}
uint8_t const *mpw_hash_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *message, const size_t messageSize) {
if (!key || !keySize || !message || !messageSize)
return NULL;
#if HAS_CPERCIVA
uint8_t *const mac = malloc( 32 );
if (!mac)
return NULL;
HMAC_SHA256_Buf( key, keySize, message, messageSize, mac );
#elif HAS_SODIUM
uint8_t *const buffer = malloc( crypto_auth_hmacsha256_BYTES );
if (!buffer)
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_SODIUM
if (!key || keySize < crypto_stream_KEYBYTES)
return NULL;
uint8_t nonce[crypto_stream_NONCEBYTES];
bzero( (void *)nonce, sizeof( nonce ) );
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (encrypt) {
uint8_t *const cipherBuf = malloc( bufSize );
if (crypto_stream_aes128ctr_xor( cipherBuf, buf, bufSize, nonce, key ) != 0) {
mpw_free( cipherBuf, bufSize );
return NULL;
}
return cipherBuf;
}
else {
uint8_t *const plainBuf = malloc( bufSize );
if (crypto_stream_aes128ctr( plainBuf, bufSize, nonce, key ) != 0) {
mpw_free( plainBuf, bufSize );
return NULL;
}
for (size_t c = 0; c < bufSize; ++c)
plainBuf[c] = buf[c] ^ plainBuf[c];
return plainBuf;
}
#pragma clang diagnostic pop
#pragma GCC diagnostic pop
#else
#error No crypto support for mpw_aes.
#endif
}
uint8_t const *mpw_aes_encrypt(const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, const size_t bufSize) {
return mpw_aes( true, key, keySize, plainBuf, bufSize );
}
uint8_t const *mpw_aes_decrypt(const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, const size_t bufSize) {
return mpw_aes( false, key, keySize, cipherBuf, bufSize );
}
MPKeyID mpw_id_buf(const void *buf, size_t length) {
if (!buf)
return "<unset>";
#if HAS_CPERCIVA
uint8_t hash[32];
SHA256_Buf( buf, length, hash );
return mpw_hex( hash, 32 );
#elif HAS_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;
}
static char *str_str;
const char *mpw_str(const char *format, ...) {
va_list args;
va_start( args, format );
vasprintf( &str_str, format, args );
va_end( args );
return str_str;
}
static char **mpw_hex_buf;
static unsigned int mpw_hex_buf_i;
const char *mpw_hex(const void *buf, size_t length) {
// FIXME
if (!mpw_hex_buf) {
mpw_hex_buf = malloc( 10 * sizeof( char * ) );
for (uint8_t i = 0; i < 10; ++i)
mpw_hex_buf[i] = NULL;
}
// FIXME: Not thread-safe
if (!mpw_hex_buf)
mpw_hex_buf = calloc( 10, sizeof( char * ) );
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
mpw_hex_buf[mpw_hex_buf_i] = realloc( mpw_hex_buf[mpw_hex_buf_i], length * 2 + 1 );
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];
}
@@ -214,7 +377,9 @@ 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;

View File

@@ -16,99 +16,167 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#ifndef _MPW_UTIL_H
#define _MPW_UTIL_H
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include "mpw-types.h"
//// Logging.
#ifndef trc
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 >= 3) \
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 >= 2) \
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 >= 1) \
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 >= 0) \
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 >= -1) \
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 >= -2) \
fprintf( stderr, __VA_ARGS__ ); })
#endif
#ifndef min
#define min(a, b) ({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
#endif
#ifndef max
#define max(a, b) ({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
#endif
#ifndef ERR
#define ERR -1
#endif
#ifndef stringify
#define stringify(s) #s
#endif
#ifndef stringify_def
#define stringify_def(s) stringify(s)
#endif
//// Buffers and memory.
/** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs. */
#define mpw_alloc_array(_count, _type, ...) ({ \
_type stackElements[] = { __VA_ARGS__ }; \
_count = sizeof( stackElements ) / sizeof( _type ); \
if (_count) \
*_count = sizeof( stackElements ) / sizeof( _type ); \
_type *allocElements = malloc( sizeof( stackElements ) ); \
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
allocElements; \
})
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
void mpw_push_buf(
bool mpw_push_buf(
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
/** Push a string onto a buffer. reallocs the given buffer and appends the given string. */
void mpw_push_string(
bool mpw_push_string(
uint8_t **buffer, size_t *const bufferSize, const char *pushString);
/** Push a string onto another string. reallocs the target string and appends the source string. */
bool mpw_string_push(
char **const string, const char *pushString);
bool mpw_string_pushf(
char **const string, const char *pushFormat, ...);
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
void mpw_push_int(
bool mpw_push_int(
uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt);
/** Reallocate the given buffer from the given size by adding the delta size.
* On success, the buffer size pointer will be updated to the buffer's new size
* and the buffer pointer may be updated to a new memory address.
* On failure, the buffer and pointers will remain unaffected.
* @param buffer A pointer to the buffer to reallocate.
* @param bufferSize A pointer to the buffer's actual size.
* @param deltaSize The amount to increase the buffer's size by.
* @return true if successful, false if reallocation failed.
*/
#define mpw_realloc(buffer, bufferSize, deltaSize) \
__mpw_realloc( (void **)buffer, bufferSize, deltaSize )
bool __mpw_realloc(void **buffer, size_t *bufferSize, const size_t deltaSize);
/** Free a buffer after zero'ing its contents. */
void mpw_free(
bool mpw_free(
const void *buffer, const size_t bufferSize);
/** Free a string after zero'ing its contents. */
void mpw_free_string(
bool mpw_free_string(
const char *string);
//// Cryptographic functions.
/** Perform a scrypt-based key derivation on the given key using the given salt and scrypt parameters.
* @return A new keySize-size allocated buffer. */
uint8_t const *mpw_scrypt(
/** Derive a key from the given secret and salt using the scrypt KDF.
* @return A new keySize allocated buffer containing the key. */
uint8_t const *mpw_kdf_scrypt(
const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
uint64_t N, uint32_t r, uint32_t p);
/** Calculate a SHA256-based HMAC by encrypting the given salt with the given key.
* @return A new 32-byte allocated buffer. */
uint8_t const *mpw_hmac_sha256(
/** Derive a subkey from the given key using the blake2b KDF.
* @return A new keySize allocated buffer containing the key. */
uint8_t const *mpw_kdf_blake2b(
const size_t subkeySize, const uint8_t *key, const size_t keySize,
const uint8_t *context, const size_t contextSize, const uint64_t id, const char *personal);
/** Calculate the MAC for the given message with the given key using SHA256-HMAC.
* @return A new 32-byte allocated buffer containing the MAC. */
uint8_t const *mpw_hash_hmac_sha256(
const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize);
/** Encrypt a plainBuf with the given key using AES-128-CBC.
* @return A new bufSize allocated buffer containing the cipherBuf. */
uint8_t const *mpw_aes_encrypt(
const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, const size_t bufSize);
/** Decrypt a cipherBuf with the given key using AES-128-CBC.
* @return A new bufSize allocated buffer containing the plainBuf. */
uint8_t const *mpw_aes_decrypt(
const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, const size_t bufSize);
//// Visualizers.
/** Compose a formatted string.
* @return A C-string in a reused buffer, do not free or store it. */
const char *mpw_str(const char *format, ...);
/** Encode a buffer as a string of hexadecimal characters.
* @return A C-string in a reused buffer, do not free or store it. */
const char *mpw_hex(const void *buf, size_t length);
const char *mpw_hex_l(uint32_t number);
/** Encode a fingerprint for a buffer.
* @return A C-string in a reused buffer, do not free or store it. */
const char *mpw_id_buf(const void *buf, size_t length);
MPKeyID mpw_id_buf(const void *buf, size_t length);
/** Compare two fingerprints for equality.
* @return true if the buffers represent identical fingerprints. */
bool mpw_id_buf_equals(const char *id1, const char *id2);
/** Encode a visual fingerprint for a user.
* @return A newly allocated string. */
const char *mpw_identicon(const char *fullName, const char *masterPassword);
@@ -117,3 +185,5 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword);
/** @return The amount of display characters in the given UTF-8 string. */
const size_t mpw_utf8_strlen(const char *utf8String);
#endif // _MPW_UTIL_H

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

View File

@@ -17,15 +17,17 @@
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */; };
93D3932889B6B4206E66A6D6 /* PearlEMail.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */; };
93D39359B0DF9823F6C56A05 /* PearlHangDetector.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39D8508BF868907E9732E /* PearlHangDetector.h */; };
93D39392DEDA376F93C6C718 /* MPCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39BAA71DE51B4D8A1286C /* MPCell.m */; };
93D3939661CE37180AF7CD6A /* MPStoreViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3957D76F71A652716EECC /* MPStoreViewController.m */; };
93D393AA69A1193401160418 /* UIView+AlphaScale.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39488AB33616661725929 /* UIView+AlphaScale.m */; };
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */; };
93D3942C1B117EE4851AA7B6 /* UIView+Visible.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3952910EDB8E0EBC94BA9 /* UIView+Visible.m */; };
93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */; };
93D394B5036C882B33C71872 /* MPPasswordsSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */; };
93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E7A12CC352B2825AA66 /* MPSitesSegue.m */; };
93D39508A6814612A5B3C226 /* MPMessageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399B36CDB2004D7C51391 /* MPMessageViewController.m */; };
93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394482BB07F90E8FD1314 /* UIResponder+PearlFirstResponder.h */; };
93D395373A425B05C86B2268 /* PearlHangDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39BDA5DB85FCDE4E6450A /* PearlHangDetector.m */; };
93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */; };
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; };
93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B381350802A194BF332 /* MPAvatarCell.m */; };
@@ -38,7 +40,9 @@
93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D3942A356B639724157982 /* PearlOverlay.h */; };
93D396D8B67DA6522CDBA142 /* MPCoachmarkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */; };
93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */; };
93D397FCAAC6FA885247A4F9 /* PearlLinks.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39FEFC296AF6E7DF8E468 /* PearlLinks.m */; };
93D3980046016EFD05B35BC5 /* PearlUICollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */; };
93D39861AEE621B287EA93E9 /* PearlLinks.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D399C408B705172FA0FA02 /* PearlLinks.h */; };
93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */; };
93D399246DC90F50913A1287 /* UIResponder+PearlFirstResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */; };
93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; };
@@ -47,13 +51,13 @@
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39FBF8FCEB4C106272334 /* NSOrderedSetOrArray.h */; };
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; };
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadItems.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */; };
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPSiteCell.m */; };
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */; };
93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */; };
93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */; };
93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; };
93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; };
93D39B8F90F58A5D158DDBA3 /* MPSitesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPSitesViewController.m */; };
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; };
93D39BFB5F5F9337F6565DE3 /* UIView+Visible.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B7B765546B1F1900CB7 /* UIView+Visible.h */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
@@ -178,6 +182,7 @@
DA5A09E0171A70E4005284AB /* play@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5A09DE171A70E4005284AB /* play@2x.png */; };
DA5A09EA171BB0F7005284AB /* unlocked.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5A09E8171BB0F7005284AB /* unlocked.png */; };
DA5A09EB171BB0F7005284AB /* unlocked@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5A09E9171BB0F7005284AB /* unlocked@2x.png */; };
DA5B0B401F36469400B663F0 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = DA5B0B3E1F36469400B663F0 /* base64.c */; };
DA5BFA49147E415C00F98B1E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA48147E415C00F98B1E /* UIKit.framework */; };
DA5BFA4B147E415C00F98B1E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA5BFA4D147E415C00F98B1E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */; };
@@ -244,6 +249,7 @@
DAA1765219D8B82B0044227B /* copy_pw.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763C19D8B82B0044227B /* copy_pw.png */; };
DAA1765319D8B82B0044227B /* choose_type@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763D19D8B82B0044227B /* choose_type@2x.png */; };
DAA1765419D8B82B0044227B /* choose_type.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763E19D8B82B0044227B /* choose_type.png */; };
DAA449D21EEC4B5800E7BDD5 /* mpw-marshall.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */; };
DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */; };
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; };
@@ -476,14 +482,14 @@
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; };
93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlAdjustInsets.m"; sourceTree = "<group>"; };
93D39149A5F1F9B174D6D061 /* MPStoreViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStoreViewController.h; sourceTree = "<group>"; };
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsViewController.h; sourceTree = "<group>"; };
93D3914D7597F9A28DB9D85E /* MPSitesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesViewController.h; sourceTree = "<group>"; };
93D39156E806BB78E04F78B9 /* PearlSizedTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlSizedTextView.m; sourceTree = "<group>"; };
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; };
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadItems.h"; sourceTree = "<group>"; };
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; };
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
93D3924EE15017F8A12CB436 /* MPSitesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesViewController.m; sourceTree = "<group>"; };
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-types.c"; sourceTree = "<group>"; };
93D392D76C091DEA3319F11D /* UIView+AlphaScale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+AlphaScale.h"; sourceTree = "<group>"; };
@@ -517,11 +523,12 @@
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewController.m; sourceTree = "<group>"; };
93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRootSegue.m; sourceTree = "<group>"; };
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; };
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = "<group>"; };
93D39975CE5AEC99E3F086C7 /* MPSiteCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteCell.h; sourceTree = "<group>"; };
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = "<group>"; };
93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV2.m; sourceTree = "<group>"; };
93D399B36CDB2004D7C51391 /* MPMessageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMessageViewController.m; sourceTree = "<group>"; };
93D399C2F3D48E57C4803BDC /* NSPersistentStore+PearlMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSPersistentStore+PearlMigration.m"; sourceTree = "<group>"; };
93D399C408B705172FA0FA02 /* PearlLinks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLinks.h; sourceTree = "<group>"; };
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = "<group>"; };
93D399F244BB522A317811BB /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = "<group>"; };
93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+PearlFirstResponder.m"; sourceTree = "<group>"; };
@@ -539,9 +546,10 @@
93D39B455A71EC98C749E623 /* MPOverlayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOverlayViewController.h; sourceTree = "<group>"; };
93D39B7B765546B1F1900CB7 /* UIView+Visible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Visible.h"; sourceTree = "<group>"; };
93D39BAA71DE51B4D8A1286C /* MPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCell.m; sourceTree = "<group>"; };
93D39BDA5DB85FCDE4E6450A /* PearlHangDetector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlHangDetector.m; sourceTree = "<group>"; };
93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSString+MPMarkDown.m"; path = "iOS/NSString+MPMarkDown.m"; sourceTree = "<group>"; };
93D39C426E03358384018E85 /* MPAnswersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnswersViewController.m; sourceTree = "<group>"; };
93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsSegue.h; sourceTree = "<group>"; };
93D39C44361BE57AF0B3071F /* MPSitesSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesSegue.h; sourceTree = "<group>"; };
93D39C86E984EC65DA5ACB1D /* MPAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppSettingsViewController.h; sourceTree = "<group>"; };
93D39CB0EABD2748740992D8 /* MPMessageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMessageViewController.h; sourceTree = "<group>"; };
93D39CC01630D0421205C4C4 /* MPNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNavigationController.m; sourceTree = "<group>"; };
@@ -551,16 +559,18 @@
93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v3.c"; sourceTree = "<group>"; };
93D39D6604447D7708039155 /* MPAnswersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAnswersViewController.h; sourceTree = "<group>"; };
93D39D72239990DDAC2D75B0 /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
93D39D8508BF868907E9732E /* PearlHangDetector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlHangDetector.h; sourceTree = "<group>"; };
93D39D8A953779B35403AF6E /* PearlUICollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUICollectionView.m; sourceTree = "<group>"; };
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAvatarCell.h; sourceTree = "<group>"; };
93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+PearlAdjustInsets.h"; sourceTree = "<group>"; };
93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordCell.m; sourceTree = "<group>"; };
93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsSegue.m; sourceTree = "<group>"; };
93D39DEA995041A13DC9CAF7 /* MPSiteCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteCell.m; sourceTree = "<group>"; };
93D39E7A12CC352B2825AA66 /* MPSitesSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesSegue.m; sourceTree = "<group>"; };
93D39F556F2F142740A65E59 /* MPWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPWebViewController.h; sourceTree = "<group>"; };
93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = "<group>"; };
93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
93D39FBF8FCEB4C106272334 /* NSOrderedSetOrArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSOrderedSetOrArray.h; sourceTree = "<group>"; };
93D39FD9623E8D5571C0AEB3 /* MPAlgorithmV3.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV3.m; sourceTree = "<group>"; };
93D39FEFC296AF6E7DF8E468 /* PearlLinks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLinks.m; sourceTree = "<group>"; };
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
DA071BF1190187FE00179766 /* empty@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "empty@2x.png"; sourceTree = "<group>"; };
DA071BF2190187FE00179766 /* empty.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = empty.png; sourceTree = "<group>"; };
@@ -776,6 +786,8 @@
DA5A09DE171A70E4005284AB /* play@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "play@2x.png"; sourceTree = "<group>"; };
DA5A09E8171BB0F7005284AB /* unlocked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = unlocked.png; sourceTree = "<group>"; };
DA5A09E9171BB0F7005284AB /* unlocked@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "unlocked@2x.png"; sourceTree = "<group>"; };
DA5B0B3E1F36469400B663F0 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = "<group>"; };
DA5B0B3F1F36469400B663F0 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = "<group>"; };
DA5BFA44147E415C00F98B1E /* MasterPassword.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MasterPassword.app; sourceTree = BUILT_PRODUCTS_DIR; };
DA5BFA48147E415C00F98B1E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@@ -847,6 +859,8 @@
DAA1763C19D8B82B0044227B /* copy_pw.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = copy_pw.png; sourceTree = "<group>"; };
DAA1763D19D8B82B0044227B /* choose_type@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "choose_type@2x.png"; sourceTree = "<group>"; };
DAA1763E19D8B82B0044227B /* choose_type.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = choose_type.png; sourceTree = "<group>"; };
DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall.c"; sourceTree = "<group>"; };
DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall.h"; sourceTree = "<group>"; };
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
DABD360F1711E29400CF925C /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
@@ -1708,12 +1722,16 @@
93D39A2239FFFE6BEC83E191 /* C */ = {
isa = PBXGroup;
children = (
DA5B0B3E1F36469400B663F0 /* base64.c */,
DA5B0B3F1F36469400B663F0 /* base64.h */,
93D390A99850139D0FF0211E /* mpw-algorithm_v0.c */,
93D396F918E6470DB846C17F /* mpw-algorithm_v1.c */,
93D390A3B351FEF1B9EDAB56 /* mpw-algorithm_v2.c */,
93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */,
93D3969393A3A46BD27D7078 /* mpw-algorithm.c */,
93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */,
DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */,
DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */,
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */,
93D39AFD17CBE324D745DAB0 /* mpw-types.h */,
93D396C311C3725870343EE0 /* mpw-util.c */,
@@ -2885,12 +2903,12 @@
93D39CC01630D0421205C4C4 /* MPNavigationController.m */,
93D39B455A71EC98C749E623 /* MPOverlayViewController.h */,
93D395105935859D71679931 /* MPOverlayViewController.m */,
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */,
93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */,
93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */,
93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */,
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */,
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */,
93D39975CE5AEC99E3F086C7 /* MPSiteCell.h */,
93D39DEA995041A13DC9CAF7 /* MPSiteCell.m */,
93D39C44361BE57AF0B3071F /* MPSitesSegue.h */,
93D39E7A12CC352B2825AA66 /* MPSitesSegue.m */,
93D3914D7597F9A28DB9D85E /* MPSitesViewController.h */,
93D3924EE15017F8A12CB436 /* MPSitesViewController.m */,
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */,
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */,
DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */,
@@ -3057,6 +3075,8 @@
DAFE45F915039823003ABA7C /* Resources */,
93D39FBF8FCEB4C106272334 /* NSOrderedSetOrArray.h */,
93D39789AAF49338F8AC8B02 /* NSOrderedSetOrArray.m */,
93D39BDA5DB85FCDE4E6450A /* PearlHangDetector.m */,
93D39D8508BF868907E9732E /* PearlHangDetector.h */,
);
path = Pearl;
sourceTree = "<group>";
@@ -3170,6 +3190,8 @@
93D392D76C091DEA3319F11D /* UIView+AlphaScale.h */,
93D3952910EDB8E0EBC94BA9 /* UIView+Visible.m */,
93D39B7B765546B1F1900CB7 /* UIView+Visible.h */,
93D39FEFC296AF6E7DF8E468 /* PearlLinks.m */,
93D399C408B705172FA0FA02 /* PearlLinks.h */,
);
path = "Pearl-UIKit";
sourceTree = "<group>";
@@ -3280,6 +3302,8 @@
93D399E4BC1E092A8C8B12AE /* NSOrderedSetOrArray.h in Headers */,
93D3959696396A91961C6148 /* UIView+AlphaScale.h in Headers */,
93D39BFB5F5F9337F6565DE3 /* UIView+Visible.h in Headers */,
93D39861AEE621B287EA93E9 /* PearlLinks.h in Headers */,
93D39359B0DF9823F6C56A05 /* PearlHangDetector.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3848,6 +3872,7 @@
DABD3C151711E2DC00CF925C /* MPiOSAppDelegate.m in Sources */,
DA0CC5941EB6B030009A8ED9 /* MPSiteEntity+CoreDataClass.m in Sources */,
DABD3C1C1711E2DC00CF925C /* MPGuideViewController.m in Sources */,
DAA449D21EEC4B5800E7BDD5 /* mpw-marshall.c in Sources */,
DABD3C1E1711E2DC00CF925C /* MPPreferencesViewController.m in Sources */,
DABD3C1F1711E2DC00CF925C /* MPTypeViewController.m in Sources */,
DABD3C211711E2DC00CF925C /* MPiOSConfig.m in Sources */,
@@ -3859,14 +3884,14 @@
DA095E75172F4CD8001C948B /* MPLogsViewController.m in Sources */,
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */,
93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */,
93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */,
93D39B8F90F58A5D158DDBA3 /* MPSitesViewController.m in Sources */,
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */,
93D39392DEDA376F93C6C718 /* MPCell.m in Sources */,
DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */,
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */,
93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */,
93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */,
DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */,
93D394B5036C882B33C71872 /* MPPasswordsSegue.m in Sources */,
93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */,
DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */,
93D39673DDC085BE72C34D7C /* MPPopdownSegue.m in Sources */,
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */,
@@ -3885,6 +3910,7 @@
93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */,
93D395B715D15F2B56F2A2EE /* mpw-types.c in Sources */,
93D39943D01E70DAC3B0DF76 /* mpw-util.c in Sources */,
DA5B0B401F36469400B663F0 /* base64.c in Sources */,
93D39577FD8BB0945DB2F0A3 /* MPAlgorithmV3.m in Sources */,
93D39E5F7F6D7F5C0FAD090F /* MPTypes.m in Sources */,
DA0CC58C1EB6B030009A8ED9 /* MPGeneratedSiteEntity+CoreDataClass.m in Sources */,
@@ -3995,6 +4021,8 @@
93D392A33CCE85431E910C7B /* NSOrderedSetOrArray.m in Sources */,
93D393AA69A1193401160418 /* UIView+AlphaScale.m in Sources */,
93D3942C1B117EE4851AA7B6 /* UIView+Visible.m in Sources */,
93D397FCAAC6FA885247A4F9 /* PearlLinks.m in Sources */,
93D395373A425B05C86B2268 /* PearlHangDetector.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -7,7 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */; };
93D390C676DF52DA7E459F19 /* MPSitesWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPSitesWindow.m */; };
93D391E61DC23E128DA4446C /* NSView+Traversing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393EE88DE554BCCBC1C2D /* NSView+Traversing.h */; };
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
93D393A1646430FAAC73E7FE /* MPMacApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F83DD151985F2C7345A /* MPMacApplication.m */; };
@@ -20,11 +20,9 @@
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */; };
93D39F833DEC1C89B2F795AC /* MPSitesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPSitesWindowController.m */; };
DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */; };
DA0933D01747B91B00DE1CEF /* appstore.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CF1747B91B00DE1CEF /* appstore.png */; };
DA09745A1E99582900F0BFE8 /* mpw-tests-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA0974561E99582200F0BFE8 /* mpw-tests-util.c */; };
DA09745B1E99582900F0BFE8 /* mpw-tests.c in Sources */ = {isa = PBXBuildFile; fileRef = DA0974571E99582200F0BFE8 /* mpw-tests.c */; };
DA09745E1E99586600F0BFE8 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA09745D1E99586600F0BFE8 /* libxml2.tbd */; };
DA0979681E9A834C00F0BFE8 /* libsodium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA0979571E9A824700F0BFE8 /* libsodium.a */; };
DA0CC53E1EB57B69009A8ED9 /* Fabric.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA0CC53D1EB57B69009A8ED9 /* Fabric.plist */; };
@@ -34,8 +32,33 @@
DA16B342170661E0000A0EAB /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; };
DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA16B343170661EE000A0EAB /* Cocoa.framework */; };
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
DA1C7AAA1F1A8F24009A3551 /* mpw-marshall.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */; };
DA1C7AAB1F1A8F24009A3551 /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA1C7AAC1F1A8F24009A3551 /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA1C7AAD1F1A8F24009A3551 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
DA1C7AAF1F1A8F24009A3551 /* libsodium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA0979571E9A824700F0BFE8 /* libsodium.a */; };
DA1C7AB01F1A8F24009A3551 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA09745D1E99586600F0BFE8 /* libxml2.tbd */; };
DA1C7AC31F1A8FBA009A3551 /* mpw-cli.c in Sources */ = {isa = PBXBuildFile; fileRef = DA1C7AB91F1A8F6E009A3551 /* mpw-cli.c */; };
DA1C7ACA1F1A8FD8009A3551 /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA1C7ACB1F1A8FD8009A3551 /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA1C7ACD1F1A8FD8009A3551 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
DA1C7ACF1F1A8FD8009A3551 /* libsodium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA0979571E9A824700F0BFE8 /* libsodium.a */; };
DA1C7AD01F1A8FD8009A3551 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA09745D1E99586600F0BFE8 /* libxml2.tbd */; };
DA1C7AD71F1A8FE6009A3551 /* mpw-bench.c in Sources */ = {isa = PBXBuildFile; fileRef = DA1C7AB81F1A8F6E009A3551 /* mpw-bench.c */; };
DA1C7AD81F1A8FF4009A3551 /* mpw-tests-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA1C7ABA1F1A8F6E009A3551 /* mpw-tests-util.c */; };
DA1C7AD91F1A8FF4009A3551 /* mpw-tests.c in Sources */ = {isa = PBXBuildFile; fileRef = DA1C7ABC1F1A8F6E009A3551 /* mpw-tests.c */; };
DA2508F119511D3600AC23F1 /* MPPasswordWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */; };
DA250925195148E200AC23F1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
DA26861D1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = DA26860A1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataClass.m */; };
DA26861E1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DA26860C1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.m */; };
DA26861F1EBFD7A40001E37E /* MPSiteEntity+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = DA26860E1EBFD7A40001E37E /* MPSiteEntity+CoreDataClass.m */; };
DA2686201EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2686101EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.m */; };
DA2686211EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2686121EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataClass.m */; };
DA2686221EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2686141EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.m */; };
DA2686231EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2686161EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.m */; };
DA2686241EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2686181EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.m */; };
DA2686251EBFD7A40001E37E /* MPUserEntity+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = DA26861A1EBFD7A40001E37E /* MPUserEntity+CoreDataClass.m */; };
DA2686261EBFD7A40001E37E /* MPUserEntity+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DA26861C1EBFD7A40001E37E /* MPUserEntity+CoreDataProperties.m */; };
DA2C3D681BD9665B001137B3 /* PearlProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2C3D661BD9665B001137B3 /* PearlProfiler.h */; };
DA2C3D691BD9665B001137B3 /* PearlProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2C3D671BD9665B001137B3 /* PearlProfiler.m */; };
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */; };
@@ -48,11 +71,6 @@
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */; };
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */; };
DA32CFDC19CF1C70004F3F0E /* MPStoredSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */; };
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */; };
DA32CFE219CF1C71004F3F0E /* MPSiteQuestionEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */; };
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */; };
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; };
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; };
DA3B844F190FC60900246EEA /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3B844A190FC5A900246EEA /* Crashlytics.framework */; };
@@ -65,6 +83,10 @@
DA4DAE951A7D8117003E5423 /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4DAE931A7D8117003E5423 /* MPTypes.m */; };
DA5180CA19FF2F9200A587E9 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5180C719FF2F9200A587E9 /* MPAlgorithmV2.m */; };
DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5180CD19FF307E00A587E9 /* MPAppDelegate_Store.m */; };
DA5B0B3A1F36467300B663F0 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = DA5B0B381F36467300B663F0 /* base64.c */; };
DA5B0B3B1F36467800B663F0 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = DA5B0B381F36467300B663F0 /* base64.c */; };
DA5B0B3C1F36467900B663F0 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = DA5B0B381F36467300B663F0 /* base64.c */; };
DA5B0B3D1F36467900B663F0 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = DA5B0B381F36467300B663F0 /* base64.c */; };
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C981724A667003798D8 /* MPAlgorithm.m */; };
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C9A1724A667003798D8 /* MPAlgorithmV0.m */; };
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5C9C1724A667003798D8 /* MPAlgorithmV1.m */; };
@@ -88,6 +110,8 @@
DA6774431A474A3B004F356A /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
DA6774451A474A3B004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA6774461A474A3B004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA7471A31F2B71AE005F3468 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */; };
DA7471A61F2B71B9005F3468 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */; };
DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */; };
DA89D4ED1A51EABD00AC64D7 /* Pearl-Cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */; };
DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; };
@@ -98,6 +122,7 @@
DA9261521BE1A86700369DE5 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261501BE1A86700369DE5 /* Fabric.framework */; };
DA9261541BE1A88900369DE5 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261531BE1A88900369DE5 /* libc++.tbd */; };
DA9261561BE1A89600369DE5 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261551BE1A89600369DE5 /* libz.tbd */; };
DAA449D51EEC4B6B00E7BDD5 /* mpw-marshall.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */; };
DAAA81B0195A8D1300FA30D9 /* gradient.png in Resources */ = {isa = PBXBuildFile; fileRef = DAAA81AF195A8D1300FA30D9 /* gradient.png */; };
DAADCC4719FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h in Headers */ = {isa = PBXBuildFile; fileRef = DAADCC3E19FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h */; };
DAADCC4819FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */; };
@@ -229,6 +254,24 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
DA1C7AB11F1A8F24009A3551 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
DA1C7AD11F1A8FD8009A3551 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
DA6774391A474A03004F356A /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -265,7 +308,7 @@
93D39240B5143E01F0B75E96 /* MPSiteModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteModel.h; sourceTree = "<group>"; };
93D392870DF659AFC1870521 /* NSView+Traversing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSView+Traversing.m"; sourceTree = "<group>"; };
93D392A4F3DE0BD758B9B056 /* MPNoStateButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNoStateButton.h; sourceTree = "<group>"; };
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindowController.h; sourceTree = "<group>"; };
93D392C3918763B3B72CF366 /* MPSitesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesWindowController.h; sourceTree = "<group>"; };
93D392FD65A1DF0E8B2A45C6 /* MPGradientView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGradientView.h; sourceTree = "<group>"; };
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInitialWindowController.h; sourceTree = "<group>"; };
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
@@ -274,21 +317,18 @@
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesTableView.m; sourceTree = "<group>"; };
93D39538C4CEFF46DF379254 /* MPNoStateButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNoStateButton.m; sourceTree = "<group>"; };
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
93D3977484534E99F9BA579D /* MPPasswordWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindow.h; sourceTree = "<group>"; };
93D3977484534E99F9BA579D /* MPSitesWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesWindow.h; sourceTree = "<group>"; };
93D39934FD8D5BFABA46F41C /* MPGradientView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGradientView.m; sourceTree = "<group>"; };
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindowController.m; sourceTree = "<group>"; };
93D39A57A7823DE98A0FF83C /* MPSitesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesWindowController.m; sourceTree = "<group>"; };
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
93D39AC6360DDC16AEAA4119 /* MPSitesTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesTableView.h; sourceTree = "<group>"; };
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInitialWindowController.m; sourceTree = "<group>"; };
93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindow.m; sourceTree = "<group>"; };
93D39D9D0061FF1159998F06 /* MPSitesWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesWindow.m; sourceTree = "<group>"; };
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteModel.m; sourceTree = "<group>"; };
93D39F83DD151985F2C7345A /* MPMacApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacApplication.m; sourceTree = "<group>"; };
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; };
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; };
DA0933CF1747B91B00DE1CEF /* appstore.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appstore.png; sourceTree = "<group>"; };
DA0974561E99582200F0BFE8 /* mpw-tests-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "mpw-tests-util.c"; path = "../../platform-independent/cli-c/cli/mpw-tests-util.c"; sourceTree = "<group>"; };
DA0974571E99582200F0BFE8 /* mpw-tests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "mpw-tests.c"; path = "../../platform-independent/cli-c/cli/mpw-tests.c"; sourceTree = "<group>"; };
DA09745C1E99583B00F0BFE8 /* mpw-tests-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "mpw-tests-util.h"; path = "../../platform-independent/cli-c/cli/mpw-tests-util.h"; sourceTree = "<group>"; };
DA09745D1E99586600F0BFE8 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
DA09745F1E995EB500F0BFE8 /* mpw_tests.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = mpw_tests.xml; path = ../../core/java/tests/src/main/resources/mpw_tests.xml; sourceTree = "<group>"; };
DA09791B1E9A824700F0BFE8 /* core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = core.h; sourceTree = "<group>"; };
@@ -362,10 +402,37 @@
DA0CC5581EB6AE45009A8ED9 /* MasterPassword 9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 9.xcdatamodel"; sourceTree = "<group>"; };
DA16B340170661DB000A0EAB /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
DA16B343170661EE000A0EAB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
DA1C7AB61F1A8F24009A3551 /* mpw-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "mpw-cli"; sourceTree = BUILT_PRODUCTS_DIR; };
DA1C7AB81F1A8F6E009A3551 /* mpw-bench.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-bench.c"; sourceTree = "<group>"; };
DA1C7AB91F1A8F6E009A3551 /* mpw-cli.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-cli.c"; sourceTree = "<group>"; };
DA1C7ABA1F1A8F6E009A3551 /* mpw-tests-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-tests-util.c"; sourceTree = "<group>"; };
DA1C7ABB1F1A8F6E009A3551 /* mpw-tests-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-tests-util.h"; sourceTree = "<group>"; };
DA1C7ABC1F1A8F6E009A3551 /* mpw-tests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-tests.c"; sourceTree = "<group>"; };
DA1C7AD61F1A8FD8009A3551 /* mpw-bench */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "mpw-bench"; sourceTree = BUILT_PRODUCTS_DIR; };
DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPPasswordWindowController.xib; sourceTree = "<group>"; };
DA2508F919513C1400AC23F1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
DA2508FA19513C1400AC23F1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
DA2508FB19513C1400AC23F1 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
DA2686091EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPGeneratedSiteEntity+CoreDataClass.h"; sourceTree = "<group>"; };
DA26860A1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPGeneratedSiteEntity+CoreDataClass.m"; sourceTree = "<group>"; };
DA26860B1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPGeneratedSiteEntity+CoreDataProperties.h"; sourceTree = "<group>"; };
DA26860C1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPGeneratedSiteEntity+CoreDataProperties.m"; sourceTree = "<group>"; };
DA26860D1EBFD7A40001E37E /* MPSiteEntity+CoreDataClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPSiteEntity+CoreDataClass.h"; sourceTree = "<group>"; };
DA26860E1EBFD7A40001E37E /* MPSiteEntity+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPSiteEntity+CoreDataClass.m"; sourceTree = "<group>"; };
DA26860F1EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPSiteEntity+CoreDataProperties.h"; sourceTree = "<group>"; };
DA2686101EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPSiteEntity+CoreDataProperties.m"; sourceTree = "<group>"; };
DA2686111EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPSiteQuestionEntity+CoreDataClass.h"; sourceTree = "<group>"; };
DA2686121EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPSiteQuestionEntity+CoreDataClass.m"; sourceTree = "<group>"; };
DA2686131EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPSiteQuestionEntity+CoreDataProperties.h"; sourceTree = "<group>"; };
DA2686141EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPSiteQuestionEntity+CoreDataProperties.m"; sourceTree = "<group>"; };
DA2686151EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPStoredSiteEntity+CoreDataClass.h"; sourceTree = "<group>"; };
DA2686161EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPStoredSiteEntity+CoreDataClass.m"; sourceTree = "<group>"; };
DA2686171EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPStoredSiteEntity+CoreDataProperties.h"; sourceTree = "<group>"; };
DA2686181EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPStoredSiteEntity+CoreDataProperties.m"; sourceTree = "<group>"; };
DA2686191EBFD7A40001E37E /* MPUserEntity+CoreDataClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPUserEntity+CoreDataClass.h"; sourceTree = "<group>"; };
DA26861A1EBFD7A40001E37E /* MPUserEntity+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPUserEntity+CoreDataClass.m"; sourceTree = "<group>"; };
DA26861B1EBFD7A40001E37E /* MPUserEntity+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPUserEntity+CoreDataProperties.h"; sourceTree = "<group>"; };
DA26861C1EBFD7A40001E37E /* MPUserEntity+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPUserEntity+CoreDataProperties.m"; sourceTree = "<group>"; };
DA2C3D661BD9665B001137B3 /* PearlProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlProfiler.h; sourceTree = "<group>"; };
DA2C3D671BD9665B001137B3 /* PearlProfiler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlProfiler.m; sourceTree = "<group>"; };
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
@@ -378,16 +445,6 @@
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Crypto.m"; sourceTree = "<group>"; };
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
DA32CFD719CF1C70004F3F0E /* MPGeneratedSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGeneratedSiteEntity.h; sourceTree = "<group>"; };
DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGeneratedSiteEntity.m; sourceTree = "<group>"; };
DA32CFDA19CF1C70004F3F0E /* MPStoredSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStoredSiteEntity.h; sourceTree = "<group>"; };
DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoredSiteEntity.m; sourceTree = "<group>"; };
DA32CFDD19CF1C70004F3F0E /* MPSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteEntity.h; sourceTree = "<group>"; };
DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteEntity.m; sourceTree = "<group>"; };
DA32CFE019CF1C71004F3F0E /* MPSiteQuestionEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteQuestionEntity.h; sourceTree = "<group>"; };
DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteQuestionEntity.m; sourceTree = "<group>"; };
DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = "<group>"; };
DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = "<group>"; };
DA3B844A190FC5A900246EEA /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
@@ -403,6 +460,8 @@
DA5180C719FF2F9200A587E9 /* MPAlgorithmV2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV2.m; sourceTree = "<group>"; };
DA5180CC19FF307E00A587E9 /* MPAppDelegate_Store.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_Store.h; sourceTree = "<group>"; };
DA5180CD19FF307E00A587E9 /* MPAppDelegate_Store.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Store.m; sourceTree = "<group>"; };
DA5B0B381F36467300B663F0 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = "<group>"; };
DA5B0B391F36467300B663F0 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = "<group>"; };
DA5BFA44147E415C00F98B1E /* Master Password.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Master Password.app"; sourceTree = BUILT_PRODUCTS_DIR; };
DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -845,6 +904,8 @@
DA6773C51A4746AF004F356A /* mpw-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-util.c"; sourceTree = "<group>"; };
DA6773C61A4746AF004F356A /* mpw-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-util.h"; sourceTree = "<group>"; };
DA67743B1A474A03004F356A /* mpw-test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "mpw-test"; sourceTree = BUILT_PRODUCTS_DIR; };
DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall-util.c"; sourceTree = "<group>"; };
DA7471A11F2B71A9005F3468 /* mpw-marshall-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall-util.h"; sourceTree = "<group>"; };
DA771FE51E6E15A1004D7EDE /* MasterPassword-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MasterPassword-Prefix.pch"; sourceTree = "<group>"; };
DA831A271A6E1146000AC234 /* mpw-algorithm_v0.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v0.c"; sourceTree = "<group>"; };
DA831A281A6E1146000AC234 /* mpw-algorithm_v1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v1.c"; sourceTree = "<group>"; };
@@ -859,6 +920,8 @@
DA9261501BE1A86700369DE5 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Fabric.framework; sourceTree = "<group>"; };
DA9261531BE1A88900369DE5 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
DA9261551BE1A89600369DE5 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall.c"; sourceTree = "<group>"; };
DAA449D41EEC4B6B00E7BDD5 /* mpw-marshall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall.h"; sourceTree = "<group>"; };
DAAA81AF195A8D1300FA30D9 /* gradient.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gradient.png; sourceTree = "<group>"; };
DAADCC3E19FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPersistentStore+PearlMigration.h"; sourceTree = "<group>"; };
@@ -984,6 +1047,24 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
DA1C7AAE1F1A8F24009A3551 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DA1C7AAF1F1A8F24009A3551 /* libsodium.a in Frameworks */,
DA1C7AB01F1A8F24009A3551 /* libxml2.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA1C7ACE1F1A8FD8009A3551 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DA1C7ACF1F1A8FD8009A3551 /* libsodium.a in Frameworks */,
DA1C7AD01F1A8FD8009A3551 /* libxml2.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA5BFA41147E415C00F98B1E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -1141,6 +1222,19 @@
path = Fabric;
sourceTree = "<group>";
};
DA1C7AB71F1A8F6E009A3551 /* cli */ = {
isa = PBXGroup;
children = (
DA1C7AB81F1A8F6E009A3551 /* mpw-bench.c */,
DA1C7AB91F1A8F6E009A3551 /* mpw-cli.c */,
DA1C7ABA1F1A8F6E009A3551 /* mpw-tests-util.c */,
DA1C7ABB1F1A8F6E009A3551 /* mpw-tests-util.h */,
DA1C7ABC1F1A8F6E009A3551 /* mpw-tests.c */,
);
name = cli;
path = "../../platform-independent/cli-c/cli";
sourceTree = "<group>";
};
DA2508F819513C1400AC23F1 /* Other Frameworks */ = {
isa = PBXGroup;
children = (
@@ -1181,6 +1275,8 @@
DAC6326C148680650075AEA5 /* libjrswizzle.a */,
DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */,
DA67743B1A474A03004F356A /* mpw-test */,
DA1C7AB61F1A8F24009A3551 /* mpw-cli */,
DA1C7AD61F1A8FD8009A3551 /* mpw-bench */,
);
name = Products;
sourceTree = "<group>";
@@ -1244,20 +1340,30 @@
DA5E5CAC1724A667003798D8 /* MPEntities.m */,
DA3B8455190FC89700246EEA /* MPFixable.h */,
DA3B8454190FC89700246EEA /* MPFixable.m */,
DA32CFD719CF1C70004F3F0E /* MPGeneratedSiteEntity.h */,
DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */,
DA2686091EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataClass.h */,
DA26860A1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataClass.m */,
DA26860B1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.h */,
DA26860C1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.m */,
DA5E5CAD1724A667003798D8 /* MPKey.h */,
DA5E5CAE1724A667003798D8 /* MPKey.m */,
DA32CFDD19CF1C70004F3F0E /* MPSiteEntity.h */,
DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */,
DA32CFE019CF1C71004F3F0E /* MPSiteQuestionEntity.h */,
DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */,
DA32CFDA19CF1C70004F3F0E /* MPStoredSiteEntity.h */,
DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */,
DA26860D1EBFD7A40001E37E /* MPSiteEntity+CoreDataClass.h */,
DA26860E1EBFD7A40001E37E /* MPSiteEntity+CoreDataClass.m */,
DA26860F1EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.h */,
DA2686101EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.m */,
DA2686111EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataClass.h */,
DA2686121EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataClass.m */,
DA2686131EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.h */,
DA2686141EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.m */,
DA2686151EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.h */,
DA2686161EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.m */,
DA2686171EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.h */,
DA2686181EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.m */,
DA5E5CAF1724A667003798D8 /* MPTypes.h */,
DA4DAE931A7D8117003E5423 /* MPTypes.m */,
DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */,
DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */,
DA2686191EBFD7A40001E37E /* MPUserEntity+CoreDataClass.h */,
DA26861A1EBFD7A40001E37E /* MPUserEntity+CoreDataClass.m */,
DA26861B1EBFD7A40001E37E /* MPUserEntity+CoreDataProperties.h */,
DA26861C1EBFD7A40001E37E /* MPUserEntity+CoreDataProperties.m */,
);
path = Source;
sourceTree = "<group>";
@@ -1284,10 +1390,10 @@
DA5E5CB61724A667003798D8 /* MPMacConfig.m */,
93D392A4F3DE0BD758B9B056 /* MPNoStateButton.h */,
93D39538C4CEFF46DF379254 /* MPNoStateButton.m */,
93D3977484534E99F9BA579D /* MPPasswordWindow.h */,
93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */,
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */,
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */,
93D3977484534E99F9BA579D /* MPSitesWindow.h */,
93D39D9D0061FF1159998F06 /* MPSitesWindow.m */,
93D392C3918763B3B72CF366 /* MPSitesWindowController.h */,
93D39A57A7823DE98A0FF83C /* MPSitesWindowController.m */,
DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */,
93D39240B5143E01F0B75E96 /* MPSiteModel.h */,
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */,
@@ -1709,15 +1815,19 @@
DA6773291A4746AF004F356A /* C */ = {
isa = PBXGroup;
children = (
DA5B0B381F36467300B663F0 /* base64.c */,
DA5B0B391F36467300B663F0 /* base64.h */,
DA1C7AB71F1A8F6E009A3551 /* cli */,
DA831A271A6E1146000AC234 /* mpw-algorithm_v0.c */,
DA831A281A6E1146000AC234 /* mpw-algorithm_v1.c */,
DA831A291A6E1146000AC234 /* mpw-algorithm_v2.c */,
DA831A2A1A6E1146000AC234 /* mpw-algorithm_v3.c */,
DA6773BB1A4746AF004F356A /* mpw-algorithm.c */,
DA6773BC1A4746AF004F356A /* mpw-algorithm.h */,
DA0974561E99582200F0BFE8 /* mpw-tests-util.c */,
DA09745C1E99583B00F0BFE8 /* mpw-tests-util.h */,
DA0974571E99582200F0BFE8 /* mpw-tests.c */,
DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */,
DA7471A11F2B71A9005F3468 /* mpw-marshall-util.h */,
DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */,
DAA449D41EEC4B6B00E7BDD5 /* mpw-marshall.h */,
DA6773C21A4746AF004F356A /* mpw-types.c */,
DA6773C31A4746AF004F356A /* mpw-types.h */,
DA6773C51A4746AF004F356A /* mpw-util.c */,
@@ -2076,6 +2186,40 @@
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
DA1C7AA61F1A8F24009A3551 /* mpw-cli */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA1C7AB21F1A8F24009A3551 /* Build configuration list for PBXNativeTarget "mpw-cli" */;
buildPhases = (
DA1C7AA71F1A8F24009A3551 /* Sources */,
DA1C7AAE1F1A8F24009A3551 /* Frameworks */,
DA1C7AB11F1A8F24009A3551 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "mpw-cli";
productName = "mpw-test";
productReference = DA1C7AB61F1A8F24009A3551 /* mpw-cli */;
productType = "com.apple.product-type.tool";
};
DA1C7AC61F1A8FD8009A3551 /* mpw-bench */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA1C7AD21F1A8FD8009A3551 /* Build configuration list for PBXNativeTarget "mpw-bench" */;
buildPhases = (
DA1C7AC71F1A8FD8009A3551 /* Sources */,
DA1C7ACE1F1A8FD8009A3551 /* Frameworks */,
DA1C7AD11F1A8FD8009A3551 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "mpw-bench";
productName = "mpw-test";
productReference = DA1C7AD61F1A8FD8009A3551 /* mpw-bench */;
productType = "com.apple.product-type.tool";
};
DA5BFA43147E415C00F98B1E /* MasterPassword */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "MasterPassword" */;
@@ -2180,6 +2324,12 @@
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Lyndir;
TargetAttributes = {
DA1C7AA61F1A8F24009A3551 = {
DevelopmentTeam = HL3Q45LX9N;
};
DA1C7AC61F1A8FD8009A3551 = {
DevelopmentTeam = HL3Q45LX9N;
};
DA5BFA43147E415C00F98B1E = {
DevelopmentTeam = HL3Q45LX9N;
ProvisioningStyle = Automatic;
@@ -2229,6 +2379,8 @@
DAC6326B148680650075AEA5 /* jrswizzle */,
DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */,
DA67743A1A474A03004F356A /* mpw-test */,
DA1C7AC61F1A8FD8009A3551 /* mpw-bench */,
DA1C7AA61F1A8F24009A3551 /* mpw-cli */,
);
};
/* End PBXProject section */
@@ -2378,43 +2530,77 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
DA1C7AA71F1A8F24009A3551 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA1C7AAA1F1A8F24009A3551 /* mpw-marshall.c in Sources */,
DA1C7AAB1F1A8F24009A3551 /* mpw-types.c in Sources */,
DA5B0B3D1F36467900B663F0 /* base64.c in Sources */,
DA1C7AAC1F1A8F24009A3551 /* mpw-util.c in Sources */,
DA1C7AC31F1A8FBA009A3551 /* mpw-cli.c in Sources */,
DA7471A31F2B71AE005F3468 /* mpw-marshall-util.c in Sources */,
DA1C7AAD1F1A8F24009A3551 /* mpw-algorithm.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA1C7AC71F1A8FD8009A3551 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA5B0B3C1F36467900B663F0 /* base64.c in Sources */,
DA1C7ACA1F1A8FD8009A3551 /* mpw-types.c in Sources */,
DA1C7ACB1F1A8FD8009A3551 /* mpw-util.c in Sources */,
DA1C7AD71F1A8FE6009A3551 /* mpw-bench.c in Sources */,
DA1C7ACD1F1A8FD8009A3551 /* mpw-algorithm.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DA5BFA40147E415C00F98B1E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA2686251EBFD7A40001E37E /* MPUserEntity+CoreDataClass.m in Sources */,
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */,
DA32CFE219CF1C71004F3F0E /* MPSiteQuestionEntity.m in Sources */,
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */,
DA2686201EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.m in Sources */,
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */,
DAA449D51EEC4B6B00E7BDD5 /* mpw-marshall.c in Sources */,
DA26861E1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.m in Sources */,
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */,
DA2686231EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.m in Sources */,
DA2686221EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.m in Sources */,
DACBFCDF1C59B22E007EF90F /* NSMutableSet+Pearl.m in Sources */,
DA2686241EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.m in Sources */,
DA6774311A4746AF004F356A /* mpw-util.c in Sources */,
DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */,
DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */,
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */,
DA5B0B3A1F36467300B663F0 /* base64.c in Sources */,
DA26861F1EBFD7A40001E37E /* MPSiteEntity+CoreDataClass.m in Sources */,
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */,
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */,
DA2686261EBFD7A40001E37E /* MPUserEntity+CoreDataProperties.m in Sources */,
DA5E5D011724A667003798D8 /* MPKey.m in Sources */,
DA32CFDC19CF1C70004F3F0E /* MPStoredSiteEntity.m in Sources */,
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */,
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */,
DA5E5D0C1724A667003798D8 /* main.m in Sources */,
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */,
DA5180CA19FF2F9200A587E9 /* MPAlgorithmV2.m in Sources */,
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */,
93D39F833DEC1C89B2F795AC /* MPSitesWindowController.m in Sources */,
DA67742F1A4746AF004F356A /* mpw-types.c in Sources */,
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */,
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */,
93D390C676DF52DA7E459F19 /* MPSitesWindow.m in Sources */,
DA26861D1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataClass.m in Sources */,
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */,
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */,
DA6774291A4746AF004F356A /* mpw-algorithm.c in Sources */,
DA0CC5591EB6AE45009A8ED9 /* MasterPassword.xcdatamodeld in Sources */,
DA2686211EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataClass.m in Sources */,
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */,
DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */,
DA4DAE951A7D8117003E5423 /* MPTypes.m in Sources */,
93D393A1646430FAAC73E7FE /* MPMacApplication.m in Sources */,
DA7471A61F2B71B9005F3468 /* mpw-marshall-util.c in Sources */,
93D398D1F5D8CD5A22AF6929 /* MPGradientView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2423,10 +2609,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA09745B1E99582900F0BFE8 /* mpw-tests.c in Sources */,
DA09745A1E99582900F0BFE8 /* mpw-tests-util.c in Sources */,
DA1C7AD81F1A8FF4009A3551 /* mpw-tests-util.c in Sources */,
DA6774451A474A3B004F356A /* mpw-types.c in Sources */,
DA6774461A474A3B004F356A /* mpw-util.c in Sources */,
DA1C7AD91F1A8FF4009A3551 /* mpw-tests.c in Sources */,
DA5B0B3B1F36467800B663F0 /* base64.c in Sources */,
DA6774431A474A3B004F356A /* mpw-algorithm.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2694,12 +2881,8 @@
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
"$(PROJECT_DIR)/External/Pearl/Pearl-Crypto/lib",
/usr/local/Cellar/libsodium/1.0.12/lib,
/usr/local/Cellar/libscrypt/1.21/lib,
"$(PROJECT_DIR)/External/libsodium/libsodium-ios/lib",
"$(PROJECT_DIR)/External/libsodium/libsodium-osx/lib",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
@@ -2708,6 +2891,138 @@
};
name = Test;
};
DA1C7AB31F1A8F24009A3551 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/usr/include/libxml2,
/usr/local/include,
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
"-DHAS_SODIUM=1",
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
DA1C7AB41F1A8F24009A3551 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/usr/include/libxml2,
/usr/local/include,
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
"-DHAS_SODIUM=1",
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
DA1C7AB51F1A8F24009A3551 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/usr/include/libxml2,
/usr/local/include,
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
"-DHAS_SODIUM=1",
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
DA1C7AD31F1A8FD8009A3551 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/usr/include/libxml2,
/usr/local/include,
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
"-DHAS_SODIUM=1",
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
DA1C7AD41F1A8FD8009A3551 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/usr/include/libxml2,
/usr/local/include,
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
"-DHAS_SODIUM=1",
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
DA1C7AD51F1A8FD8009A3551 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/usr/include/libxml2,
/usr/local/include,
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
"-DHAS_SODIUM=1",
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Test;
};
DA5BFA6B147E415C00F98B1E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -2957,12 +3272,8 @@
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
"$(PROJECT_DIR)/External/Pearl/Pearl-Crypto/lib",
/usr/local/Cellar/libsodium/1.0.12/lib,
/usr/local/Cellar/libscrypt/1.21/lib,
"$(PROJECT_DIR)/External/libsodium/libsodium-ios/lib",
"$(PROJECT_DIR)/External/libsodium/libsodium-osx/lib",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
@@ -2982,12 +3293,8 @@
);
LIBRARY_SEARCH_PATHS = (
"$(SRCROOT)/External/Pearl/Pearl-Crypto/lib",
/usr/local/lib,
"$(inherited)",
"$(PROJECT_DIR)/External/Pearl/Pearl-Crypto/lib",
/usr/local/Cellar/libsodium/1.0.12/lib,
/usr/local/Cellar/libscrypt/1.21/lib,
"$(PROJECT_DIR)/External/libsodium/libsodium-ios/lib",
"$(PROJECT_DIR)/External/libsodium/libsodium-osx/lib",
);
OTHER_CFLAGS = (
"-DHAS_CPERCIVA=0",
@@ -3067,6 +3374,26 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
DA1C7AB21F1A8F24009A3551 /* Build configuration list for PBXNativeTarget "mpw-cli" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA1C7AB31F1A8F24009A3551 /* Debug */,
DA1C7AB41F1A8F24009A3551 /* Release */,
DA1C7AB51F1A8F24009A3551 /* Test */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Test;
};
DA1C7AD21F1A8FD8009A3551 /* Build configuration list for PBXNativeTarget "mpw-bench" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA1C7AD31F1A8FD8009A3551 /* Debug */,
DA1C7AD41F1A8FD8009A3551 /* Release */,
DA1C7AD51F1A8FD8009A3551 /* Test */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Test;
};
DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-macOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -49,7 +49,7 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
- (NSData *)keyIDForKeyData:(NSData *)keyData;
- (NSData *)keyIDForKey:(MPMasterKey)masterKey;
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (NSString *)nameOfType:(MPSiteType)type;

View File

@@ -29,7 +29,7 @@
#define CRACKING_PER_SECOND 2495000000UL
#define CRACKING_PRICE 350
NSOperationQueue *_mpwQueue = nil;
static NSOperationQueue *_mpwQueue = nil;
@implementation MPAlgorithmV0
@@ -128,21 +128,21 @@ NSOperationQueue *_mpwQueue = nil;
__block NSData *keyData;
[self mpw_perform:^{
NSDate *start = [NSDate date];
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] );
if (masterKeyBytes) {
keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen];
MPMasterKey masterKey = mpw_masterKey( fullName.UTF8String, masterPassword.UTF8String, [self version] );
if (masterKey) {
keyData = [NSData dataWithBytes:masterKey length:MPMasterKeySize];
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
fullName, masterPassword, [self keyIDForKeyData:keyData], -[start timeIntervalSinceNow] );
mpw_free( masterKeyBytes, MP_dkLen );
fullName, masterPassword, [self keyIDForKey:masterKey], -[start timeIntervalSinceNow] );
mpw_free( masterKey, MPMasterKeySize );
}
}];
return keyData;
}
- (NSData *)keyIDForKeyData:(NSData *)keyData {
- (NSData *)keyIDForKey:(MPMasterKey)masterKey {
return [keyData hashWith:PearlHashSHA256];
return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256];
}
- (NSString *)nameOfType:(MPSiteType)type {
@@ -331,28 +331,28 @@ NSOperationQueue *_mpwQueue = nil;
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
variant:MPSiteVariantLogin context:nil usingKey:key];
variant:MPKeyPurposeIdentification context:nil usingKey:key];
}
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
variant:MPSiteVariantPassword context:nil usingKey:key];
variant:MPKeyPurposeAuthentication context:nil usingKey:key];
}
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
variant:MPSiteVariantAnswer context:question usingKey:key];
variant:MPKeyPurposeRecovery context:question usingKey:key];
}
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
__block NSString *content;
__block NSString *content = nil;
[self mpw_perform:^{
char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes,
char const *contentBytes = mpw_passwordForSite( [key keyForAlgorithm:self],
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
if (contentBytes) {
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
@@ -396,7 +396,7 @@ NSOperationQueue *_mpwQueue = nil;
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:encryptionKey padding:YES];
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
@@ -412,7 +412,7 @@ NSOperationQueue *_mpwQueue = nil;
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:encryptionKey padding:YES];
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
@@ -713,7 +713,7 @@ NSOperationQueue *_mpwQueue = nil;
return nil;
NSData *decryptedContent = nil;
if ([encryptedContent length]) {
NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptionKey = [key keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
}
if (!decryptedContent)

View File

@@ -104,14 +104,33 @@ 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
[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];

View File

@@ -95,13 +95,14 @@
- (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:[self createKeyQueryforUser:user origin:nil]
withAttributes:@{ (__bridge id)kSecValueData: keyData }];
[PearlKeyChain addOrUpdateItemForQuery:[self createKeyQueryforUser:user origin:nil] withAttributes:@{
(__bridge id)kSecValueData: [NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize]
}];
}
}
}

View File

@@ -19,7 +19,9 @@
#import "MPEntities.h"
#if TARGET_OS_IPHONE
@interface MPAppDelegate_Shared : PearlAppDelegate
#else
@interface MPAppDelegate_Shared : NSObject<PearlConfigDelegate>

View File

@@ -78,7 +78,6 @@
}
- (void)handleCoordinatorError:(NSError *)error {
}
@end

View File

@@ -17,6 +17,7 @@
//==============================================================================
#import "MPAppDelegate_Store.h"
#import "mpw-marshall.h"
#if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
@@ -840,68 +841,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
MPUserEntity *activeUser = [self activeUserForMainThread];
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
// Header.
NSMutableString *export = [NSMutableString new];
[export appendFormat:@"# Master Password site export\n"];
if (revealPasswords)
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
else
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
[export appendFormat:@"# \n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"# Format: 1\n"];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
[export appendFormat:@"# User Name: %@\n", activeUser.name];
[export appendFormat:@"# Full Name: %@\n", activeUser.name];
[export appendFormat:@"# Avatar: %lu\n", (unsigned long)activeUser.avatar];
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Algorithm: %d\n", activeUser.algorithm.version];
[export appendFormat:@"# Default Type: %d\n", activeUser.defaultType];
[export appendFormat:@"# Passwords: %@\n", revealPasswords? @"VISIBLE": @"PROTECTED"];
[export appendFormat:@"##\n"];
[export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Login\t Site\tSite\n"];
[export appendFormat:@"# used used type name\t name\tpassword\n"];
MPMarshalledUser exportUser = mpw_marshall_user( activeUser.name.UTF8String,
[self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version );
exportUser.avatar = activeUser.avatar;
exportUser.defaultType = activeUser.defaultType;
exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970;
// Sites.
for (MPSiteEntity *site in activeUser.sites) {
NSDate *lastUsed = site.lastUsed;
NSUInteger uses = site.uses;
MPSiteType type = site.type;
id<MPAlgorithm> algorithm = site.algorithm;
NSUInteger counter = 0;
NSString *loginName = site.loginName;
NSString *siteName = site.name;
NSString *content = nil;
MPMarshalledSite exportSite = mpw_marshall_site( &exportUser,
site.name.UTF8String, site.type, site.counter, site.algorithm.version );
exportSite.loginName = site.loginName.UTF8String;
exportSite.url = site.url.UTF8String;
exportSite.uses = site.uses;
exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
// Generated-specific
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)site).counter;
// Determine the content to export.
if (!(type & MPSiteFeatureDevicePrivate)) {
if (revealPasswords)
content = [site.algorithm resolvePasswordForSite:site usingKey:self.key];
else if (type & MPSiteFeatureExportContent)
content = [site.algorithm exportPasswordForSite:site usingKey:self.key];
}
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];
for (MPSiteQuestionEntity *siteQuestion in site.questions)
mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String );
}
return export;
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser );
}
@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"

View File

@@ -1,10 +1,20 @@
//==============================================================================
// 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+CoreDataClass.h"

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"

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

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

@@ -1,10 +1,20 @@
//==============================================================================
// 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>
@@ -37,7 +47,7 @@ __END_DECLS
} \
})
#else
#define MPError(error_, ...) ({ \
#define MPError(error_, message, ...) ({ \
err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
})
#endif

View File

@@ -1,10 +1,20 @@
//==============================================================================
// 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"

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

@@ -430,8 +430,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)terminate:(id)sender {
[self.passwordWindowController close];
self.passwordWindowController = nil;
[self.sitesWindowController close];
self.sitesWindowController = nil;
[NSApp terminate:nil];
}
@@ -460,11 +460,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
prof_rewind( @"activeUserForMainThread" );
// Don't show window if we weren't already running (ie. if we haven't been activated before).
if (!self.passwordWindowController)
self.passwordWindowController = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
if (!self.sitesWindowController)
self.sitesWindowController = [[MPSitesWindowController alloc] initWithWindowNibName:@"MPSitesWindowController"];
prof_rewind( @"initWithWindow" );
[self.passwordWindowController showWindow:self];
[self.sitesWindowController showWindow:self];
prof_finish( @"showWindow" );
}
@@ -604,7 +604,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (void)updateMenuItems {
MPUserEntity *activeUser = [self activeUserForMainThread];
// if (!(self.showItem.enabled = ![self.passwordWindowController.window isVisible])) {
// if (!(self.showItem.enabled = ![self.sitesWindowController.window isVisible])) {
// self.showItem.title = @"Show (Showing)";
// self.showItem.toolTip = @"Master Password is already showing.";
// }

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

View File

@@ -1,49 +0,0 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPasswordWindow.h
// MPPasswordWindow
//
// Created by lhunath on 2014-06-19.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPPasswordWindow.h"
@implementation MPPasswordWindow
#pragma mark - Life
- (BOOL)canBecomeKeyWindow {
return YES;
}
#pragma mark - State
- (void)update {
if ([[MPMacConfig get].fullScreen boolValue]) {
[self setLevel:NSScreenSaverWindowLevel];
[self setFrame:self.screen.frame display:YES];
}
else if (self.level != NSNormalWindowLevel) {
[self setLevel:NSNormalWindowLevel];
[self setFrame:NSMakeRect( 0, 0, 640, 600 ) display:NO];
[self center];
}
[super update];
}
#pragma mark - Private
@end

View File

@@ -7,7 +7,7 @@
<capability name="stacking Non-gravity area distributions on NSStackView" minToolsVersion="7.0" minSystemVersion="10.11"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MPPasswordWindowController">
<customObject id="-2" userLabel="File's Owner" customClass="MPSitesWindowController">
<connections>
<outlet property="inputLabel" destination="OnR-s6-d4P" id="p6G-Ut-cdu"/>
<outlet property="passwordTypesBox" destination="bZe-7q-i6q" id="Ai3-pt-i6K"/>
@@ -26,7 +26,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MPPasswordWindow">
<window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MPSitesWindow">
<windowStyleMask key="styleMask" texturedBackground="YES" unifiedTitleAndToolbar="YES" fullSizeContentView="YES"/>
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/>

View File

@@ -17,9 +17,9 @@
//==============================================================================
#import <Foundation/Foundation.h>
#import "MPSiteEntity.h"
#import "MPSiteEntity+CoreDataClass.h"
#import "MPAlgorithm.h"
#import "MPUserEntity.h"
#import "MPUserEntity+CoreDataClass.h"
@class MPSiteEntity;

View File

@@ -17,16 +17,19 @@
//==============================================================================
#import "MPSiteModel.h"
#import "MPSiteEntity.h"
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
#import "MPMacAppDelegate.h"
@implementation MPSiteModel {
NSManagedObjectID *_entityOID;
BOOL _initialized;
}
@interface MPSiteModel()
@property(nonatomic, strong) NSManagedObjectID *entityOID;
@property(nonatomic) BOOL initialized;
@end
@implementation MPSiteModel
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
@@ -34,7 +37,7 @@
return nil;
[self setEntity:entity fuzzyGroups:fuzzyGroups];
_initialized = YES;
self.initialized = YES;
return self;
}
@@ -45,16 +48,16 @@
return nil;
[self setTransientSiteName:siteName forUser:user];
_initialized = YES;
self.initialized = YES;
return self;
}
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
if ([_entityOID isEqual:entity.permanentObjectID])
if ([self.entityOID isEqual:entity.permanentObjectID])
return;
_entityOID = entity.permanentObjectID;
self.entityOID = entity.permanentObjectID;
NSString *siteName = entity.name;
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName];
@@ -87,7 +90,7 @@
- (void)setTransientSiteName:(NSString *)siteName forUser:(MPUserEntity *)user {
_entityOID = nil;
self.entityOID = nil;
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = NSCenterTextAlignment;
@@ -109,11 +112,11 @@
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
if (!_entityOID)
if (!self.entityOID)
return nil;
NSError *error;
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:_entityOID error:&error];
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:self.entityOID error:&error];
if (!entity)
MPError( error, @"Couldn't retrieve active site." );
@@ -122,15 +125,15 @@
- (void)setCounter:(NSUInteger)counter {
if (counter == _counter)
if (self.counter == counter)
return;
_counter = counter;
if (!_initialized)
if (!self.initialized)
// This wasn't a change to the entity.
return;
if (_entityOID)
if (self.entityOID)
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *entity = [self entityInContext:context];
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
@@ -146,15 +149,15 @@
- (void)setLoginGenerated:(BOOL)loginGenerated {
if (loginGenerated == _loginGenerated)
if (self.loginGenerated == loginGenerated)
return;
_loginGenerated = loginGenerated;
if (!_initialized)
if (!self.initialized)
// This wasn't a change to the entity.
return;
if (_entityOID)
if (self.entityOID)
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *entity = [self entityInContext:context];
entity.loginGenerated = loginGenerated;
@@ -179,7 +182,7 @@
self.algorithm = MPAlgorithmForVersion( algorithmVersion )?: self.algorithm;
[self didChangeValueForKey:@"outdated"];
if (_entityOID)
if (self.entityOID)
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *entity = [self entityInContext:context];
entity.algorithm = self.algorithm;
@@ -193,7 +196,7 @@
- (void)setQuestion:(NSString *)question {
if ([question isEqualToString:_question])
if ([self.question isEqualToString:question])
return;
_question = question;
@@ -217,14 +220,14 @@
- (BOOL)transient {
return _entityOID == nil;
return self.entityOID == nil;
}
- (void)updateContent {
if (_entityOID)
if (self.entityOID)
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
[self updateContent:[MPSiteEntity existingObjectWithID:self.entityOID inContext:context]];
}];
else

View File

@@ -18,10 +18,10 @@
#import <AppKit/AppKit.h>
@class MPPasswordWindowController;
@class MPSitesWindowController;
@interface MPSitesTableView : NSTableView
@property(nonatomic, weak) MPPasswordWindowController *controller;
@property(nonatomic, weak) MPSitesWindowController *controller;
@end

View File

@@ -17,7 +17,7 @@
//==============================================================================
#import "MPSitesTableView.h"
#import "MPPasswordWindowController.h"
#import "MPSitesWindowController.h"
@implementation MPSitesTableView

View File

@@ -0,0 +1,23 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import <Cocoa/Cocoa.h>
@interface MPSitesWindow : NSWindow
@end

View File

@@ -0,0 +1,49 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import "MPSitesWindow.h"
@implementation MPSitesWindow
#pragma mark - Life
- (BOOL)canBecomeKeyWindow {
return YES;
}
#pragma mark - State
- (void)update {
if ([[MPMacConfig get].fullScreen boolValue]) {
[self setLevel:NSScreenSaverWindowLevel];
[self setFrame:self.screen.frame display:YES];
}
else if (self.level != NSNormalWindowLevel) {
[self setLevel:NSNormalWindowLevel];
[self setFrame:NSMakeRect( 0, 0, 640, 600 ) display:NO];
[self center];
}
[super update];
}
#pragma mark - Private
@end

View File

@@ -1,29 +1,29 @@
/**
* 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.
//
// MPPasswordWindowController.h
// MPPasswordWindowController
// 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-06-18.
// 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 <Cocoa/Cocoa.h>
#import "MPSiteModel.h"
#import "MPSitesTableView.h"
#import "MPPasswordWindow.h"
#import "MPSitesWindow.h"
@class MPMacAppDelegate;
@interface MPPasswordWindowController : NSWindowController<NSTextViewDelegate, NSTextFieldDelegate, NSTableViewDataSource, NSTableViewDelegate>
@interface MPSitesWindowController : NSWindowController<NSTextViewDelegate, NSTextFieldDelegate, NSTableViewDataSource, NSTableViewDelegate>
@property(nonatomic) NSMutableArray *sites;
@property(nonatomic) NSString *masterPassword;

View File

@@ -17,18 +17,18 @@
//==============================================================================
#import <QuartzCore/QuartzCore.h>
#import "MPPasswordWindowController.h"
#import "MPSitesWindowController.h"
#import "MPMacAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPAppDelegate_Key.h"
@interface MPPasswordWindowController()
@interface MPSitesWindowController()
@property(nonatomic, strong) CAGradientLayer *siteGradient;
@end
@implementation MPPasswordWindowController
@implementation MPSitesWindowController
#pragma mark - Life
@@ -67,8 +67,8 @@
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue], ^(id host, NSNotification *note) {
[self updateUser];
} );
[self observeKeyPath:@"sitesController.selection" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
[_self updateSelection];
[self observeKeyPath:@"sitesController.selection" withBlock:^(id from, id to, NSKeyValueChange cause, id self) {
[self updateSelection];
}];
prof_rewind( @"observers" );

View File

@@ -1,109 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ATSApplicationFontsPath</key>
<string>.</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Master Password</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mpsites</string>
</array>
<key>CFBundleTypeIconFile</key>
<string></string>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>text/plain</string>
</array>
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.masterpassword.sites</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Master Password</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>[auto]</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
<key>Kits</key>
<array>
<dict>
<key>KitInfo</key>
<dict/>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
</array>
</dict>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2011-2014 Lyndir</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>MPMacApplication</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.utf8-plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password sites</string>
<key>UTTypeIconFile</key>
<string></string>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.sites</string>
<key>UTTypeSize320IconFile</key>
<string></string>
<key>UTTypeSize64IconFile</key>
<string></string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mpsites</string>
</array>
</dict>
</dict>
</array>
</dict>
<dict>
<key>ATSApplicationFontsPath</key>
<string>.</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Master Password</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mpsites</string>
</array>
<key>CFBundleTypeIconFile</key>
<string></string>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>text/plain</string>
</array>
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.masterpassword.sites</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Master Password</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>[auto]</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
<key>Kits</key>
<array>
<dict>
<key>KitInfo</key>
<dict/>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
</array>
</dict>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2011-2014 Lyndir</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>MPMacApplication</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.utf8-plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password sites</string>
<key>UTTypeIconFile</key>
<string></string>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.sites</string>
<key>UTTypeSize320IconFile</key>
<string></string>
<key>UTTypeSize64IconFile</key>
<string></string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mpsites</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,34 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013 Maarten Billemont. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013 Maarten Billemont. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -23,12 +23,12 @@
@interface MPAnswersViewController()
@property(nonatomic, strong) NSManagedObjectID *siteOID;
@property(nonatomic) BOOL multiple;
@end
@implementation MPAnswersViewController {
NSManagedObjectID *_siteOID;
BOOL _multiple;
}
@implementation MPAnswersViewController
#pragma mark - Life
@@ -80,21 +80,21 @@
- (void)setSite:(MPSiteEntity *)site {
_siteOID = site.permanentObjectID;
_multiple = [site.questions count] > 0;
self.siteOID = site.permanentObjectID;
self.multiple = [site.questions count] > 0;
[self.tableView reloadData];
[self updateAnimated:NO];
}
- (void)setMultiple:(BOOL)multiple animated:(BOOL)animated {
_multiple = multiple;
self.multiple = multiple;
[self updateAnimated:animated];
}
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context {
return [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
return [MPSiteEntity existingObjectWithID:self.siteOID inContext:context];
}
#pragma mark - UITableViewDelegate
@@ -109,7 +109,7 @@
if (section == 0)
return 3;
if (!_multiple)
if (!self.multiple)
return 0;
return [[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].questions count] + 1;
@@ -128,7 +128,7 @@
return [MPSendAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
if (indexPath.item == 2) {
MPMultipleAnswersCell *cell = [MPMultipleAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
cell.accessoryType = _multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
cell.accessoryType = self.multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
return cell;
}
Throw( @"Unsupported row index: %@", indexPath );
@@ -165,10 +165,10 @@
[self copyAnswer:((MPGlobalAnswersCell *)cell).answerField.text];
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
if (!_multiple)
if (!self.multiple)
[self setMultiple:YES animated:YES];
else if (_multiple) {
else if (self.multiple) {
if (![site.questions count])
[self setMultiple:NO animated:YES];
@@ -194,7 +194,7 @@
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
NSString *body;
if (!_multiple) {
if (!self.multiple) {
NSObject *answer = [site resolveSiteAnswerUsingKey:[MPiOSAppDelegate get].key];
body = strf( @"Master Password generated the following security answer for your site: %@\n\n"
@"%@\n"
@@ -245,7 +245,7 @@
PearlMainQueue( ^{
UITableViewCell *multipleAnswersCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:0]];
multipleAnswersCell.accessoryType = _multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
multipleAnswersCell.accessoryType = self.multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
@@ -291,19 +291,23 @@
@end
@implementation MPAnswersQuestionCell {
NSManagedObjectID *_siteOID;
NSManagedObjectID *_questionOID;
__weak MPAnswersViewController *_answersVC;
}
@interface MPAnswersQuestionCell()
@property(nonatomic, strong) NSManagedObjectID *siteOID;
@property(nonatomic, strong) NSManagedObjectID *questionOID;
@property(nonatomic, weak) MPAnswersViewController *answersVC;
@end
@implementation MPAnswersQuestionCell
#pragma mark - State
- (void)setQuestion:(MPSiteQuestionEntity *)question forSite:(MPSiteEntity *)site inVC:(MPAnswersViewController *)answersVC {
_siteOID = site.permanentObjectID;
_questionOID = question.permanentObjectID;
_answersVC = answersVC;
self.siteOID = site.permanentObjectID;
self.questionOID = question.permanentObjectID;
self.answersVC = answersVC;
[self updateAnswerForQuestion:question ofSite:site];
}
@@ -322,8 +326,8 @@
NSString *keyword = textField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
BOOL didAddQuestionObject = NO;
MPSiteEntity *site = [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
MPSiteQuestionEntity *question = [MPSiteQuestionEntity existingObjectWithID:_questionOID inContext:context];
MPSiteEntity *site = [MPSiteEntity existingObjectWithID:self.siteOID inContext:context];
MPSiteQuestionEntity *question = [MPSiteQuestionEntity existingObjectWithID:self.questionOID inContext:context];
if (!question) {
didAddQuestionObject = YES;
[site addQuestionsObject:question = [MPSiteQuestionEntity insertNewObjectInContext:context]];
@@ -333,11 +337,11 @@
question.keyword = keyword;
if ([context saveToStore]) {
_questionOID = question.permanentObjectID;
self.questionOID = question.permanentObjectID;
[self updateAnswerForQuestion:question ofSite:site];
if (didAddQuestionObject)
[_answersVC didAddQuestion:question toSite:site];
[self.answersVC didAddQuestion:question toSite:site];
}
}];
}

View File

@@ -19,8 +19,7 @@
#import "MPAppSettingsViewController.h"
#import "UIColor+Expanded.h"
@implementation MPAppSettingsViewController {
}
@implementation MPAppSettingsViewController
- (void)viewWillAppear:(BOOL)animated {

View File

@@ -32,11 +32,12 @@ const long MPAvatarAdd = 10000;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarRaisedConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *keyboardHeightConstraint;
@property(nonatomic, strong) CAAnimationGroup *targetedShadowAnimation;
@property(assign, nonatomic, readwrite) BOOL newUser;
@end
@implementation MPAvatarCell {
CAAnimationGroup *_targetedShadowAnimation;
}
@implementation MPAvatarCell
+ (NSString *)reuseIdentifier {
@@ -57,14 +58,14 @@ const long MPAvatarAdd = 10000;
self.avatarImageView.layer.masksToBounds = NO;
self.avatarImageView.backgroundColor = [UIColor clearColor];
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *_self) {
_self.contentView.frame = _self.bounds;
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
self.contentView.frame = self.bounds;
}];
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *_self) {
[_self updateAnimated:_self.superview != nil];
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
[self updateAnimated:self.superview != nil];
}];
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *_self) {
[_self updateAnimated:_self.superview != nil];
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
[self updateAnimated:self.superview != nil];
}];
PearlAddNotificationObserver( UIKeyboardWillShowNotification, nil, [NSOperationQueue mainQueue],
^(MPAvatarCell *self, NSNotification *note) {
@@ -85,9 +86,9 @@ const long MPAvatarAdd = 10000;
pulseShadowOpacityAnimation.autoreverses = YES;
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
_targetedShadowAnimation = [CAAnimationGroup new];
_targetedShadowAnimation.animations = @[ toShadowOpacityAnimation, pulseShadowOpacityAnimation ];
_targetedShadowAnimation.duration = MAXFLOAT;
self.targetedShadowAnimation = [CAAnimationGroup new];
self.targetedShadowAnimation.animations = @[ toShadowOpacityAnimation, pulseShadowOpacityAnimation ];
self.targetedShadowAnimation.duration = MAXFLOAT;
self.avatarImageView.layer.shadowColor = [UIColor whiteColor].CGColor;
self.avatarImageView.layer.shadowOffset = CGSizeZero;
}
@@ -96,10 +97,10 @@ const long MPAvatarAdd = 10000;
[super prepareForReuse];
_newUser = NO;
_visibility = 0;
_mode = MPAvatarModeLowered;
_spinnerActive = NO;
self.newUser = NO;
self.visibility = 0;
self.mode = MPAvatarModeLowered;
self.spinnerActive = NO;
}
- (void)dealloc {
@@ -114,13 +115,13 @@ const long MPAvatarAdd = 10000;
_avatar = avatar == MPAvatarAdd? MPAvatarAdd: (avatar + MPAvatarCount) % MPAvatarCount;
if (_avatar == MPAvatarAdd) {
if (self.avatar == MPAvatarAdd) {
self.avatarImageView.image = [UIImage imageNamed:@"avatar-add"];
self.name = strl( @"New User" );
_newUser = YES;
self.newUser = YES;
}
else
self.avatarImageView.image = [UIImage imageNamed:strf( @"avatar-%lu", (unsigned long)_avatar )];
self.avatarImageView.image = [UIImage imageNamed:strf( @"avatar-%lu", (unsigned long)self.avatar )];
}
- (NSString *)name {
@@ -140,7 +141,7 @@ const long MPAvatarAdd = 10000;
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated {
if (visibility == _visibility)
if (self.visibility == visibility)
return;
_visibility = visibility;
@@ -161,7 +162,7 @@ const long MPAvatarAdd = 10000;
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated {
if (mode == _mode)
if (self.mode == mode)
return;
_mode = mode;
@@ -175,7 +176,7 @@ const long MPAvatarAdd = 10000;
- (void)setSpinnerActive:(BOOL)spinnerActive animated:(BOOL)animated {
if (_spinnerActive == spinnerActive)
if (self.spinnerActive == spinnerActive)
return;
_spinnerActive = spinnerActive;
@@ -284,7 +285,7 @@ const long MPAvatarAdd = 10000;
if (self.mode == MPAvatarModeRaisedAndMinimized)
[self.avatarImageView.layer removeAnimationForKey:@"targetedShadow"];
else if (![self.avatarImageView.layer animationForKey:@"targetedShadow"])
[self.avatarImageView.layer addAnimation:_targetedShadowAnimation forKey:@"targetedShadow"];
[self.avatarImageView.layer addAnimation:self.targetedShadowAnimation forKey:@"targetedShadow"];
// Avatar selection and spinner.
if (self.mode != MPAvatarModeRaisedAndMinimized && (self.selected || self.highlighted) && !self.spinnerActive)

View File

@@ -18,7 +18,6 @@
#import "MPCell.h"
@implementation MPCell {
}
@implementation MPCell
@end

View File

@@ -18,17 +18,23 @@
#import "MPCoachmarkViewController.h"
@implementation MPCoachmarkViewController {
NSArray *_views;
NSUInteger _nextView;
__weak NSTimer *_viewTimer;
}
@interface MPCoachmarkViewController()
@property(nonatomic, strong) NSArray *views;
@property(nonatomic) NSUInteger nextView;
@property(nonatomic, weak) NSTimer *viewTimer;
@end
@implementation MPCoachmarkViewController
- (void)viewDidLoad {
[super viewDidLoad];
_views = @[ self.view0, self.view1, self.view2, self.view3, self.view4, self.view5, self.view6, self.view7, self.view8, self.view9 ];
self.views = @[
self.view0, self.view1, self.view2, self.view3, self.view4, self.view5, self.view6, self.view7, self.view8, self.view9
];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -37,8 +43,8 @@
self.viewProgress.visible = YES;
self.viewProgress.progress = 0;
[_views makeObjectsPerformSelector:@selector( setVisible: ) withObject:@NO];
_nextView = 0;
[self.views makeObjectsPerformSelector:@selector( setVisible: ) withObject:@NO];
self.nextView = 0;
}
- (void)viewDidAppear:(BOOL)animated {
@@ -46,23 +52,23 @@
[super viewDidAppear:animated];
[UIView animateWithDuration:0.3f animations:^{
[_views[_nextView++] setVisible:YES];
[self.views[self.nextView++] setVisible:YES];
}];
_viewTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 block:^(NSTimer *timer) {
self.viewTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 block:^(NSTimer *timer) {
self.viewProgress.progress += 1.0f / 50;
if (self.viewProgress.progress == 1)
[UIView animateWithDuration:0.3f animations:^{
self.viewProgress.progress = 0;
[_views[_nextView++] setVisible:YES];
[self.views[self.nextView++] setVisible:YES];
if (_nextView >= [_views count]) {
[_viewTimer invalidate];
if (self.nextView >= [self.views count]) {
[self.viewTimer invalidate];
self.viewProgress.visible = NO;
}
}];
} repeats:YES];
} repeats:YES];
}
- (UIStatusBarStyle)preferredStatusBarStyle {

View File

@@ -17,7 +17,7 @@
//==============================================================================
#import "MPUsersViewController.h"
#import "MPPasswordsViewController.h"
#import "MPSitesViewController.h"
#import "MPEmergencyViewController.h"
typedef NS_ENUM( NSUInteger, MPCombinedMode ) {
@@ -29,7 +29,7 @@ typedef NS_ENUM( NSUInteger, MPCombinedMode ) {
@property(nonatomic) MPCombinedMode mode;
@property(nonatomic, weak) MPUsersViewController *usersVC;
@property(nonatomic, weak) MPPasswordsViewController *passwordsVC;
@property(nonatomic, weak) MPSitesViewController *sitesVC;
@property(nonatomic, weak) MPEmergencyViewController *emergencyVC;
@end

View File

@@ -18,9 +18,9 @@
#import "MPCombinedViewController.h"
#import "MPUsersViewController.h"
#import "MPPasswordsViewController.h"
#import "MPSitesViewController.h"
#import "MPEmergencyViewController.h"
#import "MPPasswordsSegue.h"
#import "MPSitesSegue.h"
@implementation MPCombinedViewController
@@ -30,7 +30,7 @@
[super viewDidLoad];
_mode = MPCombinedModeUserSelection;
self.mode = MPCombinedModeUserSelection;
[self performSegueWithIdentifier:@"users" sender:self];
}
@@ -67,12 +67,12 @@
if ([segue.identifier isEqualToString:@"users"])
self.usersVC = segue.destinationViewController;
if ([segue.identifier isEqualToString:@"passwords"]) {
NSAssert( [segue isKindOfClass:[MPPasswordsSegue class]], @"passwords segue should be MPPasswordsSegue: %@", segue );
NSAssert( [segue isKindOfClass:[MPSitesSegue class]], @"passwords segue should be MPSitesSegue: %@", segue );
NSAssert( [sender isKindOfClass:[NSDictionary class]], @"sender should be dictionary: %@", sender );
NSAssert( [[sender objectForKey:@"animated"] isKindOfClass:[NSNumber class]], @"sender should contain 'animated': %@", sender );
[(MPPasswordsSegue *)segue setAnimated:[sender[@"animated"] boolValue]];
[(MPSitesSegue *)segue setAnimated:[sender[@"animated"] boolValue]];
UIViewController *destinationVC = segue.destinationViewController;
_passwordsVC = [destinationVC isKindOfClass:[MPPasswordsViewController class]]? (MPPasswordsViewController *)destinationVC: nil;
self.sitesVC = [destinationVC isKindOfClass:[MPSitesViewController class]]? (MPSitesViewController *)destinationVC: nil;
}
if ([segue.identifier isEqualToString:@"emergency"])
self.emergencyVC = segue.destinationViewController;
@@ -115,7 +115,7 @@
- (void)setMode:(MPCombinedMode)mode animated:(BOOL)animated {
if (_mode == mode && animated)
if (self.mode == mode && animated)
return;
_mode = mode;
@@ -129,8 +129,8 @@
case MPCombinedModeUserSelection: {
self.usersVC.view.userInteractionEnabled = YES;
[self.usersVC setActive:YES animated:animated];
if (_passwordsVC) {
MPPasswordsSegue *segue = [[MPPasswordsSegue alloc] initWithIdentifier:@"passwords" source:_passwordsVC destination:self];
if (self.sitesVC) {
MPSitesSegue *segue = [[MPSitesSegue alloc] initWithIdentifier:@"passwords" source:self.sitesVC destination:self];
[self prepareForSegue:segue sender:@{ @"animated": @(animated) }];
[segue perform];
}

View File

@@ -19,18 +19,22 @@
#import "MPEmergencyViewController.h"
#import "MPEntities.h"
@implementation MPEmergencyViewController {
MPKey *_key;
NSOperationQueue *_emergencyKeyQueue;
NSOperationQueue *_emergencyPasswordQueue;
}
@interface MPEmergencyViewController()
@property(nonatomic, strong) MPKey *key;
@property(nonatomic, strong) NSOperationQueue *emergencyKeyQueue;
@property(nonatomic, strong) NSOperationQueue *emergencyPasswordQueue;
@end
@implementation MPEmergencyViewController
- (void)viewDidLoad {
[super viewDidLoad];
[_emergencyKeyQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
[_emergencyPasswordQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
[self.emergencyKeyQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
[self.emergencyPasswordQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
self.view.backgroundColor = [UIColor clearColor];
self.dialogView.layer.cornerRadius = 5;
@@ -117,12 +121,12 @@
[self.passwordButton setTitle:nil forState:UIControlStateNormal];
[self.activity startAnimating];
[_emergencyKeyQueue cancelAllOperations];
[_emergencyKeyQueue addOperationWithBlock:^{
[self.emergencyKeyQueue cancelAllOperations];
[self.emergencyKeyQueue addOperationWithBlock:^{
if ([masterPassword length] && [fullName length])
_key = [[MPKey alloc] initForFullName:fullName withMasterPassword:masterPassword];
self.key = [[MPKey alloc] initForFullName:fullName withMasterPassword:masterPassword];
else
_key = nil;
self.key = nil;
PearlMainQueue( ^{
[self updatePassword];
@@ -139,11 +143,12 @@
[self.passwordButton setTitle:nil forState:UIControlStateNormal];
[self.activity startAnimating];
[_emergencyPasswordQueue cancelAllOperations];
[_emergencyPasswordQueue addOperationWithBlock:^{
[self.emergencyPasswordQueue cancelAllOperations];
[self.emergencyPasswordQueue addOperationWithBlock:^{
NSString *sitePassword = nil;
if (_key && [siteName length])
sitePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:siteName ofType:siteType withCounter:siteCounter usingKey:_key];
if (self.key && [siteName length])
sitePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:siteName ofType:siteType withCounter:siteCounter
usingKey:self.key];
PearlMainQueue( ^{
[self.activity stopAnimating];

View File

@@ -18,15 +18,19 @@
#import "MPOverlayViewController.h"
@implementation MPOverlayViewController {
NSMutableDictionary *_dismissSegueByButton;
}
@interface MPOverlayViewController()
@property(nonatomic, strong) NSMutableDictionary *dismissSegueByButton;
@end
@implementation MPOverlayViewController
- (void)awakeFromNib {
[super awakeFromNib];
_dismissSegueByButton = [NSMutableDictionary dictionary];
self.dismissSegueByButton = [NSMutableDictionary dictionary];
}
- (void)viewDidLoad {
@@ -60,7 +64,7 @@
dismissButton.visible = NO;
dismissButton.frame = self.view.bounds;
dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_dismissSegueByButton[[NSValue valueWithNonretainedObject:dismissButton]] =
self.dismissSegueByButton[[NSValue valueWithNonretainedObject:dismissButton]] =
[[MPOverlaySegue alloc] initWithIdentifier:@"dismiss-overlay"
source:segue.destinationViewController destination:segue.sourceViewController];
@@ -71,14 +75,14 @@
- (void)dismissOverlay:(UIButton *)dismissButton {
NSValue *dismissSegueKey = [NSValue valueWithNonretainedObject:dismissButton];
[((UIStoryboardSegue *)_dismissSegueByButton[dismissSegueKey]) perform];
[((UIStoryboardSegue *)self.dismissSegueByButton[dismissSegueKey]) perform];
}
- (void)removeDismissButtonForViewController:(UIViewController *)viewController {
UIButton *dismissButton = nil;
for (NSValue *dismissButtonValue in [_dismissSegueByButton allKeys])
if (((UIStoryboardSegue *)_dismissSegueByButton[dismissButtonValue]).sourceViewController == viewController) {
for (NSValue *dismissButtonValue in [self.dismissSegueByButton allKeys])
if (((UIStoryboardSegue *)self.dismissSegueByButton[dismissButtonValue]).sourceViewController == viewController) {
dismissButton = [dismissButtonValue nonretainedObjectValue];
NSAssert( [self.view.subviews containsObject:dismissButton], @"Missing dismiss button in dictionary." );
}
@@ -86,7 +90,7 @@
return;
NSValue *dismissSegueKey = [NSValue valueWithNonretainedObject:dismissButton];
[_dismissSegueByButton removeObjectForKey:dismissSegueKey];
[self.dismissSegueByButton removeObjectForKey:dismissSegueKey];
[UIView animateWithDuration:0.1f animations:^{
dismissButton.visible = NO;

View File

@@ -17,16 +17,15 @@
//==============================================================================
#import "MPPopdownSegue.h"
#import "MPPasswordsViewController.h"
#import "MPSitesViewController.h"
@implementation MPPopdownSegue {
}
@implementation MPPopdownSegue
- (void)perform {
MPPasswordsViewController *passwordsVC;
MPSitesViewController *passwordsVC;
UIViewController *popdownVC;
if ([self.sourceViewController isKindOfClass:[MPPasswordsViewController class]]) {
if ([self.sourceViewController isKindOfClass:[MPSitesViewController class]]) {
passwordsVC = self.sourceViewController;
popdownVC = self.destinationViewController;
UIView *popdownView = popdownVC.view;
@@ -55,7 +54,7 @@
}
else {
popdownVC = self.sourceViewController;
for (passwordsVC = self.sourceViewController; passwordsVC && ![(id)passwordsVC isKindOfClass:[MPPasswordsViewController class]];
for (passwordsVC = self.sourceViewController; passwordsVC && ![(id)passwordsVC isKindOfClass:[MPSitesViewController class]];
passwordsVC = (id)passwordsVC.parentViewController);
NSAssert( passwordsVC, @"Couldn't find passwords VC to pop back to." );

View File

@@ -32,7 +32,7 @@
@property(weak, nonatomic) IBOutlet UISegmentedControl *generated1TypeControl;
@property(weak, nonatomic) IBOutlet UISegmentedControl *generated2TypeControl;
@property(weak, nonatomic) IBOutlet UISegmentedControl *storedTypeControl;
@property(weak, nonatomic) IBOutlet UILabel *passwordTypeExample;
@property(weak, nonatomic) IBOutlet UILabel *typeSamplePassword;
- (IBAction)previousAvatar:(id)sender;
- (IBAction)nextAvatar:(id)sender;

View File

@@ -21,7 +21,7 @@
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "UIColor+Expanded.h"
#import "MPPasswordsViewController.h"
#import "MPSitesViewController.h"
#import "MPAppDelegate_InApp.h"
@interface MPPreferencesViewController()
@@ -70,7 +70,7 @@
examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType
withCounter:1 usingKey:[MPiOSAppDelegate get].key];
PearlMainQueue( ^{
self.passwordTypeExample.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
self.typeSamplePassword.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
} );
} );
}
@@ -108,7 +108,7 @@
[[MPiOSAppDelegate get] showExportForVC:self];
if (cell == self.showHelpCell) {
MPPasswordsViewController *passwordsVC = [self dismissPopup];
MPSitesViewController *passwordsVC = [self dismissPopup];
[passwordsVC performSegueWithIdentifier:@"guide" sender:self];
}
@@ -230,11 +230,11 @@
#pragma mark - Private
- (MPPasswordsViewController *)dismissPopup {
- (MPSitesViewController *)dismissPopup {
for (UIViewController *vc = self; (vc = vc.parentViewController);)
if ([vc isKindOfClass:[MPPasswordsViewController class]]) {
MPPasswordsViewController *passwordsVC = (MPPasswordsViewController *)vc;
if ([vc isKindOfClass:[MPSitesViewController class]]) {
MPSitesViewController *passwordsVC = (MPSitesViewController *)vc;
[passwordsVC dismissPopdown:self];
return passwordsVC;
}
@@ -254,32 +254,32 @@
case 1:
return MPSiteTypeGeneratedName;
default:
switch (selectedGenerated2Index) {
case 0:
return MPSiteTypeGeneratedMaximum;
case 1:
return MPSiteTypeGeneratedLong;
case 2:
return MPSiteTypeGeneratedMedium;
case 3:
return MPSiteTypeGeneratedBasic;
case 4:
return MPSiteTypeGeneratedShort;
case 5:
return MPSiteTypeGeneratedPIN;
default:
switch (selectedStoredIndex) {
switch (selectedGenerated2Index) {
case 0:
return MPSiteTypeStoredPersonal;
return MPSiteTypeGeneratedMaximum;
case 1:
return MPSiteTypeStoredDevicePrivate;
return MPSiteTypeGeneratedLong;
case 2:
return MPSiteTypeGeneratedMedium;
case 3:
return MPSiteTypeGeneratedBasic;
case 4:
return MPSiteTypeGeneratedShort;
case 5:
return MPSiteTypeGeneratedPIN;
default:
Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
(long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
switch (selectedStoredIndex) {
case 0:
return MPSiteTypeStoredPersonal;
case 1:
return MPSiteTypeStoredDevicePrivate;
default:
Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
(long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
}
}
}
}
}
- (NSInteger)generated1SegmentIndexForType:(MPSiteType)type {

View File

@@ -18,8 +18,7 @@
#import "MPRootSegue.h"
@implementation MPRootSegue {
}
@implementation MPRootSegue
- (void)perform {

View File

@@ -20,18 +20,18 @@
#import "MPEntities.h"
#import "MPCell.h"
typedef NS_ENUM ( NSUInteger, MPPasswordCellMode ) {
typedef NS_ENUM ( NSUInteger, MPSiteCellMode ) {
MPPasswordCellModePassword,
MPPasswordCellModeSettings,
};
@interface MPPasswordCell : MPCell<UIScrollViewDelegate, UITextFieldDelegate>
@interface MPSiteCell : MPCell<UIScrollViewDelegate, UITextFieldDelegate>
@property(nonatomic) NSArray *fuzzyGroups;
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated;
- (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated;
- (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated;
- (void)setMode:(MPSiteCellMode)mode animated:(BOOL)animated;
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context;
@end

View File

@@ -16,13 +16,12 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import "MPPasswordCell.h"
#import "MPSiteCell.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "UIColor+Expanded.h"
#import "MPAppDelegate_InApp.h"
@interface MPPasswordCell()
@interface MPSiteCell()
@property(nonatomic, strong) IBOutlet UILabel *siteNameLabel;
@property(nonatomic, strong) IBOutlet UITextField *passwordField;
@@ -39,16 +38,16 @@
@property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView;
@property(nonatomic, strong) IBOutlet UIButton *contentButton;
@property(nonatomic, strong) IBOutlet UIButton *loginNameButton;
@property(nonatomic, strong) IBOutlet UILabel *loginNameHint;
@property(nonatomic, strong) IBOutlet UIView *indicatorView;
@property(nonatomic) MPPasswordCellMode mode;
@property(nonatomic) MPSiteCellMode mode;
@property(nonatomic, copy) NSString *transientSite;
@property(nonatomic, strong) NSManagedObjectID *siteOID;
@end
@implementation MPPasswordCell {
NSManagedObjectID *_siteOID;
}
@implementation MPSiteCell
#pragma mark - Life cycle
@@ -65,9 +64,9 @@
[self setupLayer];
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPPasswordCell *_self) {
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPSiteCell *self) {
if (from && !CGSizeEqualToSize( [from CGRectValue].size, [to CGRectValue].size ))
[_self setupLayer];
[self setupLayer];
}];
[self.contentButton observeKeyPath:@"highlighted"
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
@@ -135,8 +134,8 @@
[super prepareForReuse];
_siteOID = nil;
_fuzzyGroups = nil;
self.siteOID = nil;
self.fuzzyGroups = nil;
self.transientSite = nil;
self.mode = MPPasswordCellModePassword;
[self updateAnimated:NO];
@@ -153,25 +152,25 @@
- (void)setFuzzyGroups:(NSArray *)fuzzyGroups {
if (_fuzzyGroups == fuzzyGroups)
if (self.fuzzyGroups == fuzzyGroups)
return;
_fuzzyGroups = fuzzyGroups;
[self updateSiteName:[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]];
}
- (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated {
- (void)setMode:(MPSiteCellMode)mode animated:(BOOL)animated {
if (mode == _mode)
if (self.mode == mode)
return;
_mode = mode;
[self updateAnimated:animated];
}
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated {
_siteOID = site.permanentObjectID;
self.siteOID = site.permanentObjectID;
[self updateAnimated:animated];
}
@@ -200,7 +199,7 @@
atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
if (textField == self.loginNameField)
self.loginNameButton.hidden = [self.loginNameField.attributedText length] || self.loginNameField.enabled;
self.loginNameHint.hidden = [self.loginNameField.attributedText length] || self.loginNameField.enabled;
}
- (IBAction)textFieldDidChange:(UITextField *)textField {
@@ -368,7 +367,7 @@
}
- (IBAction)doAction:(UIButton *)sender {
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) {
MPSiteEntity *mainSite = [self siteInContext:mainContext];
[PearlAlert showAlertWithTitle:@"Login Page" message:nil
@@ -531,7 +530,7 @@
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
// UI
self.backgroundColor = mainSite.url? [UIColor greenColor]: [UIColor redColor];
//self.backgroundColor = mainSite.url? [UIColor greenColor]: [UIColor redColor];
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
@@ -548,9 +547,9 @@
[self.passwordField resignFirstResponder];
}
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
[self.loginNameButton setTitle:@"Tap here to ⚙ generate username or the pencil to type one" forState:UIControlStateNormal];
self.loginNameHint.text = @"Tap here to ⚙ generate username or the pencil to type one";
else
[self.loginNameButton setTitle:@"Tap the pencil to type a username" forState:UIControlStateNormal];
self.loginNameHint.text = @"Tap the pencil to type a username";
// Site Name
[self updateSiteName:mainSite];
@@ -609,7 +608,7 @@
self.loginNameGenerated.hidden = !loginGenerated;
self.loginNameField.attributedText =
strarm( stra( loginName?: @"", self.siteNameLabel.textAttributes ), NSParagraphStyleAttributeName, nil );
self.loginNameButton.hidden = [loginName length] || self.loginNameField.enabled;
self.loginNameHint.hidden = [loginName length] || self.loginNameField.enabled;
if (![password length]) {
self.indicatorView.hidden = NO;
@@ -678,7 +677,7 @@
[self.window endEditing:YES];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([pasteboard respondsToSelector:@selector(setItems:options:)]) {
if ([pasteboard respondsToSelector:@selector( setItems:options: )]) {
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: password } ]
options:@{
UIPasteboardOptionLocalOnly : @NO,
@@ -718,7 +717,7 @@
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context {
return [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
return [MPSiteEntity existingObjectWithID:self.siteOID inContext:context];
}
@end

View File

@@ -18,7 +18,7 @@
#import <Foundation/Foundation.h>
@interface MPPasswordsSegue : UIStoryboardSegue
@interface MPSitesSegue : UIStoryboardSegue
@property(nonatomic, assign) BOOL animated;

View File

@@ -16,12 +16,11 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import "MPPasswordsSegue.h"
#import "MPPasswordsViewController.h"
#import "MPSitesSegue.h"
#import "MPSitesViewController.h"
#import "MPCombinedViewController.h"
@implementation MPPasswordsSegue {
}
@implementation MPSitesSegue
- (id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
@@ -35,34 +34,34 @@
- (void)perform {
if ([self.destinationViewController isKindOfClass:[MPPasswordsViewController class]]) {
__weak MPPasswordsViewController *passwordsVC = self.destinationViewController;
if ([self.destinationViewController isKindOfClass:[MPSitesViewController class]]) {
__weak MPSitesViewController *sitesVC = self.destinationViewController;
MPCombinedViewController *combinedVC = self.sourceViewController;
[combinedVC addChildViewController:passwordsVC];
passwordsVC.active = NO;
[combinedVC addChildViewController:sitesVC];
sitesVC.active = NO;
UIView *passwordsView = passwordsVC.view;
passwordsView.frame = combinedVC.view.bounds;
passwordsView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersVC.view];
UIView *sitesView = sitesVC.view;
sitesView.frame = combinedVC.view.bounds;
sitesView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[combinedVC.view insertSubview:sitesView belowSubview:combinedVC.usersVC.view];
[passwordsVC setActive:YES animated:self.animated completion:^(BOOL finished) {
[sitesVC setActive:YES animated:self.animated completion:^(BOOL finished) {
if (!finished)
return;
[passwordsVC didMoveToParentViewController:combinedVC];
[sitesVC didMoveToParentViewController:combinedVC];
}];
}
else if ([self.sourceViewController isKindOfClass:[MPPasswordsViewController class]]) {
__weak MPPasswordsViewController *passwordsVC = self.sourceViewController;
else if ([self.sourceViewController isKindOfClass:[MPSitesViewController class]]) {
__weak MPSitesViewController *sitesVC = self.sourceViewController;
[passwordsVC willMoveToParentViewController:nil];
[passwordsVC setActive:NO animated:self.animated completion:^(BOOL finished) {
[sitesVC willMoveToParentViewController:nil];
[sitesVC setActive:NO animated:self.animated completion:^(BOOL finished) {
if (!finished)
return;
[passwordsVC.view removeFromSuperview];
[passwordsVC removeFromParentViewController];
[sitesVC.view removeFromSuperview];
[sitesVC removeFromParentViewController];
}];
}
}

View File

@@ -19,22 +19,22 @@
@class MPSiteEntity;
@class MPCoachmark;
@interface MPPasswordsViewController : UIViewController<UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@interface MPSitesViewController : UIViewController<UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property(strong, nonatomic) IBOutlet UIView *passwordSelectionContainer;
@property(strong, nonatomic) IBOutlet UICollectionView *passwordCollectionView;
@property(strong, nonatomic) IBOutlet UISearchBar *passwordsSearchBar;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *popdownToTopConstraint;
@property(strong, nonatomic) IBOutlet UIView *badNameTipContainer;
@property(strong, nonatomic) IBOutlet UIView *popdownView;
@property(strong, nonatomic) IBOutlet UIView *popdownContainer;
@property(nonatomic, strong) IBOutlet UICollectionView *collectionView;
@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar;
@property(nonatomic, strong) IBOutlet UISearchBar *searchBar;
@property(nonatomic, strong) IBOutlet NSLayoutConstraint *sitesToBottomConstraint;
@property(nonatomic, strong) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
@property(nonatomic, strong) IBOutlet NSLayoutConstraint *popdownToTopConstraint;
@property(nonatomic, strong) IBOutlet UIView *badNameTipContainer;
@property(nonatomic, strong) IBOutlet UIView *popdownView;
@property(nonatomic, strong) IBOutlet UIView *popdownContainer;
@property(assign, nonatomic) BOOL active;
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void ( ^ )(BOOL finished))completion;
- (void)reloadPasswords;
- (void)reloadSites;
- (IBAction)dismissPopdown:(id)sender;

View File

@@ -16,12 +16,12 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import "MPPasswordsViewController.h"
#import "MPSitesViewController.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPPopdownSegue.h"
#import "MPAppDelegate_Key.h"
#import "MPPasswordCell.h"
#import "MPSiteCell.h"
#import "MPAnswersViewController.h"
#import "MPMessageViewController.h"
@@ -31,22 +31,17 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
MPPasswordsBadNameTip = 1 << 0,
};
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate>
@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar;
@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
@property(nonatomic, strong) NSArray *fuzzyGroups;
@property(nonatomic, strong) NSCharacterSet *siteNameAcceptableCharactersSet;
@property(nonatomic, strong) NSMutableArray<NSMutableArray *> *dataSource;
@property(nonatomic, weak) UIViewController *popdownVC;
@end
@implementation MPPasswordsViewController {
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
NSFetchedResultsController *_fetchedResultsController;
UIColor *_backgroundColor;
UIColor *_darkenedBackgroundColor;
__weak UIViewController *_popdownVC;
NSCharacterSet *_siteNameAcceptableCharactersSet;
NSArray *_fuzzyGroups;
NSMutableArray<NSMutableArray *> *_passwordCollectionSections;
}
@implementation MPSitesViewController
#pragma mark - Life
@@ -57,22 +52,20 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
NSMutableCharacterSet *siteNameAcceptableCharactersSet = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
[siteNameAcceptableCharactersSet formIntersectionWithCharacterSet:[[NSCharacterSet uppercaseLetterCharacterSet] invertedSet]];
[siteNameAcceptableCharactersSet addCharactersInString:@"@.-+~&_;:/"];
_siteNameAcceptableCharactersSet = siteNameAcceptableCharactersSet;
self.siteNameAcceptableCharactersSet = siteNameAcceptableCharactersSet;
_backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_passwordCollectionSections = [NSMutableArray new];
self.dataSource = [NSMutableArray new];
self.view.backgroundColor = [UIColor clearColor];
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
self.passwordsSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
if ([self.passwordsSearchBar respondsToSelector:@selector( keyboardAppearance )])
self.passwordsSearchBar.keyboardAppearance = UIKeyboardAppearanceDark;
[self.collectionView automaticallyAdjustInsetsForKeyboard];
self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
if ([self.searchBar respondsToSelector:@selector( keyboardAppearance )])
self.searchBar.keyboardAppearance = UIKeyboardAppearanceDark;
else
[self.passwordsSearchBar enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
[self.searchBar enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
if ([subview isKindOfClass:[UITextField class]])
((UITextField *)subview).keyboardAppearance = UIKeyboardAppearanceDark;
} recurse:YES];
} recurse:YES];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -96,9 +89,9 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
pasteboardURL = [NSURL URLWithString:pasteboard.string];
if (pasteboardURL.host)
self.query = NSNullToNil( [pasteboardURL.host firstMatchGroupsOfExpression:bareHostRE][0] );
self.query = NSNullToNil( [[pasteboardURL.host firstMatchGroupsOfExpression:bareHostRE] firstObject] );
else
[self reloadPasswords];
[self reloadSites];
}
- (void)viewDidAppear:(BOOL)animated {
@@ -131,17 +124,17 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"popdown"])
_popdownVC = segue.destinationViewController;
self.popdownVC = segue.destinationViewController;
if ([segue.identifier isEqualToString:@"answers"])
((MPAnswersViewController *)segue.destinationViewController).site =
[[MPPasswordCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
[[MPSiteCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
if ([segue.identifier isEqualToString:@"message"])
((MPMessageViewController *)segue.destinationViewController).message = sender;
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[self.passwordCollectionView.collectionViewLayout invalidateLayout];
[self.collectionView.collectionViewLayout invalidateLayout];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
@@ -159,7 +152,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
insetForSectionAtIndex:(NSInteger)section {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
UIEdgeInsets occludedInsets = [self.passwordCollectionView occludedInsets];
UIEdgeInsets occludedInsets = [self.collectionView occludedInsets];
UIEdgeInsets insets = layout.sectionInset;
insets.top = insets.bottom; // Undo storyboard hack for manual top-occluded insets.
@@ -176,19 +169,19 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return [_passwordCollectionSections count];
return [self.dataSource count];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [_passwordCollectionSections[(NSUInteger)section] count];
return [self.dataSource[(NSUInteger)section] count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
[cell setFuzzyGroups:_fuzzyGroups];
id item = _passwordCollectionSections[(NSUInteger)indexPath.section][(NSUInteger)indexPath.item];
MPSiteCell *cell = [MPSiteCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
[cell setFuzzyGroups:self.fuzzyGroups];
id item = self.dataSource[(NSUInteger)indexPath.section][(NSUInteger)indexPath.item];
if ([item isKindOfClass:[MPSiteEntity class]])
[cell setSite:item animated:NO];
else // item == MPTransientPasswordItem
@@ -201,8 +194,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if (scrollView == self.passwordCollectionView)
for (MPPasswordCell *cell in [self.passwordCollectionView visibleCells])
if (scrollView == self.collectionView)
for (MPSiteCell *cell in [self.collectionView visibleCells])
[cell setMode:MPPasswordCellModePassword animated:YES];
}
@@ -210,11 +203,11 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (controller == _fetchedResultsController)
if (controller == self.fetchedResultsController)
PearlMainQueue( ^{
[self.passwordCollectionView updateDataSource:_passwordCollectionSections
toSections:[self createPasswordCollectionSections]
reloadItems:nil completion:nil];
[self.collectionView updateDataSource:self.dataSource
toSections:[self createDataSource]
reloadItems:nil completion:nil];
} );
}
@@ -222,7 +215,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) {
if (searchBar == self.searchBar) {
searchBar.text = nil;
return YES;
}
@@ -232,24 +225,22 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) {
[self.passwordsSearchBar setShowsCancelButton:YES animated:YES];
if (searchBar == self.searchBar) {
[self.searchBar setShowsCancelButton:YES animated:YES];
[UIView animateWithDuration:0.3f animations:^{
self.passwordCollectionView.backgroundColor = _darkenedBackgroundColor;
self.collectionView.backgroundColor = [self.collectionView.backgroundColor colorWithAlphaComponent:0.6f];
}];
}
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) {
[self.passwordsSearchBar setShowsCancelButton:NO animated:YES];
if (_passwordsDismissRecognizer)
[self.view removeGestureRecognizer:_passwordsDismissRecognizer];
if (searchBar == self.searchBar) {
[self.searchBar setShowsCancelButton:NO animated:YES];
[self reloadSites];
[self reloadPasswords];
[UIView animateWithDuration:0.3f animations:^{
self.passwordCollectionView.backgroundColor = _backgroundColor;
self.collectionView.backgroundColor = [self.collectionView.backgroundColor colorWithAlphaComponent:0];
}];
}
}
@@ -267,11 +258,11 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchBar == self.passwordsSearchBar) {
if ([[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length])
if (searchBar == self.searchBar) {
if ([[self.query stringByTrimmingCharactersInSet:self.siteNameAcceptableCharactersSet] length])
[self showTips:MPPasswordsBadNameTip];
[self reloadPasswords];
[self reloadSites];
}
}
@@ -292,7 +283,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
}];
}
- (NSMutableArray<NSMutableArray *> *)createPasswordCollectionSections {
- (NSMutableArray<NSMutableArray *> *)createDataSource {
NSString *query = self.query;
BOOL needTransientItem = [query length] > 0;
@@ -321,42 +312,42 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlRemoveNotificationObservers();
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
^(MPSitesViewController *self, NSNotification *note) {
[self.view endEditing:YES];
self.passwordSelectionContainer.visible = NO;
self.view.visible = NO;
} );
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
^(MPSitesViewController *self, NSNotification *note) {
[UIView animateWithDuration:0.7f animations:^{
self.passwordSelectionContainer.visible = YES;
self.view.visible = YES;
}];
} );
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
^(MPSitesViewController *self, NSNotification *note) {
[self viewWillAppear:YES];
} );
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
^(MPSitesViewController *self, NSNotification *note) {
PearlMainQueue( ^{
self->_fetchedResultsController = nil;
self.fetchedResultsController = nil;
self.query = nil;
} );
} );
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
^(MPSitesViewController *self, NSNotification *note) {
PearlMainQueue( ^{
[self updateConfigKey:note.object];
} );
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
self->_fetchedResultsController = nil;
[self reloadPasswords];
^(MPSitesViewController *self, NSNotification *note) {
self.fetchedResultsController = nil;
[self reloadSites];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
^(MPSitesViewController *self, NSNotification *note) {
PearlMainQueue( ^{
[self reloadPasswords];
[self reloadSites];
[self registerObservers];
} );
} );
@@ -373,12 +364,12 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)updateConfigKey:(NSString *)key {
if (!key || [key isEqualToString:NSStringFromSelector( @selector( dictationSearch ) )])
self.passwordsSearchBar.keyboardType = [[MPiOSConfig get].dictationSearch boolValue]? UIKeyboardTypeDefault: UIKeyboardTypeURL;
self.searchBar.keyboardType = [[MPiOSConfig get].dictationSearch boolValue]? UIKeyboardTypeDefault: UIKeyboardTypeURL;
if (!key || [key isEqualToString:NSStringFromSelector( @selector( hidePasswords ) )])
[self.passwordCollectionView reloadData];
[self.collectionView reloadData];
}
- (void)reloadPasswords {
- (void)reloadSites {
[self.fetchedResultsController.managedObjectContext performBlock:^{
static NSRegularExpression *fuzzyRE;
@@ -395,7 +386,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
}];
_fuzzyGroups = fuzzyGroups;
self.fuzzyGroups = fuzzyGroups;
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate =
@@ -404,11 +395,11 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
MPError( error, @"Couldn't fetch sites." );
PearlMainQueue( ^{
[self.passwordCollectionView updateDataSource:_passwordCollectionSections
toSections:[self createPasswordCollectionSections]
reloadItems:@[ MPTransientPasswordItem ] completion:^(BOOL finished) {
for (MPPasswordCell *cell in self.passwordCollectionView.visibleCells)
[cell setFuzzyGroups:_fuzzyGroups];
[self.collectionView updateDataSource:self.dataSource
toSections:[self createDataSource]
reloadItems:@[ MPTransientPasswordItem ] completion:^(BOOL finished) {
for (MPSiteCell *cell in self.collectionView.visibleCells)
[cell setFuzzyGroups:self.fuzzyGroups];
}];
} );
}];
@@ -418,13 +409,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (NSString *)query {
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [self.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
- (void)setQuery:(NSString *)query {
self.passwordsSearchBar.text = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
[self reloadPasswords];
self.searchBar.text = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
[self reloadSites];
}
- (NSFetchedResultsController *)fetchedResultsController {
@@ -436,10 +427,9 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
];
fetchRequest.fetchBatchSize = 10;
_fetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:mainContext
sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
(self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:mainContext
sectionNameKeyPath:nil cacheName:nil]).delegate = self;
}];
[self registerObservers];
}
@@ -458,7 +448,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[UIView animateWithDuration:animated? 0.4f: 0 animations:^{
[self.navigationBarToTopConstraint updatePriority:active? 1: UILayoutPriorityDefaultHigh];
[self.passwordsToBottomConstraint updatePriority:active? 1: UILayoutPriorityDefaultHigh];
[self.sitesToBottomConstraint updatePriority:active? 1: UILayoutPriorityDefaultHigh];
[self.view layoutIfNeeded];
} completion:completion];
}
@@ -467,8 +457,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (IBAction)dismissPopdown:(id)sender {
if (_popdownVC)
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:_popdownVC destination:self] perform];
if (self.popdownVC)
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:self.popdownVC destination:self] perform];
else
self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
}

View File

@@ -20,7 +20,7 @@
#import "MPiOSAppDelegate.h"
#import "UIColor+Expanded.h"
#import "MPAppDelegate_InApp.h"
#import "MPPasswordsViewController.h"
#import "MPSitesViewController.h"
PearlEnum( MPDevelopmentFuelConsumption,
MPDevelopmentFuelConsumptionQuarterly, MPDevelopmentFuelConsumptionMonthly, MPDevelopmentFuelWeekly );
@@ -191,11 +191,11 @@ PearlEnum( MPDevelopmentFuelConsumption,
#pragma mark - Private
- (MPPasswordsViewController *)dismissPopup {
- (MPSitesViewController *)dismissPopup {
for (UIViewController *vc = self; (vc = vc.parentViewController);)
if ([vc isKindOfClass:[MPPasswordsViewController class]]) {
MPPasswordsViewController *passwordsVC = (MPPasswordsViewController *)vc;
if ([vc isKindOfClass:[MPSitesViewController class]]) {
MPSitesViewController *passwordsVC = (MPSitesViewController *)vc;
[passwordsVC dismissPopdown:self];
return passwordsVC;
}

View File

@@ -54,19 +54,19 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
@property(nonatomic, strong) NSTimer *marqueeTipTimer;
@property(nonatomic, strong) NSArray *marqueeTipTexts;
@property(nonatomic) NSUInteger marqueeTipTextIndex;
@property(nonatomic, copy) NSString *masterPasswordChoice;
@property(nonatomic, strong) NSOperationQueue *afterUpdates;
@property(nonatomic, weak) id contextChangedObserver;
@end
@implementation MPUsersViewController {
NSString *_masterPasswordChoice;
NSOperationQueue *_afterUpdates;
__weak id _contextChangedObserver;
}
@implementation MPUsersViewController
- (void)viewDidLoad {
[super viewDidLoad];
_afterUpdates = [NSOperationQueue new];
self.afterUpdates = [NSOperationQueue new];
self.marqueeTipTexts = @[
strl( @"Thanks, lhunath ➚" ),
@@ -205,7 +205,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
return NO;
}
if (![masterPassword isEqualToString:_masterPasswordChoice]) {
if (![masterPassword isEqualToString:self.masterPasswordChoice]) {
// Master password confirmation failed.
[self showEntryTip:strl( @"Looks like a typo!\nTry again; enter your master password twice." )];
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
@@ -655,7 +655,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
- (void)afterUpdatesMainQueue:(void ( ^ )(void))block {
[_afterUpdates addOperationWithBlock:^{
[self.afterUpdates addOperationWithBlock:^{
PearlMainQueue( block );
}];
}
@@ -664,15 +664,15 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self removeKeyPathObservers];
PearlRemoveNotificationObservers();
[[NSNotificationCenter defaultCenter] removeObserver:_contextChangedObserver];
[[NSNotificationCenter defaultCenter] removeObserver:self.contextChangedObserver];
}
- (void)registerObservers {
[self removeObservers];
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
[_self updateAvatarVisibility];
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *self) {
[self updateAvatarVisibility];
}];
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
@@ -696,13 +696,14 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
} );
if ((_contextChangedObserver = [MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
if ([[[affectedObjects allKeys] filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(NSManagedObjectID *objectID, NSDictionary *bindings) {
return [objectID.entity.name isEqualToString:NSStringFromClass( [MPUserEntity class] )];
}]] count])
[self reloadUsers];
}]))
if ((self.contextChangedObserver
= [MPiOSAppDelegate managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
if ([[[affectedObjects allKeys] filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(NSManagedObjectID *objectID, NSDictionary *bindings) {
return [objectID.entity.name isEqualToString:NSStringFromClass( [MPUserEntity class] )];
}]] count])
[self reloadUsers];
}]))
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = YES;
[self.storeLoadingActivity stopAnimating];
@@ -772,7 +773,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
isNew:&isNew].permanentObjectID;
[self.avatarCollectionView reloadData];
NSUInteger selectedAvatarItem = isNew? [_userIDs count]: selectUserID? [_userIDs indexOfObject:selectUserID]: NSNotFound;
NSUInteger selectedAvatarItem = isNew? [self.userIDs count]: selectUserID? [self.userIDs indexOfObject:selectUserID]: NSNotFound;
if (selectedAvatarItem != NSNotFound)
[self.avatarCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:selectedAvatarItem inSection:0] animated:NO
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
@@ -791,7 +792,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
- (void)setActiveUserState:(MPActiveUserState)activeUserState animated:(BOOL)animated {
_activeUserState = activeUserState;
_masterPasswordChoice = nil;
self.masterPasswordChoice = nil;
if (activeUserState != MPActiveUserStateMinimized && (!self.active || [MPiOSAppDelegate get].activeUserOID)) {
[[MPiOSAppDelegate get] signOutAnimated:YES];
@@ -799,7 +800,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
}
// Set the entry container's contents.
[_afterUpdates setSuspended:YES];
[self.afterUpdates setSuspended:YES];
__block BOOL requestFirstResponder = NO;
[self.view layoutIfNeeded];
[UIView animateWithDuration:animated? 0.4f: 0 animations:^{
@@ -828,7 +829,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
break;
}
case MPActiveUserStateMasterPasswordConfirmation: {
_masterPasswordChoice = self.entryField.text;
self.masterPasswordChoice = self.entryField.text;
self.entryLabel.text = strl( @"Confirm your master password:" );
self.entryField.secureTextEntry = YES;
self.entryField.autocapitalizationType = UITextAutocapitalizationTypeNone;
@@ -890,7 +891,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
[_afterUpdates setSuspended:NO];
[self.afterUpdates setSuspended:NO];
}];
[self.entryField resignFirstResponder];

View File

@@ -21,11 +21,10 @@
#import "MPAppDelegate_Store.h"
#import "MPStoreViewController.h"
#define MP_HANG_TIME_MAIN 3 // s
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
@property(nonatomic, strong) PearlHangDetector *hangDetector;
@end
@@ -37,7 +36,7 @@
dispatch_once( &once, ^{
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
#ifdef DEBUG
[PearlLogger get].printLevel = PearlLogLevelDebug; //Trace;
[PearlLogger get].printLevel = PearlLogLevelTrace;
#else
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
#endif
@@ -76,7 +75,11 @@
}
#endif
[self installHangDetector];
[self.hangDetector = [[PearlHangDetector alloc] initWithHangAction:^(NSTimeInterval hangTime) {
MPError( [NSError errorWithDomain:MPErrorDomain code:MPErrorHangCode userInfo:@{
@"time": @(hangTime)
}], @"Timeout waiting for main thread after %fs.", hangTime );
}] start];
}
@catch (id exception) {
err( @"During Analytics Setup: %@", exception );
@@ -143,31 +146,6 @@
return YES;
}
- (void)installHangDetector {
__block NSDate *latestPing = [NSDate date];
__block __weak VoidBlock wPingOp, wPongOp;
VoidBlock pingOp = ^{
latestPing = [NSDate date];
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC ), dispatch_get_main_queue(), wPingOp );
}, pongOp = ^{
NSTimeInterval hangTime = -[latestPing timeIntervalSinceNow];
if (hangTime > MP_HANG_TIME_MAIN) {
MPError( [NSError errorWithDomain:MPErrorDomain code:MPErrorHangCode userInfo:@{
@"time": @(hangTime)
}], @"Timeout waiting for main thread after %fs.", hangTime );
}
else
dbg( @"hangTime=%f", hangTime );
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, NSEC_PER_SEC ), dispatch_get_global_queue( QOS_CLASS_BACKGROUND, 0 ), wPongOp );
};
(wPingOp = pingOp)();
(wPongOp = pongOp)();
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
@@ -257,6 +235,40 @@
[activityOverlay cancelOverlayAnimated:YES];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
inf( @"Will foreground" );
[super applicationWillEnterForeground:application];
[self.hangDetector start];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf( @"Re-activated" );
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
PearlNotMainQueue( ^{
NSString *importHeader = @"# Master Password site export";
NSString *importedSitesString = [UIPasteboard generalPasteboard].string;
if ([importedSitesString length] > [importHeader length] &&
[[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader])
[PearlAlert showAlertWithTitle:@"Import Sites?" message:
@"We've detected Master Password import sites on your pasteboard, would you like to import them?"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[self importSites:importedSitesString];
[UIPasteboard generalPasteboard].string = @"";
} cancelTitle:@"No" otherTitles:@"Import Sites", nil];
} );
[super applicationDidBecomeActive:application];
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
inf( @"Received memory warning." );
@@ -266,10 +278,12 @@
- (void)applicationDidEnterBackground:(UIApplication *)application {
inf( @"Will background" );
inf( @"Did background" );
if (![[MPiOSConfig get].rememberLogin boolValue])
[self signOutAnimated:NO];
[self.hangDetector stop];
// self.task = [application beginBackgroundTaskWithExpirationHandler:^{
// [application endBackgroundTask:self.task];
// dbg( @"background expiring" );
@@ -311,31 +325,6 @@
[super applicationDidEnterBackground:application];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf( @"Re-activated" );
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
PearlNotMainQueue( ^{
NSString *importHeader = @"# Master Password site export";
NSString *importedSitesString = [UIPasteboard generalPasteboard].string;
if ([importedSitesString length] > [importHeader length] &&
[[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader])
[PearlAlert showAlertWithTitle:@"Import Sites?" message:
@"We've detected Master Password import sites on your pasteboard, would you like to import them?"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[self importSites:importedSitesString];
[UIPasteboard generalPasteboard].string = @"";
} cancelTitle:@"No" otherTitles:@"Import Sites", nil];
} );
[super applicationDidBecomeActive:application];
}
#pragma mark - Behavior
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {

View File

@@ -1,137 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>M. Password</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mpsites</string>
</array>
<key>CFBundleTypeIconFiles</key>
<array>
<string>Icon-Small</string>
</array>
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.masterpassword.sites</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>[auto]</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
<key>Kits</key>
<array>
<dict>
<key>KitInfo</key>
<dict/>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
</array>
</dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>© 2011-2017</string>
<key>UIAppFonts</key>
<array>
<string>Exo2.0-Bold.otf</string>
<string>Exo2.0-ExtraBold.otf</string>
<string>Exo2.0-Regular.otf</string>
<string>Exo2.0-Thin.otf</string>
<string>SourceCodePro-Black.otf</string>
<string>SourceCodePro-Regular.otf</string>
<string>SourceCodePro-ExtraLight.otf</string>
</array>
<key>UIMainStoryboardFile</key>
<string>Storyboard</string>
<key>UIStatusBarHidden</key>
<true/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDefault</string>
<key>UIStatusBarTintParameters</key>
<dict>
<key>UINavigationBar</key>
<dict>
<key>Style</key>
<string>UIBarStyleDefault</string>
<key>TintColor</key>
<dict>
<key>Blue</key>
<real>0.42745098039215684</real>
<key>Green</key>
<real>0.39215686274509803</real>
<key>Red</key>
<real>0.37254901960784315</real>
</dict>
<key>Translucent</key>
<false/>
</dict>
</dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.utf8-plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password sites</string>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.sites</string>
<key>UTTypeSize320IconFile</key>
<string>Icon-320.png</string>
<key>UTTypeSize64IconFile</key>
<string>Icon-64.png</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mpsites</string>
</array>
</dict>
</dict>
</array>
</dict>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>M. Password</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mpsites</string>
</array>
<key>CFBundleTypeIconFiles</key>
<array>
<string>Icon-Small</string>
</array>
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.masterpassword.sites</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>[auto]</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
<key>Kits</key>
<array>
<dict>
<key>KitInfo</key>
<dict/>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
</array>
</dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>© 2011-2017</string>
<key>UIAppFonts</key>
<array>
<string>Exo2.0-Bold.otf</string>
<string>Exo2.0-ExtraBold.otf</string>
<string>Exo2.0-Regular.otf</string>
<string>Exo2.0-Thin.otf</string>
<string>SourceCodePro-Black.otf</string>
<string>SourceCodePro-Regular.otf</string>
<string>SourceCodePro-ExtraLight.otf</string>
</array>
<key>UIMainStoryboardFile</key>
<string>Storyboard</string>
<key>UIStatusBarHidden</key>
<true/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDefault</string>
<key>UIStatusBarTintParameters</key>
<dict>
<key>UINavigationBar</key>
<dict>
<key>Style</key>
<string>UIBarStyleDefault</string>
<key>TintColor</key>
<dict>
<key>Blue</key>
<real>0.42745098039215684</real>
<key>Green</key>
<real>0.39215686274509803</real>
<key>Red</key>
<real>0.37254901960784315</real>
</dict>
<key>Translucent</key>
<false/>
</dict>
</dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.utf8-plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password sites</string>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.sites</string>
<key>UTTypeSize320IconFile</key>
<string>Icon-320.png</string>
<key>UTTypeSize64IconFile</key>
<string>Icon-64.png</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mpsites</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

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.
//
// NSString(MPMarkDown).h
// NSString(MPMarkDown)
// 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-09-28.
// 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.
//
// NSString(MPMarkDown).h
// NSString(MPMarkDown)
// 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-09-28.
// 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 "NSString+MPMarkDown.h"
#import "markdown_lib.h"

File diff suppressed because it is too large Load Diff

View File

@@ -39,12 +39,20 @@ else
fi
# Optional features.
mpw_color=${mpw_color:-1} # Colorized Identicon, requires libncurses-dev.
mpw_color=${mpw_color:-1} # Colorized Identicon, requires libncurses-dev.
mpw_json=${mpw_json:-1} # Support for JSON-based user configuration format.
mpw_sodium=${mpw_sodium:-1} # Use libsodium if available instead of cperciva's libscrypt.
# Default build flags.
export CFLAGS="-O3 $CFLAGS"
export LDFLAGS="$LDFLAGS"
# Version.
if { mpw_version=$(git describe --match '*-cli*' --long --dirty --broken) || mpw_version=$(<VERSION); } 2>/dev/null; then
CFLAGS+=" -DMP_VERSION=$mpw_version"
fi
echo 2>&1 "Building mpw version ${mpw_version:-<unknown>}..."
# Distribution specific configuration.
# Homebrew - openssl for scrypt
if hash brew 2>/dev/null; then
@@ -142,7 +150,6 @@ fetchSource() (
}
else
echo >&2 "error: Missing git-svn or svn."
echo >&2 "error: Please install either or manually check out the sources"
echo >&2 "error: from: $home"
@@ -213,7 +220,7 @@ depend() {
popd
}
depend_scrypt() {
if haslib sodium; then
if (( mpw_sodium )) && haslib sodium; then
if [[ $CFLAGS != *HAS_SODIUM=1* ]]; then
CFLAGS+=" -DHAS_SODIUM=1"
LDFLAGS+=" -lsodium"
@@ -254,12 +261,16 @@ mpw() {
-l"crypto"
)
# optional features
(( mpw_color )) && CFLAGS+=( -DCOLOR ) LDFLAGS+=( -l"curses" )
(( mpw_color )) && CFLAGS+=( -DMPW_COLOR ) LDFLAGS+=( -l"curses" )
(( mpw_json )) && CFLAGS+=( -DMPW_JSON ) LDFLAGS+=( -l"json-c" )
cc "${CFLAGS[@]}" "$@" -c core/mpw-algorithm.c -o core/mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-types.c -o core/mpw-types.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-util.c -o core/mpw-util.o
cc "${CFLAGS[@]}" "$@" "core/mpw-algorithm.o" "core/mpw-types.o" "core/mpw-util.o" \
cc "${CFLAGS[@]}" "$@" -c core/base64.c -o core/base64.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-algorithm.c -o core/mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-types.c -o core/mpw-types.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-util.c -o core/mpw-util.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-marshall-util.c -o core/mpw-marshall-util.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-marshall.c -o core/mpw-marshall.o
cc "${CFLAGS[@]}" "$@" "core/base64.o" "core/mpw-algorithm.o" "core/mpw-types.o" "core/mpw-util.o" "core/mpw-marshall-util.o" "core/mpw-marshall.o" \
"${LDFLAGS[@]}" "cli/mpw-cli.c" -o "mpw"
echo "done! Now run ./install or use ./mpw"
}
@@ -294,10 +305,11 @@ mpw-bench() {
-l"crypto"
)
cc "${CFLAGS[@]}" "$@" -c core/base64.c -o core/base64.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-algorithm.c -o core/mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-types.c -o core/mpw-types.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-util.c -o core/mpw-util.o
cc "${CFLAGS[@]}" "$@" "core/mpw-algorithm.o" "core/mpw-types.o" "core/mpw-util.o" \
cc "${CFLAGS[@]}" "$@" "core/base64.o" "core/mpw-algorithm.o" "core/mpw-types.o" "core/mpw-util.o" \
"${LDFLAGS[@]}" "cli/mpw-bench.c" -o "mpw-bench"
echo "done! Now use ./mpw-bench"
}
@@ -326,11 +338,12 @@ mpw-tests() {
-l"crypto" -l"xml2"
)
cc "${CFLAGS[@]}" "$@" -c core/base64.c -o core/base64.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-algorithm.c -o core/mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-types.c -o core/mpw-types.o
cc "${CFLAGS[@]}" "$@" -c core/mpw-util.c -o core/mpw-util.o
cc "${CFLAGS[@]}" "$@" -c cli/mpw-tests-util.c -o cli/mpw-tests-util.o
cc "${CFLAGS[@]}" "$@" "core/mpw-algorithm.o" "core/mpw-types.o" "core/mpw-util.o" \
cc "${CFLAGS[@]}" "$@" "core/base64.o" "core/mpw-algorithm.o" "core/mpw-types.o" "core/mpw-util.o" \
"${LDFLAGS[@]}" "cli/mpw-tests-util.o" "cli/mpw-tests.c" -o "mpw-tests"
echo "done! Now use ./mpw-tests"
}
@@ -339,7 +352,7 @@ mpw-tests() {
### TARGETS
haslib() {
return cc -l"$1" -x c -o /dev/null - <<< 'int main() { return 0; }'
cc -l"$1" -x c -o /dev/null - <<< 'int main() { return 0; }'
}
cc() {
if hash llvm-gcc 2>/dev/null; then

View File

@@ -21,13 +21,11 @@
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_dkLen 64
#define MP_hash PearlHashSHA256
static void mpw_getTime(struct timeval *time) {
if (gettimeofday( time, NULL ) != 0)
ftl( "Could not get time: %d\n", errno );
ftl( "Could not get time: %s\n", strerror( errno ) );
}
static const double mpw_showSpeed(struct timeval startTime, const unsigned int iterations, const char *operation) {
@@ -51,27 +49,27 @@ int main(int argc, char *const argv[]) {
const char *fullName = "Robert Lee Mitchel";
const char *masterPassword = "banana colored duckling";
const char *siteName = "masterpasswordapp.com";
const uint32_t siteCounter = 1;
const MPSiteType siteType = MPSiteTypeGeneratedLong;
const MPSiteVariant siteVariant = MPSiteVariantPassword;
const char *siteContext = NULL;
const MPCounterValue siteCounter = MPCounterValueDefault;
const MPResultType resultType = MPResultTypeDefault;
const MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication;
const char *keyContext = NULL;
struct timeval startTime;
unsigned int iterations;
float percent;
const uint8_t *masterKey;
MPMasterKey masterKey;
// Start HMAC-SHA-256
// Similar to phase-two of mpw
uint8_t *sitePasswordInfo = malloc( 128 );
iterations = 3000000;
masterKey = mpw_masterKeyForUser( fullName, masterPassword, MPAlgorithmVersionCurrent );
masterKey = mpw_masterKey( fullName, masterPassword, MPAlgorithmVersionCurrent );
if (!masterKey) {
ftl( "Could not allocate master key: %d\n", errno );
ftl( "Could not allocate master key: %s\n", strerror( errno ) );
abort();
}
mpw_getTime( &startTime );
for (int i = 1; i <= iterations; ++i) {
free( (void *)mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, 128 ) );
free( (void *)mpw_hash_hmac_sha256( masterKey, MPMasterKeySize, sitePasswordInfo, 128 ) );
if (modff(100.f * i / iterations, &percent) == 0)
fprintf( stderr, "\rhmac-sha-256: iteration %d / %d (%.0f%%)..", i, iterations, percent );
@@ -97,7 +95,7 @@ int main(int argc, char *const argv[]) {
iterations = 50;
mpw_getTime( &startTime );
for (int i = 1; i <= iterations; ++i) {
free( (void *)mpw_masterKeyForUser( fullName, masterPassword, MPAlgorithmVersionCurrent ) );
free( (void *)mpw_masterKey( fullName, masterPassword, MPAlgorithmVersionCurrent ) );
if (modff(100.f * i / iterations, &percent) == 0)
fprintf( stderr, "\rscrypt_mpw: iteration %d / %d (%.0f%%)..", i, iterations, percent );
@@ -109,14 +107,14 @@ int main(int argc, char *const argv[]) {
iterations = 50;
mpw_getTime( &startTime );
for (int i = 1; i <= iterations; ++i) {
masterKey = mpw_masterKeyForUser( fullName, masterPassword, MPAlgorithmVersionCurrent );
masterKey = mpw_masterKey( fullName, masterPassword, MPAlgorithmVersionCurrent );
if (!masterKey) {
ftl( "Could not allocate master key: %d\n", errno );
abort();
ftl( "Could not allocate master key: %s\n", strerror( errno ) );
break;
}
free( (void *)mpw_passwordForSite(
masterKey, siteName, siteType, siteCounter, siteVariant, siteContext, MPAlgorithmVersionCurrent ) );
free( (void *)mpw_siteResult(
masterKey, siteName, siteCounter, keyPurpose, keyContext, resultType, NULL, MPAlgorithmVersionCurrent ) );
free( (void *)masterKey );
if (modff(100.f * i / iterations, &percent) == 0)

View File

@@ -1,11 +1,10 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sysexits.h>
#if defined(READLINE)
#include <readline/readline.h>
@@ -15,54 +14,120 @@
#include "mpw-algorithm.h"
#include "mpw-util.h"
#include "mpw-marshall.h"
#define MP_env_fullname "MP_FULLNAME"
#define MP_env_sitetype "MP_SITETYPE"
#define MP_env_sitecounter "MP_SITECOUNTER"
#define MP_env_algorithm "MP_ALGORITHM"
#ifndef MP_VERSION
#define MP_VERSION ?
#endif
#define MP_ENV_fullName "MP_FULLNAME"
#define MP_ENV_algorithm "MP_ALGORITHM"
static void usage() {
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] [-a algorithm] [-V variant] [-C context] [-v|-q] [-h] site\n\n" );
fprintf( stderr, " -u name Specify the full name of the user.\n"
" Defaults to %s in env or prompts.\n\n", MP_env_fullname );
fprintf( stderr, " -t type Specify the password's template.\n"
" Defaults to %s in env or 'long' for password, 'name' for login.\n"
" x, max, maximum | 20 characters, contains symbols.\n"
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
" b, basic | 8 characters, no symbols.\n"
" s, short | Copy-friendly, 4 characters, no symbols.\n"
" i, pin | 4 numbers.\n"
" n, name | 9 letter name.\n"
" p, phrase | 20 character sentence.\n\n", MP_env_sitetype );
fprintf( stderr, " -c counter The value of the counter.\n"
" Defaults to %s in env or 1.\n\n", MP_env_sitecounter );
fprintf( stderr, " -a version The algorithm version to use.\n"
" Defaults to %s in env or %d.\n\n", MP_env_algorithm, MPAlgorithmVersionCurrent );
fprintf( stderr, " -V variant The kind of content to generate.\n"
" Defaults to 'password'.\n"
" p, password | The password to log in with.\n"
" l, login | The username to log in as.\n"
" a, answer | The answer to a security question.\n\n" );
fprintf( stderr, " -C context A variant-specific context.\n"
" Defaults to empty.\n"
" -V p, password | Doesn't currently use a context.\n"
" -V l, login | Doesn't currently use a context.\n"
" -V a, answer | Empty for a universal site answer or\n"
" | the most significant word(s) of the question.\n\n" );
fprintf( stderr, " -v Increase output verbosity (can be repeated).\n\n" );
fprintf( stderr, " -q Decrease output verbosity (can be repeated).\n\n" );
fprintf( stderr, " ENVIRONMENT\n\n"
" %-14s | The full name of the user (see -u).\n"
" %-14s | The default password template (see -t).\n"
" %-14s | The default counter value (see -c).\n"
" %-14s | The default algorithm version (see -a).\n\n",
MP_env_fullname, MP_env_sitetype, MP_env_sitecounter, MP_env_algorithm );
inf( ""
" Master Password v%s\n"
" https://masterpasswordapp.com\n\n", stringify_def( MP_VERSION ) );
inf( ""
"Usage:\n"
" mpw [-u|-U full-name] [-t pw-type] [-c counter] [-a algorithm] [-s value]\n"
" [-p purpose] [-C context] [-f|-F format] [-R 0|1] [-v|-q] [-h] site-name\n\n" );
inf( ""
" -u full-name Specify the full name of the user.\n"
" -u checks the master password against the config,\n"
" -U allows updating to a new master password.\n"
" Defaults to %s in env or prompts.\n\n", MP_ENV_fullName );
inf( ""
" -t pw-type Specify the password's template.\n"
" Defaults to 'long' (-p a), 'name' (-p i) or 'phrase' (-p r).\n"
" x, maximum | 20 characters, contains symbols.\n"
" l, long | Copy-friendly, 14 characters, symbols.\n"
" m, medium | Copy-friendly, 8 characters, symbols.\n"
" b, basic | 8 characters, no symbols.\n"
" s, short | Copy-friendly, 4 characters, no symbols.\n"
" i, pin | 4 numbers.\n"
" n, name | 9 letter name.\n"
" p, phrase | 20 character sentence.\n"
" K, key | encryption key (set key size -s bits).\n"
" P, personal | saved personal password (save with -s pw).\n\n" );
inf( ""
" -c counter The value of the counter.\n"
" Defaults to 1.\n\n" );
inf( ""
" -a version The algorithm version to use, %d - %d.\n"
" Defaults to %s in env or %d.\n\n",
MPAlgorithmVersionFirst, MPAlgorithmVersionLast, MP_ENV_algorithm, MPAlgorithmVersionCurrent );
inf( ""
" -s value The value to save for -t P or -p i.\n"
" The size of they key to generate for -t K, in bits (eg. 256).\n\n" );
inf( ""
" -p purpose The purpose of the generated token.\n"
" Defaults to 'auth'.\n"
" a, auth | An authentication token such as a password.\n"
" i, ident | An identification token such as a username.\n"
" r, rec | A recovery token such as a security answer.\n\n" );
inf( ""
" -C context A purpose-specific context.\n"
" Defaults to empty.\n"
" -p a | -\n"
" -p i | -\n"
" -p r | Most significant word in security question.\n\n" );
inf( ""
" -f|F format The mpsites format to use for reading/writing site parameters.\n"
" -F forces the use of the given format,\n"
" -f allows fallback/migration.\n"
" Defaults to json, falls back to plain.\n"
" f, flat | ~/.mpw.d/Full Name.%s\n"
" j, json | ~/.mpw.d/Full Name.%s\n\n",
mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) );
inf( ""
" -R redacted Whether to save the mpsites in redacted format or not.\n"
" Defaults to 1, redacted.\n\n" );
inf( ""
" -v Increase output verbosity (can be repeated).\n"
" -q Decrease output verbosity (can be repeated).\n\n" );
inf( ""
" ENVIRONMENT\n\n"
" %-14s | The full name of the user (see -u).\n"
" %-14s | The default algorithm version (see -a).\n\n",
MP_ENV_fullName, MP_ENV_algorithm );
exit( 0 );
}
static char *homedir(const char *filename) {
static const char *mpw_getenv(const char *variableName) {
char *envBuf = getenv( variableName );
return envBuf? strdup( envBuf ): NULL;
}
static const char *mpw_getline(const char *prompt) {
fprintf( stderr, "%s ", prompt );
char *buf = NULL;
size_t bufSize = 0;
ssize_t lineSize = getline( &buf, &bufSize, stdin );
if (lineSize <= 1) {
free( buf );
return NULL;
}
// Remove the newline.
buf[lineSize - 1] = '\0';
return buf;
}
static const char *mpw_getpass(const char *prompt) {
char *passBuf = getpass( prompt );
if (!passBuf)
return NULL;
char *buf = strdup( passBuf );
bzero( passBuf, strlen( passBuf ) );
return buf;
}
static char *mpw_path(const char *prefix, const char *extension) {
char *homedir = NULL;
struct passwd *passwd = getpwuid( getuid() );
@@ -73,71 +138,78 @@ static char *homedir(const char *filename) {
if (!homedir)
homedir = getcwd( NULL, 0 );
char *homefile = NULL;
asprintf( &homefile, "%s/%s", homedir, filename );
return homefile;
}
char *mpwPath = NULL;
asprintf( &mpwPath, "%s.%s", prefix, extension );
static char *getline_prompt(const char *prompt) {
char *slash = strstr( mpwPath, "/" );
if (slash)
*slash = '\0';
char *buf = NULL;
size_t bufSize = 0;
ssize_t lineSize;
fprintf( stderr, "%s", prompt );
fprintf( stderr, " " );
if ((lineSize = getline( &buf, &bufSize, stdin )) <= 1) {
free( buf );
return NULL;
}
buf[lineSize - 1] = 0;
return buf;
asprintf( &mpwPath, "%s/.mpw.d/%s", homedir, mpwPath );
return mpwPath;
}
int main(int argc, char *const argv[]) {
// Read the environment.
const char *fullName = NULL;
const char *masterPassword = NULL;
const char *siteName = NULL;
MPSiteType siteType = MPSiteTypeGeneratedLong;
const char *siteTypeString = getenv( MP_env_sitetype );
MPSiteVariant siteVariant = MPSiteVariantPassword;
const char *siteVariantString = NULL;
const char *siteContextString = NULL;
uint32_t siteCounter = 1;
const char *siteCounterString = getenv( MP_env_sitecounter );
// Master Password defaults.
const char *fullName = NULL, *masterPassword = NULL, *siteName = NULL, *resultParam = NULL, *keyContext = NULL;
MPCounterValue siteCounter = MPCounterValueDefault;
MPResultType resultType = MPResultTypeDefault;
MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication;
MPAlgorithmVersion algorithmVersion = MPAlgorithmVersionCurrent;
const char *algorithmVersionString = getenv( MP_env_algorithm );
if (algorithmVersionString && strlen( algorithmVersionString ))
if (sscanf( algorithmVersionString, "%u", &algorithmVersion ) != 1)
ftl( "Invalid %s: %s\n", MP_env_algorithm, algorithmVersionString );
MPMarshallFormat sitesFormat = MPMarshallFormatDefault;
bool allowPasswordUpdate = false, sitesFormatFixed = false, sitesRedacted = true;
// Read the options.
for (int opt; (opt = getopt( argc, argv, "u:P:t:c:V:a:C:vqh" )) != -1;)
// Read the environment.
const char *fullNameArg = NULL, *masterPasswordArg = NULL, *siteNameArg = NULL;
const char *resultTypeArg = NULL, *resultParamArg = NULL, *siteCounterArg = NULL, *algorithmVersionArg = NULL;
const char *keyPurposeArg = NULL, *keyContextArg = NULL, *sitesFormatArg = NULL, *sitesRedactedArg = NULL;
fullNameArg = mpw_getenv( MP_ENV_fullName );
algorithmVersionArg = mpw_getenv( MP_ENV_algorithm );
// Read the command-line options.
for (int opt; (opt = getopt( argc, argv, "u:U:M:t:P:c:a:s:p:C:f:F:R:vqh" )) != EOF;)
switch (opt) {
case 'u':
fullName = strdup( optarg );
fullNameArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
allowPasswordUpdate = false;
break;
case 'P':
// Do not use this. Passing your master password via the command-line
// is insecure. This is here for non-interactive testing purposes only.
masterPassword = strcpy( malloc( strlen( optarg ) + 1 ), optarg );
case 'U':
fullNameArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
allowPasswordUpdate = true;
break;
case 'M':
// Passing your master password via the command-line is insecure. Testing purposes only.
masterPasswordArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 't':
siteTypeString = optarg;
resultTypeArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 'P':
resultParamArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 'c':
siteCounterString = optarg;
break;
case 'V':
siteVariantString = optarg;
siteCounterArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 'a':
if (sscanf( optarg, "%u", &algorithmVersion ) != 1)
ftl( "Not a version: %s\n", optarg );
algorithmVersionArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 'p':
keyPurposeArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 'C':
siteContextString = optarg;
keyContextArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 'f':
sitesFormatArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
sitesFormatFixed = false;
break;
case 'F':
sitesFormatArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
sitesFormatFixed = true;
break;
case 'R':
sitesRedactedArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
break;
case 'v':
++mpw_verbosity;
@@ -152,91 +224,345 @@ int main(int argc, char *const argv[]) {
switch (optopt) {
case 'u':
ftl( "Missing full name to option: -%c\n", optopt );
break;
return EX_USAGE;
case 't':
ftl( "Missing type name to option: -%c\n", optopt );
break;
return EX_USAGE;
case 'c':
ftl( "Missing counter value to option: -%c\n", optopt );
break;
return EX_USAGE;
default:
ftl( "Unknown option: -%c\n", optopt );
return EX_USAGE;
}
default:
ftl("Unexpected option: %c", opt);
ftl( "Unexpected option: %c\n", opt );
return EX_USAGE;
}
if (optind < argc)
siteName = strdup( argv[optind] );
siteNameArg = strdup( argv[optind] );
// Convert and validate input.
if (!fullName && (fullName = getenv( MP_env_fullname )))
fullName = strdup( fullName );
if (!fullName && !(fullName = getline_prompt( "Your full name:" )))
// Determine fullName, siteName & masterPassword.
if (!(fullNameArg && (fullName = strdup( fullNameArg ))) &&
!(fullName = mpw_getline( "Your full name:" ))) {
ftl( "Missing full name.\n" );
if (!siteName && !(siteName = getline_prompt( "Site name:" )))
return EX_DATAERR;
}
if (!(siteNameArg && (siteName = strdup( siteNameArg ))) &&
!(siteName = mpw_getline( "Site name:" ))) {
ftl( "Missing site name.\n" );
if (siteCounterString)
siteCounter = (uint32_t)atol( siteCounterString );
if (siteCounter < 1)
ftl( "Invalid site counter: %d\n", siteCounter );
if (siteVariantString)
siteVariant = mpw_variantWithName( siteVariantString );
if (siteVariant == MPSiteVariantLogin)
siteType = MPSiteTypeGeneratedName;
if (siteVariant == MPSiteVariantAnswer)
siteType = MPSiteTypeGeneratedPhrase;
if (siteTypeString)
siteType = mpw_typeWithName( siteTypeString );
trc( "algorithmVersion: %u\n", algorithmVersion );
return EX_DATAERR;
}
if (!(masterPasswordArg && (masterPassword = strdup( masterPasswordArg ))))
while (!masterPassword || !strlen( masterPassword ))
masterPassword = mpw_getpass( "Your master password: " );
if (sitesFormatArg) {
sitesFormat = mpw_formatWithName( sitesFormatArg );
if (ERR == (int)sitesFormat) {
ftl( "Invalid sites format: %s\n", sitesFormatArg );
return EX_USAGE;
}
}
// Read the master password.
char *mpwConfigPath = homedir( ".mpw" );
if (!mpwConfigPath)
ftl( "Couldn't resolve path for configuration file: %d\n", errno );
trc( "mpwConfigPath: %s\n", mpwConfigPath );
FILE *mpwConfig = fopen( mpwConfigPath, "r" );
free( mpwConfigPath );
if (mpwConfig) {
char *line = NULL;
size_t linecap = 0;
while (getline( &line, &linecap, mpwConfig ) > 0) {
char *lineData = line;
if (strcmp( strsep( &lineData, ":" ), fullName ) == 0) {
masterPassword = strcpy( malloc( strlen( lineData ) ), strsep( &lineData, "\n" ) );
// Find the user's sites file.
FILE *sitesFile = NULL;
char *sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) );
if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) {
dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
free( sitesPath );
// Try to fall back to the flat format.
if (!sitesFormatFixed) {
sitesFormat = MPMarshallFormatFlat;
sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) );
if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" )))
dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
}
}
// Read the user's sites file.
MPMarshalledUser *user = NULL;
MPMarshalledSite *site = NULL;
if (!sitesFile) {
free( sitesPath );
sitesPath = NULL;
}
else {
// Read file.
size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0;
char *buf = NULL;
while ((mpw_realloc( &buf, &bufSize, readAmount )) &&
(bufOffset += (readSize = fread( buf + bufOffset, 1, readAmount, sitesFile ))) &&
(readSize == readAmount));
if (ferror( sitesFile ))
wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) );
fclose( sitesFile );
// Parse file.
MPMarshallError marshallError = { MPMarshallSuccess };
user = mpw_marshall_read( buf, sitesFormat, masterPassword, &marshallError );
if (marshallError.type == MPMarshallErrorMasterPassword) {
// Incorrect master password.
if (!allowPasswordUpdate) {
ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description );
mpw_marshal_free( user );
mpw_free( buf, bufSize );
free( sitesPath );
return EX_DATAERR;
}
// Update user's master password.
while (marshallError.type == MPMarshallErrorMasterPassword) {
inf( "Given master password does not match configuration.\n" );
inf( "To update the configuration with this new master password, first confirm the old master password.\n" );
const char *importMasterPassword = NULL;
while (!importMasterPassword || !strlen( importMasterPassword ))
importMasterPassword = mpw_getpass( "Old master password: " );
mpw_marshal_free( user );
user = mpw_marshall_read( buf, sitesFormat, importMasterPassword, &marshallError );
}
if (user) {
mpw_free_string( user->masterPassword );
user->masterPassword = strdup( masterPassword );
}
}
mpw_free( buf, bufSize );
if (!user || marshallError.type != MPMarshallSuccess) {
err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description );
mpw_marshal_free( user );
user = NULL;
free( sitesPath );
sitesPath = NULL;
}
if (user) {
// Load defaults.
mpw_free_string( fullName );
mpw_free_string( masterPassword );
fullName = strdup( user->fullName );
masterPassword = strdup( user->masterPassword );
algorithmVersion = user->algorithm;
resultType = user->defaultType;
sitesRedacted = user->redacted;
if (!sitesRedacted && !sitesRedactedArg)
wrn( "Sites configuration is not redacted. Use -R 1 to change this.\n" );
for (size_t s = 0; s < user->sites_count; ++s) {
site = &user->sites[s];
if (strcmp( siteName, site->name ) != 0) {
site = NULL;
continue;
}
mpw_free_string( resultParam );
resultType = site->type;
siteCounter = site->counter;
algorithmVersion = site->algorithm;
break;
}
}
mpw_free( line, linecap );
}
while (!masterPassword || !strlen(masterPassword))
masterPassword = getpass( "Your master password: " );
// Summarize operation.
// Parse default/config-overriding command-line parameters.
if (sitesRedactedArg)
sitesRedacted = strcmp( sitesRedactedArg, "1" ) == 0;
if (siteCounterArg) {
long long int siteCounterInt = atoll( siteCounterArg );
if (siteCounterInt < MPCounterValueFirst || siteCounterInt > MPCounterValueLast) {
ftl( "Invalid site counter: %s\n", siteCounterArg );
return EX_USAGE;
}
siteCounter = (MPCounterValue)siteCounterInt;
}
if (algorithmVersionArg) {
int algorithmVersionInt = atoi( algorithmVersionArg );
if (algorithmVersionInt < MPAlgorithmVersionFirst || algorithmVersionInt > MPAlgorithmVersionLast) {
ftl( "Invalid algorithm version: %s\n", algorithmVersionArg );
return EX_USAGE;
}
algorithmVersion = (MPAlgorithmVersion)algorithmVersionInt;
}
if (keyPurposeArg) {
keyPurpose = mpw_purposeWithName( keyPurposeArg );
if (ERR == (int)keyPurpose) {
ftl( "Invalid purpose: %s\n", keyPurposeArg );
return EX_USAGE;
}
}
char *purposeResult = "password";
switch (keyPurpose) {
case MPKeyPurposeAuthentication:
break;
case MPKeyPurposeIdentification: {
resultType = MPResultTypeTemplateName;
purposeResult = "login";
break;
}
case MPKeyPurposeRecovery: {
resultType = MPResultTypeTemplatePhrase;
purposeResult = "answer";
break;
}
}
if (resultTypeArg) {
resultType = mpw_typeWithName( resultTypeArg );
if (ERR == (int)resultType) {
ftl( "Invalid type: %s\n", resultTypeArg );
return EX_USAGE;
}
}
if (resultParamArg) {
mpw_free_string( resultParam );
resultParam = strdup( resultParamArg );
}
if (keyContextArg) {
mpw_free_string( keyContext );
keyContext = strdup( keyContextArg );
}
mpw_free_string( fullNameArg );
mpw_free_string( masterPasswordArg );
mpw_free_string( siteNameArg );
mpw_free_string( resultTypeArg );
mpw_free_string( resultParamArg );
mpw_free_string( siteCounterArg );
mpw_free_string( algorithmVersionArg );
mpw_free_string( keyPurposeArg );
mpw_free_string( keyContextArg );
mpw_free_string( sitesFormatArg );
mpw_free_string( sitesRedactedArg );
// Operation summary.
const char *identicon = mpw_identicon( fullName, masterPassword );
if (!identicon) {
err( "Couldn't determine identicon.\n" );
} else {
fprintf( stderr, "%s's password for %s:\n[ %s ]: ", fullName, siteName, identicon );
mpw_free_string( identicon );
}
if (!identicon)
wrn( "Couldn't determine identicon.\n" );
dbg( "-----------------\n" );
dbg( "fullName : %s\n", fullName );
trc( "masterPassword : %s\n", masterPassword );
dbg( "identicon : %s\n", identicon );
dbg( "sitesFormat : %s%s\n", mpw_nameForFormat( sitesFormat ), sitesFormatFixed? " (fixed)": "" );
dbg( "sitesPath : %s\n", sitesPath );
dbg( "siteName : %s\n", siteName );
dbg( "siteCounter : %u\n", siteCounter );
dbg( "resultType : %s (%u)\n", mpw_nameForType( resultType ), resultType );
dbg( "resultParam : %s\n", resultParam );
dbg( "keyPurpose : %s (%u)\n", mpw_nameForPurpose( keyPurpose ), keyPurpose );
dbg( "keyContext : %s\n", keyContext );
dbg( "algorithmVersion : %u\n", algorithmVersion );
dbg( "-----------------\n\n" );
inf( "%s's %s for %s:\n[ %s ]: ", fullName, purposeResult, siteName, identicon );
mpw_free_string( identicon );
if (sitesPath)
free( sitesPath );
// Output the password.
const uint8_t *masterKey = mpw_masterKeyForUser(
// Determine master key.
MPMasterKey masterKey = mpw_masterKey(
fullName, masterPassword, algorithmVersion );
mpw_free_string( masterPassword );
mpw_free_string( fullName );
if (!masterKey)
ftl( "Couldn't derive master key." );
if (!masterKey) {
ftl( "Couldn't derive master key.\n" );
return EX_SOFTWARE;
}
const char *sitePassword = mpw_passwordForSite(
masterKey, siteName, siteType, siteCounter, siteVariant, siteContextString, algorithmVersion );
mpw_free( masterKey, MP_dkLen );
// Output the result.
if (keyPurpose == MPKeyPurposeIdentification && site && !site->loginGenerated && site->loginName)
fprintf( stdout, "%s\n", site->loginName );
else if (resultParam && site && resultType & MPResultTypeClassState) {
mpw_free_string( site->content );
if (!(site->content = mpw_siteState( masterKey, siteName, siteCounter,
keyPurpose, keyContext, resultType, resultParam, algorithmVersion ))) {
ftl( "Couldn't encrypt site content.\n" );
mpw_free( masterKey, MPMasterKeySize );
return EX_SOFTWARE;
}
inf( "saved.\n" );
}
else {
if (!resultParam && site && site->content && resultType & MPResultTypeClassState)
resultParam = strdup( site->content );
const char *siteResult = mpw_siteResult( masterKey, siteName, siteCounter,
keyPurpose, keyContext, resultType, resultParam, algorithmVersion );
if (!siteResult) {
ftl( "Couldn't generate site result.\n" );
mpw_free( masterKey, MPMasterKeySize );
return EX_SOFTWARE;
}
fprintf( stdout, "%s\n", siteResult );
mpw_free_string( siteResult );
}
if (site && site->url)
inf( "See: %s\n", site->url );
mpw_free( masterKey, MPMasterKeySize );
mpw_free_string( siteName );
if (!sitePassword)
ftl( "Couldn't derive site password." );
mpw_free_string( resultParam );
mpw_free_string( keyContext );
fprintf( stdout, "%s\n", sitePassword );
mpw_free_string( sitePassword );
// Update the mpsites file.
if (user) {
// TODO: Move this up above the summary and replace the mpw lvars by user/site accessors.
if (keyPurpose == MPKeyPurposeAuthentication && !(resultType & MPSiteFeatureAlternative)) {
if (!site)
site = mpw_marshall_site( user, siteName, resultType, siteCounter, algorithmVersion );
else {
site->type = resultType;
site->counter = siteCounter;
site->algorithm = algorithmVersion;
}
}
else if (keyPurpose == MPKeyPurposeIdentification && site) {
// TODO: We're not persisting the resultType of the generated login
if (resultType & MPResultTypeClassTemplate)
site->loginGenerated = true;
}
else if (keyPurpose == MPKeyPurposeRecovery && site && keyContext) {
// TODO: We're not persisting the resultType of the recovery question
MPMarshalledQuestion *question = NULL;
for (size_t q = 0; q < site->questions_count; ++q) {
question = &site->questions[q];
if (strcmp( keyContext, question->keyword ) != 0) {
question = NULL;
continue;
}
break;
}
if (!question)
mpw_marshal_question( site, keyContext );
}
if (site) {
site->lastUsed = user->lastUsed = time( NULL );
site->uses++;
}
if (!sitesFormatFixed)
sitesFormat = MPMarshallFormatDefault;
user->redacted = sitesRedacted;
sitesPath = mpw_path( user->fullName, mpw_marshall_format_extension( sitesFormat ) );
dbg( "Updating: %s (%s)\n", sitesPath, mpw_nameForFormat( sitesFormat ) );
if (!sitesPath || !(sitesFile = fopen( sitesPath, "w" )))
wrn( "Couldn't create updated configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
else {
char *buf = NULL;
MPMarshallError marshallError = { MPMarshallSuccess };
if (!mpw_marshall_write( &buf, sitesFormat, user, &marshallError ) || marshallError.type != MPMarshallSuccess)
wrn( "Couldn't encode updated configuration file:\n %s: %s\n", sitesPath, marshallError.description );
else if (fwrite( buf, sizeof( char ), strlen( buf ), sitesFile ) != strlen( buf ))
wrn( "Error while writing updated configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) );
mpw_free_string( buf );
fclose( sitesFile );
}
free( sitesPath );
mpw_marshal_free( user );
}
return 0;
}

View File

@@ -48,7 +48,7 @@ xmlNodePtr mpw_xmlTestCaseNode(xmlNodePtr testCaseNode, const char *nodeName) {
for (xmlNodePtr otherTestCaseNode = testCaseNode->parent->children; otherTestCaseNode; otherTestCaseNode = otherTestCaseNode->next) {
xmlChar *id = mpw_xmlTestCaseString( otherTestCaseNode, "id" );
int foundParent = xmlStrcmp( id, parentId ) == 0;
int foundParent = id && xmlStrcmp( id, parentId ) == 0;
xmlFree( id );
if (foundParent) {
@@ -57,19 +57,20 @@ xmlNodePtr mpw_xmlTestCaseNode(xmlNodePtr testCaseNode, const char *nodeName) {
}
}
ftl( "Missing parent: %s, for case: %s\n", parentId, mpw_xmlTestCaseString( testCaseNode, "id" ) );
err( "Missing parent: %s, for case: %s\n", parentId, mpw_xmlTestCaseString( testCaseNode, "id" ) );
return NULL;
}
xmlChar *mpw_xmlTestCaseString(xmlNodePtr context, const char *nodeName) {
xmlNodePtr child = mpw_xmlTestCaseNode( context, nodeName );
return xmlNodeGetContent( child );
return child? xmlNodeGetContent( child ): NULL;
}
uint32_t mpw_xmlTestCaseInteger(xmlNodePtr context, const char *nodeName) {
xmlChar *string = mpw_xmlTestCaseString( context, nodeName );
uint32_t integer = (uint32_t)atol( (char *)string );
uint32_t integer = string? (uint32_t)atol( (char *)string ): 0;
xmlFree( string );
return integer;

View File

@@ -1,5 +1,3 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
@@ -26,19 +24,19 @@ int main(int argc, char *const argv[]) {
// Read in the test case.
xmlChar *id = mpw_xmlTestCaseString( testCase, "id" );
uint32_t algorithm = mpw_xmlTestCaseInteger( testCase, "algorithm" );
MPAlgorithmVersion algorithm = (MPAlgorithmVersion)mpw_xmlTestCaseInteger( testCase, "algorithm" );
xmlChar *fullName = mpw_xmlTestCaseString( testCase, "fullName" );
xmlChar *masterPassword = mpw_xmlTestCaseString( testCase, "masterPassword" );
xmlChar *keyID = mpw_xmlTestCaseString( testCase, "keyID" );
xmlChar *siteName = mpw_xmlTestCaseString( testCase, "siteName" );
uint32_t siteCounter = mpw_xmlTestCaseInteger( testCase, "siteCounter" );
xmlChar *siteTypeString = mpw_xmlTestCaseString( testCase, "siteType" );
xmlChar *siteVariantString = mpw_xmlTestCaseString( testCase, "siteVariant" );
xmlChar *siteContext = mpw_xmlTestCaseString( testCase, "siteContext" );
MPCounterValue siteCounter = (MPCounterValue)mpw_xmlTestCaseInteger( testCase, "siteCounter" );
xmlChar *resultTypeString = mpw_xmlTestCaseString( testCase, "resultType" );
xmlChar *keyPurposeString = mpw_xmlTestCaseString( testCase, "keyPurpose" );
xmlChar *keyContext = mpw_xmlTestCaseString( testCase, "keyContext" );
xmlChar *result = mpw_xmlTestCaseString( testCase, "result" );
MPSiteType siteType = mpw_typeWithName( (char *)siteTypeString );
MPSiteVariant siteVariant = mpw_variantWithName( (char *)siteVariantString );
MPResultType resultType = mpw_typeWithName( (char *)resultTypeString );
MPKeyPurpose keyPurpose = mpw_purposeWithName( (char *)keyPurposeString );
// Run the test case.
fprintf( stdout, "test case %s... ", id );
@@ -48,17 +46,21 @@ int main(int argc, char *const argv[]) {
}
// 1. calculate the master key.
const uint8_t *masterKey = mpw_masterKeyForUser(
MPMasterKey masterKey = mpw_masterKey(
(char *)fullName, (char *)masterPassword, algorithm );
if (!masterKey)
ftl( "Couldn't derive master key." );
if (!masterKey) {
ftl( "Couldn't derive master key.\n" );
continue;
}
// 2. calculate the site password.
const char *sitePassword = mpw_passwordForSite(
masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext, algorithm );
mpw_free( masterKey, MP_dkLen );
if (!sitePassword)
ftl( "Couldn't derive site password." );
const char *sitePassword = mpw_siteResult(
masterKey, (char *)siteName, siteCounter, keyPurpose, (char *)keyContext, resultType, NULL, algorithm );
mpw_free( masterKey, MPMasterKeySize );
if (!sitePassword) {
ftl( "Couldn't derive site password.\n" );
continue;
}
// Check the result.
if (xmlStrcmp( result, BAD_CAST sitePassword ) == 0)
@@ -76,9 +78,9 @@ int main(int argc, char *const argv[]) {
xmlFree( masterPassword );
xmlFree( keyID );
xmlFree( siteName );
xmlFree( siteTypeString );
xmlFree( siteVariantString );
xmlFree( siteContext );
xmlFree( resultTypeString );
xmlFree( keyPurposeString );
xmlFree( keyContext );
xmlFree( result );
}

View File

@@ -5,7 +5,6 @@ cd "${BASH_SOURCE%/*}"
tag=$(git describe --match '*-cli*')
commit=$(git describe --long --dirty --match '*-cli*')
[[ $commit != *-dirty ]] || { echo >&2 "Tree is dirty, first commit any changes."; exit 1; }
git show --show-signature --pretty=format:%H --quiet "$tag" > VERSION
mpwArchive=mpw-$commit.tar.gz
[[ -e $mpwArchive ]] && echo >&2 "WARNING: $mpwArchive already exists. Will overwrite."
@@ -15,6 +14,7 @@ echo "Cleaning .."
( git clean -ffdx . && cd core && git clean -ffdx . )
echo "Creating archive $mpwArchive .."
git show --show-signature --pretty=format:%H --quiet "$tag" > VERSION
git ls-files -z . | xargs -0 tar -Lcvzf "$mpwArchive"
echo "Creating archive signature $mpwArchive.sig .."

3
public/site/2015-05/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
_site
.sass-cache
.jekyll-metadata

View File

@@ -0,0 +1,21 @@
# Welcome to Jekyll!
#
# This config file is meant for settings that affect your whole blog, values
# which you are expected to set up once and rarely need to edit after that.
# For technical reasons, this file is *NOT* reloaded automatically when you use
# 'jekyll serve'. If you change this file, please restart the server process.
# Site settings
title: Your awesome title
email: your-email@domain.com
description: > # this means to ignore newlines until "baseurl:"
Write an awesome description for your new site here. You can edit this
line in _config.yml. It will appear in your document head meta (for
Google search results) and in your feed.xml site description.
baseurl: "" # the subpath of your site, e.g. /blog
url: "http://yourdomain.com" # the base hostname & protocol for your site
twitter_username: jekyllrb
github_username: jekyll
# Build settings
markdown: kramdown

View File

@@ -0,0 +1,6 @@
<footer>
</footer>
<script src="/js/jquery.paroller.min.js"></script>
<script>$("body").paroller();</script>

View File

@@ -0,0 +1,15 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}">
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="/js/main.js"></script>
</head>

View File

@@ -0,0 +1,31 @@
<header>
<!--div class="hero">
<img class="ios" src="/img/hero_ios.jpg" />
<img class="android" src="/img/hero_android.jpg" />
<img class="mac" src="/img/hero_mac.jpg" />
<img class="desktop" src="/img/hero_desktop.jpg" />
<img class="cli" src="/img/hero_cli.jpg" />
<img class="web" src="/img/hero_web.jpg" />
</div-->
<nav>
<a href="/">Master Password</a>
<div class="flex-auto">
<a href="https://itunes.apple.com/app/id510296984" data-hero="ios">iOS.<img class="popup" src="img/ios.png" /></a>
<a href="https://ssl.masterpasswordapp.com/masterpassword-android.apk" data-hero="android">Android.<img class="popup" src="img/android.png" /></a>
<a href="https://ssl.masterpasswordapp.com/masterpassword-mac.zip" data-hero="mac">Mac.<img class="popup border" src="img/mac-gui.png" /></a>
<a href="https://ssl.masterpasswordapp.com/masterpassword-gui.jar" data-hero="desktop">Desktop.<img class="popup" src="img/java-gui.png" /></a>
<a href="https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz" data-hero="cli">Terminal.<img class="popup" src="img/c-cli.png" /></a>
<a href="https://js.masterpasswordapp.com/">Web.<img class="popup border" src="img/web.png" data-hero="web" /></a>
</div>
<a href="http://help.masterpasswordapp.com">Support</a>
</nav>
<div class="flex-auto"></div>
<nav>
<a href="/"{% if page.nav == "why" %} class="active"{% endif %}>Why</a>
<a href="how.html"{% if page.nav == "how" %} class="active"{% endif %}>How</a>
<a href="what.html"{% if page.nav == "what" %} class="active"{% endif %}>What</a>
</nav>
</header>

View File

@@ -0,0 +1 @@
<a href="https://github.com/{{ include.username }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">{{ include.username }}</span></a>

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16"><path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/></svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@@ -0,0 +1 @@
<a href="https://twitter.com/{{ include.username }}"><span class="icon icon--twitter">{% include icon-twitter.svg %}</span><span class="username">{{ include.username }}</span></a>

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