2
0

Compare commits

...

51 Commits

Author SHA1 Message Date
Maarten Billemont
7f5bb9e114 [maven-release-plugin] prepare release 2.3 2015-04-18 17:41:45 -04:00
Maarten Billemont
4c4aaac08b Update to remove the db4o repository. 2015-04-18 17:31:12 -04:00
Maarten Billemont
5320e079ce [maven-release-plugin] rollback changes from release preparation of 2.3 2015-04-18 16:24:34 -04:00
Maarten Billemont
636c337c78 [maven-release-plugin] prepare release 2.3 2015-04-18 16:24:34 -04:00
Maarten Billemont
c2b08678df Also release android. 2015-04-18 16:22:26 -04:00
Maarten Billemont
7117d82aa4 [maven-release-plugin] rollback changes from release preparation of 2.3-android 2015-04-18 16:02:40 -04:00
Maarten Billemont
4fa6436de2 [maven-release-plugin] prepare release 2.3-android 2015-04-18 16:02:40 -04:00
Maarten Billemont
3e30087856 Try to fix releasing just masterpassword-android. 2015-04-18 16:01:40 -04:00
Maarten Billemont
6bd2d70270 [maven-release-plugin] rollback changes from release preparation of 2.3 2015-04-18 15:35:26 -04:00
Maarten Billemont
cf11b01ed6 [maven-release-plugin] prepare release 2.3 2015-04-18 15:35:26 -04:00
Maarten Billemont
7e11f01331 Try to fix maven-release-plugin inside a subdir. 2015-04-18 15:30:31 -04:00
Maarten Billemont
aeca5e18fe [maven-release-plugin] rollback changes from release preparation of 2.3 2015-04-18 15:07:08 -04:00
Maarten Billemont
615e7c98a8 [maven-release-plugin] prepare release 2.3 2015-04-18 15:07:08 -04:00
Maarten Billemont
e713776123 Update to the correct Android SDK dependency. 2015-04-18 15:02:57 -04:00
Maarten Billemont
7d5b7e53d4 Explain what is necessary to build the Android app. 2015-04-18 14:59:11 -04:00
Maarten Billemont
950c68437a Prepare for 2.3 android release. 2015-04-18 14:49:26 -04:00
Maarten Billemont
300a04f5c7 Merge branch 'master' of github.com:Lyndir/MasterPassword 2015-04-18 14:48:35 -04:00
Maarten Billemont
8faf6b48dd Fixed UTF-8 issue, click on A4.4, add notification and expiry.
[FIXED]     A Java UTF-8 encoding issue.
[FIXED]     Android 4.4 wasn't triggering onClick on TextViews.
[ADDED]     A notification when the password is copied.
[ADDED]     Expire the password from the clipboard after 20 seconds.
[UPDATED]   -underscore variant of slf4j-android to make tags settable with setprops
2015-04-18 14:48:27 -04:00
Maarten Billemont
5f0367ad29 Support for deleting sites from the Java GUI. 2015-04-03 12:12:12 -04:00
Maarten Billemont
e126a55912 Update mpw-js. 2015-04-01 08:44:55 -04:00
Maarten Billemont
d05c5eedd8 Update site's masterpassword-gui.jar 2015-04-01 08:11:43 -04:00
Maarten Billemont
6819a2ace5 Delete users + window resize fix.
[ADDED]     Java: support for deleting users.
[FIXED]     Java: Password window resizing issue.
2015-04-01 08:09:48 -04:00
Maarten Billemont
645b6c5f54 Avoid trouble with s3cmd sync not noticing when remote is already up to date. 2015-03-29 21:10:10 -04:00
Maarten Billemont
d3c09fd979 Update masterpassword-gui to have identicon on the bottom. 2015-03-29 21:03:25 -04:00
Maarten Billemont
a41ae1814a Update site for new Java with emoticons. 2015-03-29 20:32:26 -04:00
Maarten Billemont
70f7fa1345 Java identicon support. 2015-03-29 20:30:57 -04:00
Maarten Billemont
ea9d8cc275 Update masterpassword-gui-2.2.jar for Windows L&F improvements. 2015-03-20 14:37:46 -04:00
Maarten Billemont
20d1811b5c Fixes to mpw.completion.bash when no mpsites. 2015-03-13 11:02:30 -04:00
Maarten Billemont
634ef062f3 Merge branch 'master' of github.com:Lyndir/MasterPassword 2015-03-13 09:57:35 -04:00
Maarten Billemont
a424531a8a Some more anchors on secutity.html. 2015-03-13 09:56:28 -04:00
Maarten Billemont
9cffe53993 A bash completion script for mpw. 2015-03-12 01:03:02 -04:00
Maarten Billemont
fd35fea8cf mpw C code is not thread-safe + bad performance long site queries. 2015-03-11 17:31:39 -04:00
Maarten Billemont
01c21e95bb Merge branch 'master' of github.com:Lyndir/MasterPassword 2015-03-10 16:40:42 -04:00
Maarten Billemont
5af3ffa178 Merge branch 'master' of github.com:Lyndir/MasterPassword
Conflicts:
	Site/2013-05/index.html
2015-03-10 14:39:05 -04:00
Maarten Billemont
651d07f982 mpw is now available via homebrew. 2015-03-10 14:37:47 -04:00
Maarten Billemont
a383d0eee7 Make new site creation on Mac same as iOS.
[FIXED]     Unable to create a site that is a substring of an existing site.
2015-03-05 17:28:04 -05:00
Maarten Billemont
ca8f14fd3e Site-specific support for keys of different algorithm versions.
[ADDED]     Ability to downgrade sites.
[ADDED]     A more explicit message that sites need to be upgraded.
2015-02-28 10:01:41 -05:00
Maarten Billemont
fd855bb025 Add link to Tom's test suite. 2015-02-28 09:53:00 -05:00
Maarten Billemont
af340806af Update mpw-js to include Tom's latest fixes. 2015-02-28 09:20:28 -05:00
Maarten Billemont
cdeee2576d More algorithm trace logging. 2015-02-27 08:49:04 -05:00
Maarten Billemont
779d2776a0 Fix V0 in Java and support for testing algorithms. 2015-02-27 08:35:10 -05:00
Maarten Billemont
563aab9a81 More verbose in TRC about what algorithm is used + fix mpw_charlen on some platforms. 2015-02-18 17:32:33 -05:00
Maarten Billemont
73de98c5e2 Ignore Windows Thumbs.db 2015-02-16 23:58:03 -05:00
Maarten Billemont
c330728ac3 UI Improvements for Windows and system L&F. 2015-02-16 23:56:58 -05:00
Maarten Billemont
2db601475f UI improvements for Windows and system look and feel. 2015-02-16 19:04:43 -05:00
Maarten Billemont
d898646097 Build fixes for mpw_color. 2015-02-09 20:51:12 +00:00
Maarten Billemont
afaa17948f A fix build fixes on other platforms. 2015-02-09 18:02:22 +00:00
Maarten Billemont
8514a58e64 Sizing and memory related C fixes.
[FIXED]     mpw_templatesForType now returns a fully allocated array, was relying on undefined behavior before.
[FIXED]     mpw_variantWithName was allocating a badly sized standard string.
2015-02-07 11:10:59 -05:00
Maarten Billemont
f80bbff46e Small C tweaks and add 2.2 to the site.
[UPDATED]   mpw-cli's usage: line.
[IMPROVED]  Safer code for standardizing of type name.
[FIXED]     Detection of missing Crashlytics API Key wasn't detecting the file being missing.
[ADDED]     2.2 desktop and android to the site.
2015-02-07 09:16:17 -05:00
Maarten Billemont
f7d595b0e7 Update site with new versions. 2015-02-05 21:02:06 -05:00
Maarten Billemont
422066ad4a [maven-release-plugin] rollback changes from release preparation of 2.2-android 2015-02-05 20:28:25 -05:00
112 changed files with 2394 additions and 683 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# OS-Specific junk.
.DS_Store
Thumbs.db
# IntelliJ
/MasterPassword/Java/.idea

8
.idea/encodings.xml generated Normal file → Executable file
View File

@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
<file url="file://$PROJECT_DIR$/MasterPassword/Java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-algorithm" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-cli" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-gui" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-model" charset="UTF-8" />
</component>
</project>

2
External/Pearl vendored

View File

@@ -218,3 +218,8 @@ OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash;
@end
/**
* `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, simply use `Crashlytics()`
*/
#define CrashlyticsKit [Crashlytics sharedInstance]

View File

@@ -15,13 +15,13 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.2.5</string>
<string>2.2.9</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>40</string>
<string>44</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>MinimumOSVersion</key>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# FIXME
# partials are currently readline words, but these can't be reliably compared against literal data. We need to make them literal first.. in a safe way. Currently using xargs' quote parser as a hack.
# Process literal completion options in COMPREPLY
#
# 1. Filter COMPREPLY by excluding the options that do not match the word that is being completed.
# 2. Shell-escape the COMPREPLY words so they remain syntactical words when injected into the completed command.
# 3. Add a space after the words so successful completions advance to the next word
# (we disabled this default behavior with -o nospace so we can do completions that don't want this, eg. directory names)
_comp_finish_completions() {
local partial=$(xargs <<< "${COMP_WORDS[COMP_CWORD]}") # FIXME
local word words=( "${COMPREPLY[@]}" )
COMPREPLY=()
for word in "${words[@]}"; do
( shopt -s nocasematch; [[ $word = $partial* ]] ) && COMPREPLY+=( "$(printf '%q ' "$word")" )
done
if (( ${#COMPREPLY[@]} > 1 )) && [[ $_comp_title ]]; then
printf '\n%s:' "$_comp_title"
unset _comp_title
fi
}
# Perform pathname completion.
#
# 1. Populate COMPREPLY with pathnames.
# 2. Shell-escape the COMPREPLY words so they remain syntactical words when injected into the completed command.
# 3. Add a space after file names so successful completions advance to the next word.
# Directory names are suffixed with a / instead so we can keep completing the files inside.
_comp_complete_path() {
local partial=$(xargs <<< "${COMP_WORDS[COMP_CWORD]}")
local path
COMPREPLY=()
for path in "$partial"*; do
if [[ -d $path ]]; then
COMPREPLY+=( "$(printf '%q/' "$path")" )
elif [[ -e $path ]]; then
COMPREPLY+=( "$(printf '%q ' "$path")" )
fi
done
}
_show_args() {
echo
local i=0
for arg; do
printf "arg %d: %s\n" "$((i++))" "$arg"
done
i=0
for word in "${COMP_WORDS[@]}"; do
printf "word %d: %s -> %s %s\n" "$i" "$word" "$(xargs <<< "$word")" "$( ((i == $COMP_CWORD)) && echo '<CWORD>' )"
let i++
done
}

View File

@@ -39,7 +39,7 @@ else
fi
# Optional features.
mpw_color=0 # Colorized Identicon, requires libncurses-dev
mpw_color=1 # Colorized Identicon, requires libncurses-dev
### DEPENDENCIES
@@ -204,11 +204,11 @@ mpw() {
echo
echo "Building target: $target..."
CFLAGS=(
local CFLAGS=(
# include paths
-I"lib/include"
)
LDFLAGS=(
local LDFLAGS=(
# scrypt
"lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o"
@@ -240,11 +240,11 @@ mpw-bench() {
echo
echo "Building target: $target..."
CFLAGS=(
local CFLAGS=(
# include paths
-I"lib/include"
)
LDFLAGS=(
local LDFLAGS=(
# scrypt
"lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o"
@@ -264,6 +264,9 @@ mpw-bench() {
-l"crypto"
)
cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
cc "${CFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
"${LDFLAGS[@]}" "mpw-bench.c" -o "mpw-bench"
echo "done! Now use ./mpw-bench"
@@ -276,13 +279,13 @@ mpw-tests() {
echo
echo "Building target: $target..."
CFLAGS=(
local CFLAGS=(
# include paths
-I"lib/include"
-I"/usr/include/libxml2"
-I"/usr/local/include/libxml2"
)
LDFLAGS=(
local LDFLAGS=(
# scrypt
"lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o"
@@ -296,7 +299,10 @@ mpw-tests() {
-l"crypto" -l"xml2"
)
cc "${CFLAGS[@]}" "$@" -c mpw-tests-util.c -o mpw-tests-util.o
cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
cc "${CFLAGS[@]}" "$@" -c mpw-tests-util.c -o mpw-tests-util.o
cc "${CFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" "mpw-tests-util.o" \
"${LDFLAGS[@]}" "mpw-tests.c" -o "mpw-tests"
echo "done! Now use ./mpw-tests"

View File

@@ -6,6 +6,7 @@
// Copyright (c) 2014 Lyndir. All rights reserved.
//
// NOTE: mpw is currently NOT thread-safe.
#include "mpw-types.h"
typedef enum(unsigned int, MPAlgorithmVersion) {

View File

@@ -11,6 +11,7 @@
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"
#define MP_N 32768
@@ -37,7 +38,8 @@ static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedBy
static const uint8_t *mpw_masterKeyForUser_v0(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName );
trc( "algorithm: v%d\n", 0 );
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
@@ -71,11 +73,16 @@ static const char *mpw_passwordForSite_v0(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) {
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 == NULL? "<empty>": siteContext );
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 )

View File

@@ -11,6 +11,7 @@
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"
#define MP_N 32768
@@ -21,7 +22,8 @@
static const uint8_t *mpw_masterKeyForUser_v1(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName );
trc( "algorithm: v%d\n", 1 );
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v1(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) {
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 == NULL? "<empty>": siteContext );
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 )

View File

@@ -11,6 +11,7 @@
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"
#define MP_N 32768
@@ -21,7 +22,8 @@
static const uint8_t *mpw_masterKeyForUser_v2(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName );
trc( "algorithm: v%d\n", 2 );
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v2(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) {
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 == NULL? "<empty>": siteContext );
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 )

View File

@@ -11,6 +11,7 @@
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"
#define MP_N 32768
@@ -21,7 +22,8 @@
static const uint8_t *mpw_masterKeyForUser_v3(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName );
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 );
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v3(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) {
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 == NULL? "<empty>": siteContext );
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 )

View File

@@ -25,7 +25,7 @@
static void usage() {
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" );
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] [-V version] [-v variant] [-C context] [-h] site\n\n" );
fprintf( stderr, " -u name Specify the full name of the user.\n"
" Defaults to %s in env.\n\n", MP_env_fullname );
fprintf( stderr, " -t type Specify the password's template.\n"

View File

@@ -21,6 +21,7 @@ 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" );
xmlChar *fullName = mpw_xmlTestCaseString( testCase, "fullName" );
xmlChar *masterPassword = mpw_xmlTestCaseString( testCase, "masterPassword" );
xmlChar *keyID = mpw_xmlTestCaseString( testCase, "keyID" );
@@ -36,16 +37,20 @@ int main(int argc, char *const argv[]) {
// Run the test case.
fprintf( stdout, "test case %s... ", id );
if (!xmlStrlen( result )) {
fprintf( stdout, "abstract.\n" );
continue;
}
// 1. calculate the master key.
const uint8_t *masterKey = mpw_masterKeyForUser(
(char *)fullName, (char *)masterPassword, MPAlgorithmVersionCurrent );
(char *)fullName, (char *)masterPassword, algorithm );
if (!masterKey)
ftl( "Couldn't derive master key." );
// 2. calculate the site password.
const char *sitePassword = mpw_passwordForSite(
masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext, MPAlgorithmVersionCurrent );
masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext, algorithm );
mpw_free( masterKey, MP_dkLen );
if (!sitePassword)
ftl( "Couldn't derive site password." );
@@ -56,7 +61,7 @@ int main(int argc, char *const argv[]) {
else {
++failedTests;
fprintf( stdout, "FAILED! (result %s != expected %s)\n", result, sitePassword );
fprintf( stdout, "FAILED! (got %s != expected %s)\n", sitePassword, result );
}
// Free test case.

View File

@@ -21,15 +21,17 @@
const MPSiteType mpw_typeWithName(const char *typeName) {
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it.
size_t stdTypeNameOffset = 0;
size_t stdTypeNameSize = strlen( typeName );
char stdTypeName[strlen( typeName )];
if (stdTypeNameSize > strlen( "generated" ))
strcpy( stdTypeName, typeName + strlen( "generated" ) );
else
strcpy( stdTypeName, typeName );
for (char *tN = stdTypeName; *tN; ++tN)
*tN = (char)tolower( *tN );
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" ))
@@ -47,11 +49,10 @@ const MPSiteType mpw_typeWithName(const char *typeName) {
if (0 == strcmp( stdTypeName, "p" ) || 0 == strcmp( stdTypeName, "phrase" ))
return MPSiteTypeGeneratedPhrase;
fprintf( stderr, "Not a generated type name: %s", stdTypeName );
abort();
ftl( "Not a generated type name: %s", stdTypeName );
}
inline const char **mpw_templatesForType(MPSiteType type, size_t *count) {
const char **mpw_templatesForType(MPSiteType type, size_t *count) {
if (!(type & MPSiteTypeClassGenerated)) {
ftl( "Not a generated type: %d", type );
@@ -61,42 +62,42 @@ inline const char **mpw_templatesForType(MPSiteType type, size_t *count) {
switch (type) {
case MPSiteTypeGeneratedMaximum: {
*count = 2;
return (const char *[]){ "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" };
return alloc_array( *count, const char *,
"anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
}
case MPSiteTypeGeneratedLong: {
*count = 21;
return (const char *[]){ "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" };
return alloc_array( *count, const char *,
"CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
}
case MPSiteTypeGeneratedMedium: {
*count = 2;
return (const char *[]){ "CvcnoCvc", "CvcCvcno" };
return alloc_array( *count, const char *,
"CvcnoCvc", "CvcCvcno" );
}
case MPSiteTypeGeneratedBasic: {
*count = 3;
return (const char *[]){ "aaanaaan", "aannaaan", "aaannaaa" };
return alloc_array( *count, const char *,
"aaanaaan", "aannaaan", "aaannaaa" );
}
case MPSiteTypeGeneratedShort: {
*count = 1;
return (const char *[]){"Cvcn"};
return alloc_array( *count, const char *,
"Cvcn" );
}
case MPSiteTypeGeneratedPIN: {
*count = 1;
return (const char *[]){ "nnnn" };
return alloc_array( *count, const char *,
"nnnn" );
}
case MPSiteTypeGeneratedName: {
*count = 1;
return (const char *[]) {"cvccvcvcv"};
return alloc_array( *count, const char *,
"cvccvcvcv" );
}
case MPSiteTypeGeneratedPhrase: {
*count = 3;
return (const char *[]){ "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
return alloc_array( *count, const char *,
"cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
}
default: {
ftl( "Unknown generated type: %d", type );
@@ -113,15 +114,19 @@ const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
if (!count)
return NULL;
return templates[seedByte % count];
char const *template = templates[seedByte % count];
free( templates );
return template;
}
const MPSiteVariant mpw_variantWithName(const char *variantName) {
char stdVariantName[strlen( variantName )];
strcpy( stdVariantName, variantName );
for (char *vN = stdVariantName; *vN; ++vN)
*vN = (char)tolower( *vN );
// 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';
if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" ))
return MPSiteVariantPassword;

View File

@@ -6,6 +6,8 @@
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#ifndef _MPW_TYPES_H
#define _MPW_TYPES_H
#include <stdlib.h>
#include <stdint.h>
@@ -73,9 +75,10 @@ const char *mpw_scopeForVariant(MPSiteVariant variant);
const MPSiteType mpw_typeWithName(const char *typeName);
/**
* @return An array of internal strings that express the templates to use for the given type.
* @return A newly allocated array of internal strings that express the templates to use for the given type.
* The amount of elements in the array is stored in count.
* 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);
/**
@@ -93,3 +96,4 @@ const char *mpw_charactersInClass(char characterClass);
*/
const char mpw_characterFromClass(char characterClass, uint8_t seedByte);
#endif // _MPW_TYPES_H

View File

@@ -9,7 +9,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#ifdef COLOR
#include <unistd.h>
#include <curses.h>
#include <term.h>
#endif
#include <scrypt/sha256.h>
#include <scrypt/crypto_scrypt.h>
@@ -75,7 +80,7 @@ uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_
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 buffer = malloc(32);
uint8_t *const buffer = malloc( 32 );
if (!buffer)
return NULL;
@@ -91,20 +96,35 @@ const char *mpw_idForBuf(const void *buf, size_t length) {
return mpw_hex( hash, 32 );
}
static char *mpw_hex_buf = NULL;
static char **mpw_hex_buf = NULL;
static unsigned int mpw_hex_buf_i = 0;
const char *mpw_hex(const void *buf, size_t length) {
mpw_hex_buf = realloc( mpw_hex_buf, length * 2 + 1 );
for (size_t kH = 0; kH < length; kH++)
sprintf( &(mpw_hex_buf[kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
// 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;
}
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
return mpw_hex_buf;
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] );
return mpw_hex_buf[mpw_hex_buf_i];
}
const char *mpw_hex_l(uint32_t number) {
return mpw_hex( &number, sizeof( number ) );
}
#ifdef COLOR
static int putvari;
static char *putvarc = NULL;
static bool istermsetup = false;
static int istermsetup = 0;
static void initputvar() {
if (putvarc)
free(putvarc);
@@ -128,7 +148,8 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
const char *accessory[] = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" };
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
};
uint8_t identiconSeed[32];
HMAC_SHA256_Buf( masterPassword, strlen( masterPassword ), fullName, strlen( fullName ), identiconSeed );
@@ -165,8 +186,33 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
return identicon;
}
const size_t mpw_charlen(const char *string) {
/**
* @return the amount of bytes used by UTF-8 to encode a single character that starts with the given byte.
*/
static int mpw_charByteSize(unsigned char utf8Byte) {
setlocale( LC_ALL, "en_US.UTF-8" );
return mbstowcs( NULL, string, strlen( string ) );
if (!utf8Byte)
return 0;
if ((utf8Byte & 0x80) == 0)
return 1;
if ((utf8Byte & 0xC0) != 0xC0)
return 0;
if ((utf8Byte & 0xE0) == 0xC0)
return 2;
if ((utf8Byte & 0xF0) == 0xE0)
return 3;
if ((utf8Byte & 0xF8) == 0xF0)
return 4;
return 0;
}
const size_t mpw_charlen(const char *utf8String) {
size_t charlen = 0;
char *remainingString = (char *)utf8String;
for (int charByteSize; (charByteSize = mpw_charByteSize( (unsigned char)*remainingString )); remainingString += charByteSize)
++charlen;
return charlen;
}

View File

@@ -25,6 +25,14 @@
//// Buffers and memory.
#define alloc_array(_count, _type, ...) ({ \
_type stackElements[] = { __VA_ARGS__ }; \
_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_pushBuf(
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
@@ -58,6 +66,7 @@ uint8_t const *mpw_hmac_sha256(
/** 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_idForBuf(const void *buf, size_t length);
@@ -67,4 +76,5 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword);
//// String utilities.
const size_t mpw_charlen(const char *string);
/** @return The amount of display characters in the given UTF-8 string. */
const size_t mpw_charlen(const char *utf8String);

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
source bashcomplib
# completing the 'mpw' command.
_comp_mpw() {
local optarg= cword=${COMP_WORDS[COMP_CWORD]} pcword=
(( COMP_CWORD )) && pcword=${COMP_WORDS[COMP_CWORD - 1]}
case $pcword in
-u) # complete full names.
COMPREPLY=( ~/.mpw.d/*.mpsites )
[[ -e $COMPREPLY ]] || COMPREPLY=()
COMPREPLY=( "${COMPREPLY[@]##*/}" ) COMPREPLY=( "${COMPREPLY[@]%.mpsites}" )
;;
-t) # complete types.
COMPREPLY=( maximum long medium basic short pin name phrase )
;;
-c) # complete counter.
COMPREPLY=( 1 )
;;
-V) # complete versions.
COMPREPLY=( 0 1 2 3 )
;;
-v) # complete variants.
COMPREPLY=( password login answer )
;;
-C) # complete context.
;;
*)
# previous word is not an option we can complete, complete site name (or option if leading -)
if [[ $cword = -* ]]; then
COMPREPLY=( -u -t -c -V -v -C )
else
local w fullName=$MP_FULLNAME
for (( w = 0; w < ${#COMP_WORDS[@]}; ++w )); do
[[ ${COMP_WORDS[w]} = -u ]] && fullName=$(xargs <<< "${COMP_WORDS[w + 1]}") && break
done
if [[ -e ~/.mpw.d/"$fullName.mpsites" ]]; then
IFS=$'\n' read -d '' -ra COMPREPLY < <(awk -F$'\t' '!/^ *#/{sub(/^ */, "", $2); print $2}' ~/.mpw.d/"$fullName.mpsites")
printf -v _comp_title 'Sites for %s' "$fullName"
else
# Default list from the Alexa Top 500
COMPREPLY=(
163.com 360.cn 9gag.com adobe.com alibaba.com aliexpress.com amazon.com
apple.com archive.org ask.com baidu.com battle.net booking.com buzzfeed.com
chase.com cnn.com comcast.net craigslist.org dailymotion.com dell.com
deviantart.com diply.com disqus.com dropbox.com ebay.com engadget.com
espn.go.com evernote.com facebook.com fedex.com feedly.com flickr.com
flipkart.com github.com gizmodo.com go.com goodreads.com google.com
huffingtonpost.com hulu.com ign.com ikea.com imdb.com imgur.com
indiatimes.com instagram.com jd.com kickass.to kickstarter.com linkedin.com
live.com livedoor.com mail.ru mozilla.org naver.com netflix.com newegg.com
nicovideo.jp nytimes.com pandora.com paypal.com pinterest.com pornhub.com
qq.com rakuten.co.jp reddit.com redtube.com shutterstock.com skype.com
soso.com spiegel.de spotify.com stackexchange.com steampowered.com
stumbleupon.com taobao.com target.com thepiratebay.se tmall.com
torrentz.eu tripadvisor.com tube8.com tubecup.com tudou.com tumblr.com
twitter.com uol.com.br vimeo.com vk.com walmart.com weibo.com whatsapp.com
wikia.com wikipedia.org wired.com wordpress.com xhamster.com xinhuanet.com
xvideos.com yahoo.com yandex.ru yelp.com youku.com youporn.com ziddu.com
)
fi
fi ;;
esac
_comp_finish_completions
}
#complete -F _show_args mpw
complete -o nospace -F _comp_mpw mpw

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>2.2</version>
<version>2.3</version>
</parent>
<name>Master Password Algorithm Implementation</name>

View File

@@ -0,0 +1,106 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.collect.ImmutableMap;
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*;
import java.nio.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
/**
* @author lhunath, 15-03-29
*/
public class MPIdenticon {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPIdenticon.class );
private static final Charset charset = StandardCharsets.UTF_8;
private static final Color[] colors = new Color[]{
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
private static final char[] leftArm = new char[]{ '╔', '╚', '╰', '═' };
private static final char[] rightArm = new char[]{ '╗', '╝', '╯', '═' };
private static final char[] body = new char[]{ '█', '░', '▒', '▓', '☺', '☻' };
private static final char[] accessory = new char[]{
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
private final String fullName;
private final Color color;
private final String text;
public MPIdenticon(String fullName, String masterPassword) {
this( fullName, masterPassword.toCharArray() );
}
public MPIdenticon(String fullName, char[] masterPassword) {
this.fullName = fullName;
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
Arrays.fill( masterPasswordBytes, (byte) 0 );
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
while (identiconSeedBytes.hasRemaining())
identiconSeedBuffer.put( identiconSeedBytes.get() & 0xFF );
int[] identiconSeed = identiconSeedBuffer.array();
color = colors[identiconSeed[4] % colors.length];
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
}
public String getFullName() {
return fullName;
}
public String getText() {
return text;
}
public Color getColor() {
return color;
}
public enum BackgroundMode {
DARK, LIGHT
}
public enum Color {
RED( "#dc322f", "#dc322f" ),
GREEN( "#859900", "#859900" ),
YELLOW( "#b58900", "#b58900" ),
BLUE( "#268bd2", "#268bd2" ),
MAGENTA( "#d33682", "#d33682" ),
CYAN( "#2aa198", "#2aa198" ),
MONO( "#93a1a1", "#586e75" );
private final String rgbDark;
private final String rgbLight;
Color(final String rgbDark, final String rgbLight) {
this.rgbDark = rgbDark;
this.rgbLight = rgbLight;
}
public java.awt.Color getAWTColor(BackgroundMode backgroundMode) {
switch (backgroundMode) {
case DARK:
return new java.awt.Color( Integer.decode( rgbDark ) );
case LIGHT:
return new java.awt.Color( Integer.decode( rgbLight ) );
}
throw new UnsupportedOperationException( "Unsupported background mode: " + backgroundMode );
}
}
}

View File

@@ -6,6 +6,7 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract;
/**
@@ -147,6 +148,7 @@ public enum MPSiteType {
*
* @return The type registered with the given name.
*/
@Contract("!null -> !null, null -> null")
public static MPSiteType forName(@Nullable final String name) {
if (name == null)

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List;
import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract;
/**
@@ -66,6 +67,7 @@ public enum MPSiteVariant {
*
* @return The variant registered with the given name.
*/
@Contract("!null -> !null, null -> null")
public static MPSiteVariant forName(@Nullable final String name) {
if (name == null)

View File

@@ -65,6 +65,7 @@ public abstract class MasterKey {
@Nonnull
protected byte[] getKey() {
Preconditions.checkState( isValid() );
return Preconditions.checkNotNull( masterKey );
}

View File

@@ -58,8 +58,11 @@ public class MasterKeyV0 extends MasterKey {
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
byte[] mpBytes = MP_charset.encode( mpChars ).array();
ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
@@ -89,29 +92,37 @@ public class MasterKeyV0 extends MasterKey {
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
siteContextBytes == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
byte[] sitePasswordSeedBytes = MP_mac.of( getKey(), sitePasswordInfo );
int[] sitePasswordSeed = new int[sitePasswordSeedBytes.length];
for (int i = 0; i < sitePasswordSeedBytes.length; ++i) {
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( ByteOrder.BIG_ENDIAN );
Arrays.fill( buf.array(), sitePasswordSeedBytes[i] > 0? (byte)0x00: (byte) 0xFF );
buf.position( 2 );
buf.put( sitePasswordSeedBytes[i] ).rewind();
sitePasswordSeed[i] = buf.getInt() & 0xFFFF;
}
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFFFF;
int templateIndex = sitePasswordSeed[0];
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFFFF;
int characterIndex = sitePasswordSeed[i + 1];
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,

View File

@@ -46,12 +46,12 @@ public class MasterKeyV1 extends MasterKeyV0 {
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
siteContextBytes == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)

View File

@@ -45,12 +45,12 @@ public class MasterKeyV2 extends MasterKeyV1 {
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
siteContextBytes == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)

View File

@@ -4,6 +4,7 @@ import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.security.GeneralSecurityException;
import java.util.Arrays;
@@ -42,8 +43,11 @@ public class MasterKeyV3 extends MasterKeyV2 {
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
byte[] mpBytes = MP_charset.encode( mpChars ).array();
ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte)0 );
try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}

View File

@@ -1,10 +1,10 @@
package com.lyndir.masterpassword;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.NNSupplier;
import com.lyndir.lhunath.opal.system.util.NSupplier;
import com.lyndir.lhunath.opal.system.util.*;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -25,8 +25,9 @@ public class MPWTests {
@XmlElement(name = "case")
private List<Case> cases;
@Nonnull
public List<Case> getCases() {
return cases;
return checkNotNull( cases );
}
public Case getCase(String identifier) {
@@ -45,6 +46,8 @@ public class MPWTests {
@XmlAttribute
private String parent;
@XmlElement
private String algorithm;
@XmlElement
private String fullName;
@XmlElement
private String masterPassword;
@@ -65,76 +68,86 @@ public class MPWTests {
private transient Case parentCase;
public void setTests(MPWTests tests) {
public void initializeParentHierarchy(MPWTests tests) {
if (parent != null) {
parentCase = tests.getCase( parent );
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getFullName();
}
} );
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return new String( parentCase.getMasterPassword() );
}
} );
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getKeyID();
}
} );
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteName();
}
} );
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
@Nonnull
@Override
public Integer get() {
return parentCase.getSiteCounter();
}
} );
siteType = ifNotNullElse( siteType, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteType().name();
}
} );
siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteVariant().name();
}
} );
siteContext = ifNotNullElseNullable( siteContext, new NSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteContext();
}
} );
result = ifNotNullElse( result, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getResult();
}
} );
parentCase.initializeParentHierarchy( tests );
}
algorithm = ifNotNullElse( algorithm, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.algorithm );
}
} );
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.fullName );
}
} );
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.masterPassword );
}
} );
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.keyID );
}
} );
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.siteName );
}
} );
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
@Nonnull
@Override
public Integer get() {
return checkNotNull( parentCase.siteCounter );
}
} );
siteType = ifNotNullElse( siteType, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.siteType );
}
} );
siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.siteVariant );
}
} );
siteContext = ifNotNullElse( siteContext, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase == null? "": checkNotNull( parentCase.siteContext );
}
} );
result = ifNotNullElse( result, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase == null? "": checkNotNull( parentCase.result );
}
} );
}
@Nonnull
public String getIdentifier() {
return identifier;
}
@@ -144,40 +157,53 @@ public class MPWTests {
return parentCase;
}
@Nonnull
public MasterKey.Version getAlgorithm() {
return MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( algorithm ) );
}
@Nonnull
public String getFullName() {
return fullName;
return checkNotNull( fullName );
}
@Nonnull
public char[] getMasterPassword() {
return masterPassword == null? null: masterPassword.toCharArray();
return checkNotNull( masterPassword ).toCharArray();
}
@Nonnull
public String getKeyID() {
return keyID;
return checkNotNull( keyID );
}
@Nonnull
public String getSiteName() {
return siteName;
return checkNotNull( siteName );
}
public int getSiteCounter() {
return ifNotNullElse( siteCounter, 1 );
}
@Nonnull
public MPSiteType getSiteType() {
return MPSiteType.forName( siteType );
return MPSiteType.forName( checkNotNull( siteType ) );
}
@Nonnull
public MPSiteVariant getSiteVariant() {
return MPSiteVariant.forName( siteVariant );
return MPSiteVariant.forName( checkNotNull( siteVariant ) );
}
@Nonnull
public String getSiteContext() {
return siteContext;
return checkNotNull( siteContext );
}
@Nonnull
public String getResult() {
return result;
return checkNotNull( result );
}
@Override

View File

@@ -5,6 +5,7 @@ import static org.testng.Assert.*;
import com.google.common.io.Resources;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.StringUtils;
import java.net.URL;
import javax.xml.bind.JAXBContext;
import org.testng.annotations.BeforeMethod;
@@ -26,7 +27,7 @@ public class MasterKeyTest {
URL testCasesResource = Resources.getResource( "mpw_tests.xml" );
tests = (MPWTests) JAXBContext.newInstance( MPWTests.class ).createUnmarshaller().unmarshal( testCasesResource );
for (MPWTests.Case testCase : tests.getCases())
testCase.setTests( tests );
testCase.initializeParentHierarchy( tests );
defaultCase = tests.getCase( MPWTests.ID_DEFAULT );
}
@@ -35,10 +36,15 @@ public class MasterKeyTest {
throws Exception {
for (MPWTests.Case testCase : tests.getCases()) {
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
if (testCase.getResult().isEmpty())
continue;
logger.inf( "Running test case: %s [testEncode]", testCase.getIdentifier() );
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() );
assertEquals(
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(),
testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase );
logger.inf( "passed!" );
}
}
@@ -55,8 +61,13 @@ public class MasterKeyTest {
throws Exception {
for (MPWTests.Case testCase : tests.getCases()) {
if (testCase.getResult().isEmpty())
continue;
logger.inf( "Running test case: %s [testGetKeyID]", testCase.getIdentifier() );
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase );
logger.inf( "passed!" );
}
}

View File

@@ -1,5 +1,7 @@
<tests>
<!-- Default values for all parameters. -->
<case id="default">
<algorithm><!-- current --></algorithm>
<fullName>Robert Lee Mitchell</fullName>
<masterPassword>banana colored duckling</masterPassword>
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
@@ -7,67 +9,271 @@
<siteCounter>1</siteCounter>
<siteType>GeneratedLong</siteType>
<siteVariant>Password</siteVariant>
<result><!-- abstract --></result>
</case>
<!-- Algorithm 3 -->
<case id="v3" parent="default">
<algorithm>3</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="mb_fullName" parent="default">
<case id="v3_mb_fullName" parent="v3">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>NopaDajh8=Fene</result>
</case>
<case id="mb_masterPassword" parent="default">
<case id="v3_mb_masterPassword" parent="v3">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="mb_siteName" parent="default">
<case id="v3_mb_siteName" parent="v3">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="loginName" parent="default">
<case id="v3_loginName" parent="v3">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>wohzaqage</result>
</case>
<case id="securityAnswer" parent="default">
<case id="v3_securityAnswer" parent="v3">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="securityAnswer_context" parent="securityAnswer">
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
<siteContext>question</siteContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="type_maximum" parent="default">
<case id="v3_type_maximum" parent="v3">
<siteType>GeneratedMaximum</siteType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="type_medium" parent="default">
<case id="v3_type_medium" parent="v3">
<siteType>GeneratedMedium</siteType>
<result>Jej2$Quv</result>
</case>
<case id="type_basic" parent="default">
<case id="v3_type_basic" parent="v3">
<siteType>GeneratedBasic</siteType>
<result>WAo2xIg6</result>
</case>
<case id="type_short" parent="default">
<case id="v3_type_short" parent="v3">
<siteType>GeneratedShort</siteType>
<result>Jej2</result>
</case>
<case id="type_pin" parent="default">
<case id="v3_type_pin" parent="v3">
<siteType>GeneratedPIN</siteType>
<result>7662</result>
</case>
<case id="type_name" parent="default">
<case id="v3_type_name" parent="v3">
<siteType>GeneratedName</siteType>
<result>jejraquvo</result>
</case>
<case id="type_phrase" parent="default">
<case id="v3_type_phrase" parent="v3">
<siteType>GeneratedPhrase</siteType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="counter_ceiling" parent="default">
<case id="v3_counter_ceiling" parent="v3">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 2 -->
<case id="v2" parent="default">
<algorithm>2</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v2_mb_fullName" parent="v2">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v2_mb_masterPassword" parent="v2">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v2_mb_siteName" parent="v2">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="v2_loginName" parent="v2">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>wohzaqage</result>
</case>
<case id="v2_securityAnswer" parent="v2">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
<siteContext>question</siteContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v2_type_maximum" parent="v2">
<siteType>GeneratedMaximum</siteType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v2_type_medium" parent="v2">
<siteType>GeneratedMedium</siteType>
<result>Jej2$Quv</result>
</case>
<case id="v2_type_basic" parent="v2">
<siteType>GeneratedBasic</siteType>
<result>WAo2xIg6</result>
</case>
<case id="v2_type_short" parent="v2">
<siteType>GeneratedShort</siteType>
<result>Jej2</result>
</case>
<case id="v2_type_pin" parent="v2">
<siteType>GeneratedPIN</siteType>
<result>7662</result>
</case>
<case id="v2_type_name" parent="v2">
<siteType>GeneratedName</siteType>
<result>jejraquvo</result>
</case>
<case id="v2_type_phrase" parent="v2">
<siteType>GeneratedPhrase</siteType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v2_counter_ceiling" parent="v2">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 1 -->
<case id="v1" parent="default">
<algorithm>1</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v1_mb_fullName" parent="v1">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v1_mb_masterPassword" parent="v1">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v1_mb_siteName" parent="v1">
<siteName></siteName>
<result>WawiYarp2@Kodh</result>
</case>
<case id="v1_loginName" parent="v1">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>wohzaqage</result>
</case>
<case id="v1_securityAnswer" parent="v1">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
<siteContext>question</siteContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v1_type_maximum" parent="v1">
<siteType>GeneratedMaximum</siteType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v1_type_medium" parent="v1">
<siteType>GeneratedMedium</siteType>
<result>Jej2$Quv</result>
</case>
<case id="v1_type_basic" parent="v1">
<siteType>GeneratedBasic</siteType>
<result>WAo2xIg6</result>
</case>
<case id="v1_type_short" parent="v1">
<siteType>GeneratedShort</siteType>
<result>Jej2</result>
</case>
<case id="v1_type_pin" parent="v1">
<siteType>GeneratedPIN</siteType>
<result>7662</result>
</case>
<case id="v1_type_name" parent="v1">
<siteType>GeneratedName</siteType>
<result>jejraquvo</result>
</case>
<case id="v1_type_phrase" parent="v1">
<siteType>GeneratedPhrase</siteType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v1_counter_ceiling" parent="v1">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 0 -->
<case id="v0" parent="default">
<algorithm>0</algorithm>
<result>Feji5@ReduWosh</result>
</case>
<case id="v0_mb_fullName" parent="v0">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>HajrYudo7@Mamh</result>
</case>
<case id="v0_mb_masterPassword" parent="v0">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>MewmDini0]Meho</result>
</case>
<case id="v0_mb_siteName" parent="v0">
<siteName></siteName>
<result>HahiVana2@Nole</result>
</case>
<case id="v0_loginName" parent="v0">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>lozwajave</result>
</case>
<case id="v0_securityAnswer" parent="v0">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>miy lirfijoja dubu</result>
</case>
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
<siteContext>question</siteContext>
<result>movm bex gevrica jaf</result>
</case>
<case id="v0_type_maximum" parent="v0">
<siteType>GeneratedMaximum</siteType>
<result>w1!3bA3icmRAc)SS@lwl</result>
</case>
<case id="v0_type_medium" parent="v0">
<siteType>GeneratedMedium</siteType>
<result>Fej7]Jug</result>
</case>
<case id="v0_type_basic" parent="v0">
<siteType>GeneratedBasic</siteType>
<result>wvH7irC1</result>
</case>
<case id="v0_type_short" parent="v0">
<siteType>GeneratedShort</siteType>
<result>Fej7</result>
</case>
<case id="v0_type_pin" parent="v0">
<siteType>GeneratedPIN</siteType>
<result>2117</result>
</case>
<case id="v0_type_name" parent="v0">
<siteType>GeneratedName</siteType>
<result>fejrajugo</result>
</case>
<case id="v0_type_phrase" parent="v0">
<siteType>GeneratedPhrase</siteType>
<result>fejr jug gabsibu bax</result>
</case>
<case id="v0_counter_ceiling" parent="v0">
<siteCounter>4294967295</siteCounter>
<result>QateDojh1@Hecn</result>
</case>
</tests>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lyndir.masterpassword"
android:versionCode="1"
android:versionName="2.2">
android:versionCode="2003"
android:versionName="2.3">
<uses-sdk
android:minSdkVersion="19"

View File

@@ -0,0 +1,13 @@
To build this module, please ensure you've done the following setup:
1. Installed the Android SDK and fully downloaded the Android SDK platform 21 in it.
2. Set the environment variable ANDROID_HOME in your shell or in ~/.mavenrc to point to the root of your Android SDK install.
3. Installed the Android SDK into your Maven's local repository.
3a. Clone the maven-android-sdk-deployer available from here: https://github.com/mosabua/maven-android-sdk-deployer.git
3b. In the root of this project, run: mvn install -P 5.0
To build this module:
1. Build the parent, by going into 'MasterPassword/Java' and running: mvn clean install
2. Build this module, by going into 'MasterPassword/Java/masterpassword-android' and running: mvn clean install
3. You can then find the APK in: 'MasterPassword/Java/masterpassword-android/target'

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>2.2</version>
<version>2.3</version>
</parent>
<name>Master Password Android</name>
@@ -104,7 +104,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>2.2</version>
<version>2.3</version>
</dependency>
<dependency>
@@ -115,13 +115,13 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-android</artifactId>
<version>1.7.13-underscore</version>
</dependency>
<!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git
run mvn install -P 4.4 -->
<dependency>
<groupId>android</groupId>
<artifactId>android</artifactId>
<version>5.0.1_r2</version>
</dependency>
<dependency>

View File

@@ -85,7 +85,7 @@
android:layout_gravity="center"
android:orientation="vertical">
<TextView
<Button
android:id="@id/sitePasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -2,10 +2,11 @@ package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import android.app.Activity;
import android.app.*;
import android.content.*;
import android.content.ClipboardManager;
import android.graphics.Paint;
import android.os.Build;
import android.os.Bundle;
import android.text.*;
import android.text.method.PasswordTransformationMethod;
@@ -18,7 +19,7 @@ import com.google.common.base.Throwables;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import java.util.Arrays;
import java.util.*;
import java.util.concurrent.*;
import javax.annotation.Nullable;
@@ -26,7 +27,9 @@ import javax.annotation.Nullable;
public class EmergencyActivity extends Activity {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( EmergencyActivity.class );
private static final Logger logger = Logger.get( EmergencyActivity.class );
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
private static final int PASSWORD_NOTIFICATION = 0;
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
@@ -66,7 +69,7 @@ public class EmergencyActivity extends Activity {
Spinner siteVersionField;
@InjectView(R.id.sitePasswordField)
TextView sitePasswordField;
Button sitePasswordField;
@InjectView(R.id.sitePasswordTip)
TextView sitePasswordTip;
@@ -306,13 +309,40 @@ public class EmergencyActivity extends Activity {
}
public void copySitePassword(View view) {
if (TextUtils.isEmpty( sitePassword ))
final String currentSitePassword = this.sitePassword;
if (TextUtils.isEmpty( currentSitePassword ))
return;
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ),
new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
ClipData clipData = new ClipData( description, new ClipData.Item( sitePassword ) );
((ClipboardManager) getSystemService( CLIPBOARD_SERVICE )).setPrimaryClip( clipData );
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
String title = strf( "Password for %s", siteNameField.getText() );
ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
.setContentText( "Paste the password into your app." )
.setSmallIcon( R.drawable.icon )
.setAutoCancel( true );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
.setCategory( Notification.CATEGORY_RECOMMENDATION )
.setLocalOnly( true );
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
final Timer timer = new Timer();
timer.schedule( new TimerTask() {
@Override
public void run() {
ClipData clip = clipboardManager.getPrimaryClip();
for (int i = 0; i < clip.getItemCount(); ++i)
if (currentSitePassword.equals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
clipboardManager.setPrimaryClip( EMPTY_CLIP );
break;
}
notificationManager.cancel( PASSWORD_NOTIFICATION );
timer.cancel();
}
}, 20000 );
Intent startMain = new Intent( Intent.ACTION_MAIN );
startMain.addCategory( Intent.CATEGORY_HOME );

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>2.2</version>
<version>2.3</version>
</parent>
<name>Master Password CLI</name>
@@ -85,7 +85,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>2.2</version>
<version>2.3</version>
</dependency>
<dependency>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>2.2</version>
<version>2.3</version>
</parent>
<name>Master Password GUI</name>
@@ -134,7 +134,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-model</artifactId>
<version>2.2</version>
<version>2.3</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->

View File

@@ -1,9 +1,13 @@
package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import org.jetbrains.annotations.NotNull;
/**
@@ -29,6 +33,8 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
}
} );
add( Box.createVerticalGlue() );
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
}
protected void updateUser(boolean repack) {
@@ -39,8 +45,11 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
unlockFrame.repack();
}
@Nullable
protected abstract User getSelectedUser();
@NotNull
@Nonnull
public abstract char[] getMasterPassword();
public Component getFocusComponent() {

View File

@@ -21,6 +21,7 @@ import com.google.common.base.Charsets;
import com.google.common.io.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils;
import java.io.*;
import java.net.URI;
import java.net.URL;
@@ -48,7 +49,13 @@ public class GUI implements UnlockFrame.SignInCallback {
if (Config.get().checkForUpdates())
checkUpdate();
TypeUtils.<GUI>newInstance( AppleGUI.class ).or( new GUI() ).open();
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
}
TypeUtils.<GUI>newInstance( "com.lyndir.masterpassword.gui.platform.mac.AppleGUI" ).or( new GUI() ).open();
}
private static void checkUpdate() {
@@ -80,7 +87,7 @@ public class GUI implements UnlockFrame.SignInCallback {
}
}
void open() {
protected void open() {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {

View File

@@ -7,6 +7,7 @@ import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.jetbrains.annotations.NotNull;
/**
@@ -58,6 +59,7 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
return new IncognitoUser( fullNameField.getText() );
}
@NotNull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();

View File

@@ -41,4 +41,8 @@ public class IncognitoUser extends User {
@Override
public void addSite(final Site site) {
}
@Override
public void deleteSite(Site site) {
}
}

View File

@@ -1,5 +1,7 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
@@ -13,6 +15,8 @@ import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.metal.MetalComboBoxEditor;
import org.jetbrains.annotations.NotNull;
/**
@@ -51,6 +55,15 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
userField.setFont( Res.valueFont().deriveFont( 12f ) );
userField.addItemListener( this );
userField.addActionListener( this );
userField.setEditor( new MetalComboBoxEditor() {
@Override
protected JTextField createEditorComponent() {
JTextField editorComponents = Components.textField();
editorComponents.setForeground( Color.red );
return editorComponents;
}
} );
add( userField );
add( Components.stud() );
@@ -94,6 +107,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
return userField.getModel().getElementAt( selectedIndex );
}
@NotNull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
@@ -114,6 +128,29 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
updateUser( true );
}
} );
setToolTipText( "Add a new user to the list." );
}
}, new JButton( Res.iconDelete() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
ModelUser deleteUser = getSelectedUser();
if (deleteUser == null)
return;
if (JOptionPane.showConfirmDialog( ModelAuthenticationPanel.this, //
strf( "Are you sure you want to delete the user and sites remembered for:\n%s.",
deleteUser.getFullName() ), //
"Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ) == JOptionPane.CANCEL_OPTION)
return;
MPUserFileManager.get().deleteUser( deleteUser.getModel() );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true );
}
} );
setToolTipText( "Delete the selected user." );
}
}, new JButton( Res.iconQuestion() ) {
{
@@ -121,10 +158,12 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
@Override
public void actionPerformed(final ActionEvent e) {
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, //
"Reads users and sites from the directory at ~/.mpw.", //
strf( "Reads users and sites from the directory at:\n%s",
MPUserFileManager.get().getPath().getAbsolutePath() ), //
"Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
setToolTipText( "More information." );
}
} );
}

View File

@@ -16,6 +16,10 @@ public class ModelSite extends Site {
this.model = result.getSite();
}
public MPSite getModel() {
return model;
}
public String getSiteName() {
return model.getSiteName();
}

View File

@@ -42,7 +42,7 @@ public class ModelUser extends User {
}
public void setAvatar(final int avatar) {
model.setAvatar( avatar % Res.avatars() );
model.setAvatar(avatar % Res.avatars());
MPUserFileManager.get().save();
}
@@ -67,8 +67,8 @@ public class ModelUser extends User {
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
@Nullable
@Override
public Site apply(@Nullable final MPSiteResult result) {
return new ModelSite( Preconditions.checkNotNull( result ) );
public Site apply(@Nullable final MPSiteResult site) {
return new ModelSite( Preconditions.checkNotNull( site ) );
}
} );
}
@@ -80,6 +80,14 @@ public class ModelUser extends User {
MPUserFileManager.get().save();
}
@Override
public void deleteSite(Site site) {
if (site instanceof ModelSite) {
model.deleteSite(((ModelSite) site).getModel());
MPUserFileManager.get().save();
}
}
public boolean keySaved() {
// TODO
return false;

View File

@@ -1,9 +1,13 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.util.NNSupplier;
import com.lyndir.lhunath.opal.system.util.PredicateNN;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
@@ -24,7 +28,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
private final User user;
private final Components.GradientPanel root;
private final JTextField siteNameField;
private final JButton siteAddButton;
private final JButton siteActionButton;
private final JComboBox<MPSiteType> siteTypeField;
private final JComboBox<MasterKey.Version> siteVersionField;
private final JSpinner siteCounterField;
@@ -33,8 +37,10 @@ public class PasswordFrame extends JFrame implements DocumentListener {
private final JCheckBox maskPasswordField;
private final char passwordEchoChar;
private final Font passwordEchoFont;
private boolean updatingUI;
@Nullable
private Site currentSite;
private boolean updatingUI;
public PasswordFrame(User user)
throws HeadlessException {
@@ -42,7 +48,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
this.user = user;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
// Site
@@ -50,52 +57,55 @@ public class PasswordFrame extends JFrame implements DocumentListener {
sitePanel.setOpaque( true );
sitePanel.setBackground( Res.colors().controlBg() );
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
root.add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
// User
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), JLabel.CENTER ) );
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), SwingConstants.CENTER ) );
sitePanel.add( Components.stud() );
// Site Name
sitePanel.add( Components.label( "Site Name:" ) );
sitePanel.add(Components.label("Site Name:"));
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteNameField = Components.textField(), Components.stud(),
siteAddButton = Components.button( "Add Site" ) );
siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener( new ActionListener() {
siteActionButton = Components.button( "Add Site" ) );
siteNameField.getDocument().addDocumentListener(this);
siteNameField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
Futures.addCallback( updatePassword(), new FutureCallback<String>() {
Futures.addCallback(updatePassword(true), new FutureCallback<String>() {
@Override
public void onSuccess(@Nullable final String sitePassword) {
StringSelection clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
StringSelection clipboardContents = new StringSelection(sitePassword);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(clipboardContents, null);
SwingUtilities.invokeLater( new Runnable() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
passwordField.setText( null );
siteNameField.setText( null );
passwordField.setText(null);
siteNameField.setText(null);
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
dispatchEvent(new WindowEvent(PasswordFrame.this, WindowEvent.WINDOW_CLOSING));
}
} );
});
}
@Override
public void onFailure(final Throwable t) {
}
} );
});
}
} );
siteAddButton.setVisible( false );
siteAddButton.addActionListener( new ActionListener() {
});
siteActionButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
PasswordFrame.this.user.addSite( currentSite );
siteAddButton.setVisible( false );
if (currentSite instanceof ModelSite)
PasswordFrame.this.user.deleteSite(currentSite);
else
PasswordFrame.this.user.addSite(currentSite);
updatePassword(true);
}
} );
});
sitePanel.add( siteControls );
sitePanel.add( Components.stud() );
@@ -114,7 +124,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
siteTypeField.addItemListener( new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent e) {
updatePassword();
updatePassword(true);
}
} );
@@ -124,7 +134,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
siteVersionField.addItemListener( new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent e) {
updatePassword();
updatePassword(true);
}
} );
@@ -133,7 +143,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
siteCounterField.addChangeListener( new ChangeListener() {
@Override
public void stateChanged(final ChangeEvent e) {
updatePassword();
updatePassword(true);
}
} );
@@ -149,25 +159,27 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} );
// Password
passwordField = new JPasswordField();
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
passwordField.setEditable( false );
passwordField.setHorizontalAlignment( JTextField.CENTER );
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
passwordField = Components.passwordField();
passwordField.setAlignmentX(Component.CENTER_ALIGNMENT);
passwordField.setHorizontalAlignment(JTextField.CENTER);
passwordField.putClientProperty("JPasswordField.cutCopyAllowed", true);
passwordField.setEditable(false);
passwordField.setBackground(null);
passwordField.setBorder( null );
passwordEchoChar = passwordField.getEchoChar();
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
updateMask();
// Tip
tipLabel = Components.label( " ", JLabel.CENTER );
tipLabel = Components.label( " ", SwingConstants.CENTER );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField, Box.createGlue(), tipLabel );
passwordContainer.setOpaque( true );
passwordContainer.setBackground( Color.white );
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
BorderLayout.SOUTH );
root.add( Box.createVerticalStrut( 8 ) );
root.add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
BorderLayout.SOUTH );
pack();
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
@@ -183,25 +195,33 @@ public class PasswordFrame extends JFrame implements DocumentListener {
}
@Nonnull
private ListenableFuture<String> updatePassword() {
private ListenableFuture<String> updatePassword(boolean allowNameCompletion) {
final String siteNameQuery = siteNameField.getText();
if (updatingUI)
return Futures.immediateCancelledFuture();
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.isKeyAvailable()) {
siteActionButton.setVisible(false);
tipLabel.setText( null );
passwordField.setText( null );
return Futures.immediateCancelledFuture();
}
final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
final MPSiteType siteType = siteTypeField.getModel().getElementAt(siteTypeField.getSelectedIndex());
final MasterKey.Version siteVersion = siteVersionField.getItemAt(siteVersionField.getSelectedIndex());
final int siteCounter = (Integer) siteCounterField.getValue();
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
: Iterables.getFirst( user.findSitesByName( siteNameQuery ),
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
assert site != null;
if (site == currentSite) {
Iterable<Site> siteResults = user.findSitesByName(siteNameQuery);
if (!allowNameCompletion)
siteResults = FluentIterable.from(siteResults).filter(new PredicateNN<Site>() {
@Override
public boolean apply(Site input) {
return siteNameQuery.equals(input.getSiteName());
}
});
final Site site = Iterables.getFirst(siteResults,
new IncognitoSite(siteNameQuery, siteType, siteCounter, siteVersion) );
if (currentSite != null && site.getSiteName().equals(currentSite.getSiteName())) {
site.setSiteType( siteType );
site.setAlgorithmVersion( siteVersion );
site.setSiteCounter( siteCounter );
@@ -223,8 +243,12 @@ public class PasswordFrame extends JFrame implements DocumentListener {
public void run() {
updatingUI = true;
currentSite = site;
siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) );
siteTypeField.setSelectedItem( currentSite.getSiteType() );
siteActionButton.setVisible(user instanceof ModelUser);
if (currentSite instanceof ModelSite)
siteActionButton.setText("Delete Site");
else
siteActionButton.setText("Add Site");
siteTypeField.setSelectedItem(currentSite.getSiteType());
siteVersionField.setSelectedItem( currentSite.getAlgorithmVersion() );
siteCounterField.setValue( currentSite.getSiteCounter() );
siteNameField.setText( currentSite.getSiteName() );
@@ -248,15 +272,16 @@ public class PasswordFrame extends JFrame implements DocumentListener {
@Override
public void insertUpdate(final DocumentEvent e) {
updatePassword();
updatePassword(true);
}
@Override
public void removeUpdate(final DocumentEvent e) {
updatePassword(false);
}
@Override
public void changedUpdate(final DocumentEvent e) {
updatePassword();
updatePassword(true);
}
}

View File

@@ -85,6 +85,10 @@ public abstract class Res {
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
}
public static Icon iconDelete() {
return new RetinaIcon( Resources.getResource( "media/icon_delete@2x.png" ) );
}
public static Icon iconQuestion() {
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
}
@@ -97,6 +101,10 @@ public abstract class Res {
return 19;
}
public static Font emoticonsFont() {
return emoticonsRegular();
}
public static Font controlFont() {
return arimoRegular();
}
@@ -109,6 +117,10 @@ public abstract class Res {
return sourceSansProBlack();
}
public static Font emoticonsRegular() {
return font( "fonts/Emoticons-Regular.otf" );
}
public static Font sourceCodeProRegular() {
return font( "fonts/SourceCodePro-Regular.otf" );
}

View File

@@ -2,10 +2,12 @@ package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.awt.*;
import java.awt.event.*;
import javax.annotation.Nullable;
import javax.swing.*;
@@ -16,6 +18,7 @@ public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback;
private final Components.GradientPanel root;
private final JLabel identiconLabel;
private final JButton signInButton;
private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel;
@@ -28,20 +31,39 @@ public class UnlockFrame extends JFrame {
this.signInCallback = signInCallback;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
addWindowFocusListener( new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
root.setGradientColor( Res.colors().frameBg() );
}
authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS );
authenticationContainer.setOpaque( true );
authenticationContainer.setBackground( Res.colors().controlBg() );
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.borderPanel( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
@Override
public void windowLostFocus(WindowEvent e) {
root.setGradientColor( Color.RED );
}
} );
// Sign In
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
Box.createGlue() );
signInBox.setBackground( null );
root.add( signInBox, BorderLayout.SOUTH );
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
root.add( Components.borderPanel( authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS ),
BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
root.add( Box.createVerticalStrut( 8 ) );
root.add( identiconLabel = Components.label( " ", SwingConstants.CENTER ) );
root.add( Box.createVerticalStrut( 8 ) );
root.add( signInBox );
authenticationContainer.setOpaque( true );
authenticationContainer.setBackground( Res.colors().controlBg() );
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
identiconLabel.setFont( Res.emoticonsFont().deriveFont( 14.f ) );
identiconLabel.setToolTipText(
"A representation of your identity across all Master Password apps.\nIt should always be the same." );
signInButton.addActionListener( new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
@@ -74,6 +96,7 @@ public class UnlockFrame extends JFrame {
authenticationContainer.add( Components.stud() );
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( new ItemListener() {
@Override
@@ -91,10 +114,11 @@ public class UnlockFrame extends JFrame {
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
authenticationContainer.add( toolsPanel );
for (JButton button : authenticationPanel.getButtons()) {
toolsPanel.add( button );
button.setBorder( BorderFactory.createEmptyBorder() );
button.setMargin( new Insets( 0, 0, 0, 0 ) );
button.setAlignmentX( RIGHT_ALIGNMENT );
button.setBorder( null );
toolsPanel.add( button );
button.setContentAreaFilled( false );
}
checkSignIn();
@@ -109,13 +133,24 @@ public class UnlockFrame extends JFrame {
} );
}
void updateUser(User user) {
void updateUser(@Nullable User user) {
this.user = user;
checkSignIn();
}
boolean checkSignIn() {
boolean enabled = user != null && !user.getFullName().isEmpty() && authenticationPanel.getMasterPassword().length > 0;
String fullName = user == null? "": user.getFullName();
char[] masterPassword = authenticationPanel.getMasterPassword();
boolean enabled = !fullName.isEmpty() && masterPassword.length > 0;
if (fullName.isEmpty() || masterPassword.length == 0)
identiconLabel.setText( " " );
else {
MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
identiconLabel.setText( identicon.getText() );
identiconLabel.setForeground( identicon.getColor().getAWTColor( MPIdenticon.BackgroundMode.DARK ) );
}
signInButton.setEnabled( enabled );
return enabled;
@@ -158,7 +193,6 @@ public class UnlockFrame extends JFrame {
}
} );
}
}
} );
}

View File

@@ -62,6 +62,8 @@ public abstract class User {
public abstract void addSite(final Site site);
public abstract void deleteSite(final Site site);
@Override
public boolean equals(final Object obj) {
return this == obj || obj instanceof User && Objects.equals( getFullName(), ((User) obj).getFullName() );

View File

@@ -1,6 +1,10 @@
package com.lyndir.masterpassword.gui;
package com.lyndir.masterpassword.gui.platform.mac;
import com.apple.eawt.*;
import com.lyndir.masterpassword.gui.GUI;
import com.lyndir.masterpassword.gui.PasswordFrame;
import com.lyndir.masterpassword.gui.User;
import javax.swing.*;

View File

@@ -116,6 +116,7 @@ public abstract class Components {
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
setBorder( null );
}
@Override
@@ -125,12 +126,21 @@ public abstract class Components {
};
}
public static JLabel label(final String label) {
return label( label, JLabel.LEADING );
public static JLabel label(@Nullable String label) {
return label( label, SwingConstants.LEADING );
}
public static JLabel label(final String label, final int alignment) {
return new JLabel( label, alignment ) {
/**
* @param horizontalAlignment One of the following constants
* defined in <code>SwingConstants</code>:
* <code>LEFT</code>,
* <code>CENTER</code>,
* <code>RIGHT</code>,
* <code>LEADING</code> or
* <code>TRAILING</code>.
*/
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
return new JLabel( label, horizontalAlignment ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
@@ -148,6 +158,7 @@ public abstract class Components {
return new JCheckBox( label ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@@ -162,9 +173,14 @@ public abstract class Components {
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
return new JComboBox<M>( model ) {
{
// CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
// setBorder(null);
}
@Override
@@ -195,14 +211,17 @@ public abstract class Components {
public void setGradientColor(@Nullable final Color gradientColor) {
this.gradientColor = gradientColor;
revalidate();
}
@Override
public void doLayout() {
super.doLayout();
if (gradientColor != null)
if (gradientColor != null) {
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
repaint();
}
}
@Override

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>2.2</version>
<version>2.3</version>
</parent>
<name>Master Password Site Model</name>
@@ -23,7 +23,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>2.2</version>
<version>2.3</version>
</dependency>
<dependency>

View File

@@ -59,6 +59,10 @@ public class MPUser implements Comparable<MPUser> {
sites.add( site );
}
public void deleteSite(final MPSite site) {
sites.remove( site );
}
public String getFullName() {
return fullName;
}

View File

@@ -5,6 +5,7 @@ import com.google.common.collect.*;
import com.google.common.io.CharSink;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.*;
import java.util.SortedSet;
import javax.annotation.Nullable;
@@ -15,7 +16,7 @@ public class MPUserFileManager extends MPUserManager {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPUserFileManager.class );
private static final File mpwd = new File( System.getProperty( "user.home" ), ".mpw.d" );
private static final File mpwd = new File( System.getProperty( "user.home" ), ".mpw.d" );
private static final MPUserFileManager instance;
static {
@@ -70,19 +71,53 @@ public class MPUserFileManager extends MPUserManager {
} ).filter( Predicates.notNull() );
}
@Override
public void addUser(final MPUser user) {
super.addUser( user );
save();
}
@Override
public void deleteUser(final MPUser user) {
super.deleteUser( user );
save();
}
/**
* Write the current user state to disk.
*/
public void save() {
// Save existing users.
for (final MPUser user : getUsers())
try {
new CharSink() {
@Override
public Writer openStream()
throws IOException {
return new FileWriter( new File(userFilesDirectory, user.getFullName() + ".mpsites" ) );
return new FileWriter( new File( userFilesDirectory, user.getFullName() + ".mpsites" ) );
}
}.write( MPSiteMarshaller.marshallSafe( user ).getExport() );
}
catch (IOException e) {
logger.err( e, "Unable to save sites for user: %s", user );
}
// Remove deleted users.
for (File userFile : userFilesDirectory.listFiles( new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return name.endsWith( ".mpsites" );
}
} ))
if (getUserNamed( userFile.getName().replaceFirst( "\\.mpsites$", "" ) ) == null)
if (!userFile.delete())
logger.err( "Couldn't delete file: %s", userFile );
}
/**
* @return The location on the file system where the user models are stored.
*/
public File getPath() {
return mpwd;
}
}

View File

@@ -18,14 +18,22 @@ public abstract class MPUserManager {
protected MPUserManager(final Iterable<MPUser> users) {
for (MPUser user : users)
addUser( user );
usersByName.put( user.getFullName(), user );
}
public SortedSet<MPUser> getUsers() {
return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() );
}
public MPUser getUserNamed(String fullName) {
return usersByName.get( fullName );
}
public void addUser(final MPUser user) {
usersByName.put( user.getFullName(), user );
}
public void deleteUser(final MPUser user) {
usersByName.remove( user.getFullName() );
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.lhunath</groupId>
<artifactId>lyndir</artifactId>
<version>1.20</version>
<version>1.22</version>
</parent>
<name>Master Password</name>
@@ -15,7 +15,7 @@
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>2.2</version>
<version>2.3</version>
<packaging>pom</packaging>
<modules>
@@ -26,6 +26,12 @@
</modules>
<profiles>
<profile>
<id>release</id>
<modules>
<module>masterpassword-android</module>
</modules>
</profile>
<profile>
<id>mod:android</id>
<modules>
@@ -52,4 +58,7 @@
</repository>
</repositories>
<scm>
<tag>2.3</tag>
</scm>
</project>

View File

@@ -48,9 +48,8 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
- (MPKey *)keyFromKeyData:(NSData *)keyData;
- (NSData *)keyIDForKeyData:(NSData *)keyData;
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (NSString *)nameOfType:(MPSiteType)type;
- (NSString *)shortNameOfType:(MPSiteType)type;

View File

@@ -28,8 +28,10 @@
#define CRACKING_PER_SECOND 2495000000UL
#define CRACKING_PRICE 350
NSOperationQueue *_mpwQueue = nil;
@implementation MPAlgorithmV0 {
BN_CTX *ctx;
BN_CTX *_ctx;
}
- (id)init {
@@ -37,15 +39,22 @@
if (!(self = [super init]))
return nil;
ctx = BN_CTX_new();
_ctx = BN_CTX_new();
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
_mpwQueue = [NSOperationQueue new];
_mpwQueue.maxConcurrentOperationCount = 1;
_mpwQueue.name = @"mpw queue";
} );
return self;
}
- (void)dealloc {
BN_CTX_free( ctx );
ctx = NULL;
BN_CTX_free( _ctx );
_ctx = NULL;
}
- (MPAlgorithmVersion)version {
@@ -68,6 +77,19 @@
return [(id<MPAlgorithm>)other version] == [self version];
}
- (void)mpw_perform:(void ( ^ )(void))operationBlock {
if ([NSOperationQueue currentQueue] == _mpwQueue) {
operationBlock();
return;
}
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:operationBlock];
if ([operation respondsToSelector:@selector( qualityOfService )])
operation.qualityOfService = NSQualityOfServiceUserInitiated;
[_mpwQueue addOperations:@[ operation ] waitUntilFinished:YES];
}
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
NSError *error = nil;
@@ -89,7 +111,7 @@
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if (site.version != [self version] - 1)
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
@@ -101,24 +123,23 @@
// Apply migration.
site.requiresExplicitMigration = NO;
site.version = [self version];
site.algorithm = self;
return YES;
}
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
NSDate *start = [NSDate date];
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( userName.UTF8String, password.UTF8String, [self version] );
MPKey *masterKey = [self keyFromKeyData:[NSData dataWithBytes:masterKeyBytes length:MP_dkLen]];
mpw_free( masterKeyBytes, MP_dkLen );
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [masterKey.keyID encodeHex],
-[start timeIntervalSinceNow] );
return masterKey;
}
__block NSData *keyData;
[self mpw_perform:^{
NSDate *start = [NSDate date];
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] );
keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen];
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", //
fullName, masterPassword, [self keyIDForKeyData:keyData], -[start timeIntervalSinceNow] );
mpw_free( masterKeyBytes, MP_dkLen );
}];
- (MPKey *)keyFromKeyData:(NSData *)keyData {
return [[MPKey alloc] initWithKeyData:keyData algorithm:self];
return keyData;
}
- (NSData *)keyIDForKeyData:(NSData *)keyData {
@@ -322,10 +343,13 @@
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
char const *contentBytes = mpw_passwordForSite( key.keyData.bytes, name.UTF8String, type, (uint32_t)counter,
variant, context.UTF8String, [self version] );
NSString *content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
mpw_freeString( contentBytes );
__block NSString *content;
[self mpw_perform:^{
char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes,
name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
mpw_freeString( contentBytes );
}];
return content;
}
@@ -342,7 +366,7 @@
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
@@ -363,8 +387,9 @@
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
encryptWithSymmetricKey:encryptionKey padding:YES];
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
return NO;
@@ -378,14 +403,15 @@
return NO;
}
NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
encryptWithSymmetricKey:encryptionKey padding:YES];
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
if (!encryptedContent)
[PearlKeyChain deleteItemForQuery:siteQuery];
else
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
(__bridge id)kSecValueData : encryptedContent,
(__bridge id)kSecValueData : encryptedContent,
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
#endif
@@ -456,14 +482,14 @@
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
NSString *loginName = loginGenerated? nil: site.loginName;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!siteKey.keyData.length)
else if (!siteKey)
err( @"Missing key." );
else
algorithm = site.algorithm;
@@ -478,7 +504,7 @@
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
@@ -500,7 +526,7 @@
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!siteKey.keyData.length)
else if (!siteKey)
err( @"Missing key." );
else
algorithm = site.algorithm;
@@ -546,12 +572,12 @@
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!siteKey.keyData.length)
else if (!siteKey)
err( @"Missing key." );
else
algorithm = site.algorithm;
@@ -565,13 +591,14 @@
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [siteKey.keyID isEqualToData:question.site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
@"Site does not belong to current user." );
NSString *name = question.site.name;
NSString *keyword = question.keyword;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!siteKey.keyData.length)
else if (!siteKey)
err( @"Missing key." );
else
algorithm = question.site.algorithm;
@@ -585,7 +612,7 @@
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
@@ -603,7 +630,7 @@
(long)site.type, [site class] );
break;
}
if ([importKey.keyID isEqualToData:siteKey.keyID])
if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]])
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
else {
@@ -620,7 +647,7 @@
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
@@ -644,7 +671,7 @@
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (!(site.type & MPSiteFeatureExportContent))
return nil;
@@ -701,8 +728,10 @@
if (!key)
return nil;
NSData *decryptedContent = nil;
if ([encryptedContent length])
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
if ([encryptedContent length]) {
NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize];
decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
}
if (!decryptedContent)
return nil;
@@ -711,7 +740,7 @@
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
if (!type)
if (!(type & MPSiteTypeClassGenerated))
return NO;
size_t count = 0;
const char **templates = mpw_templatesForType( type, &count );
@@ -730,6 +759,7 @@
BN_add( permutations, permutations, templatePermutations );
}
BN_free( templatePermutations );
free( templates );
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
}
@@ -748,7 +778,7 @@
if (strchr( charactersForClass, passwordCharacter )) {
// Found class for password character.
characterEntropy = (BN_ULONG)strlen(charactersForClass);
characterEntropy = (BN_ULONG)strlen( charactersForClass );
break;
}
}

View File

@@ -27,7 +27,7 @@
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if (site.version != [self version] - 1)
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
@@ -41,7 +41,7 @@
// Apply migration.
site.requiresExplicitMigration = NO;
site.version = [self version];
site.algorithm = self;
return YES;
}

View File

@@ -15,7 +15,6 @@
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import <objc/runtime.h>
#import "MPAlgorithmV2.h"
#import "MPEntities.h"
@@ -28,7 +27,7 @@
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if (site.version != [self version] - 1)
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
@@ -42,7 +41,7 @@
// Apply migration.
site.requiresExplicitMigration = NO;
site.version = [self version];
site.algorithm = self;
return YES;
}

View File

@@ -27,12 +27,13 @@
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if (site.version != [self version] - 1)
if ([site.algorithm version] != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
if (site.type & MPSiteTypeClassGenerated &&
site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;
return NO;
@@ -41,7 +42,7 @@
// Apply migration.
site.requiresExplicitMigration = NO;
site.version = [self version];
site.algorithm = self;
return YES;
}

View File

@@ -31,12 +31,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )];
if (!keyData) {
inf( @"No key found in keychain for: %@", user.userID );
inf( @"No key found in keychain for user: %@", user.userID );
return nil;
}
inf( @"Found key in keychain for: %@", user.userID );
return [MPAlgorithmDefault keyFromKeyData:keyData];
inf( @"Found key in keychain for user: %@", user.userID );
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm];
}
- (void)storeSavedKeyFor:(MPUserEntity *)user {
@@ -44,12 +44,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (user.saveKey) {
NSData *existingKeyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )];
if (![existingKeyData isEqualToData:self.key.keyData]) {
inf( @"Saving key in keychain for: %@", user.userID );
if (![existingKeyData isEqualToData:[self.key keyDataForAlgorithm:user.algorithm]]) {
inf( @"Saving key in keychain for user: %@", user.userID );
[PearlKeyChain addOrUpdateItemForQuery:keyQuery( user )
withAttributes:@{
(__bridge id)kSecValueData : self.key.keyData,
(__bridge id)kSecValueData : [self.key keyDataForAlgorithm:user.algorithm],
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
#endif
@@ -62,7 +62,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery( user )];
if (result == noErr) {
inf( @"Removed key from keychain for: %@", user.userID );
inf( @"Removed key from keychain for user: %@", user.userID );
[[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self];
}
@@ -88,8 +88,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
// Method 1: When the user has no keyID set, set a new key from the given master password.
if (!user.keyID) {
if ([password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
user.keyID = tryKey.keyID;
if ([password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password])) {
user.keyID = [tryKey keyIDForAlgorithm:MPAlgorithmDefault];
// Migrate existing sites.
[self migrateSitesForUser:user saveInContext:moc toKey:tryKey];
@@ -103,9 +103,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
else if (!tryKey) {
// Key should be saved in keychain. Load it.
if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:tryKey.keyID]) {
if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
inf( @"Saved password doesn't match keyID for: %@", user.userID );
inf( @"Saved password doesn't match keyID for user: %@", user.userID );
trc( @"user keyID: %@ (version: %d) != authentication keyID: %@",
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
tryKey = nil;
[self forgetSavedKeyFor:user];
@@ -113,9 +115,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
}
// Method 3: Check the given master password string.
if (!tryKey && [password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name]) &&
![user.keyID isEqual:tryKey.keyID]) {
inf( @"Key derived from password doesn't match keyID for: %@", user.userID );
if (!tryKey && [password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password]) &&
![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
inf( @"Key derived from password doesn't match keyID for user: %@", user.userID );
trc( @"user keyID: %@ (version: %u) != authentication keyID: %@",
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
tryKey = nil;
}
@@ -123,13 +127,22 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
// No more methods left, fail if key still not known.
if (!tryKey) {
if (password)
inf( @"Login failed for: %@", user.userID );
inf( @"Password login failed for user: %@", user.userID );
else
dbg( @"Automatic login failed for user: %@", user.userID );
return NO;
}
inf( @"Logged in: %@", user.userID );
inf( @"Logged in user: %@", user.userID );
if (![self.key isEqualToKey:tryKey]) {
// Upgrade the user's keyID if not at the default version yet.
if (user.algorithm.version != MPAlgorithmDefaultVersion) {
user.algorithm = MPAlgorithmDefault;
user.keyID = [tryKey keyIDForAlgorithm:user.algorithm];
inf( @"Upgraded keyID to version %u for user: %@", user.algorithm.version, user.userID );
}
self.key = tryKey;
[self storeSavedKeyFor:user];
}
@@ -205,7 +218,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
// Don't Migrate
break;
recoverKey = [site.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
recoverKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:masterPassword];
}
if (!content)

View File

@@ -190,14 +190,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
[mainManagedObjectContext performBlock:^{
@try {
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@",[exception fullDescription] );
}
}];
[mainManagedObjectContext performBlock:^{
@try {
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@", [exception fullDescription] );
}
}];
} );
@@ -423,14 +423,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
}
MPSiteType type = activeUser.defaultType;
NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type];
id<MPAlgorithm> algorithm = MPAlgorithmDefault;
Class entityType = [algorithm classOfType:type];
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.name = siteName;
site.user = activeUser;
site.type = type;
site.lastUsed = [NSDate date];
site.version = MPAlgorithmDefaultVersion;
site.algorithm = algorithm;
NSError *error = nil;
if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] error:&error])
@@ -454,14 +455,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
else {
// Type requires a different class of site. Recreate the site.
NSString *typeEntityName = [site.algorithm classNameOfType:type];
MPSiteEntity *newSite = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
Class entityType = [site.algorithm classOfType:type];
MPSiteEntity *newSite = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
newSite.type = type;
newSite.name = site.name;
newSite.user = site.user;
newSite.uses = site.uses;
newSite.lastUsed = site.lastUsed;
newSite.version = site.version;
newSite.algorithm = site.algorithm;
newSite.loginName = site.loginName;
NSError *error = nil;
@@ -685,13 +686,13 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
inf( @"Import cancelled." );
return MPImportResultCancelled;
}
MPKey *userKey = [MPAlgorithmDefault keyForPassword:userMasterPassword ofUserNamed:user? user.name: importUserName];
if (user && ![userKey.keyID isEqualToData:user.keyID])
MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword];
if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID])
return MPImportResultInvalidPassword;
__block MPKey *importKey = userKey;
if (importKeyID && ![importKey.keyID isEqualToData:importKeyID])
importKey = [importAlgorithm keyForPassword:askImportPassword( importUserName ) ofUserNamed:importUserName];
if (importKeyID && ![importKey.keyID isEqualToData:importKeyID])
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )];
if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
return MPImportResultInvalidPassword;
// Delete existing sites.
@@ -710,7 +711,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
else {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = importUserName;
user.keyID = [userKey keyID];
user.algorithm = MPAlgorithmDefault;
user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created User: %@", [user debugDescription] );
@@ -728,19 +730,20 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSString *exportContent = siteElements[7];
// Create new site.
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
if (!typeEntityName) {
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version );
Class entityType = [algorithm classOfType:type];
if (!entityType) {
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
return MPImportResultInternalError;
}
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
site.name = siteName;
site.loginName = loginName;
site.user = user;
site.type = type;
site.uses = uses;
site.lastUsed = lastUsed;
site.version = version;
site.algorithm = algorithm;
if ([exportContent length]) {
if (clearText)
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
@@ -768,7 +771,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
MPUserEntity *activeUser = [self activeUserForMainThread];
inf( @"Exporting sites, %@, for: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
// Header.
NSMutableString *export = [NSMutableString new];
@@ -799,7 +802,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSDate *lastUsed = site.lastUsed;
NSUInteger uses = site.uses;
MPSiteType type = site.type;
NSUInteger version = site.version;
id<MPAlgorithm> algorithm = site.algorithm;
NSUInteger counter = 0;
NSString *loginName = site.loginName;
NSString *siteName = site.name;
@@ -818,10 +821,17 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
content = [site.algorithm exportPasswordForSite:site usingKey:self.key];
}
[export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
[strf( @"%lu:%lu:%lu", (long)type, (long)version, (long)counter ) UTF8String],
[(loginName?: @"") UTF8String], [siteName UTF8String], content?: @""];
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 unichar *)[typeExport cStringUsingEncoding:NSUTF16StringEncoding],
(const unichar *)[loginNameExport cStringUsingEncoding:NSUTF16StringEncoding],
(const unichar *)[siteName cStringUsingEncoding:NSUTF16StringEncoding],
contentExport];
}
return export;

View File

@@ -31,9 +31,8 @@
@property(readonly) NSString *typeClassName;
@property(readonly) Class typeClass;
@property(assign) NSUInteger uses;
@property(assign) NSUInteger version;
@property(assign) BOOL requiresExplicitMigration;
@property(readonly) id<MPAlgorithm> algorithm;
@property(strong) id<MPAlgorithm> algorithm;
- (NSUInteger)use;
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
@@ -56,6 +55,7 @@
@property(assign) BOOL saveKey;
@property(assign) MPSiteType defaultType;
@property(readonly) NSString *userID;
@property(strong) id<MPAlgorithm> algorithm;
+ (NSString *)idFor:(NSString *)userName;

View File

@@ -8,6 +8,7 @@
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Key.h"
@implementation NSManagedObjectContext(MP)
@@ -90,14 +91,14 @@
self.uses_ = @(anUses);
}
- (NSUInteger)version {
- (id<MPAlgorithm>)algorithm {
return [self.version_ unsignedIntegerValue];
return MPAlgorithmForVersion( MIN( MPAlgorithmVersionCurrent, MAX( MPAlgorithmVersion0, [self.version_ unsignedIntegerValue] ) ) );
}
- (void)setVersion:(NSUInteger)version {
- (void)setAlgorithm:(id<MPAlgorithm>)algorithm {
self.version_ = @(version);
self.version_ = @([algorithm version]);
}
- (BOOL)requiresExplicitMigration {
@@ -110,11 +111,6 @@
self.requiresExplicitMigration_ = @(requiresExplicitMigration);
}
- (id<MPAlgorithm>)algorithm {
return MPAlgorithmForVersion( self.version );
}
- (NSUInteger)use {
self.lastUsed = [NSDate date];
@@ -128,21 +124,31 @@
- (NSString *)debugDescription {
@try {
return strf( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed,
(long)self.version,
self.loginName, self.requiresExplicitMigration );
} @catch (NSException *exception) {
return strf( @"{%@: inaccessible: %@}",
NSStringFromClass( [self class] ), [exception fullDescription] );
}
__block NSString *debugDescription = strf( @"{%@: [recursing]}", [self class] );
static BOOL recursing = NO;
PearlIfNotRecursing( &recursing, ^{
@try {
debugDescription = strf(
@"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed,
(long)[self.algorithm version],
self.loginName, self.requiresExplicitMigration );
}
@catch (NSException *exception) {
debugDescription = strf( @"{%@: inaccessible: %@}",
NSStringFromClass( [self class] ), [exception fullDescription] );
}
} );
return debugDescription;
}
- (BOOL)tryMigrateExplicitly:(BOOL)explicit {
while (self.version < MPAlgorithmDefaultVersion) {
NSUInteger toVersion = self.version + 1;
MPAlgorithmVersion algorithmVersion;
while ((algorithmVersion = [self.algorithm version]) < MPAlgorithmDefaultVersion) {
NSUInteger toVersion = algorithmVersion + 1;
if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) {
wrn( @"%@ migration to version: %ld failed for site: %@",
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
@@ -287,6 +293,17 @@
self.defaultType_ = @(aDefaultType);
}
- (id<MPAlgorithm>)algorithm {
return MPAlgorithmForVersion( MIN( MPAlgorithmVersionCurrent, MAX( MPAlgorithmVersion0, [self.version_ unsignedIntegerValue] ) ) );
}
- (void)setAlgorithm:(id<MPAlgorithm>)version {
self.version_ = @([version version]);
[[MPAppDelegate_Shared get] forgetSavedKeyFor:self];
}
- (NSString *)userID {
return [MPUserEntity idFor:self.name];

View File

@@ -16,17 +16,21 @@
//
#import <Foundation/Foundation.h>
#import "MPAlgorithm.h"
@protocol MPAlgorithm;
@interface MPKey : NSObject
@property(nonatomic, readonly, strong) id<MPAlgorithm> algorithm;
@property(nonatomic, readonly, strong) NSData *keyData;
@property(nonatomic, readonly, strong) NSData *keyID;
@property(nonatomic, readonly) NSString *fullName;
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData forAlgorithm:(id<MPAlgorithm>)algorithm;
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength;
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm;
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength;
- (BOOL)isEqualToKey:(MPKey *)key;
@end

View File

@@ -1,12 +1,12 @@
/**
* 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
*/
* 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
*/
//
// MPKey
@@ -19,38 +19,61 @@
@interface MPKey()
@property(nonatomic, readwrite, strong) id<MPAlgorithm> algorithm;
@property(nonatomic, readwrite, strong) NSData *keyData;
@property(nonatomic, readwrite, strong) NSData *keyID;
@property(nonatomic) NSString *fullName;
@property(nonatomic) NSString *masterPassword;
@end
@implementation MPKey
@implementation MPKey {
NSCache *_keyCache;
};
@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID;
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm {
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
if (!(self = [super init]))
return nil;
self.keyData = keyData;
self.algorithm = algorithm;
self.keyID = [self.algorithm keyIDForKeyData:keyData];
_keyCache = [NSCache new];
self.fullName = fullName;
self.masterPassword = masterPassword;
return self;
}
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength {
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData forAlgorithm:(id<MPAlgorithm>)algorithm {
NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange( 0, MIN(subKeyLength, self.keyData.length) )];
if (!(self = [self initForFullName:fullName withMasterPassword:nil]))
return nil;
return [self.algorithm keyFromKeyData:subKeyData];
[_keyCache setObject:keyData forKey:algorithm];
return self;
}
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm {
return [algorithm keyIDForKeyData:[self keyDataForAlgorithm:algorithm]];
}
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm {
NSData *keyData = [_keyCache objectForKey:algorithm];
if (!keyData)
[_keyCache setObject:keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword]
forKey:algorithm];
return keyData;
}
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
NSData *keyData = [self keyDataForAlgorithm:algorithm];
return [keyData subdataWithRange:NSMakeRange( 0, MIN( subKeyLength, keyData.length ) )];
}
- (BOOL)isEqualToKey:(MPKey *)key {
return [self.keyID isEqualToData:key.keyID];
return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword];
}
- (BOOL)isEqual:(id)object {

View File

@@ -19,6 +19,7 @@
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSSet *sites;
@end

View File

@@ -18,6 +18,7 @@
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic version_;
@dynamic sites;
@end

View File

@@ -17,6 +17,7 @@
//
#import <QuartzCore/QuartzCore.h>
#import <Foundation/Foundation.h>
#import "MPPasswordWindowController.h"
#import "MPMacAppDelegate.h"
#import "MPAppDelegate_Store.h"
@@ -463,26 +464,26 @@
- (void)useSite {
MPSiteModel *selectedSite = [self selectedSite];
if (selectedSite) {
// Performing action while content is available. Copy it.
[self copyContent:selectedSite.content];
if (!selectedSite)
return;
[self fadeOut];
if (selectedSite.transient) {
[self createNewSite:selectedSite.name];
return;
}
NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied";
if (selectedSite.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
else
notification.subtitle = selectedSite.name;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
else {
NSString *siteName = [self.siteField stringValue];
if ([siteName length])
// Performing action without content but a site name is written.
[self createNewSite:siteName];
}
// Performing action while content is available. Copy it.
[self copyContent:selectedSite.content];
[self fadeOut];
NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied";
if (selectedSite.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
else
notification.subtitle = selectedSite.name;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
- (void)updateUser {
@@ -518,6 +519,7 @@
- (void)updateSites {
NSAssert( [NSOperationQueue currentQueue] == [NSOperationQueue mainQueue], @"updateSites should be called on the main queue." );
if (![MPMacAppDelegate get].key) {
self.sites = nil;
return;
@@ -530,13 +532,18 @@
} );
NSString *queryString = self.siteField.stringValue;
NSString *queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
NSString *queryPattern;
if ([queryString length] < 13)
queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
else
// If query is too long, a wildcard per character makes the CoreData fetch take excessively long.
queryPattern = strf( @"*%@*", queryString );
NSMutableArray *fuzzyGroups = [NSMutableArray new];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
}];
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
@@ -549,10 +556,22 @@
return;
}
BOOL exact = NO;
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
for (MPSiteEntity *site in siteResults)
for (MPSiteEntity *site in siteResults) {
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site fuzzyGroups:fuzzyGroups]];
self.sites = newSites;
exact |= [site.name isEqualToString:queryString];
}
if (!exact && [queryString length]) {
MPUserEntity *activeUser = [[MPAppDelegate_Shared get] activeUserInContext:context];
[newSites addObject:[[MPSiteModel alloc] initWithName:queryString forUser:activeUser]];
}
dbg( @"newSites: %@", newSites );
if (![newSites isEqualToArray:self.sites])
PearlMainQueue( ^{
self.sites = newSites;
} );
}];
}
@@ -605,6 +624,8 @@
CGDirectDisplayID displayID = [self.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
CGImageRef capturedImage = CGDisplayCreateImage( displayID );
if (!capturedImage || CGImageGetWidth( capturedImage ) <= 1) {
if (capturedImage)
CFRelease( capturedImage );
wrn( @"Failed to capture screen image for display: %d", displayID );
return;
}

View File

@@ -19,6 +19,7 @@
#import <Foundation/Foundation.h>
#import "MPSiteEntity.h"
#import "MPAlgorithm.h"
#import "MPUserEntity.h"
@class MPSiteEntity;
@@ -35,10 +36,12 @@
@property (nonatomic) NSUInteger counter;
@property (nonatomic) NSDate *lastUsed;
@property (nonatomic) id<MPAlgorithm> algorithm;
@property (nonatomic) BOOL generated;
@property (nonatomic) BOOL stored;
@property (nonatomic, readonly) BOOL generated;
@property (nonatomic, readonly) BOOL stored;
@property (nonatomic, readonly) BOOL transient;
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups;
- (instancetype)initWithName:(NSString *)siteName forUser:(MPUserEntity *)user;
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc;
- (void)updateContent;

View File

@@ -28,7 +28,7 @@
BOOL _initialized;
}
- (id)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
if (!(self = [super init]))
return nil;
@@ -39,6 +39,17 @@
return self;
}
- (instancetype)initWithName:(NSString *)siteName forUser:(MPUserEntity *)user {
if (!(self = [super init]))
return nil;
[self setTransientSiteName:siteName forUser:user];
_initialized = YES;
return self;
}
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
if ([_entityOID isEqual:entity.objectID])
@@ -59,7 +70,7 @@
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = NSCenterTextAlignment;
[attributedSiteName addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange( 0, [siteName length] )];
self.displayedName = attributedSiteName;
self.name = siteName;
self.algorithm = entity.algorithm;
@@ -73,6 +84,28 @@
[self updateContent:entity];
}
- (void)setTransientSiteName:(NSString *)siteName forUser:(MPUserEntity *)user {
_entityOID = nil;
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = NSCenterTextAlignment;
self.displayedName = stra( siteName, @{
NSBackgroundColorAttributeName : [NSColor alternateSelectedControlColor],
NSParagraphStyleAttributeName : paragraphStyle,
} );
self.name = siteName;
self.algorithm = MPAlgorithmDefault;
self.lastUsed = nil;
self.type = user.defaultType;
self.typeName = [self.algorithm nameOfType:self.type];
self.uses = @0;
self.counter = 1;
// Find all password types and the index of the current type amongst them.
[self updateContent];
}
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
if (!_entityOID)
@@ -96,15 +129,18 @@
// This wasn't a change to the entity.
return;
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *entity = [self entityInContext:context];
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
((MPGeneratedSiteEntity *)entity).counter = counter;
[context saveToStore];
if (_entityOID)
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *entity = [self entityInContext:context];
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
((MPGeneratedSiteEntity *)entity).counter = counter;
[context saveToStore];
[self updateContent:entity];
}
}];
[self updateContent:entity];
}
}];
else
[self updateContent];
}
- (BOOL)generated {
@@ -117,36 +153,60 @@
return self.type & MPSiteTypeClassStored;
}
- (BOOL)transient {
return _entityOID == nil;
}
- (void)updateContent {
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
}];
if (_entityOID)
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
}];
else
PearlNotMainQueue( ^{
NSString *password = [self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter
usingKey:[MPAppDelegate_Shared get].key];
NSString *loginName = [self.algorithm generateLoginForSiteNamed:self.name usingKey:[MPAppDelegate_Shared get].key];
[self updatePasswordWithResult:password];
[self updateLoginNameWithResult:loginName];
} );
}
- (void)updateContent:(MPSiteEntity *)entity {
[entity resolvePasswordUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
[self updatePasswordWithResult:result];
}];
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
[self updateLoginNameWithResult:result];
}];
}
- (void)updatePasswordWithResult:(NSString *)result {
static NSRegularExpression *re_anyChar;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
re_anyChar = [NSRegularExpression regularExpressionWithPattern:@"." options:0 error:nil];
} );
[entity resolvePasswordUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
NSString *displayResult = result;
if ([[MPConfig get].hidePasswords boolValue] && !([NSEvent modifierFlags] & NSAlternateKeyMask))
displayResult = [displayResult stringByReplacingMatchesOfExpression:re_anyChar withTemplate:@"●"];
NSString *displayResult = result;
if ([[MPConfig get].hidePasswords boolValue] && !([NSEvent modifierFlags] & NSAlternateKeyMask))
displayResult = [displayResult stringByReplacingMatchesOfExpression:re_anyChar withTemplate:@"●"];
PearlMainQueue( ^{
self.content = result;
self.displayedContent = displayResult;
} );
}];
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
PearlMainQueue( ^{
self.loginName = result;
} );
}];
PearlMainQueue( ^{
self.content = result;
self.displayedContent = displayResult;
} );
}
- (void)updateLoginNameWithResult:(NSString *)loginName {
PearlMainQueue( ^{
self.loginName = loginName;
} );
}
@end

View File

@@ -31,7 +31,6 @@
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
DA2508F119511D3600AC23F1 /* MPPasswordWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */; };
DA250925195148E200AC23F1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */; };
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */; };
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */; };
DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */; };
@@ -92,6 +91,7 @@
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 */; };
DA67744A1A47C8F7004F356A /* mpw-tests-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6774481A47C8F7004F356A /* mpw-tests-util.c */; };
DA8495301A915EF400B3053D /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */; };
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 */; };
@@ -294,11 +294,6 @@
DA25090719513C1400AC23F1 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
DA2509261951B86C00AC23F1 /* small-screen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "small-screen.png"; sourceTree = "<group>"; };
DA2509271951B86C00AC23F1 /* screen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = screen.png; sourceTree = "<group>"; };
DA29992719C6A89900AF7DF1 /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = "<group>"; };
DA29992819C6A89900AF7DF1 /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = "<group>"; };
DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 3.xcdatamodel"; sourceTree = "<group>"; };
DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = "<group>"; };
DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 5.xcdatamodel"; sourceTree = "<group>"; };
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = "<group>"; };
@@ -321,7 +316,6 @@
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>"; };
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; 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>"; };
@@ -796,6 +790,13 @@
DA831A281A6E1146000AC234 /* mpw-algorithm_v1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v1.c"; sourceTree = "<group>"; };
DA831A291A6E1146000AC234 /* mpw-algorithm_v2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v2.c"; sourceTree = "<group>"; };
DA831A2A1A6E1146000AC234 /* mpw-algorithm_v3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v3.c"; sourceTree = "<group>"; };
DA8495291A915EF400B3053D /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = "<group>"; };
DA84952A1A915EF400B3053D /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = "<group>"; };
DA84952B1A915EF400B3053D /* MasterPassword 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 3.xcdatamodel"; sourceTree = "<group>"; };
DA84952C1A915EF400B3053D /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = "<group>"; };
DA84952D1A915EF400B3053D /* MasterPassword 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 5.xcdatamodel"; sourceTree = "<group>"; };
DA84952E1A915EF400B3053D /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; sourceTree = "<group>"; };
DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 7.xcdatamodel"; sourceTree = "<group>"; };
DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Pearl-Cocoa.h"; sourceTree = "<group>"; };
DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Cocoa.m"; sourceTree = "<group>"; };
DA8ED891192906920099B726 /* PearlTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlTween.m; sourceTree = "<group>"; };
@@ -1060,8 +1061,8 @@
DA5E5C961724A667003798D8 /* ObjC */ = {
isa = PBXGroup;
children = (
DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */,
DA5E5CB21724A667003798D8 /* Mac */,
DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */,
DA5E5C971724A667003798D8 /* MPAlgorithm.h */,
DA5E5C981724A667003798D8 /* MPAlgorithm.m */,
DA5E5C991724A667003798D8 /* MPAlgorithmV0.h */,
@@ -2245,7 +2246,6 @@
DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */,
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */,
DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */,
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */,
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */,
DA5E5D011724A667003798D8 /* MPKey.m in Sources */,
@@ -2262,6 +2262,7 @@
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */,
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */,
DA8495301A915EF400B3053D /* MasterPassword.xcdatamodeld in Sources */,
DA6774291A4746AF004F356A /* mpw-algorithm.c in Sources */,
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */,
DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */,
@@ -2943,17 +2944,18 @@
/* End XCConfigurationList section */
/* Begin XCVersionGroup section */
DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */ = {
DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DA29992719C6A89900AF7DF1 /* MasterPassword 1.xcdatamodel */,
DA29992819C6A89900AF7DF1 /* MasterPassword 2.xcdatamodel */,
DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */,
DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */,
DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */,
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */,
DA8495291A915EF400B3053D /* MasterPassword 1.xcdatamodel */,
DA84952A1A915EF400B3053D /* MasterPassword 2.xcdatamodel */,
DA84952B1A915EF400B3053D /* MasterPassword 3.xcdatamodel */,
DA84952C1A915EF400B3053D /* MasterPassword 4.xcdatamodel */,
DA84952D1A915EF400B3053D /* MasterPassword 5.xcdatamodel */,
DA84952E1A915EF400B3053D /* MasterPassword 6.xcdatamodel */,
DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */,
);
currentVersion = DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */;
currentVersion = DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */;
path = MasterPassword.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MasterPassword 6.xcdatamodel</string>
<string>MasterPassword 7.xcdatamodel</string>
</dict>
</plist>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C109" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPGeneratedSiteEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPSiteEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C109" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPGeneratedSiteEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPSiteEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPSiteEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="loginGenerated_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
<userInfo/>
</attribute>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
<relationship name="questions" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MPSiteQuestionEntity" inverseName="site" inverseEntity="MPSiteQuestionEntity" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="sites" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
</entity>
<entity name="MPSiteQuestionEntity" representedClassName="MPSiteQuestionEntity" syncable="YES">
<attribute name="keyword" attributeType="String" syncable="YES"/>
<relationship name="site" maxCount="1" deletionRule="Nullify" destinationEntity="MPSiteEntity" inverseName="questions" inverseEntity="MPSiteEntity" syncable="YES"/>
</entity>
<entity name="MPStoredSiteEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPSiteEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
<userInfo/>
</attribute>
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="2" syncable="YES"/>
<relationship name="sites" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPSiteEntity" inverseName="user" inverseEntity="MPSiteEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
</entity>
<elements>
<element name="MPGeneratedSiteEntity" positionX="216" positionY="-288" width="128" height="58"/>
<element name="MPSiteEntity" positionX="-0" positionY="-286" width="128" height="208"/>
<element name="MPSiteQuestionEntity" positionX="-2" positionY="-9" width="128" height="73"/>
<element name="MPStoredSiteEntity" positionX="214" positionY="-171" width="128" height="58"/>
<element name="MPUserEntity" positionX="-218" positionY="-288" width="128" height="165"/>
</elements>
</model>

View File

@@ -22,7 +22,7 @@
@property(weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property(weak, nonatomic) IBOutlet UIView *dialogView;
@property(weak, nonatomic) IBOutlet UIView *containerView;
@property(weak, nonatomic) IBOutlet UITextField *userNameField;
@property(weak, nonatomic) IBOutlet UITextField *fullNameField;
@property(weak, nonatomic) IBOutlet UITextField *masterPasswordField;
@property(weak, nonatomic) IBOutlet UITextField *siteField;
@property(weak, nonatomic) IBOutlet UIStepper *counterStepper;

View File

@@ -75,7 +75,7 @@
- (IBAction)controlChanged:(UIControl *)control {
if (control == self.userNameField || control == self.masterPasswordField)
if (control == self.fullNameField || control == self.masterPasswordField)
[self updateKey];
else
[self updatePassword];
@@ -103,15 +103,15 @@
- (void)updateKey {
NSString *userName = self.userNameField.text;
NSString *fullName = self.fullNameField.text;
NSString *masterPassword = self.masterPasswordField.text;
self.passwordLabel.text = nil;
[self.activity startAnimating];
[_emergencyKeyQueue cancelAllOperations];
[_emergencyKeyQueue addOperationWithBlock:^{
if ([masterPassword length] && [userName length])
_key = [MPAlgorithmDefault keyForPassword:masterPassword ofUserNamed:userName];
if ([masterPassword length] && [fullName length])
_key = [[MPKey alloc] initForFullName:fullName withMasterPassword:masterPassword];
else
_key = nil;
@@ -165,7 +165,7 @@
- (void)reset {
self.userNameField.text = nil;
self.fullNameField.text = nil;
self.masterPasswordField.text = nil;
self.siteField.text = nil;
self.counterStepper.value = 1;

View File

@@ -0,0 +1,25 @@
//
// MPPreferencesViewController.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MPMessage : NSObject
@property(nonatomic) NSString *title;
@property(nonatomic) NSString *text;
@property(nonatomic) BOOL info;
+ (instancetype)messageWithTitle:(NSString *)title text:(NSString *)text info:(BOOL)info;
@end
@interface MPMessageViewController : UIViewController
@property (nonatomic) MPMessage *message;
@end

View File

@@ -0,0 +1,78 @@
//
// MPPreferencesViewController.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPMessageViewController.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPOverlayViewController.h"
@interface MPMessageViewController()
@property(nonatomic) IBOutlet UILabel *titleLabel;
@property(nonatomic) IBOutlet UILabel *messageLabel;
@property(nonatomic) IBOutlet UIView *infoView;
@end
@implementation MPMessage
+ (instancetype)messageWithTitle:(NSString *)title text:(NSString *)text info:(BOOL)info {
MPMessage *message = [MPMessage new];
message.title = title;
message.text = text;
message.info = info;
return message;
}
@end
@implementation MPMessageViewController
#pragma mark - Life
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.titleLabel.text = self.message.title;
self.messageLabel.text = self.message.text;
self.infoView.gone = !self.message.info;
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
^(MPMessageViewController *self, NSNotification *note) {
if (![note.userInfo[@"animated"] boolValue])
[UIView setAnimationsEnabled:NO];
[[MPOverlaySegue dismissViewController:self] perform];
[UIView setAnimationsEnabled:YES];
} );
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
PearlRemoveNotificationObservers();
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
#pragma mark - State
@end

View File

@@ -59,6 +59,8 @@
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doRevealPassword: )]];
[self.counterButton addGestureRecognizer:
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doResetCounter: )]];
[self.upgradeButton addGestureRecognizer:
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doDowngrade: )]];
[self setupLayer];
@@ -331,13 +333,36 @@
- (IBAction)doUpgrade:(UIButton *)sender {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if (![[self siteInContext:context] tryMigrateExplicitly:YES]) {
MPSiteEntity *siteEntity = [self siteInContext:context];
if (![siteEntity tryMigrateExplicitly:YES]) {
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
return;
}
[context saveToStore];
[PearlOverlay showTemporaryOverlayWithTitle:@"Site Upgraded" dismissAfter:2];
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Upgraded to V%d", siteEntity.algorithm.version )
dismissAfter:2];
[self updateAnimated:YES];
}];
}
- (IBAction)doDowngrade:(UILongPressGestureRecognizer *)recognizer {
if (recognizer.state != UIGestureRecognizerStateBegan)
return;
if (![[MPiOSConfig get].allowDowngrade boolValue])
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *siteEntity = [self siteInContext:context];
if (siteEntity.algorithm.version <= 0)
return;
siteEntity.algorithm = MPAlgorithmForVersion( siteEntity.algorithm.version - 1 );
[context saveToStore];
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Downgraded to V%d", siteEntity.algorithm.version )
dismissAfter:2];
[self updateAnimated:YES];
}];
}
@@ -479,7 +504,7 @@
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
// UI
self.upgradeButton.gone = !mainSite.requiresExplicitMigration;
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0;
@@ -533,15 +558,15 @@
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
BOOL requiresExplicitMigration = site.requiresExplicitMigration;
PearlMainQueue( ^{
self.loginNameField.text = loginName;
self.passwordField.text = password;
self.strengthLabel.text = timeToCrackString;
self.loginNameButton.titleLabel.alpha = [loginName length] || self.loginNameField.enabled? 0: 1;
if ([password length])
self.indicatorView.alpha = 0;
else {
if (![password length]) {
self.indicatorView.alpha = 1;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
@@ -551,6 +576,18 @@
@"target" : settingsMode? self.editButton: self.modeButton
}];
}
else if (requiresExplicitMigration) {
self.indicatorView.alpha = 1;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
metrics:nil views:@{
@"indicator" : self.indicatorView,
@"target" : settingsMode? self.upgradeButton: self.modeButton
}];
}
else
self.indicatorView.alpha = 0;
} );
}];

View File

@@ -23,6 +23,7 @@
#import "MPAppDelegate_Key.h"
#import "MPPasswordCell.h"
#import "MPAnswersViewController.h"
#import "MPMessageViewController.h"
typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
MPPasswordsBadNameTip = 1 << 0,
@@ -90,7 +91,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
if (![MPAlgorithmDefault tryMigrateUser:activeUser inContext:context])
[PearlOverlay showTemporaryOverlayWithTitle:@"Some Sites Need Upgrade" dismissAfter:2];
[self performSegueWithIdentifier:@"message" sender:
[MPMessage messageWithTitle:@"You have sites that can be upgraded." text:
@"Upgrading a site allows it to take advantage of the latest improvements in the Master Password algorithm.\n\n"
"When you upgrade a site, a new and stronger password will be generated for it. To upgrade a site, first log into the site, navigate to your account preferences where you can change the site's password. Make sure you fill in any \"current password\" fields on the website first, then press the upgrade button here to get your new site password.\n\n"
"You can then update your site's account with the new and stronger password.\n\n"
"The upgrade button can be found in the site's settings and looks like this:"
info:YES]];
[context saveToStore];
}];
}
@@ -109,6 +116,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
if ([segue.identifier isEqualToString:@"answers"])
((MPAnswersViewController *)segue.destinationViewController).site =
[[MPPasswordCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
if ([segue.identifier isEqualToString:@"message"])
((MPMessageViewController *)segue.destinationViewController).message = sender;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
@@ -383,7 +392,12 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
} );
NSString *queryString = self.query;
NSString *queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
NSString *queryPattern;
if ([queryString length] < 13)
queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
else
// If query is too long, a wildcard per character makes the CoreData fetch take excessively long.
queryPattern = strf( @"*%@*", queryString );
NSMutableArray *fuzzyGroups = [NSMutableArray arrayWithCapacity:[queryString length]];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {

View File

@@ -214,6 +214,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
MPUserEntity *user = [self userForAvatar:avatarCell inContext:context isNew:&isNew];
if (isNew) {
user = [MPUserEntity insertNewObjectInContext:context];
user.algorithm = MPAlgorithmDefault;
user.avatar = avatarCell.avatar;
user.name = avatarCell.name;
}

View File

@@ -73,9 +73,9 @@
PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue], ^(id self, NSNotification *note) {
[self updateConfigKey:note.object];
} );
PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
} );
// PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) {
// [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
// } );
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, nil, ^(id self, NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
} );
@@ -496,7 +496,7 @@
return;
[moc performBlockAndWait:^{
inf( @"Unsetting master password for: %@.", user.userID );
inf( @"Clearing keyID for user: %@.", user.userID );
user.keyID = nil;
[self forgetSavedKeyFor:user];
[moc saveToStore];

View File

@@ -18,6 +18,7 @@
@property(nonatomic, retain) NSNumber *loginNameTipShown;
@property(nonatomic, retain) NSNumber *traceMode;
@property(nonatomic, retain) NSNumber *dictationSearch;
@property(nonatomic, retain) NSNumber *allowDowngrade;
@property(nonatomic, retain) NSNumber *developmentFuelRemaining;
@property(nonatomic, retain) NSNumber *developmentFuelInvested;
@property(nonatomic, retain) NSNumber *developmentFuelConsumption;

View File

@@ -8,7 +8,7 @@
@implementation MPiOSConfig
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch;
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch, allowDowngrade;
@dynamic developmentFuelRemaining, developmentFuelInvested, developmentFuelConsumption, developmentFuelChecked;
- (id)init {
@@ -26,6 +26,7 @@
NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO,
NSStringFromSelector( @selector( traceMode ) ) : @NO,
NSStringFromSelector( @selector( dictationSearch ) ) : @NO,
NSStringFromSelector( @selector( allowDowngrade ) ) : @NO,
}];
return self;

View File

@@ -22,6 +22,7 @@
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */; };
93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */; };
93D394B5036C882B33C71872 /* MPPasswordsSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.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 */; };
93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */; };
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; };
@@ -179,6 +180,8 @@
DA7304E5194E025900E72520 /* tip_basic_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38901711E29700CF925C /* tip_basic_black.png */; };
DA7304E6194E025900E72520 /* tip_basic_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38911711E29700CF925C /* tip_basic_black@2x.png */; };
DA7304E7194E027C00E72520 /* Square-bottom.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5C3C1723681B003798D8 /* Square-bottom.png */; };
DA8495311A93049300B3053D /* icon_down.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD375C1711E29500CF925C /* icon_down.png */; };
DA8495321A93049300B3053D /* icon_down@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD375D1711E29500CF925C /* icon_down@2x.png */; };
DA854C8318D4CFBF00106317 /* avatar-add@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8118D4CFBF00106317 /* avatar-add@2x.png */; };
DA854C8418D4CFBF00106317 /* avatar-add.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8218D4CFBF00106317 /* avatar-add.png */; };
DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA945C8617E3F3FD0053236B /* Images.xcassets */; };
@@ -515,6 +518,7 @@
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.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>"; };
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>"; };
@@ -540,6 +544,7 @@
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>"; };
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>"; };
93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEmergencyViewController.h; sourceTree = "<group>"; };
93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_InApp.h; sourceTree = "<group>"; };
@@ -669,6 +674,7 @@
DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-sim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios-sim.a"; sourceTree = "<group>"; };
DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopensslcrypto-ios-dev.a"; sourceTree = "<group>"; };
DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 7.xcdatamodel"; sourceTree = "<group>"; };
DA854C8118D4CFBF00106317 /* avatar-add@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add@2x.png"; sourceTree = "<group>"; };
DA854C8218D4CFBF00106317 /* avatar-add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add.png"; sourceTree = "<group>"; };
DA945C8617E3F3FD0053236B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
@@ -1745,6 +1751,8 @@
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */,
93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */,
93D393CB0B1F4EC8C17CFE43 /* NSString+MPMarkDown.h */,
93D399B36CDB2004D7C51391 /* MPMessageViewController.m */,
93D39CB0EABD2748740992D8 /* MPMessageViewController.h */,
);
sourceTree = "<group>";
};
@@ -2606,55 +2614,55 @@
DABD3BD71711E2DC00CF925C /* iOS */ = {
isa = PBXGroup;
children = (
DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */,
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */,
93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */,
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */,
93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */,
DABD3BD81711E2DC00CF925C /* MPiOSAppDelegate.h */,
DABD3BD91711E2DC00CF925C /* MPiOSAppDelegate.m */,
DABD3BE61711E2DC00CF925C /* MPGuideViewController.h */,
DABD3BE71711E2DC00CF925C /* MPGuideViewController.m */,
DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */,
DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */,
DABD3BEC1711E2DC00CF925C /* MPTypeViewController.h */,
DABD3BED1711E2DC00CF925C /* MPTypeViewController.m */,
DABD3BF01711E2DC00CF925C /* MPiOSConfig.h */,
DABD3BF11711E2DC00CF925C /* MPiOSConfig.m */,
DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */,
DABD3BFC1711E2DC00CF925C /* main.m */,
DABD3BF31711E2DC00CF925C /* MasterPassword-Info.plist */,
DABD3BF41711E2DC00CF925C /* MasterPassword-Prefix.pch */,
DABD3BF81711E2DC00CF925C /* MasterPassword.entitlements */,
DABD3BF91711E2DC00CF925C /* Settings.bundle */,
DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */,
DABD3BFC1711E2DC00CF925C /* main.m */,
93D39A28369954D147E239BA /* MPSetupViewController.m */,
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */,
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */,
93D391943675426839501BB8 /* MPLogsViewController.h */,
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */,
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */,
93D393310223DDB35218467A /* MPCombinedViewController.m */,
93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */,
93D39B381350802A194BF332 /* MPAvatarCell.m */,
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */,
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */,
93D3971FE104BB4052484151 /* MPUsersViewController.h */,
93D39BAA71DE51B4D8A1286C /* MPCell.m */,
93D390519405B76CC6A57C4F /* MPCell.h */,
93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */,
93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */,
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */,
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */,
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */,
93D39C86E984EC65DA5ACB1D /* MPAppSettingsViewController.h */,
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */,
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */,
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */,
93D39B381350802A194BF332 /* MPAvatarCell.m */,
93D390519405B76CC6A57C4F /* MPCell.h */,
93D39BAA71DE51B4D8A1286C /* MPCell.m */,
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */,
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */,
93D39F556F2F142740A65E59 /* MPWebViewController.h */,
93D39CC01630D0421205C4C4 /* MPNavigationController.m */,
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */,
93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */,
93D393310223DDB35218467A /* MPCombinedViewController.m */,
93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */,
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */,
DABD3BE61711E2DC00CF925C /* MPGuideViewController.h */,
DABD3BE71711E2DC00CF925C /* MPGuideViewController.m */,
DABD3BD81711E2DC00CF925C /* MPiOSAppDelegate.h */,
DABD3BD91711E2DC00CF925C /* MPiOSAppDelegate.m */,
DABD3BF01711E2DC00CF925C /* MPiOSConfig.h */,
DABD3BF11711E2DC00CF925C /* MPiOSConfig.m */,
93D391943675426839501BB8 /* MPLogsViewController.h */,
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */,
93D3970502644794E8A027BE /* MPNavigationController.h */,
93D395105935859D71679931 /* MPOverlayViewController.m */,
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 */,
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */,
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */,
DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */,
DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */,
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */,
93D39A28369954D147E239BA /* MPSetupViewController.m */,
DABD3BEC1711E2DC00CF925C /* MPTypeViewController.h */,
DABD3BED1711E2DC00CF925C /* MPTypeViewController.m */,
93D3971FE104BB4052484151 /* MPUsersViewController.h */,
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */,
93D39F556F2F142740A65E59 /* MPWebViewController.h */,
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */,
DABD3BF91711E2DC00CF925C /* Settings.bundle */,
DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */,
);
path = iOS;
sourceTree = "<group>";
@@ -3503,6 +3511,7 @@
DABD3B961711E29800CF925C /* pull-down@2x.png in Resources */,
DABD3B971711E29800CF925C /* pull-up.png in Resources */,
DA24EBEA19DAD6EE00FF010B /* Icon-Small.png in Resources */,
DA8495321A93049300B3053D /* icon_down@2x.png in Resources */,
DABD3B981711E29800CF925C /* pull-up@2x.png in Resources */,
DA7304A0194E022B00E72520 /* ui_textfield@2x.png in Resources */,
DAA1765119D8B82B0044227B /* copy_pw@2x.png in Resources */,
@@ -3526,6 +3535,7 @@
DABD3FCF1714F45C00CF925C /* identity@2x.png in Resources */,
DAA1764619D8B82B0044227B /* name_new.png in Resources */,
DA45224B190628B2008F650A /* icon_gear.png in Resources */,
DA8495311A93049300B3053D /* icon_down.png in Resources */,
DA25C5FF197DBF200046CDCF /* icon_thumbs-up@2x.png in Resources */,
DAE1EF2217E942DE00BC0086 /* Localizable.strings in Resources */,
DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */,
@@ -3656,6 +3666,7 @@
93D39943D01E70DAC3B0DF76 /* mpw-util.c in Sources */,
93D39577FD8BB0945DB2F0A3 /* MPAlgorithmV3.m in Sources */,
93D39E5F7F6D7F5C0FAD090F /* MPTypes.m in Sources */,
93D39508A6814612A5B3C226 /* MPMessageViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -4423,6 +4434,7 @@
DA32D00119CF4735004F3F0E /* MasterPassword.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */,
DA32D00219CF4735004F3F0E /* MasterPassword 1.xcdatamodel */,
DA32D00319CF4735004F3F0E /* MasterPassword 2.xcdatamodel */,
DA32D00419CF4735004F3F0E /* MasterPassword 3.xcdatamodel */,
@@ -4430,7 +4442,7 @@
DA32D00619CF4735004F3F0E /* MasterPassword 5.xcdatamodel */,
DA32D00719CF4735004F3F0E /* MasterPassword 6.xcdatamodel */,
);
currentVersion = DA32D00719CF4735004F3F0E /* MasterPassword 6.xcdatamodel */;
currentVersion = DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */;
path = MasterPassword.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@@ -147,6 +147,24 @@ To see a site&apos;s password anyway, tap and hold your finger down for a while
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>When site downgrades are enabled, a long tap on the upgrade button will downgrade the site instead. This is useful if you accidentally upgraded a site and need to downgrade it again temporarily to see your old password.</string>
<key>Title</key>
<string></string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>DefaultValue</key>
<false/>
<key>Key</key>
<string>allowDowngrade</string>
<key>Title</key>
<string>Allow Downgrade</string>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
</dict>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="Q1S-vU-GGO">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6254" systemVersion="14C109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="Q1S-vU-GGO">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
<capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="Unknown constraint types" minToolsVersion="5.1"/>
</dependencies>
@@ -31,6 +32,7 @@
<string>Exo2.0-Bold</string>
<string>Exo2.0-Bold</string>
<string>Exo2.0-Bold</string>
<string>Exo2.0-Bold</string>
</mutableArray>
<mutableArray key="Exo2.0-ExtraBold.otf">
<string>Exo2.0-ExtraBold</string>
@@ -182,10 +184,10 @@
<constraint firstAttribute="width" secondItem="Aca-he-7Qi" secondAttribute="height" multiplier="1:1" id="a8Q-UO-SH0"/>
</constraints>
</imageView>
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Sa-Vg-EEI" userLabel="Name Backdrop">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0Sa-Vg-EEI" userLabel="Name Backdrop">
<rect key="frame" x="43" y="263" width="128.5" height="16"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalCompressionResistancePriority="1000" misplaced="YES" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cLT-s0-4SQ" userLabel="Name Field">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalCompressionResistancePriority="1000" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cLT-s0-4SQ" userLabel="Name Field">
<rect key="frame" x="5" y="0.0" width="118.5" height="16"/>
<fontDescription key="fontDescription" name="Exo2.0-ExtraBold" family="Exo 2.0" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -249,7 +251,7 @@
<outlet property="delegate" destination="S8q-YF-Kt9" id="det-Eh-phM"/>
</connections>
</collectionView>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<rect key="frame" x="0.0" y="244" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="Ay6-Jg-c3T"/>
@@ -262,7 +264,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="lNu-mK-3zD"/>
</connections>
</button>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<rect key="frame" x="331" y="244" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="oAm-YX-Fx5"/>
@@ -297,20 +299,20 @@
<outlet property="delegate" destination="S8q-YF-Kt9" id="5u3-XN-LOe"/>
</connections>
</textField>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fdS-zb-K9I" userLabel="Entry Tip">
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fdS-zb-K9I" userLabel="Entry Tip">
<rect key="frame" x="71" y="-33" width="234.5" height="82.5"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="g2g-5i-er4">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="g2g-5i-er4">
<rect key="frame" x="0.0" y="0.0" width="234.5" height="82.5"/>
<rect key="contentStretch" x="0.15000000000000002" y="0.14999999999999999" width="0.69999999999999973" height="0.44999999999999996"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Looks like a typo!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="ZI7-qg-7OW">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Looks like a typo!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="ZI7-qg-7OW">
<rect key="frame" x="20" y="12" width="194.5" height="17"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Try again; the password was wrong." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KhE-Yj-Kvm">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Try again; the password was wrong." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KhE-Yj-Kvm">
<rect key="frame" x="20" y="37" width="194.5" height="14.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="12"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
@@ -369,14 +371,14 @@
<segue destination="Sd5-eW-Cx2" kind="modal" identifier="web" id="gtb-zE-u9H"/>
</connections>
</button>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="069-Pu-yXe" userLabel="Thanks Tip">
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="069-Pu-yXe" userLabel="Thanks Tip">
<rect key="frame" x="72" y="0.0" width="232.5" height="60"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z8P-ZK-aS0">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z8P-ZK-aS0">
<rect key="frame" x="0.0" y="0.0" width="232.5" height="60"/>
<rect key="contentStretch" x="0.15000000000000002" y="0.0" width="0.69999999999999973" height="1"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Why is Master Password free?" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="BLV-3x-Q0z">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Why is Master Password free?" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="BLV-3x-Q0z">
<rect key="frame" x="20" y="12" width="192.5" height="17"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
@@ -407,14 +409,14 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cF4-TE-GEj" userLabel="Avatar Tip">
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cF4-TE-GEj" userLabel="Avatar Tip">
<rect key="frame" x="49.5" y="184" width="276" height="60"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="V4W-bK-age">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="V4W-bK-age">
<rect key="frame" x="0.0" y="0.0" width="276" height="60"/>
<rect key="contentStretch" x="0.15000000000000002" y="0.0" width="0.69999999999999973" height="1"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Change your avatar using the arrows." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="MoM-8d-jlm">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Change your avatar using the arrows." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="MoM-8d-jlm">
<rect key="frame" x="20" y="12" width="236" height="17"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
@@ -432,14 +434,14 @@
<constraint firstAttribute="trailing" secondItem="MoM-8d-jlm" secondAttribute="trailing" constant="20" id="Hc3-Mb-x5c"/>
</constraints>
</view>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Um-Ot-hI6" userLabel="Preferences Tip">
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0Um-Ot-hI6" userLabel="Preferences Tip">
<rect key="frame" x="72" y="42" width="230" height="60"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="5H0-ml-Uso">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="5H0-ml-Uso">
<rect key="frame" x="0.0" y="0.0" width="230" height="60"/>
<rect key="contentStretch" x="0.15000000000000002" y="0.0" width="0.69999999999999973" height="1"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Tap for preferences and more." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Er5-X1-ejQ">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Tap for preferences and more." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Er5-X1-ejQ">
<rect key="frame" x="20" y="31.5" width="190" height="17"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
@@ -847,7 +849,7 @@
<constraint firstAttribute="height" constant="110" id="zBf-EA-iDN"/>
</constraints>
</imageView>
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DzC-Ts-gew" userLabel="Previous Avatar">
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DzC-Ts-gew" userLabel="Previous Avatar">
<rect key="frame" x="20" y="117" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="1Wu-gS-flK"/>
@@ -861,7 +863,7 @@
<action selector="previousAvatar:" destination="JFc-sj-awD" eventType="touchUpInside" id="D92-6I-zFd"/>
</connections>
</button>
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yAf-fc-SKl" userLabel="Next Avatar">
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yAf-fc-SKl" userLabel="Next Avatar">
<rect key="frame" x="311" y="117" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="pEf-Vp-D7s"/>
@@ -1040,13 +1042,13 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="152"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sPw-mV-mFF">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sPw-mV-mFF">
<rect key="frame" x="20" y="4" width="335" height="12"/>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="10"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rl7-cr-FHf">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rl7-cr-FHf">
<rect key="frame" x="20" y="24" width="335" height="26"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
<state key="normal" title="Home Page">
@@ -1056,7 +1058,7 @@
<action selector="homePageButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="ptD-cv-NMr"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="epW-Rm-9St">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="epW-Rm-9St">
<rect key="frame" x="20" y="58" width="335" height="26"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
<state key="normal" title="Understanding Master Password's Security">
@@ -1066,7 +1068,7 @@
<action selector="securityButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="Efv-cp-Xfh"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LTN-ch-h8D">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LTN-ch-h8D">
<rect key="frame" x="20" y="92" width="335" height="26"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
<state key="normal" title="Get the Master Password source code">
@@ -1076,7 +1078,7 @@
<action selector="sourceButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="Y3O-di-CZo"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Z60-lc-Nka">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Z60-lc-Nka">
<rect key="frame" x="20" y="126" width="335" height="26"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
<state key="normal" title="Send Thanks">
@@ -1158,7 +1160,7 @@
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<inset key="scrollIndicatorInsets" minX="0.0" minY="108" maxX="0.0" maxY="0.0"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="Mv1-29-TWx">
<size key="itemSize" width="300" height="100"/>
<size key="itemSize" width="355" height="100"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="10" minY="118" maxX="10" maxY="10"/>
@@ -1168,34 +1170,34 @@
<rect key="frame" x="10" y="118" width="300" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xph-TW-9QO" userLabel="Content">
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" pagingEnabled="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bff-RU-OcY">
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aDw-qY-VjU" userLabel="Copy Content">
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
<color key="backgroundColor" red="0.18431372549019609" green="0.15686274509803921" blue="0.15686274509803921" alpha="0.5" colorSpace="calibratedRGB"/>
<connections>
<action selector="doContent:" destination="W2g-yv-V3V" eventType="touchUpInside" id="ukg-D8-8O3"/>
</connections>
</button>
<view alpha="0.59999999999999998" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="w2g-zN-1wZ" userLabel="Login Container">
<rect key="frame" x="0.0" y="0.0" width="300" height="21"/>
<rect key="frame" x="0.0" y="0.0" width="355" height="21"/>
<subviews>
<view alpha="0.80000000000000004" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="q3g-CJ-LbN" userLabel="Separator">
<rect key="frame" x="0.0" y="20" width="300" height="1"/>
<rect key="frame" x="0.0" y="20" width="355" height="1"/>
<color key="backgroundColor" red="0.14901960784313725" green="0.14901960784313725" blue="0.14901960784313725" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="jyk-dC-QLb"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Iwe-rQ-ma0" userLabel="Copy Login">
<rect key="frame" x="0.0" y="0.0" width="300" height="33"/>
<rect key="frame" x="0.0" y="0.0" width="355" height="33"/>
<color key="backgroundColor" red="0.18431372549019609" green="0.15686274509803921" blue="0.15686274509803921" alpha="0.01" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
<inset key="titleEdgeInsets" minX="0.0" minY="-4" maxX="0.0" maxY="0.0"/>
@@ -1207,7 +1209,7 @@
</connections>
</button>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="3I9-vf-IZK" userLabel="Login">
<rect key="frame" x="8" y="0.0" width="284" height="20"/>
<rect key="frame" x="8" y="0.0" width="339" height="20"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="9gA-Ti-g1e"/>
@@ -1235,11 +1237,11 @@
<constraint firstItem="Iwe-rQ-ma0" firstAttribute="top" secondItem="w2g-zN-1wZ" secondAttribute="top" id="q2j-Aa-lEd"/>
</constraints>
</view>
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2tX-WK-ASq" userLabel="Password Container">
<rect key="frame" x="0.0" y="20" width="300" height="43"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2tX-WK-ASq" userLabel="Password Container">
<rect key="frame" x="0.0" y="20" width="355" height="43"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="CuzaSasy3*Rimo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="blw-Ou-8I8" userLabel="Password">
<rect key="frame" x="8" y="0.0" width="284" height="31"/>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="CuzaSasy3*Rimo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="blw-Ou-8I8" userLabel="Password">
<rect key="frame" x="8" y="0.0" width="339" height="31"/>
<color key="textColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="24"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="alphabet" keyboardAppearance="alert" returnKeyType="next"/>
@@ -1248,8 +1250,8 @@
<outlet property="delegate" destination="W2g-yv-V3V" id="YKp-IE-zEQ"/>
</connections>
</textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="3" contentMode="left" misplaced="YES" text="&gt; age of the universe" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="wfM-xz-roR" userLabel="Strength">
<rect key="frame" x="0.0" y="31" width="300" height="12"/>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="3" contentMode="left" text="&gt; age of the universe" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="wfM-xz-roR" userLabel="Strength">
<rect key="frame" x="0.0" y="31" width="355" height="12"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="10"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
@@ -1271,10 +1273,10 @@
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tl3-hd-x35" userLabel="Main Container">
<rect key="frame" x="0.0" y="0.0" width="300" height="100"/>
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.20000000000000001" contentMode="left" text="apple.com - Long" lineBreakMode="tailTruncation" minimumFontSize="8" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OwP-sb-Wxl" userLabel="Site Name">
<rect key="frame" x="8" y="76" width="248" height="15.5"/>
<rect key="frame" x="8" y="76" width="303" height="15.5"/>
<accessibility key="accessibilityConfiguration" label="">
<accessibilityTraits key="traits" none="YES" staticText="YES" summaryElement="YES"/>
</accessibility>
@@ -1295,12 +1297,13 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LvK-28-fbm" userLabel="Settings Container">
<rect key="frame" x="300" y="0.0" width="300" height="100"/>
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LvK-28-fbm" userLabel="Settings Container">
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
<subviews>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IKd-Ot-0n4" userLabel="Upgrade">
<rect key="frame" x="-15" y="56" width="44" height="44"/>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IKd-Ot-0n4" userLabel="Upgrade">
<rect key="frame" x="40" y="56" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="width" constant="44" id="5DT-m6-RYu"/>
<constraint firstAttribute="height" constant="44" id="kyZ-o1-ntZ"/>
@@ -1318,8 +1321,8 @@
<action selector="doUpgrade:" destination="W2g-yv-V3V" eventType="touchUpInside" id="kTZ-AM-qGa"/>
</connections>
</button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vGk-t6-hZn" userLabel="Answers">
<rect key="frame" x="29" y="56" width="44" height="44"/>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vGk-t6-hZn" userLabel="Answers">
<rect key="frame" x="84" y="56" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
<constraints>
<constraint firstAttribute="width" constant="44" id="8gK-8v-Q0K"/>
@@ -1338,8 +1341,8 @@
<segue destination="aow-In-vb8" kind="custom" identifier="answers" customClass="MPOverlaySegue" id="5Wo-YZ-8HZ"/>
</connections>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.5" contentMode="left" misplaced="YES" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="PKP-M9-T8E" userLabel="Counter">
<rect key="frame" x="73" y="67" width="7" height="21.5"/>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.5" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="PKP-M9-T8E" userLabel="Counter">
<rect key="frame" x="128" y="67" width="7" height="21.5"/>
<accessibility key="accessibilityConfiguration" hint="Site's counter."/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -1348,7 +1351,7 @@
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uZi-FT-Fe8" userLabel="Incrementer">
<rect key="frame" x="80" y="56" width="44" height="44"/>
<rect key="frame" x="135" y="56" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Increments the site counter."/>
<gestureRecognizers/>
<constraints>
@@ -1369,7 +1372,7 @@
</connections>
</button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qBo-Kw-vN9" userLabel="Edit">
<rect key="frame" x="124" y="56" width="44" height="44"/>
<rect key="frame" x="179" y="56" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Edits the password."/>
<constraints>
<constraint firstAttribute="width" constant="44" id="j1z-K8-OXJ"/>
@@ -1389,7 +1392,7 @@
</connections>
</button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="I0v-ou-hDb" userLabel="Delete">
<rect key="frame" x="168" y="56" width="44" height="44"/>
<rect key="frame" x="223" y="56" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Edits the password."/>
<constraints>
<constraint firstAttribute="height" constant="44" id="XvV-Lr-Z21"/>
@@ -1409,7 +1412,7 @@
</connections>
</button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K8q-bM-tzh" userLabel="Type">
<rect key="frame" x="212" y="56" width="44" height="44"/>
<rect key="frame" x="267" y="56" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Edits the password."/>
<constraints>
<constraint firstAttribute="height" constant="44" id="K3c-Ok-krB"/>
@@ -1446,7 +1449,7 @@
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="b5f-wN-2xb" userLabel="Site Mode">
<rect key="frame" x="256" y="56" width="44" height="44"/>
<rect key="frame" x="311" y="56" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
<constraints>
<constraint firstAttribute="width" constant="44" id="I1C-Ok-DUv"/>
@@ -1467,7 +1470,7 @@
</connections>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="⬇︎" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tuh-Au-J9k">
<rect key="frame" x="270" y="35" width="17" height="20.5"/>
<rect key="frame" x="325" y="35" width="17" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
@@ -1599,14 +1602,14 @@
<outlet property="delegate" destination="nkY-z6-8jd" id="ENG-q5-XwX"/>
</connections>
</searchBar>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LEX-BK-PdS" userLabel="Bad Name Tip">
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LEX-BK-PdS" userLabel="Bad Name Tip">
<rect key="frame" x="37" y="86" width="300.5" height="75.5"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="Rt5-v4-I0R">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black_top.png" translatesAutoresizingMaskIntoConstraints="NO" id="Rt5-v4-I0R">
<rect key="frame" x="0.0" y="0.0" width="300.5" height="75.5"/>
<rect key="contentStretch" x="0.050000000000000003" y="0.49999999999999994" width="0.90000000000000002" height="0.20000000000000001"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" verticalCompressionResistancePriority="1000" misplaced="YES" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Eie-8u-hV2">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" verticalCompressionResistancePriority="1000" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Eie-8u-hV2">
<rect key="frame" x="20" y="26" width="260.5" height="43.5"/>
<string key="text">Try using exclusively bare domain names.
Avoid capitals and use @ to include a user name.
@@ -1725,6 +1728,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<outlet property="popdownToTopConstraint" destination="BdD-Kc-eHl" id="59Y-ap-Yn4"/>
<outlet property="popdownView" destination="XNM-XQ-rMe" id="FaW-4m-Fff"/>
<segue destination="z9O-w0-6oR" kind="modal" identifier="guide" id="Ql4-wf-T8u"/>
<segue destination="Foa-Er-RBr" kind="custom" identifier="message" customClass="MPOverlaySegue" id="Xne-Sm-HQt"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="APh-u5-vFI" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -1750,7 +1754,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<rect key="frame" x="0.0" y="0.5" width="375" height="333.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1lc-e7-Qme" userLabel="Emergency Generator">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1lc-e7-Qme" userLabel="Emergency Generator">
<rect key="frame" x="20" y="140" width="335" height="387"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Emergency Generator" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4Lh-s0-Dbt">
@@ -1761,7 +1765,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Generate your password without logging in. Great for if you're borrowing a friend's device or are having trouble logging in." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="vHS-3A-Tae">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Generate your password without logging in. Great for if you're borrowing a friend's device or are having trouble logging in." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="vHS-3A-Tae">
<rect key="frame" x="20" y="49" width="295" height="51.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -1769,7 +1773,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Name" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="XAC-Da-lpf" userLabel="User Name">
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Name" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="XAC-Da-lpf" userLabel="User Name">
<rect key="frame" x="20" y="109" width="295" height="30"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="words" keyboardAppearance="alert" returnKeyType="next" enablesReturnKeyAutomatically="YES"/>
@@ -1778,7 +1782,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<outlet property="delegate" destination="osn-5H-SWW" id="VQI-Lq-GWG"/>
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Master Password" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="J46-0E-no3" userLabel="Master Password">
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Master Password" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="J46-0E-no3" userLabel="Master Password">
<rect key="frame" x="20" y="147" width="295" height="30"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardAppearance="alert" returnKeyType="next" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
@@ -1787,7 +1791,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<outlet property="delegate" destination="osn-5H-SWW" id="bpf-YA-5XP"/>
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Site Name" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="56H-xR-09J" userLabel="Site Name">
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Site Name" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="56H-xR-09J" userLabel="Site Name">
<rect key="frame" x="20" y="185" width="295" height="30"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL" keyboardAppearance="alert" returnKeyType="done" enablesReturnKeyAutomatically="YES"/>
@@ -1796,7 +1800,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<outlet property="delegate" destination="osn-5H-SWW" id="QgA-TS-5KG"/>
</connections>
</textField>
<segmentedControl opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="e4b-Iv-Pk9" userLabel="Type">
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="e4b-Iv-Pk9" userLabel="Type">
<rect key="frame" x="20" y="223" width="295" height="29"/>
<segments>
<segment title="Max"/>
@@ -1811,7 +1815,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<action selector="controlChanged:" destination="osn-5H-SWW" eventType="valueChanged" id="sRc-3g-wqY"/>
</connections>
</segmentedControl>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="Counter" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="cAo-K2-E23">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Counter" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="cAo-K2-E23">
<rect key="frame" x="20" y="262.5" width="64" height="21.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -1819,13 +1823,13 @@ eg. apple.com, rmitchell@twitter.com</string>
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<stepper opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="ZPT-EI-yuv" userLabel="Counter">
<stepper opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="ZPT-EI-yuv" userLabel="Counter">
<rect key="frame" x="221" y="259" width="94" height="29"/>
<connections>
<action selector="controlChanged:" destination="osn-5H-SWW" eventType="valueChanged" id="eQA-3X-uc9"/>
</connections>
</stepper>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="3Cd-XH-Wau">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="3Cd-XH-Wau">
<rect key="frame" x="206" y="262.5" width="7" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -1866,10 +1870,10 @@ eg. apple.com, rmitchell@twitter.com</string>
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" misplaced="YES" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="4sN-hm-xio">
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="4sN-hm-xio">
<rect key="frame" x="149" y="329" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="XapaNuwjFihn6$" textAlignment="center" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bHR-he-dnZ" userLabel="Password Label">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="XapaNuwjFihn6$" textAlignment="center" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bHR-he-dnZ" userLabel="Password Label">
<rect key="frame" x="20" y="328" width="295" height="39"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="30"/>
@@ -1880,7 +1884,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<outletCollection property="gestureRecognizers" destination="gJb-50-mjy" appends="YES" id="3Ho-tp-mDE"/>
</connections>
</label>
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="beo-cJ-jIn" userLabel="View - Content Tip">
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="beo-cJ-jIn" userLabel="View - Content Tip">
<rect key="frame" x="62" y="287.5" width="210" height="60"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="nyL-cO-aPa">
@@ -1984,13 +1988,13 @@ eg. apple.com, rmitchell@twitter.com</string>
<outlet property="counterLabel" destination="3Cd-XH-Wau" id="wv7-jZ-6tX"/>
<outlet property="counterStepper" destination="ZPT-EI-yuv" id="w5c-hO-T51"/>
<outlet property="dialogView" destination="1lc-e7-Qme" id="JYt-mv-XV2"/>
<outlet property="fullNameField" destination="XAC-Da-lpf" id="XCk-0H-IcI"/>
<outlet property="masterPasswordField" destination="J46-0E-no3" id="DfH-4n-cop"/>
<outlet property="passwordLabel" destination="bHR-he-dnZ" id="0Mo-gc-Ls2"/>
<outlet property="scrollView" destination="gRG-Ys-94p" id="K0v-cS-3VW"/>
<outlet property="siteField" destination="56H-xR-09J" id="8no-IN-nsH"/>
<outlet property="tipContainer" destination="beo-cJ-jIn" id="BdT-0M-8qC"/>
<outlet property="typeControl" destination="e4b-Iv-Pk9" id="S69-yO-7bv"/>
<outlet property="userNameField" destination="XAC-Da-lpf" id="XCk-0H-IcI"/>
<segue destination="p6o-h3-NRH" kind="unwind" identifier="unwind-popover" unwindAction="unwindToCombined:" id="XI2-ax-Rkg"/>
</connections>
</viewController>
@@ -2082,7 +2086,7 @@ eg. apple.com, rmitchell@twitter.com</string>
<pageControl opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" numberOfPages="3" translatesAutoresizingMaskIntoConstraints="NO" id="8A2-ly-WTX">
<rect key="frame" x="168" y="630" width="39" height="37"/>
</pageControl>
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" pagingEnabled="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="i2y-lo-HXR">
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" pagingEnabled="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="i2y-lo-HXR">
<rect key="frame" x="0.0" y="64" width="375" height="476"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="m8O-kY-22j">
@@ -2122,13 +2126,13 @@ eg. apple.com, rmitchell@twitter.com</string>
<outlet property="delegate" destination="z9O-w0-6oR" id="RcD-gy-C1W"/>
</connections>
</collectionView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" text="To begin, tap the &quot;New User&quot; icon and add yourself as a user to the application." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ciw-56-nNy" userLabel="Caption">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="To begin, tap the &quot;New User&quot; icon and add yourself as a user to the application." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ciw-56-nNy" userLabel="Caption">
<rect key="frame" x="8" y="548" width="359" height="82"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="13"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Oop-Ff-gbz" userLabel="Caption Height Strut">
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Oop-Ff-gbz" userLabel="Caption Height Strut">
<rect key="frame" x="8" y="548" width="1" height="82"/>
<string key="text" base64-UTF8="YES">
CgoKCgoKCgoKCgoKCg
@@ -2308,7 +2312,7 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
<rect key="frame" x="163" y="532" width="51" height="31"/>
<color key="onTintColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="calibratedRGB"/>
</switch>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e80-98-V6D">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e80-98-V6D">
<rect key="frame" x="20" y="137" width="335" height="151.5"/>
<string key="text">The right balance between security and convenience is often very personal.
@@ -2392,7 +2396,7 @@ However, it means that anyone who finds your device unlocked can do the same.</s
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="J90-SQ-ljR">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="J90-SQ-ljR">
<rect key="frame" x="20" y="136" width="335" height="522.5"/>
<attributedString key="attributedText">
<fragment content="The passwords generated by this app are not stored but ">
@@ -2580,7 +2584,7 @@ See </string>
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="cef-sc-aph">
<rect key="frame" x="168" y="100" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" userInteractionEnabled="NO" tag="3" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FWu-V6-mLT">
<label opaque="NO" userInteractionEnabled="NO" tag="3" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FWu-V6-mLT">
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -2645,7 +2649,7 @@ See </string>
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="X2g-Go-2Hz">
<rect key="frame" x="169" y="100" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="N9y-ue-L8d">
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="N9y-ue-L8d">
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -2691,7 +2695,7 @@ See </string>
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="iOS Integration" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zch-DS-J3I">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="iOS Integration" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zch-DS-J3I">
<rect key="frame" x="20" y="226" width="244" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -2710,13 +2714,13 @@ See </string>
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="yUe-TX-fli">
<rect key="frame" x="169" y="100" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ec8-P9-KPY">
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ec8-P9-KPY">
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3jH-eX-9N2">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3jH-eX-9N2">
<rect key="frame" x="272" y="226" width="83" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -2756,7 +2760,7 @@ See </string>
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="TouchID Login" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e1D-jp-GBs">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="TouchID Login" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e1D-jp-GBs">
<rect key="frame" x="20" y="226" width="244.5" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -2775,13 +2779,13 @@ See </string>
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="Dv5-t7-lL1">
<rect key="frame" x="169" y="100" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yZX-ns-8oV">
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="✔︎" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yZX-ns-8oV">
<rect key="frame" x="200" y="-6" width="92.5" height="132"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="110"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZGg-O6-rsg">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Coming Soon" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZGg-O6-rsg">
<rect key="frame" x="272" y="226" width="83" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -2821,13 +2825,13 @@ See </string>
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" ambiguous="YES" misplaced="YES" text="Fuel Top-Up" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jnv-uN-xeg">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Fuel Top-Up" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jnv-uN-xeg">
<rect key="frame" x="20" y="226" width="292" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" ambiguous="YES" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fz2-AO-aGW">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fz2-AO-aGW">
<rect key="frame" x="20" y="254" width="335" height="132"/>
<string key="text">You really love Master Password and how it's solving your password problems. You're eager to encourage the maintenance, technical support and development of new features. I am a one-man shop, fuel enables me to allocate more work hours to Master Password.
@@ -2840,22 +2844,22 @@ UPCOMING:
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" image="thumb_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="PnG-hP-syh">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="thumb_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="PnG-hP-syh">
<rect key="frame" x="88" y="20" width="198" height="198"/>
</imageView>
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" misplaced="YES" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="eS4-59-Xny">
<activityIndicatorView hidden="YES" opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="eS4-59-Xny">
<rect key="frame" x="169" y="100" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" ambiguous="YES" misplaced="YES" text="$2.95" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbU-DV-fKF">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="$2.95" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbU-DV-fKF">
<rect key="frame" x="320" y="226" width="34.5" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" misplaced="YES" image="meter_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="aGb-QC-A92">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="meter_fuel.png" translatesAutoresizingMaskIntoConstraints="NO" id="aGb-QC-A92">
<rect key="frame" x="261" y="208" width="12" height="10"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dsR-fr-dY4">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dsR-fr-dY4">
<rect key="frame" x="20" y="20" width="107" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="W6p-kB-VBX"/>
@@ -2868,7 +2872,7 @@ UPCOMING:
<action selector="toggleFuelConsumption:" destination="pdl-xv-zjX" eventType="touchUpInside" id="NkB-Dy-IeY"/>
</connections>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kYb-j4-32C">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kYb-j4-32C">
<rect key="frame" x="20" y="64" width="117.5" height="26.5"/>
<string key="text">fuel left: 0.3 work hours
invested: 3.7 work hours</string>
@@ -2920,7 +2924,7 @@ invested: 3.7 work hours</string>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="Vjt-7c-BJ4">
<rect key="frame" x="169" y="20" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" misplaced="YES" text="Loading products..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ARC-xH-0U0">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Loading products..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ARC-xH-0U0">
<rect key="frame" x="114.5" y="97" width="145.5" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -2944,7 +2948,7 @@ invested: 3.7 work hours</string>
<rect key="frame" x="0.0" y="0.0" width="320" height="152"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IOk-WZ-HJ8">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IOk-WZ-HJ8">
<rect key="frame" x="20" y="58" width="335" height="26"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
<state key="normal" title="Restore Previous Purchases">
@@ -2954,7 +2958,7 @@ invested: 3.7 work hours</string>
<action selector="restorePurchases:" destination="pdl-xv-zjX" eventType="touchUpInside" id="lKu-PL-PfX"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bCe-a3-cDC">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bCe-a3-cDC">
<rect key="frame" x="8" y="24" width="359" height="26"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="11"/>
<state key="normal" title="Send Thanks">
@@ -2964,7 +2968,7 @@ invested: 3.7 work hours</string>
<action selector="sendThanks:" destination="pdl-xv-zjX" eventType="touchUpInside" id="r5l-3U-jCA"/>
</connections>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" ambiguous="YES" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DxV-2L-bxL">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" text="© 2012-2014, Maarten Billemont (lhunath)" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DxV-2L-bxL">
<rect key="frame" x="20" y="4" width="335" height="12"/>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="10"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -3042,13 +3046,13 @@ invested: 3.7 work hours</string>
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" misplaced="YES" text="Answer for lyndir.com:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tal-1I-HQw" userLabel="Title Label">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="Answer for lyndir.com:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tal-1I-HQw" userLabel="Title Label">
<rect key="frame" x="8" y="8" width="180" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="hok petwuvaqu xixo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="GfC-j4-Qx7" userLabel="Answer Field">
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="hok petwuvaqu xixo" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="GfC-j4-Qx7" userLabel="Answer Field">
<rect key="frame" x="8" y="48" width="359" height="43"/>
<color key="textColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="24"/>
@@ -3132,7 +3136,7 @@ invested: 3.7 work hours</string>
<rect key="frame" x="0.0" y="0.0" width="287" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="mother" textAlignment="center" minimumFontSize="14" clearButtonMode="unlessEditing" translatesAutoresizingMaskIntoConstraints="NO" id="T2F-PD-Nw8" userLabel="Question Field">
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="mother" textAlignment="center" minimumFontSize="14" clearButtonMode="unlessEditing" translatesAutoresizingMaskIntoConstraints="NO" id="T2F-PD-Nw8" userLabel="Question Field">
<rect key="frame" x="8" y="19" width="359" height="30"/>
<color key="backgroundColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="0.5" colorSpace="calibratedRGB"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -3143,13 +3147,13 @@ invested: 3.7 work hours</string>
<outlet property="delegate" destination="iFm-3w-hOv" id="olS-Rx-56Y"/>
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="pifm gup balvabi yiz" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="3xA-ez-efa" userLabel="Answer Field">
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="pifm gup balvabi yiz" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="3xA-ez-efa" userLabel="Answer Field">
<rect key="frame" x="20" y="90" width="335" height="31"/>
<color key="textColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="24"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="alphabet" keyboardAppearance="alert" returnKeyType="next"/>
</textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" misplaced="YES" text="Enter the single most significant word in the question above." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qqg-Ny-7Po">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Enter the single most significant word in the question above." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qqg-Ny-7Po">
<rect key="frame" x="8" y="57" width="359" height="13.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="11"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -3242,7 +3246,7 @@ invested: 3.7 work hours</string>
<rect key="frame" x="163" y="532" width="51" height="31"/>
<color key="onTintColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="calibratedRGB"/>
</switch>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yx2-Eh-hM0">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yx2-Eh-hM0">
<rect key="frame" x="20" y="137" width="335" height="202"/>
<string key="text">To make it easy for you to recognize and copy passwords manually using a keyboard or other means, Master Password makes your site passwords visible on your screen by default.
@@ -3299,6 +3303,198 @@ You can temporarily reveal a password by holding your finger down on the site's
</objects>
<point key="canvasLocation" x="-152.5" y="2175.5"/>
</scene>
<!--Message View Controller-->
<scene sceneID="5u0-2d-uNC">
<objects>
<viewController id="Foa-Er-RBr" customClass="MPMessageViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="TXr-gp-hra"/>
<viewControllerLayoutGuide type="bottom" id="Ol8-pt-KDt"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="LFy-pG-nMQ" userLabel="Root">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" barStyle="black" translatesAutoresizingMaskIntoConstraints="NO" id="aNU-Nq-clY">
<rect key="frame" x="0.0" y="341" width="375" height="325.5"/>
<items/>
</toolbar>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="You have sites that can be upgraded." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Qi-GN-vhQ" userLabel="Title Label">
<rect key="frame" x="16" y="361" width="343" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" verticalCompressionResistancePriority="1000" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="0" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rnV-lT-dTT">
<rect key="frame" x="16" y="390" width="343" height="216.5"/>
<attributedString key="attributedText">
<fragment>
<string key="content">Upgrading a site allows it to take advantage of
the latest improvements in the Master Password algorithm.
</string>
<attributes>
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
<fragment>
<string key="content">
When you upgrade a site, </string>
<attributes>
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
<paragraphStyle key="NSParagraphStyle" alignment="justified" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
<fragment content="a new and stronger password will be generated for it">
<attributes>
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<font key="NSFont" size="12" name="Exo2.0-Bold"/>
<paragraphStyle key="NSParagraphStyle" alignment="justified" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
<fragment>
<string key="content">. To upgrade a site, first log into the site, navigate to your account preferences where you can change the site's password. Make sure you fill in any "current password" fields on the website first, then press the upgrade button here to get your new site password.
</string>
<attributes>
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
<paragraphStyle key="NSParagraphStyle" alignment="justified" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
<fragment>
<string key="content">
You can then update your site's account with the new and stronger password.
</string>
<attributes>
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
<fragment content="The upgrade button can be found in the site's settings and looks like this:">
<attributes>
<color key="NSBackgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="NSColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<font key="NSFont" size="12" name="Exo2.0-Regular"/>
<paragraphStyle key="NSParagraphStyle" alignment="center" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
</attributedString>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kPc-D9-n24" userLabel="Info">
<rect key="frame" x="16" y="615" width="343" height="44"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3Ld-qt-w7n" userLabel="Stud">
<rect key="frame" x="0.0" y="0.0" width="120.5" height="44"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wkN-yK-VVb" userLabel="Stud">
<rect key="frame" x="222.5" y="0.0" width="120.5" height="44"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hMB-S7-ldC" userLabel="Site Mode">
<rect key="frame" x="120.5" y="0.0" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
<constraints>
<constraint firstAttribute="height" constant="44" id="HIL-wf-3AX"/>
<constraint firstAttribute="width" constant="44" id="WBC-Zk-skQ"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="6" minY="6" maxX="6" maxY="6"/>
<state key="normal" image="icon_tools.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="selected" image="icon_thumbs-up.png"/>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xbh-Rx-L5u" userLabel="Upgrade">
<rect key="frame" x="179" y="0.0" width="44" height="44"/>
<accessibility key="accessibilityConfiguration" hint="Upgrades the password."/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="height" constant="44" id="O4i-vP-Uf4"/>
<constraint firstAttribute="width" constant="44" id="kMf-yo-8c5"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="6" minY="6" maxX="6" maxY="6"/>
<state key="normal" image="icon_up.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="➙" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kta-kZ-FbX">
<rect key="frame" x="164.5" y="12" width="14.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="3Ld-qt-w7n" firstAttribute="leading" secondItem="kPc-D9-n24" secondAttribute="leading" id="1BM-jA-NHO"/>
<constraint firstAttribute="bottom" secondItem="xbh-Rx-L5u" secondAttribute="bottom" id="1wo-8P-DpL"/>
<constraint firstItem="3Ld-qt-w7n" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="2v6-tn-vzT"/>
<constraint firstItem="wkN-yK-VVb" firstAttribute="width" secondItem="3Ld-qt-w7n" secondAttribute="width" id="AHa-IC-DwT"/>
<constraint firstAttribute="bottom" secondItem="3Ld-qt-w7n" secondAttribute="bottom" id="Cu9-Dt-vRu"/>
<constraint firstAttribute="centerY" secondItem="kta-kZ-FbX" secondAttribute="centerY" id="EgL-EZ-xNL"/>
<constraint firstItem="hMB-S7-ldC" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="HVS-r6-AFH"/>
<constraint firstItem="xbh-Rx-L5u" firstAttribute="leading" secondItem="kta-kZ-FbX" secondAttribute="trailing" id="Ksm-jT-qOL"/>
<constraint firstAttribute="bottom" secondItem="wkN-yK-VVb" secondAttribute="bottom" id="LyD-YV-Yoh"/>
<constraint firstItem="kta-kZ-FbX" firstAttribute="leading" secondItem="hMB-S7-ldC" secondAttribute="trailing" id="Mqi-c7-yB1"/>
<constraint firstItem="wkN-yK-VVb" firstAttribute="leading" secondItem="xbh-Rx-L5u" secondAttribute="trailing" id="TYI-ve-ABO"/>
<constraint firstItem="xbh-Rx-L5u" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="aTm-i3-Niu"/>
<constraint firstAttribute="trailing" secondItem="wkN-yK-VVb" secondAttribute="trailing" id="dY8-BI-M6Q"/>
<constraint firstItem="wkN-yK-VVb" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="top" id="hUt-8M-m1o"/>
<constraint firstAttribute="bottom" secondItem="hMB-S7-ldC" secondAttribute="bottom" id="t4s-92-9p4"/>
<constraint firstItem="hMB-S7-ldC" firstAttribute="leading" secondItem="3Ld-qt-w7n" secondAttribute="trailing" id="wiD-xK-udZ"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="3Qi-GN-vhQ" secondAttribute="trailing" id="01x-aR-50B"/>
<constraint firstItem="rnV-lT-dTT" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leadingMargin" id="5sg-Cn-A5f"/>
<constraint firstItem="3Qi-GN-vhQ" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leadingMargin" id="B93-hf-itk"/>
<constraint firstItem="kPc-D9-n24" firstAttribute="top" secondItem="rnV-lT-dTT" secondAttribute="bottom" constant="8" id="Jd6-5C-J2q"/>
<constraint firstItem="aNU-Nq-clY" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leading" id="Jo0-Nd-LRa"/>
<constraint firstAttribute="trailingMargin" secondItem="rnV-lT-dTT" secondAttribute="trailing" id="LsP-s5-0Cd"/>
<constraint firstAttribute="trailing" secondItem="aNU-Nq-clY" secondAttribute="trailing" id="OOa-cS-g9q"/>
<constraint firstItem="Ol8-pt-KDt" firstAttribute="top" secondItem="aNU-Nq-clY" secondAttribute="bottom" id="T27-2g-NuB"/>
<constraint firstAttribute="trailingMargin" secondItem="kPc-D9-n24" secondAttribute="trailing" id="gVh-xu-hF3"/>
<constraint firstItem="rnV-lT-dTT" firstAttribute="top" secondItem="3Qi-GN-vhQ" secondAttribute="bottom" constant="8" id="gz3-TT-ylK"/>
<constraint firstItem="3Qi-GN-vhQ" firstAttribute="top" secondItem="aNU-Nq-clY" secondAttribute="top" constant="20" id="ozH-hI-ID9"/>
<constraint firstItem="kPc-D9-n24" firstAttribute="leading" secondItem="LFy-pG-nMQ" secondAttribute="leadingMargin" id="pTx-Ay-1WY"/>
<constraint firstItem="Ol8-pt-KDt" firstAttribute="top" secondItem="kPc-D9-n24" secondAttribute="bottom" constant="8" id="sNM-dC-2ES"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<connections>
<outlet property="infoView" destination="kPc-D9-n24" id="e7c-dG-hqT"/>
<outlet property="messageLabel" destination="rnV-lT-dTT" id="AdF-oe-XmL"/>
<outlet property="titleLabel" destination="3Qi-GN-vhQ" id="w2W-Tk-eMH"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="gdm-wd-QhM" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1408.5" y="2995.5"/>
</scene>
</scenes>
<resources>
<image name="avatar-0.png" width="110" height="110"/>

View File

@@ -51,10 +51,11 @@ setSettingWithTitle "Copyright" "$(getPlistWithKey NSHumanReadableCopyright)"
if [[ $DEPLOYMENT_LOCATION = YES ]]; then
# This build is a release. Do some release checks.
crashlyticsPlist="$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Crashlytics.plist"
passed=1
[[ $description != *-dirty ]] || \
{ passed=0; err 'ERROR: Cannot release a dirty version, first commit any changes.'; }
[[ $(PlistBuddy -c "Print :'API Key'" "$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Crashlytics.plist") ]] || \
[[ -r "$crashlyticsPlist" && $(PlistBuddy -c "Print :'API Key'" "$crashlyticsPlist" 2>/dev/null) ]] || \
{ passed=0; err 'ERROR: Cannot release: Crashlytics API key is missing.'; }
(( passed )) || \
{ ftl "Failed to pass release checks. Fix the above errors and re-try. Aborting."; exit 1; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

View File

@@ -80,9 +80,10 @@
<h4>
Get it for:
<a title="iPhone, iPad, iPod touch" href="https://itunes.apple.com/app/id510296984" onclick="_gaq.push(['_trackPageview', '/outbound/ios']);">iPhone / iPad<img class="popup" src="img/ios.png" /></a> |
<a title="Mac (graphical interface)" href="https://itunes.apple.com/app/id662763204" onclick="_gaq.push(['_trackPageview', '/outbound/gui/mac']);">Mac<img class="popup border" src="img/mac-gui.png" /></a> |
Mac <a title="Mac (graphical interface)" href="https://itunes.apple.com/app/id662763204" onclick="_gaq.push(['_trackPageview', '/outbound/gui/mac']);">App Store<img class="popup border" src="img/mac-gui.png" /></a>,
<a title="Mac (command-line interface)" href="#">Homebrew<img class="popup" src="img/mac-homebrew.png" /></a> |
<a title="Mac, Linux, UNIX, Windows (graphical interface)" href="https://ssl.masterpasswordapp.com/masterpassword-gui.jar" onclick="_gaq.push(['_trackPageview', '/outbound/gui/java']);">Desktop (Java)<img class="popup" src="img/java-gui.png" /></a> |
Terminal (<a title="Mac, Linux, POSIX (command line interface)" href="https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz" onclick="_gaq.push(['_trackPageview', '/outbound/cli/c']);">Native C<img class="popup" src="img/c-cli.png" /></a>) |
<a title="Mac, Linux, POSIX (command line interface)" href="https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz" onclick="_gaq.push(['_trackPageview', '/outbound/cli/c']);">Terminal (C)<img class="popup" src="img/c-cli.png" /></a> |
<a title="Android" href="https://ssl.masterpasswordapp.com/masterpassword-android.apk" onclick="_gaq.push(['_trackPageview', '/outbound/android']);">Android (Beta)<img class="popup" src="img/android.png" /></a> |
<a title="JavaScript" href="https://js.masterpasswordapp.com/" onclick="_gaq.push(['_trackPageview', '/outbound/js']);">Web (Beta)<img class="popup border" src="img/web.png" /></a>
</h4>

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